Working with non-uniform environment sprites (Game Object vs Tilemap)

I’ve been doing a bit of forum reading, and it appears as if I might be in for a very large reality check fairly soon…

In my project, my environments are very “non-uniform”, in that the sprites are placed in a sort of “freeform” mode, and not on any sort of grid, as a tile map could do.

To make matters worse, there are a lot of sprites that use custom materials. Most of the sprites in my project also use normal maps as a secondary texture. Therefore, I have just been placing these environment objects wherever I needed them, as separate game objects.

My plan was to enable/disable large chunks of these environment game objects whenever the player leaves a specific area, and then enable them again once the player comes back, but I fear this would only be a bandaid…

From what I am reading, it sounds like this may not be the best choice, performance-wise. At the moment, I’m looking at about 1400 game objects in the scene, with ~25% or so disabled at any given time (a lot more than this once the scene becomes larger). When all is said and done, however, I’m likely to have tens of thousands of environment sprites, each its own game object and with its own sprite renderer and (possibly) custom material.

I do have a few sprites that I could place into a tileset, but it would only be 2-3 individual sprites per tile set, at most, since the custom materials I am using vary greatly, and there are quite a few.

Just for reference, with the above number of game objects while using this disable/enable method, my FPS in an actual build varies anywhere from 400-800 FPS, depending on where the player happens to be. I will also mention that this is all running on a very powerful system. I do have a mid-tier laptop, which will be used for testing purposes later.

Some other things to mention here - the project’s resolution will be 1280x720, without the capability of changing it. The largest sprite at the moment is no larger than 512x512, with many environment sprites being smaller than this. I’m also going to be packing most of my sprites into sprite atlases in later builds, but have not done so yet.

Am I in for a rude awakening here, or can this still be done using game objects while using sprite atlases to minimize draw calls?

1st i would like to warn you that its taboo in these forums to ask about future performance issues and many people will come here soon to chastise you on that

2nd i will answer your question, yes it will destroy your performance in the future, the solution is to go a step further to what you are already doing. Dont stop at enabling/disabling when objects are out of range, you have to instantiate/destroy the objects that are not in sight(use object pooling for best results), think of it as a kind of loading and unloading of chunks like in minecraft ( this problem wont be solved just by using a large tilemap instead)

10k renderers - that alone is going to require a system to reduce their number. How far you need to go, however, is dependent upon your lowest-end target device.

It sounds like a mid-tier laptop is your closest match to that. To see if you’re in the ballpark, copy-paste your scene x10 over the place so it’s roughly at the complexity level you expect to have. Assuming your target device is only a bit worse than your laptop, assume that 2x+ worse milliseconds/frame is possible. Obviously this is a dumb heuristic; it would better to have an even lower-tier device. But that should get you started.

You need to use the Frame Debugger to study in detail what is breaking your batches. Adding sprites to a Sprite Atlas can’t solve every broken batch. Batches can break for a lot of reasons. Materials vary. Shaders vary.

You may be interested in the SRP Batcher because it excels at speeding up the situation where you have relatively few shaders but many variants. For 2D, the SRP Batcher just got support in the 2023.1 alpha. This old Unity blog posts explains how it works. It’s less brittle than dynamic batching which has stricter rules. It may be especially helpful if you can merge shaders together so their number is smaller.

Fixing broken batches involves merging materials/shaders, reducing their batch-breaking variations, atlasing textures, and more smartly organizing the scene so objects sort consistently in front/behind other objects.

Enabling/disabling chunks of objects would be a good first step. Pooling it further as flasker points out may be necessary after that.

What this suggests is that you’re unlikely to be GPU-bound unless your target device is very low end. Optimizing the CPU-side of the equation will be important and you should consult the Profiler for that. Reducing the number of batches saves you time on the CPU-side too.

You can always test if you’re GPU vs CPU bound by reducing the resolution further. If a lower resolution speeds up frame time, then you’re GPU bound.

Noted :slight_smile:

I’ll look into that, thanks!

That sounds like it might be very useful. Thanks for the heads up!

Now that’s something I hadn’t considered. Excellent idea.

Pooling is definitely planned, and I should have mentioned that, but thanks for reminding me :wink:

All great suggestions. Thanks guys!