I’m making a game with traffic and roads that could end up having a thousand or more traffic cars, using Chaos Vehicles and some vehicle AI to follow a line and evade other traffic, cop AI that sets up roadblocks, etc.
I’m going to assume actually spawning those cars all at once would be a bad idea, but I’m not sure what my options are to make it less so. Would thousands of cars still be problematic if their meshes were culled by distance or sight range, leaving only the physics components doing work? Would they still be problematic if physics were disabled and they just translated along a spline? Would the act of constantly checking distance and/or nearest spline point on thousands of actors itself be problematic?
I suppose my question is why spawning thousands of cars would be a bad idea, so I know what problem I’m actually trying to solve. (This needs to run on a potato, but the car meshes are low poly.)
Spawning/Destroying a lot of actors at once is and will always be a performance hit.
If you need that many actors you are better off using object pooling to negate the spawning/destroying hits. You’ll add a few seconds to level loading times though.
Object pooling is simply managing pre-loaded actors. Drop them in the level (below map is common). The obj pool manager will add each instance to an array. On request the manager provides a reference to one the array actors and removes it from the array. On return it adds it back to the array.
Reducing physics load depends on your game overall. Is it single player or multiplayer?
With multiplayer the server loads the entire map and all the actors in it. Not much you can do in a sweeping manner to reduce its load. You’d have to go granular.
For example a massive sphere trace on the actor to detect character proxies in range. If null, turn off physics etc OR return to pool.
For client performance you’d simply reduce the network cull distance and render cull distance.
In single player you’d simply teleport the vehicles to other areas as the client transitions into them. So instead of needing 1,000 vehicles you could potentially get away with a few hundred if managed right.
You could setup Grid volumes to detect transitions, then move the actors as needed.
2 Likes
Thanks for your suggestions! Based on your post, I made some changes.
I implemented empty proxy actors with no physics, components (other than a box collision and debug box) or custom properties; simulated their progress along the road by moving them every few seconds; and gave the player a sphere collision that interacts with them by destroying them and fetching a vehicle from the object pool. (Sphere trace seems to be incredibly slow compared to a sphere collision, so I went with that, wondering what the point of sphere trace is then.)
The “march of the cubes” made an immense difference, but not enough; going from 20 fps to 80 or so with placeholder scenery, so I limited the range in which the proxies would spawn to a “block” of 4km long; when the player is halfway into a block, the next block spawns, limiting the number of proxies on the map from tens of thousands to just a few hundred at most. There are some edge cases such as oncoming traffic becoming denser at higher speeds, but this is only relevant at block seams and probably unnoticeable.
Now uncapped FPS is a solid 140 or so on a 3070 with most of the load being the game thread and much of that being Find Location Closest to World Location and friends, which seems unavoidable when your traffic is physics enabled and needs to follow a spline. (?)
I considered changing road procgen to a block based system as well, but this is a bit harder: traffic splines require the road to exist and the road requires the road spline to exist, and rolling this out across 20 or 30 spline points takes real world time, which I think you can’t offload to another thread (loading times will be their own problem to solve eventually). It doesn’t seem entirely necessary though, UE5 seems to be pretty good about culling static objects out of clipping range.
The next challenge is when to clean up spawned traffic. You want to get rid of cars behind the player, but this requires frequent distance checks on dozens of actors, which turns out to be surprisingly heavy, and it gets more complicated in multiplayer where players may be so far behind other players that you don’t want to keep track of the 500 cars between them. I’ll need to investigate this.
Still plenty of work to do, but going from 20 to 140 fps is already massive. Thanks!
For the Sphere collision load I would create a custom collision channel for it. Have the players collision sphere only overlap that special collision. Ignore everything else except potentially visibility. This is what I do with special case sphere traces.
Also make use of Game Play Tags. For example… Loaded (Loaded.True, Loaded.False)
On overlap check the tag. No need to cast if you use the IGameplayTagAssetInterface
.
You’ll need C++ for this, but it’s very simple. Create a new C++ Actor class. Implement the interface, create a gameplay tag container variable and get owned tags.
Simply reparent your Actor classes to this new Actor and you’ll have GP tags ready to go.
For vehicles you’ll need to edit the Parent C++ class, OR duplicate that class and make the modifications…then reparent.
On overlap check for matching tag Loaded.True → then unload as needed.
If you could provide me with a vid showing the game play and some more details on what you want I’ll be able to get more specific. DM’s are open.