I’m planning my game system, I have some ideas about level optimization and I’ll be grateful for some hints.
I have a large level that is generated procedurally. It’s divided into chunks, so we have e.g. a one big square-shaped level that consists of 25 quite big chunks (5x5).
Many different objects/monsters/npc’s spawn on top of it procedurally or randomly.
That obviously needs to be optimized. I have some ideas and some questions about them:
The game uses top-down view. It can’t be zoomed out too much, so there’s no need for LOD’s and distant object culling… Am I right? Do I need to cull distant objects even if they’ll be not visible? Not on screen == no drawcall, but maybe a large amount of (invisible) actors can be a problem?
Even if I’m right and I don’t need to cull invisible distant objects, I will certainly need to ‘cull’ their logic, if they have one. For example, I will need to disable distant AI of monsters, probably their MovementComponents, maybe animation blueprints, etc.
Now, I have some ideas on how to manage the culling:
Something like level streaming, but for procedural content. Level chunks have uniformly distributed box triggers that act similarly to level streaming volumes. Each trigger has a list of assigned actors and loads/unloads* them when player enters/leaves the trigger. The logic would be built to always have 9 ‘active’ trigger sections around a player, and cull the rest:
Player has e.g. Sphere trigger with big radius that loads/unloads* actors when they enter/leave the trigger. Would this hurt performance? Because the trigger would be big and when player moves, it would need to check against many actors very often.
*** Loading/unloading mechanic:** This is also something that bothers me. When ‘unloading’, is it better to disable any performance-consuming components (like AI, MovementComponent, Navmesh Invokers, etc.) or to destroy a whole actor?
… If it would be destroyed, then it would need to store some info on where to spawn it again when loaded. Maybe it could:
Spawn a special ‘UnloadedActor’ blueprint that will contain variables like location (its own location) and e.g. ‘ActorToSpawn’ variable.
Destroy the actor
When it needs to be loaded again, spawn the actor from specified variable. (Variant 2: List of structs [Location, ActorToSpawn] stored in corresponding level section)
That’s my ideas, what do you think? I’m still trying to figure out the best solution, maybe I miss some other, better way to accomplish this?
You should try also set up distance culling for objects that aren’t even close to being (partly) visible. Since that should be a less expensive check to make for the engine than frustrum culling.
I agree with Chariots for the loading/unloading mechanic you should NOT spawn and destroy actors. That is a really expensive operation in UE4.
If you decide to go the route of enabling/disabling components that have a huge impact on performance. Make sure to profile the performance gains, so you don’t make assumptions about which components cause the biggest performance hit.
“Run “stat startfile” in the console to start profiling and “stat stopfile” to stop. This should save a file to Saved/Profiling. Then you can open that file from the profiler tab in the session frontend.” Quote from Epic staff.
So instead of using triggers, every chunk would need to e.g. check distance to player every x seconds? I’m not sure how to define chunk boundaries without using triggers, the first thing that comes to mind is to have some ‘Chunk’ actors that would define chunk origin (location) and compare its distance to a player every X seconds…
The other thing is… How to detect that e.g. some monster/NPC moved to a different chunk without using triggers? Again, first thing that I can think of is distance check every X seconds, but it would need to check on every moving actor + we don’t know what chunks we should check against. That’s why my first idea was to use triggers, since they can process *anything *that enters/leaves them.
… But maybe there’s a better solution and just my weird brain is limiting me
Indeed that’s a good idea, I’ve also thought about having a list of assigned actors for every chunk + re-assigning actors that traveled to a different chunk.
Oh ok, I didn’t know that actor spawning is that heavy. I’ve checked it and read about object pooling, it’s interesting and seems doeable in Blueprints (I work mostly on BP’s, since I’m a C++ newbie). I see that maybe garbage collector could also cause some hitches when using destroy/spawn approach…
It’s surprising for me that spawning actors is heavier than going through full list of pooled actors to find an unused one and then filling out all the stored instance info, but I’m not so bright when it comes to lower level stuff. PS. I’m targeting a desktop platform, not mobile.
BTW. I’m curious on how the default UE Level Streaming system loads/unload actors. Does it destroy/spawn them or uses some kind of pooling?
Please bear with me, but I’m not sure if I understood the actor|logic separation idea correctly:
If I would use pooling, then I could create objects like ‘MonsterPooler’, ‘NPCPooler’, etc. that would store pool list of corresponding type (‘Monster’ type objects, ‘NPC’ type objects, etc.) with functions like ‘GetUnusedObject’. So when it needs to instantiate a Troll, it will search for Troll type object in the array that has ‘unused’ status (Maybe an [Object, bool] struct?). If not found, it will spawn it and add to the pool. Wouldn’t this be sufficient for pooling?
On un-cull, the chunk will instantiate a monster and set its stats from e.g. a ‘CulledMonsters_Stats’ struct, like You said.
Pooling system would need some function similar to ‘Spawn Actor From Class’ that would have ‘Instantiate with default values’ bool. Because when a monster get hit by something and now has only 1/2 of its HP and it’s suddenly culled, it will be added to a pool like that and later ‘spawn’ from the pool with 1/2 HP when it needs to be a new, fresh monster. I think about variable reset function in e.g. Monster class that will use ‘Get Class Defaults’](Get Class Defaults | Unreal Engine 5.2 Documentation).
When using pooling like this, I would also need to move any Begin Play logic into equivalent function called when the object is activated and taken from pool… Am I right?
Since there is no simple Deactivate/Activate function for Actors, I guess that I will need to create some custom ‘Deactivate’/‘Activate’ function that will set all the relevant components inactive/active. So it will deactivate when entering the actor pool with ‘unused’ status and activate when needed.
So… I should cull them manually by e.g. executing Set Hidden In Game for their components? But for culling system like this something would need to check what to cull/un-cull - so every actor would need to compare its distance from player every X seconds… Would this be really better than default frustum culling? Sorry, I don’t have much knowledge in this area.
Thanks for the hints!
No, just try using the cull distance volumes that are already in the engine. Since you have a top down view you can probably set them up quite aggressively. Just make sure that giant objects like buildings have enough distance that they don’t pop into view.
Thank you very much, I’ll look into it and try to implement some optimization system.
[HR][/HR]There is one problem though: It’s hard to deactivate an actor in UE. Even after deactivating all components, disabling tick & collision + setting ‘Hidden In Game’, it still eats Game ms.
For example, I spawn 200 characters in level, deactivate them and I get around 20 Game ms. When I don’t spawn them or destroy them, I get around 10 Game ms.
… And I can’t get any close to that 10 ms level after deactivating them. What I’ve done:
For ALL Components: Deactivate, Set Visibility to FALSE, Set Collision to ‘No Collision’
Set Actor Tick Enabled to FALSE, Set Actor Hidden In Game to HIDDEN, Set Actor Enable Collision to FALSE.
What else can be deactivated to truly deactivate an actor? I remember that in Unity there is a simple deactivation function that completely ‘shuts down’ any game object. I’m trying to recreate that…
For example, CPU Profiling (‘stat game’ command) says that ‘Char Movement Total’ eats up to 3ms (instead of 0.4ms when only player is active) even after I disable MovementComponent on ALL spawned characters! Something is wrong…
No, probably the closest thing to the Unity GameObject deactivation would be just destroying an actor, then spawning it again when needed… + Maybe some pooling implementation if you plan to do this very often.