Editor freezes when using GameObject.Find()

I’m developing a simple strategy-based 2D mining game in Unity, and I’ve run into this strange issue where GameObject.Find() seems to crash the game and causes Unity’s memory usage to skyrocket.

public class Block : Tile
{
    // ...
    public static Tilemap collidableTilemap;
    public static Sprite[] blockSprites = new Sprite[2];
    public void Init(int id, bool isSolid, Vector3Int pos)
    {
        // ...
        collidableTilemap ??= GameObject.Find("Grid/Collidable Tilemap").GetComponent<Block>();

        Block.blockSprites[0] ??= Resources.Load<Sprite>("Tiles/Environment/Sprites/Background Stone");
        Block.blockSprites[1] ??= Resources.Load<Sprite>("Tiles/Environment/Sprites/Stone");
    }

}

I did my best to identify the potential problem lines, and out of those the ones I believe to be causing my issue are 6-9. My class extends the built-in Tile class, which itself inherits from ScriptableObject, IIRC. The procedural generator calls ScriptableObject.CreateInstance<Block>() once for every single tile, and then calls the object it returns’ Init() method. Our map is a circle 512 tiles in diameter, so this method gets called around 200K times. I’m not sure if that’s the reason for this. Everything else in the class I took out is very basic C# - nothing that should be resource intensive. And, since I used a null-coalescing assignment (if that’s what it’s called) then Find() and Resources.Load shouldn’t be called multiple times anyways.

In conclusion: those three lines of code cause Unity to freeze, a ton of memory usage - yet they should only be called once.

Any help would be greatly appreciated!

I think the answer is to not use GameObject.Find() seeing as that method is incredibly inefficient and slow. What it does is go through every GameObject in your hierarchy until it finds the result. So imagine the inefficiency of having method called that iterates through your hierarchy 200k times?

What is ‘collidable’ exactly and can you devise better methods of retrieving it?

1 Like

It looks like you do it on every tile on map?rip your cpu :slight_smile:

2 Likes

I would also suggest the Unity Profiler to your rescue to identify culprits like this.

1 Like

Oops, when I was cleaning up the code I changed “collidable” to “collidableTilemap” to try and make it more clear … they are the same thing in my code. There were a couple other things that I forgot to put into the snippet, I’ve fixed that now. As far as I now, wouldn’t the ??= mean Find & Load only get called once?

If it succeeds, yes. If it fails, it will try it every cell, and who knows how long that could take. And since GameObject.Find is notorious for failing on the slightest change, your code should definitely be written to survive GameObject.Find failure gracefully… or better yet, avoid GameObject.Find entirely.

Yeah, that’s what I’m thinking right now, too. GameObject.Find fails, returns null, and then creates a loop where every Block calls it, instead of just the first one generated.

Here’s something interesting: I tried w/ tags, which seems to have a very minor effect on memory usage, but more importantly - if I just wrap it in a Debug.Log it works completely fine.

            if (Block.k)
            {
                Block.collidable = GameObject.FindGameObjectWithTag("CollidableTilemap").GetComponent<Tilemap>();
                Block.k = false;
            }

“K” is a bool I created to ensure that the above code would run only once. Strangely enough, when you run the code above, but remove the assignments and just use Debug.Log, it works fine, so it must have something to do with the assignment of collidable.

Maybe you can try when you Instantiate your tile then add it to List and then when you want find it you loop only throught tiles in that list, or i dont know what are you doing with your collider but why dont you use OnCollisionEnter or something similar? And your Block.blockSprites[1] you can also setup in inspector before play on your tile prefab

I’m pretty new to Unity, so please excuse me if this is a basic question, but - say I was to drag and drop my Tilemap into a script. Would this field be the same for every object instantiated?

Depends where this is. If you mean your script file in your project files, anything you drag into the fields there will only be populated at the editor level; this doesn’t work in build or at run time.

Can the object that’s instantiating all these tile scriptable objects and calling your Init() method store and pass through a reference to your Tilemap? Should be the most straight-forward way if they both exist at the scene level.