Performance lag with a higher number of citizen AI

Hi all,

I am building a game set in a city and want to populate it with AI “citizens” that roam freely around the city. I have this setup currently but once I start approaching 100 citizens the game takes a serious performance hit. The citizens were implemented using Blueprints. I do not think the blueprint is particulary complex. I am looking for options on optimising them so I can make the city feel alive and densely populated. The blueprint essentially works off a timer and the GetRandomPointInNavigableRadious node, picking a new destination every so often and alternating their walk speed.

Initially I am thinking about reimplementing them using a combination of behaviour trees and C++.

Is there anything else I can do for optimisation here?

Thanks.

I would check the distance from the AI to the Player and disable the AI if the Player is far - basically, only allow the AI to run around the Player.

The character class itself is fairly unoptimized. You’ll get big performance problems with hundred+ characters running about.

Yes, you need to set up some kind of culling system that will disable AI when it’s out of range.
… It would be nice from Epic to optimize the Character class though, by the way :wink:

Yeah, use distance to remove AI that’s too far for the player to care about.

Sounds more like you have non-threaded AI. We can use melee combat with complex animations and +200 characters fighting. They all do Perception calls every 1 second.

If you are doing any kind of Perception call to check the surrounding environment, make sure you use a proper threaded way.

If you are not even using some kind of Perception but just have characters walking around using path-finding, then something else is wrong, or you have very very old hardware.

I’m going to copy and paste this here as I feel like the my reply to another question was similar. In addition to below, just think in terms of what you need, and how you can mathematically simplify behind the scenes or create an illusion. There’s a reason why pedestrians in Grand Theft Auto (which was programmed by a team of people with a lot of resources) eventually completely disappear/get removed from the game when they turn a corner or are far enough away.

As another example, Zombies in the Dead Rising series, are well, zombies and thus are easier to have multiple levels of detail/hierarchy in their AI based on distant to player (they only actually attack when super-nearby the player, and don’t all attack at the same time), as well as using some sort of animated skinned mesh instancing which is not by default implemented in UE4. Their navigation also probably uses some sort of simple Boyds/flocking algorithm for distant crowd movements, and actual pathfinding for the most active zombies to follow the player around corners. The latest games in the Hitman Series or Assassin’s Creed definiately use some sort of Boyds, hierarchical distance-based AI, and skinned instancing.

Actors/characters in Skyrim/Fallout 4 only have AI in a certain amount of active/fully loaded cells around the player, and switch to simple periodic math location updates if they’re traveling from one distance to another via their schedule that’s not in current active cells. The crowds in games like Fable/Oblivion or any arena game were animated cardboard-cutouts with a possible depth of field on them, with more modern versions of arena-scenarios being skinned instances.

Previous comment to similar thread:

"I’d honestly be wary of doing large groups of AI on a mobile device, the default Character and its associated path-finding isn’t exactly the most optimized for large groups of very active AI, even on PC having a blueprint-based character will be relatively complex to maintain 200+ AI on decent hardware. Nativizing blueprints to C++ on full game compile/packaging might speed things up considerably (there was a drastic difference at least for me when my AI was nativized) Running in PIE will be the slowest, running in standalone will be closer to actual performance, and running compiled/packaged will of course be the actual end-result in terms of speed.

If it’s a mobile game, you can spend time optimizing by doing things like disabling collision between certain actors, literally having AI be enabled/disabled per frame in groups or on a level-of-detail/distance basis where you do simple math to calculate around where the AI might of been if he was still “moving normally” while offscreen and update his location periodically, etc.

As an example, for more expensive routines I have in my AI I have a counter as it iterates through an array of my currently active AI actors, and stops iterating through the loop when the counter reaches it’s limit such as “20.” When completed it remembers the last index it left off on, and so when it runs the next frame the counter is reset, and it picks off at the last index it passed through in doing its routine. (Of course if the last index was the last index in the actual array, it resets to start off again from index 0 if not empty)

You can also possibly go the route of games like civilization where its actually just one actor/unit that has multiple character models or animations attached to it. X3 terran conflict by egosoft is a pretty good example of two different methods of AI that allow the game to simulate thousands of ships in the game universe. It literally has two methods, “in-sector” where the current streamed map/3d space the player is in is fully loaded with physics simulations and fully-loaded/most expensive ai, and “Out-of-sector” AI, where the game is doing mathematical simplifications to abstractly represent the ships moving towards their destinations as well as any possible battles/damage that might of occurred with other ships in its area. (Fun fact, certain ships perform better in-sector such as nimble fighters because of the speed at which they move, and perform poorly out of sector/get mathmatically eliminated quickly due to their total health/weapons being the few attributes taken into the simplified equation or vice-versa)

Along with AI, the most costly factors I’ve found in terms of AI are getting new paths/dynamic regeneration of paths, collision if a ton of actors bunch up (I can have 1000 physics models of spaceships fly around with thrusters and steering on my pc at 60fps, but when a bunch of them collide into each other at the same time, queue the major frame hit), anything with a large amount of entries in an array that need to be done in one frame (at least in blueprints), and the total amount of draw calls/materials/etc.

Keep in mind that bone count, skeletal animations and skeletal meshes being updated also contributes to the cost as well when dealing with large numbers of characters. Skeletal meshes being more expensive than static. I think I read somewhere that the Total Battle War Simulator or whatever it is called did kind of a hacked version of instanced static meshes in unity where instead of interpolated animations it’s just a bunch of instances cycling through a bunch of static models that are swapped out to give the illusion of animation, sort of like stop-motion, which is why when the game is in “slow-mo” the animations don’t seem smooth even at a high frame rate. The current penultimate solution for thousands of animated meshes would be skinned mesh instancing, which Nvidia released tech docs on, although that’s currently not a built-in feature of the engine.

Although from the little I’ve seen from your blueprints, I’d honestly recommend going through Epic’s video tutorial series on youtube (multiple free hour long presentations on things such as AI where they guide you through it). They have a pretty good series that could help you understand Epic’s practices with their currently-implemented solutions to AI such as how to use behavior trees. I’m pretty sure the AI-move to for example, only needs to be called once per action/destination, not multiple times every frame. When it comes to AI, it might be best to think of things in terms of functions, and triggers / Causes and Effects. Generally speaking and as a simplified example, it’s much better to say, call a function that does damage to an AI when it enters a context OnOverlap Event, instead of checking and telling the character to do something all the time per tick/frame."

Best of luck in whatever solution you go for!

2 Likes

Thanks for the great response! I am currently doing a line of sight check between the citizens and player and hiding the citizen if it is not seen. It lets citizens still walk around that are directly behind me so I may try to get them to be hidden too.

Is there some way I can set it to use threading?

Should I be creating an empty class and working from that instead?

Fantastic information you have provided jonimake, thanks! I will certainly save this thread and come back to look at some of the smoke and mirror methods you have shared once I get further into development.

Just to let you know I am building a plugin because of all this stuff

https://forums.unrealengine.com/unreal-engine/marketplace/1605253-utility-ai-plugin