1
\$\begingroup\$

I'm trying to avoid using GameObject.Find during runtime to find particular GameObject components when wanting to instantiate them.

I have created a cache for the gameobjects from which I can either return the original instance or create a new instance.

Components which should only have one instance are returned by GetOriginal<UnityObject>

A new instance of an Original component is returned using GetInstanceOf<UnityObject>

public class GameObjectTools : MonoBehaviour
{
    static List<Object> gameObjectCache = new List<Object>();

    // This is called BEFORE Start() when the script is being loaded
    void Awake()
    {
        // Cache any GameObject classes that are required by other classes here
        gameObjectCache.Add(GameObject.Find("Timer").GetComponent<Timer>());
        gameObjectCache.Add(GameObject.Find("Object Rotator").GetComponent<ObjectRotator>());
        gameObjectCache.Add(GameObject.Find("Object Translator").GetComponent<ObjectTranslator>());
    }

    public static T GetOriginal<T>() where T : Object
    {
        int index = -1;
        int numObjects = gameObjectCache.Count;

        for (int i = 0; i < numObjects; ++i)
        {
            if (gameObjectCache[i].GetType() == typeof(T))
            {
                index = i;
                break;
            }
        }

        return index == -1 ? null : gameObjectCache[index] as T;
    }

    public static T GetInstanceOf<T>() where T : Object
    {
        var o = GetOriginal<T>();
        return o == null ? null : GameObject.Instantiate(o) as T;
    }
}

I'm slightly concerned that I am not approaching this problem in the right manner and was hoping for some feedback regarding my methodology.

Is there a more efficient or straightforward approach to achieve what I am trying to achieve here?

EDIT

public class Ball : MonoBehaviour
{
    int lifeSpan = 1000;

    void Update()
    {
        if (Timer.CurrentTime > lifeSpan)
            GameObject.Destroy(gameObject);
    }
}


public class GameObjectTools : MonoBehaviour
{
    public static Ball Ball;

    void Awake()
    {

        Ball = GameObject.Find("Ball").GetComponent<Ball>();
    }
}

If I call

var ball = GameObject.Instantiate(GameObjectTools.Ball) as GameObject;

anytime after the lifespan of the ball has expired then I will get a null reference error.

How then can I ensure that this doesn't happen while still being able to create new instances from a static cache?

\$\endgroup\$
  • \$\begingroup\$ I started down the same route but Unity's object management is such a botched mess, it soon became impossible to maintain any sort of reliable cache (personally I gave up when I found that this == null is true if the .Net object exists but the underlying c++ representation has been released). When mixed with DontDestroyOnLoad and Unity's serialization at the oddest times, I'd personally say don't bother unless you're in a deeply nested loop or you've actually profiled and found a bottleneck. \$\endgroup\$ – Basic Sep 3 '15 at 21:17
3
\$\begingroup\$

As long as you're hard coding things, you may as well put the types in there too.

public class GameObjectTools : MonoBehaviour
{
    static Timer timer;
    static ObjectRotator objectRotator;
    static ObjectTranslator objectTranslator;

    // This is called BEFORE Start() when the script is being loaded
    void Awake()
    {
        timer = GameObject.Find("Timer").GetComponent<Timer>();
        objectRotator = GameObject.Find("Object Rotator").GetComponent<ObjectRotator>();
        objectTranslator = 
                  GameObject.Find("Object Translator").GetComponent<ObjectTranslator>();
    }

Then access them with:

GameObjectTools.timer.DoStuff();

This avoids the expensive reflection call typeOf. Which your code does multiple times per look up.

Additionally, make sure you're actually solving a problem. Your trying to avoid using GameObject.Find, but have you actually tested this to see if it's a significant problem with your game? Remember what they say about premature optimization.

Use some profiling to ensure that you're not wasting your efforts on something that's not going to improve performance much at all.

\$\endgroup\$
  • \$\begingroup\$ If one of those objects called GameObject.Destroy(this.gameObject); in their original code. How could I make sure that further instances could be created? See my edit. \$\endgroup\$ – user1423893 Jun 16 '14 at 21:37
  • \$\begingroup\$ You either check to see if the object is null before accessing it, or you make sure you don't destroy the original object. \$\endgroup\$ – MichaelHouse Jun 16 '14 at 21:41
  • \$\begingroup\$ But the class itself has the code to self destruct and remove the game object. Do I have to check whether this is the original object and not a clone then. Can't the original object be deactivated in some way so it doesn't have to exist in the game world and only the instantiated clones exist? \$\endgroup\$ – user1423893 Jun 16 '14 at 21:43
  • 1
    \$\begingroup\$ Yes, you can use enabled = false for the script. It's also pretty unclear why you'd put a self destructing object as a instance you want to access anywhere. If you just want to instantiate copies of it, make it a prefab and load new ones on demand. \$\endgroup\$ – MichaelHouse Jun 16 '14 at 21:46
1
\$\begingroup\$

Just have your "single instance" type scripts take care of themselves:

Timer : MonoBehavior {
    static Timer instance;

    void Awake() {
        instance = FindObjectOfType(typeof(Timer))();
    }

    void i_TimeSomething() {
        //Do work
    }

    void static TimeSomething() {
        instance.i_TimeSomething();
    }
}

Now you can access them from anywhere with something like:

Timer.TimeSomething();

or

Timer.instance.OtherMethod();

I think, but I haven't tested it, you could create a base "single instance" class where you can have your other classes extend it, maybe something like:

SingleInstance : MonoBehavior {
    static SingleInstance instance;

    void Awake() {
        //not so sure about this
        instance = FindObjectOfType(this.GetType())();
    }
}

Then have your classes extend this:

Timer : SingleInstance {

    void i_TimeSomething() {
        //Do work
    }

    void static TimeSomething() {
        instance.i_TimeSomething();
    }
}

And access them the same way I described above.

\$\endgroup\$
  • \$\begingroup\$ I like the idea of single instance classes taking care of themselves, thank you. As for instantiating objects from a cache, is comparing the type T with GetType a bit icky? \$\endgroup\$ – user1423893 Jun 16 '14 at 21:08
  • \$\begingroup\$ Type checking like that can be spendy. Your current strategy has to do that multiple times, for every time you want to access your classes. \$\endgroup\$ – MichaelHouse Jun 16 '14 at 21:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.