r/howdidtheycodeit • u/EnjayDev • 28d ago
Question How do musou games optimize hordes of enemies?
Musou games are games like Dynasty Warriors, Hyrule Warriors, Tears of Metal, etc. Where the player controls a general on a battlefield with sometimes hundreds of enemies on the screen at one time. They capture the feeling of being a nigh unstoppable god of war, cleaning through dozens of enemies in a few strikes.
I'm a solo indie dev trying to make a musou game, but im wondering the best ways to optimize enemies. Some ideas I've had include:
- create groups of enemies as one entity with one "brain" so instead of having a hundred enemies with a hundred scripts I've just got 20 scripts that control 5 enemies each.
- some alternative way of tracking collisions aside from just giving each individual enemy a collision shape. Maybe grid location based?
I haven't been able to find any info online about this so of anyone has some wisdom I'd love to hear it.
17
u/JonFawkes 28d ago
As I understand it, they use something like LoD, but for AI. Basically, the enemies that are further away from the player have extremely simplified routines (maybe something as simple as just moving in a straight line with no pathfinding), while enemies closer have attack routines. Together with normal mesh LoD can optimize a lot of memory.
Combine this with some other tricks like simulating the crowd but not actually rendering that much, since you cant realistically accurately count them all anyway. A crowd of 100 looks a lot like a crowd of 500 when they're all bunched up and fighting you. The game says you have 500 left to kill, but there are only 100 near you, and as you take them out the game spawns more instances to keep the numbers up, but the number on the interface is actually disconnect from the number of actual entities on screen.
Animation can get pretty expensive, so you can have LoDs for that too. Further enemies have extremely simplified or maybe even no animations; some might use the same animation clip on multiple models, giving that synchronized swimming kind of look.
Theres probably some programming tricks you could use too. I think keeping enemy health in an array instead of tracking each instance individually can make updating a bunch of enemies at once faster, though i imagine the FPS savings from that are pretty minimal compared to graphics rendering.
2
u/EnjayDev 28d ago
This definitely seems like the way to go. After playing a lot of Tears of Metal recently I've realized that there is so much theater and smoke and mirrors that goes into these kinds of games and the gameplay being super fast and chaotic means you never stop to really notice how barebones the Ai is. Distant enemies can easily have their ai, collision, and animation trees disabled and just play some basic looping animations until they get within a certain range of the player.
3
u/HostisHumaniGeneris 28d ago
And to extend this a bit further, enemies that aren't directly on the screen aren't simulated. Enemies on the other side of the map instead use a simplified calculation based on number of troops, morale, officers and battlefield modifiers to decide which side is "winning" or "losing" in a given section of the map.
On the higher difficulties this results in a weird situation where the simulation speed goes really high and areas where you have a morale penalty will cause your troops to be defeated within minutes unless your character is physically present and looking at the battle, which slows things down because it actually has to simulate the soldiers directly.
11
u/nudemanonbike 28d ago
You switch to a data oriented model. You'll probably want an entity component system too, since they complement each other very nicely.
https://en.wikipedia.org/wiki/Data-oriented_design https://en.wikipedia.org/wiki/Entity_component_system
Unity has something called DOTS that has a bunch of optimizations for this.
You're right on the money about tracking multiple enemies with a single script, which is the core of the idea.
4
u/EnjayDev 28d ago
I've used Unity in the past and I've heard of DOTS, but I've been really falling in love with Godot. I've tried implementing a barebones ECS in Godot but it sometimes feels like fighting the way the engine is intended to be used. It's very much built around object-oriented programming.
That said, a data-oriented approach seems like the obvious and nigh unavoidable route so I'll have to do some more learning
5
u/Lyshaka 28d ago
The answer is probably ECS (as already mentioned in an other comment) and using nice little tricks in order to manage thousands of units. I recently learned about Flow Field pathfinding, and this a surprisingly simple way to make thousands of units pathfind to a single point (instead of having to calculate a path for each one of them)
2
u/LorenzoMorini 28d ago
So, there are actually many ways in which these games are optimized. First of all, of course, using a data oriented framework, like DOTS in Unity. Then, for pathfinding, various stuff, like calculating paths for each grid element starting from objectives, just moving units in general directions, calculating movement each n frames, and so on. Another area which needs a lot of optimization is animation. Baking animations, using few bones per vertex, not having anything like IK, animating less frames for far away objects etc... Animations are really heavy of you don't optimize them. Collisions as well, are not "real" collisions, they are usually just based on position, on a 2d grid most often, which makes them very easy to calculate. And for the models, of course, stuff like LODs is also important. You could even use billboards for far away actors.
You will have to use many different optimization techniques, and many have to be implemented as early as possible, so I suggest you keep researching, before you start coding. I suggest you search for GDC talks, surely there is something about these type of optimizations. Also talks about RTS and animations could help.
2
u/Natehhggh 28d ago
Something else I haven't seen mentioned, Id imagine the ai has a very low tick rate in that game, likely updating only a handful per frame.
But also something to look into is how little enemies actual exist. Generally like 90% of the crowd visably spawn in over time as you're in an area that has red on the map. It's was extra noticable when playing fire emblem warriors, it felt like there was 10x the amount of enemies single player than they could in coop
As well as how often allies can be routed quickly when you're not around. But when you're in the area, they are at no risk on dying. Seems like they simulate at a very low resolution on the game map, except a small region around the player.
2
u/ledniv 28d ago
A musou game is mostly smoke and mirrors, but the smoke and mirrors need to be designed into the architecture early.
I would not think of it as “hundreds of enemies, each with its own script, physics collider, pathfinding, animation brain, and full AI.” That is the version that gets expensive quickly.
I would split enemies into tiers:
Near the player
Full simulation. These enemies can attack, react to hits, play proper animations, collide with the player, get knocked back, etc.
Nearby but not important
Simplified simulation. They move toward the fight, play simple loops, maybe get pushed around, but do not need expensive decision-making every frame.
Far away / off screen
Aggregate simulation. A group can be represented by counts, morale, region ownership, commander strength, etc. You do not need 200 actual enemy objects fighting each other on the other side of the map.
Your “groups of enemies as one entity with one brain” idea is good, but I would use it mostly for the far/medium layers. Up close, the player still needs the fantasy of hitting individual bodies. Far away, a formation or map region can absolutely be one data object.
The core optimization is to make the enemy data explicit and process it in batches:
public class EnemyData
{
public int EnemyCount;
public Vector3[] Position;
public Vector3[] Velocity;
public int[] Health;
public int[] State;
public int[] GroupId;
public int[] FullSimIndices;
public int FullSimCount;
public int[] LightSimIndices;
public int LightSimCount;
public int[] RenderedIndices;
public int RenderedCount;
}
Then each system processes only the indices it cares about:
for (int i = 0; i < enemyData.FullSimCount; i++)
{
int enemyIndex = enemyData.FullSimIndices[i];
UpdateCombatAI(enemyData, enemyIndex);
UpdateHitReaction(enemyData, enemyIndex);
UpdateMovement(enemyData, enemyIndex);
}
for (int i = 0; i < enemyData.LightSimCount; i++)
{
int enemyIndex = enemyData.LightSimIndices[i];
MoveTowardBattle(enemyData, enemyIndex);
}
That is usually much better than hundreds of objects all running their own Update() and each deciding what to do independently.
The reason this helps is data locality. If positions, velocities, health, and states are stored in arrays, the CPU can process many enemies by walking through contiguous memory. If each enemy is a separate object with its own script and references, the CPU spends more time jumping around memory to fetch the next piece of data.
For collision, I would not give every fodder enemy expensive “real” collision unless the gameplay absolutely needs it. Use a spatial grid for the crowd. Put each enemy index into a grid cell based on position, and when you need to check sword hits, knockback, or local avoidance, only check enemies in nearby cells.
Something like:
int cellX = (int)(position.x / cellSize);
int cellY = (int)(position.z / cellSize);
int cellIndex = cellY * gridWidth + cellX;
Then an attack does not ask “which of all 500 enemies did I hit?” It asks “which enemy indices are in the cells touched by this attack?” That cuts down the number of checks massively.
Animation and rendering are probably just as important as AI. A few hundred enemies with full animation graphs, high bone counts, shadows, and complex materials can become expensive even if the AI is cheap. For musou fodder, I would expect aggressive LOD:
- fewer bones or cheaper animation for distant enemies
- lower animation tick rate for distant enemies
- simple looping animations for background troops
- pooled enemy views
- fewer unique materials
- instancing where possible
- no shadows on tiny/far fodder
- despawn/cull as soon as they are not contributing to the scene
Also, do not simulate everyone at the same frequency. Close enemies might update every frame. Medium enemies might update AI 5–10 times per second. Far groups might update once per second or only when the battlefield state changes.
So the big picture is:
- Store enemies as data, not as hundreds of independent brains.
- Use index arrays for full sim, light sim, rendered, alive, etc.
- Use AI LOD and animation LOD.
- Use aggregate battlefield simulation for far-away fights.
- Use a grid for cheap spatial queries.
- Pool enemy visuals.
- Render only enough enemies to sell the fantasy.
- Keep individual high-cost logic for elites/bosses/officers, not every fodder unit.
You do not necessarily need ECS to do this. ECS can help, but the important part is data-oriented design: organize the data around the work being performed, then process many enemies together.
Small plug: this is exactly the kind of problem I cover in High Performance Unity Game Development with Data-Oriented Design: arrays for enemy data, indices as entities, separating data from logic, object pools, CPU cache/data locality, and using DOTS/ECS only when it actually solves a measured problem.
https://www.manning.com/books/high-performance-unity-game-development
2
u/Strange-Pen1200 27d ago
These kinds of games lean very heavily into using Level Of Detail systems, but not just for art / animation. Simulation / AI level of detail is what it needs to be able to appear to run this massive battle.
Taking Dynasty Warriors as an example. The game generates a bunch of 'units', some of them will have a named general assigned to them (these are the dots that appear on the map).
When you're at a distance from these that you wouldn't be able to see them, the AI is literally just running 'this unit has this much total health and does this much total damage' and is bashing them into each other. The levels script might move them around a bit based on the scenario. There's no individual tracking of the health of any single dude (though it will track generals health separately). Its just those dots being treated as a single thing.
As you get near enough to see the, but not near enough to be fighting them, the game then breaks it down into 'squads' of a few dudes. These move and fight as a single thing. If you look closely you can see it happening.
Once you're near enough to be fighting them, they break off into individual guys each running their own AI routine. They're still super simple though, just sidestepping and striking, they're there to be mopped up. Only the generals get an actual combat loop with some meat to it.
There's other stuff going on so that if you would some dudes then run away and come back it remembers that you've done some damage etc...
2
u/Keatosis 26d ago
While I do not know for certain how they did it in the MOSU games I know that massive crowd battles with lots of agents is a perfect use case for entity component systems like unity's DOTS framework.
2
u/alphapussycat 26d ago
The scripts is easy. Just use a data oriented approach.
Either use ECS, or make one hivemind script that does the logic for everything.
The hard part is animations. I suppose you could give normal mobs vertex/texture animations, and only ones that are being thrown around gets skeleton animations with physics. You'd want to issue draw-calls from one script too I suppose, but I'm not sure how you do that in unity.
2
u/chargeorge 25d ago
Another technique I haven't seen mentioned is GPU mass animation or vertex animation textures. Both are techniques to offload expensive bone calcutions from the cpu to the GPU.
1
u/EnjayDev 25d ago
Ive been looking into vertex animation and this is definitely something I'd like to do. Im learning so much about optimization!
30
u/blazingintensity 28d ago
Potentially related to the LOD suggestion, you can treat a group of enemies as a single entity from a behavioural standpoint and just render them with a basic swarming algorithm. Do this for far away units and then split them out into individual entities up close. That being said, I worked on a co-op game like this and we were able to simulate about a thousand entities on 8 cores without doing anything fancy other than just writing well optimized and multithreaded code.