Custom Humanoid efficiency

As you probably know, Humanoids in large numbers can cause a LOT of lag and so when you are making a game that needs a lot of units moving around you can run into some problems. In the game im working on a need a lot animals and so Ive made my own basic humanoid for them, it just includes basic gravity, being able to move up and down slopes and moving to any given point in a straight line

Ive successfully made my custom Humanoid for these units and have already seen drastic improvements from normal Humanoids. I was able to spawn 200 units on my baseplate and have them all move to the center without no-clipping through anything while still having everything running smoothly without lag. When i attempted to do this with regular Humanoids it lagged so much I could barely move the camera. So there is already some fair improvement.

However in the script performance tab under Activity it says im using 50% which is higher than id like. When I try this on my phone it runs ok, but there is some obvious lag, nothing game-breaking but its enough to be annoying to look at

This is my first time making something like this and Id like help optimizing my code for this so that it will run as efficiently as possible, thus more units. This is about 120 lines of code so I understand if you don’t want to take the trouble to analyze it but if anyone sees anything that could be improved please chime in with a reply, thanks for any suggestions!

The code

This code is in a server script calling functions from a module script in a Stepped event which I believe is being called every 1/60th of a second. The functions being called are applying gravity because the animals are anchored and they are updating the animals’ CFrames and trying to move them towards their target point every iteration

game["Run Service"].Stepped:connect(function()
	for x, animal in ipairs(workspace.Animals:GetChildren()) do


This code for the above functions is in a seperate module script shown below

local gravity = 1
local stepsPerSecond = 60
local lerpIterations = 10

--Applies gravity to the animal also makes the animal stop falling when it hits the ground and allows it to stay on top of a slope
function module.applyGravityAndFloorStops(animal)
	local animoid = animal.Animoid
	local animalCframe, animalSize = animal:GetBoundingBox()
	local gravityRay =,, -animalSize.Y/1.5, 0))
	local partHit, hitPoint = workspace:FindPartOnRayWithIgnoreList(gravityRay, animal:GetChildren(), false, true)
	--Detect if the animal is no longer on the ground
	if not partHit and animoid.Grounded.Value == true then
		animoid.Grounded.Value = false
	--Detect if the animal is moving into a slope
	if partHit and animalCframe.p.Y ~= (hitPoint.Y + animalSize.Y/2) then
		animoid.Grounded.Value = false
	--Apply gravity to the animal
	if partHit and not animoid.Grounded.Value then
		local distLeft = (animalCframe.p.Y - animalSize.Y/2) - hitPoint.Y
		animal:SetPrimaryPartCFrame(animal:GetPrimaryPartCFrame() -, distLeft, 0))
		animoid.Grounded.Value = true
	elseif not animoid.Grounded.Value then
		animal:SetPrimaryPartCFrame(animal:GetPrimaryPartCFrame() -, gravity, 0))
	return true

function distanceBetweenPoints(v1, v2)
	return math.sqrt(math.abs(v1.X - v2.X)^2 + math.abs(v1.Z - v2.Z)^2)

--Makes the animal walk in a straight line towards the point set in its Animoid Folder
function module.walkToPoint(animal)
	local animoid = animal.Animoid
	local point = animoid.WalkToPoint.Value
	local animalPosition = animal.PrimaryPart.Position
	point =, animalPosition.Y, point.Z)
	--Make sure the animal isn't already at the point, ignore Y-axis
	if animalPosition ~= point then
		--Make the animal face the point its moving to
		if animoid.Core.FaceLerpIteration.Value <= lerpIterations then
			local goalCframe =, point)
			local lerpCframe = animal:GetPrimaryPartCFrame():lerp(goalCframe, animoid.Core.FaceLerpIteration.Value/lerpIterations)
			animoid.Core.FaceLerpIteration.Value = animoid.Core.FaceLerpIteration.Value + 1
		local direction = (point - animal.PrimaryPart.Position).unit--The direction that the animal needs to travel in
		local distance = distanceBetweenPoints(point, animalPosition)
		local walkSpeed = animoid.WalkSpeed.Value/stepsPerSecond
		local increment = direction * walkSpeed
		local newCFrame = animal:GetPrimaryPartCFrame() + increment
		---Collision Detection---
		local _, animalSize = animal:GetBoundingBox()
		local ray =, direction * animalSize.Z/0.5)
		local partHit = workspace:FindPartOnRayWithIgnoreList(ray, animal:GetChildren(), false, true)
		--Check if we should increment regularly or just jump to the end position
		if distance > 0.2 and not partHit then
			animal:SetPrimaryPartCFrame(newCFrame)--Just increment the movement
		elseif not partHit then
			--Jump to the end position
			local rotation = animal.PrimaryPart.CFrame - animal.PrimaryPart.CFrame.p
			animal:SetPrimaryPartCFrame( *rotation)

I have a similar system in place but my update loop runs once every .4 seconds and what I do is I calculate the distance the NPCs would have traveled in that time and then I send it to the client, who then promptly lerps the positions to show a smooth movement.
Depending on what you plan you use the system .4 might seem too high or too low but by doing this you can greatly reduce the amount of iterations.


I tried out your method and it increased my max units by a TON. I got a bare bones version of it running and i can get 1000 units going at a time without lag, even on my phone. Theres still some extra validation and security stuff i gotta add which i imagine would decrease the number i can spawn but its still a huge improvement. Thanks