Handling Off-Screen Objects Efficiently

Hey folks. This is my first time posting on the new forums. I hope I have this in the right place-- apologies, if not.

Anyway, I’m trying to deal with an optimization issue. My my game (2D, top-down, local multiplayer) has a fairly large number of objects on the map at any given time. It’s important that objects moving anywhere on the map are allowed to continue moving, but most things are relatively stationary (high drag/angular drag), so I don’t think that’s a huge concern. Rather, it’s the abundance of sprite renderers and (probably even more-so) objects with my custom classes for animation and changing sprite colours. I don’t want/need these things running their coroutines when they’re off-screen. As long as their collisions are functioning, they should be good.

My current set-up has a large trigger collider on the camera object. Any object that I want to “turn off” (not the GO, just the things I mentioned) has a script that responds to OnTriggerEnter/Exit callbacks from the camera. This works, and it’s an improvement over how the game was running previously. However, I have noticed the framerate dropping as the large objects break into increasingly smaller objects (think, “Asteroids”). I’m thinking that OnTriggerEnter/Exit might not be the best ways to handle this.

I have an alternative plan, but it’ll be a pain to switch to it, so I’m hoping to get some feedback before I proceed. First, I’ll completely do away with the existing script that responds to the camera trigger enter/exit. I’ll create an interface with OnCameraEnter and OnCameraExit, which can be implemented by my “space junk” and “stardust” objects. I’d have another class, CameraTriggerHandler which would use OnTriggerEnter/Exit (instead of every other object using their own) to add/remove objects to/from lists: objectsToEnable and objectsToDisable. Then, in Update (or maybe LateUpdate), I’d have it iterate through these lists to handle them (with a reliable call to the interface) before clearing the lists.

Any thoughts? I’ll provide more information if necessary/requested. Thank you for your time.

OnBecameVisible
OnBecameInvisible

3 Likes

Haha oh my God, seriously? How did I miss this? Thank you.

I’m not able to work this in at the moment (will do later for sure), but I wonder if I’ll get any sort of pop-in from objects entering the camera and then having their sprite-renderers enabled. The trigger collider that I use is sized to be a bit bigger than the actual camera to prevent this. Or maybe disabling sprite renderers is unnecessary altogether? Stopping my animations and colour change coroutines is probably worthwhile, though.

Thanks again.

Unity uses the renderer to determine if something is visible and so your sprites renderers will need to be always active. You’ll need to do tests to see if renderer based culling is better than physics based culling.

I was going to test it myself but quitting out of play mode with 250000 objects active takes too long. This seems to be an issue with 2D physics. I don’t get this problem with 3D physics.

BTW - if the “asteroids” aren’t important then it’ll be better to just spawn them ahead of the player as the player moves through the world and remove them when they go off screen.

1 Like

I’m curious, how many is “fairly large number” in digits?

What’s taking too long specifically? Are these primitives? 1/4 million bodies/colliders is huge and I’d be amazed you could even create that many GameObject/Components in a reasonable time.

I don’t see why 2D physics would be slower in this case unless you’re comparing apples/oranges in terms of primitives in 3D and PolygonCollider2D (multiple shapes) in 2D or something like that.

(OP: sorry for the off-topic!)

After hitting the stop button to return to the editor is taking too long. But I also tested to see if I could reduce the time by destroying objects through script before quitting. The time taken was the same and so I suspect it’s related to how the 2D physics engine removes the objects from its internal spatial data.

The gameObject has just a sprite renderer and a 2D box collider. Removing the 2D collider and replacing it with a 3D box collider reduces the time taken considerably. 80 seconds vs 3 seconds.

I’m using Unity 2021.3.

I had removed my comment on this but now that you mention it … it was about this: :exploding_head:

I’m surprised the editor doesn’t slow to a crawl beyond several 100k game objects. I once had 500k game objects and had to wait minutes before any button press responded.

2 Likes

Really?? These are not using a composite or anything?

I don’t think Box2D is that slow really so sounds like something else.

If you could send me an example project I’d be more than happy to figure out if something changed to cause this and get it fixed. If not, I’ll try some tests this week.

I instantiated the objects with this:

public GameObject prefab;
void Start()
{
    for (int x=0;x<500;x++)
        for (int y=0;y<500;y++)
            Instantiate(prefab,new Vector3(x*5,y*5,0),Quaternion.identity);
}

The reason I needed so many objects is because I was planning on testing the frustum culling and most of the objects would be off screen. So I needed lots of objects to have any impact on the frame rate.

They instantiate in a couple of seconds.

BTW - I’m not really a 2D guy and so I’ve nothing invested in this. It was just idle curiosity.

1 Like

So the prefab has Transform/SpriteRenderer2D/BoxCollider2D only?

Yep. But you could leave the sprite renderer out of it. So create an empty gameObject and add a 2D collider and you should see the “issue”.

Wow, that’s an insane number of objects.

My asteroids (and related “space junk”) needs to have active colliders for as long as they are alive. They basically create the layout of the map which is otherwise empty space. Also, the edges of the arena have teleporters that allow a sort of faux wrapping. This only works for ships and projectiles though. Other objects are pushed away.

I’m convinced that my biggest bottleneck is related to physics rather than rendering/coroutines, as I previously thought.

I found some really bad code that I wrote a few years ago through the profiler, so I completely re-worked a few functions. We’ll see how it goes now. I don’t get much time for game dev, so testing will be a tomorrow project.

It’s a little hard to say concretely because of how objects semi-randomly spawn in at the start of a match (effectively, creating the “map”), and some objects break into smaller objects. At most (at any given time), it’s likely in the hundreds, maybe very low thousands. From what I’ve read here, it isn’t an amount that should be an issue, really. They all have discrete collision, start asleep, and I even set their angular drag to be quite high when it makes sense to avoid unnecessary movement.