- Home /
Access random GameObject from List on another script.
Sadly I've wasted quite a few hours trying to crack this using an Array, but I keep failing.
With an Array I've been able to select a random GameObject and populate an object field using...
WayPointArray = (GameObject.FindGameObjectsWithTag("WayPoint"));
with
other.GetComponent<NPC_AI>().WayPoint = NPCWayPoint;
To find a way to get my NPCs to walk to random waypoints.
Naturally rather than having my NPCs active across a large map, I'm trying to keep them local to the player. Having a map of Spawn and WayPoint objects which direct the NPCs where to walk to next within an area.
On 'Start' the WayPoint script finds GameObjects tagged with 'WayPoint', and when it's collider is triggered by an NPC, the script delivers a position for the NPC to go to. ...using the code exampled above.
Only the problem is, my coding isn't great and I can't get the individual Arrays to only find tagged WayPoints within a small distance from each other, building a mesh of points that help the NPCs go A>B rather than A>Z across the map. My limitations in code means each WayPoint finds every WayPoint Tag across the map. Not ideal.
So I've now turned to Lists instead (for the first time), finding that On Start I can now get each WayPoint to find each other within a distance using a collider. (Couldn't with an Array)
public List<GameObject> WayPoints;
void OnTriggerStay(Collider other)
{
if (other.tag == "WayPoint")
{
if (!WayPoints.Contains(other.gameObject))
{
WayPoints.Add(other.gameObject);
}
}
}
With that working as hoped, I now have the opposite problem where I can't get my other NPC_AI script to select a random WayPoint GameObject from the above.
Is there anyone that can help? The only way I could easily get my WayPoints to find each other close by, was to switch to lists instead.
As I mentioned, I'm not brilliant at coding generally and have managed with Arrays till now, but Lists so far have been alien to me and not as similar as I was hoping.
Any help I'd be really grateful. Thank you!!
Answer by Captain_Pineapple · Nov 23, 2021 at 10:29 AM
Hey there,
as this information is only needed to be created once and using colliders is smart but bad on performance let me introduce you to the wonderful world of editor scripts:
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
//assuming your waypoint class has these variables:
public class Waypoint : MonoBehaviour
{
public Waypoint[] nextWaypoints;
// distance value of this specifc waypoint. if distance between 2 points is less then their dist sum you can move between them.
public float maxWaypointDistance = 100f; // point of this is to give individual waypoints a way to generate a smaller grid..
}
public class EditorCustomCommands
{
[MenuItem("CustomCommand/Initialize Waypoints")]
public static void initializeWaypoints()
{
//find all "waypoints" in the current open scene:
var allWaypoints = UnityEngine.Object.FindObjectsOfType<Waypoint>();
//create an array of buffer lists for neighbour waypoints of each waypoint:
var waypointBuffer = new System.Collections.Generic.List<Waypoint>[allWaypoints.Length];
for (int i = 0; i < allWaypoints.Length; i++)
{
for (int j = 0; j < allWaypoints.Length; j++)
{
//check if the relation between these 2 points has already been discovered:
if (waypointBuffer[i].Contains(allWaypoints[j]))
continue;
//check if the distance between these 2 waypoints is less than their added max waypoint distances:
if (allWaypoints[i].maxWaypointDistance + allWaypoints[j].maxWaypointDistance > Vector3.Magnitude(allWaypoints[i].transform.position - allWaypoints[j].transform.position))
{
//in this case we can move between the 2 waypoints and thus buffer this information:
waypointBuffer[i].Add(allWaypoints[j]);
waypointBuffer[j].Add(allWaypoints[i]);
}
}
}
// now we have processed all waypoints so it is time to save the information on the object:
for (int i = 0; i < allWaypoints.Length; i++)
{
//this is needed to be able to save the changes in the scene later on:
Undo.RecordObject(allWaypoints[i], "set new next waypoint array");
allWaypoints[i].nextWaypoints = waypointBuffer[i].ToArray();
//this is needed to be able to save the changes in the scene later on as well:
PrefabUtility.RecordPrefabInstancePropertyModifications(allWaypoints[i]);
}
}
}
#endif
What this does (in theory at least) is create a button in your top menu in the editor which you can click to automatically set all waypoint relations in your current scene. These are then saved in the nextWaypoints
array of each waypoint.
Let me know if anything in there is unclear. I hope the comments in the code explain everythin as needed.
Wow @Captain_Pineapple thank for such detailed help and effort on my behalf. I never knew something like that was possible. Great that you can get the editor to do the leg work, rather than manually or in game.
However I'm having issues making it work. I've tried changing some of my labelling to accommodate your script, but so far it doesn't seem to be making any changes. It'll tell you what I've got setup...
I have a test scene with a grid of waypoints 10x&y apart. Generally labelled NPC_Destination(XX) and have made sure the tags on these are 'Waypoint' as described in your code, and reinstated the Arrays again with your na$$anonymous$$g convention 'nextWaypoints'.
Hitting 'Initialise Waypoints' does nothing so far. :(
After my first failed attempt, I wasn't sure of FindObjectsOfType<Waypoint>
so I tried changing all tags to that and even also the name on some of the waypoints to 'Waypoint', in case. But no joy, and not sure how to get your script to recognise my waypoints, or change my objects to match that label.
How should I be setting up my waypoints?
Thank you so much, this help is incredible and far beyond what I expected.
[Edit] For some reason the Waypoint script didn't show properly in the inspector when attached to an object/waypoint, despite the console being clear. ...went to lunch, came back, suddenly they're now loading properly. FindObjectOfType I'm guessing must relate to the script - find all objects with 'Waypoint' attached.
Now when I hit 'Initialise Waypoints', I get an error ...Object reference not set to instance of an object - line 30 if (waypointBuffer[i].Contains(allWaypoints[j]))
I'm now trying to work that one out. Thanks again!
Hey there,
glad you like the approach.
Just realized that you'll have to initialize all Lists in the buffer array after creating it with this:
for (int i = 0; i < allWaypoints.Length; i++)
waypointBuffer[i] = new System.Collections.Generic.List<Waypoint>();
Should have been more clear about what FindObjectsOfType
does i guess. :)
Thank you, not at all - even though I hadn't come across that command before, I think it was still clear. However the script not loading in the inspector threw me off, until it was suddenly working.
I added the line as suggested, then I got the same error on line 38. So duplicated your line with the J alternative within the next for(int...
and now it works!!! Brilliant.
Only... not 100%
I have 8 identical waypoints in close proximity, and they're each only finding one waypoint - the same waypoint. And if I turn that object off, it finds the next one and only that one. I would wonder if there's a limit on how many waypoints each can list, but that one waypoint lists itself - twice!
Sorry to keep asking - this is brilliant, I'm really grateful - I just wish I was at the point where I can interpret C# like Neo see's the Matrix. Thank you!
Answer by elkawee · Nov 23, 2021 at 09:34 AM
the lazy way to do it would be linq
convert to array and reuse your old code
using System.Linq;
// ...
WayPoint [] waypointsArray = WayPoints.ToArray();
yourOriginalMethodOfRandomSelection ( waypointsArray );
// ...
or keep the lists and work directly on them
using System.Linq;
// ....
var R = new Random();
var randomWP = WayPoints.Skip(R.Next(WayPoints.Count())).First();
both have a performance impact
which one is faster depends on how long your list is
( the collider idea is kinda clever :) )
Thank you so much @elkawee, I did look into Linqs, but they just scrabbled my brain further.
My intention was for each waypoint to have only around 3-6 findable other waypoints on each. Thank you, the intention for the collider was to log other waypoints that are near on Start and then disable the collider after the Array was populated. So hopefully $$anonymous$$imal impact once it's done the job.
If I fail with what the Captain has suggested, using a script in the Editor to do the graft rather than in-game, I'll come back to your post and see if the Linq conversion method works. Again thank you