More efficient way to access an object?

Hello everyone. I’m working on a tower defense type of game, where there are 2 gates on each scene, from which enemy critters spawn and then move along a different set of waypoints.

Each of these objects currently have attached a script called Waypoints, that is accessed separately by each critter when spawned.

My question is, what would be an efficient and optimized way to do so? I’m currently using:

waypoints1 = GameObject.Find(“Waypoints1”).GetComponent();

waypoints2 = GameObject.Find(“Waypoints2”).GetComponent();

Since I cannot drag and drop these objects, because the enemies don’t exist when the scene is loaded; I figured I could have a third script, like GameManager, that accesses the Waypoint script in each object just once at the start, then have the critters Movement script access this instead, but it feels kind of hacky as well.

What would be an efficient way to do this? Any input will be greatly appreciated, thanks in advance.

Do you run into any performance issues?
Make it, stress test, profile it, look for bottlenecks.

1 Like

One pattern that I like using is having instances of a type add themselves to a static collection during OnEnable, and removing themselves from it during OnDisable. This way the collection will always contain all currently active instances.

using System.Collections.Generic;
using UnityEngine;

public class Waypoints : MonoBehaviour
{
    public static readonly Dictionary<int, Waypoints> ByIndex = new Dictionary<int, Waypoints>();

    public int index; // assign this using the inspector

    private void OnEnable()
    {
        ByIndex.Add(index, this);
    }

    private void OnDisable()
    {
        ByIndex.Remove(index);
    }
}

You can then use Waypoints.ByIndex to find your waypoints like this:

using UnityEngine;

public class Critter : MonoBehaviour
{
    public int waypointsIndex; // assign this using the inspector

    private Waypoints waypoints;

    private void OnEnable()
    {
        waypoints = Waypoints.ByIndex[waypointsIndex];
    }
}
1 Like

@SisusCo : Thanks a lot for your thorough answer Sisus! And sorry for the late reply, I wasn’t at home this past weekend.

It works great, but I have one more question, if you don’t mind.

In the Critter script, when you mention to manually assign a value using the inspector to the line:

public int waypointsIndex;

What would be the most efficient way to have this value change after the game starts, for the critters that must follow the second set of waypoints?

I’ve done this already but ended up duplicating most of the code inside the Critter script and using an OnTriggerEnter function, and this doesn’t feel right.

I’d appreciate your help again if you have the time, thanks again Sisus.

@Antypodish : I hadn’t run into performance problems yet using a Find method, but, the game has a long way to go still, plus, it’s mobile oriented, so I really felt like avoiding using lots of times a method that Unity itself lists as “very slow”.

This article has a bunch of different ideas for ways to access objects without using GameObject.Find. GO.F does have performance problems (which as you’ve discovered are minimal unless you do it a whole lot), but that’s not the main reason to avoid it - reliability is. If you typo an object’s name, it won’t work, and you won’t get this error until you play through it. (Compile errors are always preferred to runtime errors - faster iteration/development)

I think having a script for “a set of waypoints” - which is contained in the same scene as the waypoints and can therefore be assigned with drag and drop - is the best approach, with this set having an array that you can access from anywhere. Assuming only one scene is loaded at a time, this script could be a singleton (see the link for a basic example).

This is where you should run your stress tests. Also, don’t use find at runtime. But is totally fine at initialization, if you have to.

Performance-wise, sure, but it’s still unreliable and unnecessarily difficult to refactor. And there’s no “if you have to” - you don’t have to. There’s always another, better way to get access to objects than GameObject.Find.

@GarrettBundy

I’m Glad I could help!

I assume that you have some code that is responsible for spawning your critters. That would probably be the best place to also allow specifying the waypointsIndex for the spawned critters.

The only problem is, with the way that Critters is currently set up in my example, the Waypoint will be fetched as soon as OnEnable is called, and OnEnable is called immediately after instantiation by default. You will have to handle this in some way, so that you can first set waypointsIndex, and only then fetch the Waypoints.

There are many possible solutions for this. One is to spawn the critters directly inside an inactive GameObject, which would suppress their OnEnable even function from firing. You could then first set waypointsIndex, and unparent them from the inactive GameObject once you’re ready.

Another solution (and the more reliable / less hack-y one) is to not use OnEnable at all, but to roll your own Setup method, and have the Waypoints only be fetched after that has been called.

So your Critter class would be changed to something more like this:

using UnityEngine;

public class Critter : MonoBehaviour
{
    private Waypoints waypoints;

    public void Setup(int waypointsIndex)
    {
        waypoints = Waypoints.ByIndex[waypointsIndex];
    }
}

Then have your class that is responsible for spawning critters always call Setup with the proper waypointIndex when you’re creating critters:

using UnityEngine;

public static class CritterSpawner
{
    public static Critter Spawn(Critter critterPrefab, Vector3 position, int waypointsIndex)
    {
        var critter = Object.Instantiate(critterPrefab, position);
        critter.Setup(waypointsIndex);
        return critter;
    }
}

Or better yet, if you don’t actually even want the critters themselves to determine which waypoint they are going to use, you could perhaps do something like this instead of using the static collection pattern:

public class Gate : MonoBehaviour
{
    public Waypoints waypoints; // assign this using the inspector
    public Transform startingPoint; // assign this using the inspector

    public void Spawn(Critter critterPrefab)
    {
        var critter = Object.Instantiate(critterPrefab, startingPoint.position);
        critter.Setup(waypoints);
    }
}

It’s difficult to know exactly what is the best solution in your case, without knowing the specifics of how your game works :smile:

Apologies about the delay guys.

@SisusCo Thanks a lot man! I ended up using the static collection method combined with colliders at each gate, and worked great. I really appreciate all the time and effort you put into this, cheers! :slight_smile:

@StarManta Hey StarManta, thanks for the article, I read through all of the different ways to tackle situations like this one; interesting stuff. Cheers!

2 Likes