Best way to handle large amounts of NPC units? Humanoids vs custom movement
I’m currently working on a MOBA-style game and I’m trying to work out the best architecture for handling a fairly large number of minions/units at once.
At the moment my units use Humanoids and mainly move through Humanoid:MoveTo(). Each unit has its own target, aggro state, attack cooldowns, lane/path information, etc.
The actual combat logic is contained inside ModuleScripts, so I don’t have a separate attack Script inside every NPC, but each unit still obviously has its own state and needs to be updated.
With smaller amounts of units everything is fine, but once a lot of minions are active and moving/fighting at the same time I start getting things like:
- Slight movement jitter/stuttering
- Units bunching together
- Movement becoming less smooth during fights
- General performance getting noticeably worse as the unit count increases
There can potentially be dozens of units active at once, and ideally I’d like the system to comfortably handle 100+ if possible.
My current setup is roughly:
Server
- Handles unit AI/state
- Target acquisition
- Attack logic/damage
- Lane movement
- Pathfinding when required
- Calls
Humanoid:MoveTo()for movement
Modules
- Attack/combat manager
- Targeting logic
- Other shared unit behaviour
Each unit still has its own state table, cooldowns, current target, etc.
I’m mainly wondering how people normally approach this in Roblox games with lots of NPCs, especially RTS/MOBA/tower defence games.
Is using a Humanoid for every unit actually reasonable at this scale, or would it be better to move towards something like AnimationController + custom movement?
I’ve also been considering:
- Moving NPC root parts manually instead of using
Humanoid:MoveTo() - Disabling most Humanoid states
- Setting server network ownership with
SetNetworkOwner(nil) - Having one central unit manager update every NPC rather than individual loops/connections
- Updating AI at something like 5-10hz instead of every frame
- Staggering/batching unit updates so every NPC isn’t processing on the same frame
- Client-side visual movement/interpolation while keeping the server authoritative
- Using simple steering/separation instead of relying heavily on physical collisions between units
- Only recalculating paths when actually necessary rather than continuously
I’m also curious how expensive Humanoids themselves are nowadays. Is the main issue normally the Humanoid, physics/collisions, pathfinding, replication, or simply having too many per-unit update loops?
For anyone who has built a system with 50-100+ moving NPCs, what setup ended up working best?
Would especially appreciate advice on how you split responsibilities between the server and client while still keeping combat authoritative.