I’m developing a first prototype for a Roblox game that heavily utilises Humanoid movement, in addition to Pathfinding and spatial searches (i.e. what NPCs or objects are nearby?).
Addressing the problems separately, the primary performance questions I need to address are:
- How can I minimise the number of paths needing to be calculated?
- What is the performance impact of the ‘MoveTo()’ call itself?
- What is the performance impact of the Humanoid characters themselves?
- What is the performance impact of the animations on the Humanoids?
- How can I optimise spatial searches to reduce the number of objects being searched, and how much data needs to be searched for each one?
In this article, I’m only covering the MoveTo question. It gives a good baseline for what we can hope for in terms of optimisations when using default Roblox Humanoids.
Initial Test
These are my initial findings, each time I ran the script, I started a new testing window in Studio.
Here is the code used to generate this graph:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local unit = ReplicatedStorage:FindFirstChild("unit")
local PhysicsService = game:GetService("PhysicsService")
local RunService = game:GetService("RunService")
local StatsService = game:GetService("Stats")
local spawnLocation = Vector3.new(0, 5, 0) -- spawn location
local moveToLocation = Vector3.new(60, 0, 60) -- moveTo location
local function spawnNPCs(count)
local npcs = {}
for i = 1, count do
local newUnit = unit:Clone()
newUnit.HumanoidRootPart.CFrame = CFrame.new(spawnLocation)
newUnit.Parent = workspace
table.insert(npcs, newUnit)
end
return npcs
end
local function moveNPCs(npcs)
local totalTimes = 0
local totalNpcs = #npcs
local completed = 0
local totalPhysicsUpdates = 0
local memoryUsageTable = {}
local function onStepped()
totalPhysicsUpdates = totalPhysicsUpdates + 1
-- also add current memory usage to table
table.insert(memoryUsageTable, StatsService:GetTotalMemoryUsageMb())
end
local conn
conn = RunService.Stepped:Connect(onStepped)
for _, npc in ipairs(npcs) do
local startTime = tick()
npc.Humanoid:MoveTo(moveToLocation)
--print("Moving")
task.spawn(function()
--print("Waiting for movement finished")
npc.Humanoid.MoveToFinished:Wait()
local endTime = tick()
totalTimes = totalTimes + (endTime - startTime)
completed = completed + 1
--print("Movement finished")
end)
end
repeat wait() until completed == #npcs
conn:Disconnect(onStepped)
local averageMemory = 0
local tempTotal = 0
for _, memorySample in pairs (memoryUsageTable) do
tempTotal = tempTotal + memorySample
end
averageMemory = tempTotal / #memoryUsageTable
return {
averageTime = totalTimes / #npcs,
averageMemory = averageMemory / #npcs,
TotalPhysicsUpdates = totalPhysicsUpdates,
npcCount = #npcs
}
end
local function deleteNPCs(npcs)
for _, npc in ipairs(npcs) do
npc:Destroy()
end
end
local function runTest(i)
local results = {}
local npcs = spawnNPCs(i)
print("Npcs Spawned")
wait(10)
print("Attempting movement of NPCs")
local returnedStats = moveNPCs(npcs)
print("Completed")
deleteNPCs(npcs)
return returnedStats
end
local results = runTest(175)
print(results)
print(results.npcCount ..","..results.averageTime..","..results.TotalPhysicsUpdates)
The sampling is based off this graphic.

What are ideal values?
For Render steps per second: This can essentially be equated to FPS. It’s how often the character visibly updates it’s position & animations. I would say anything below 30 is an unacceptably slow update frequency. That puts us at somewhere around 70-80 NPCs as the hard limit for OK visual updates (everything here is happening on the server). Remember, there is no pathfinding happening, and all of the NPCs are navigating to the same point, from the same point.
Removing animations as a variable
For this next test, I removed the animations script (the default Roblox animation script, converted to a server script) from the character models. This should allow us to see how much of this performance drop is due to animations.
After removing animations we can see a very small improvement in performance, but towards the 150-200 range, it gets much closer to the point that it doesn’t really affect things.
I believe the studio ‘run’ test mode is likely factoring in both the clientside and serverside for performance, because it’s having to render the characters as well as do the server side processing.
Testing in a real environment
In the interest of time, I only tested the version without animations when using the Roblox player.
So, in game it does perform better, from a visual standpoint, in Studio the units all followed exactly the same path. As in, it looked like a single NPC moving. In-game, you can see each NPC moving, one after the other with small spacing, I’d assume this is due to latency between the server and the client.
There seems to be a heavy lag spike right at the start of the MoveTo, and then the movement did feel much smoother from the client, but I’d assume this is because the client itself isn’t using much processing, the load is on the server.
Further testing and how you can help
I’ve created a Roblox game that automatically runs various tests while you are in the server, and displays the data in charts.

Helping to contribute to the dataset on this is as simple as joining the game and sitting there. The tests happen automatically. The charts update every 20 seconds, so you should see your test on there. I also added a leaderboard (lol).
This uses BoatBomber’s Graph Module, along with a (really bad, but functioning) X axis I added in, and extrapolation between datapoints (as the module doesnt have this by default).
Leave a Reply