FPS Changing Walk Speed of Zombie (Tower Defense)

I’ve started making a tower defense project and I’ve encountered a bug that I do not know how to fix.

Basically, the higher I set the FPS, or the amount of position updates in a second of the zombie the slower it is.

Here’s a video that shows how slow 3 walkspeed becomes at a high value like 1080 FPS:

As you can see that is clearly not 3 walkspeed.

Here’s the whole movement function I use:

function Normal:StartMoving()
	local enemy = self
	local enemyModel: Model = self._model
	local humanoid: Humanoid = self._model:FindFirstChild("Humanoid")
	local root: BasePart = self._model:FindFirstChild("HumanoidRootPart")

	if not enemy or not enemyModel or not humanoid or not root then return end

	AnimateEvent:FireAllClients(humanoid, enemyModel.Animations.Move)
	
	--ClientLerpEnemyEvent:FireAllClients(enemyModel)

	for waypoint = 2, #workspace.Waypoints:GetChildren() do
		if not enemyModel then
			return
		end

		local previousPoint = workspace.Waypoints[waypoint - 1]
		local nextPoint = workspace.Waypoints[waypoint]

		enemy._movingTo.Value = workspace.Waypoints[waypoint]

		local CFrame1 = root.CFrame
		local CFrame2 = CFrame.new(CFrame1.Position, nextPoint.CFrame.Position) 

		for i=0,1,1 do
			root.CFrame = CFrame1:Lerp(CFrame2,i)
		end

		local PosDifference = nextPoint.Position - root.Position
		local Distance = PosDifference.magnitude

		local HumanoidCFrame = root.CFrame
		local newCFrame = CFrame.new(root.Position, nextPoint.Position) + PosDifference

		local speed = humanoid.WalkSpeed

		local timeToReach = Distance / speed
		local steps = math.ceil(timeToReach / (1 / FPS))

		for i = 0, steps do
			if enemyModel:FindFirstChild("HumanoidRootPart") then
				root.CFrame = HumanoidCFrame:Lerp(newCFrame, i / steps)
			else
				break
			end
			task.wait(1 / FPS)
		end
	end

	if enemyModel then
		enemyModel:Destroy()

		setmetatable(self, nil)
	end
end

I want the walkspeed to be the same at low FPS and high FPS.

If you could, please help me fix this bug.

2 Likes

sorry but can you quickly give me the entire part of the script that runs this function? if its a runservice loop, then the entire function for that part please

2 Likes

Sure, here’s my entire script:

local RS = game:GetService("ReplicatedStorage")
local Events = RS.Events

local AnimateEvent = Events:WaitForChild("Animate")
local ClientLerpEnemyEvent = Events:WaitForChild("ClientLerpEnemy")

local RigModel = script:WaitForChild("Normal")

local EnemyModule = require(script.Parent)

local ServerInfo = require(script.Parent.Parent.ServerInfo)

local FPS = ServerInfo.FPS

local Normal = {}
Normal.__index = Normal

function Normal.new()
	local self = setmetatable(
		{
			_model = RigModel:Clone(),
		}, Normal)

	return self
end

function Normal:Spawn()
	local model: Model = self._model
	local hum: Humanoid = model:FindFirstChildOfClass("Humanoid")

	if not model then
		return
	end

	local Config = EnemyModule.AddConfig(model, false)

	self._movingTo = Config.movingTo

	hum.Health = 4
	hum.MaxHealth = 4
	hum.WalkSpeed = 3

	model.Parent = workspace.Enemies

	model:MoveTo(workspace.Waypoints["1"].Position)

	task.spawn(function()
		self:StartMoving()
	end)

	hum.Died:Connect(function()
		model:Destroy()

		setmetatable(self, nil)
	end)
end

function Normal:StartMoving()
	local enemy = self
	local enemyModel: Model = self._model
	local humanoid: Humanoid = self._model:FindFirstChild("Humanoid")
	local root: BasePart = self._model:FindFirstChild("HumanoidRootPart")

	if not enemy or not enemyModel or not humanoid or not root then return end

	AnimateEvent:FireAllClients(humanoid, enemyModel.Animations.Move)
	
	--ClientLerpEnemyEvent:FireAllClients(enemyModel)

	for waypoint = 2, #workspace.Waypoints:GetChildren() do
		if not enemyModel then
			return
		end

		local previousPoint = workspace.Waypoints[waypoint - 1]
		local nextPoint = workspace.Waypoints[waypoint]

		enemy._movingTo.Value = workspace.Waypoints[waypoint]

		local CFrame1 = root.CFrame
		local CFrame2 = CFrame.new(CFrame1.Position, nextPoint.CFrame.Position) 

		for i=0,1,1 do
			root.CFrame = CFrame1:Lerp(CFrame2,i)
		end

		local PosDifference = nextPoint.Position - root.Position
		local Distance = PosDifference.magnitude

		local HumanoidCFrame = root.CFrame
		local newCFrame = CFrame.new(root.Position, nextPoint.Position) + PosDifference

		local speed = humanoid.WalkSpeed

		local timeToReach = Distance / speed
		local steps = math.ceil(timeToReach / (1 / FPS))

		for i = 0, steps do
			if enemyModel:FindFirstChild("HumanoidRootPart") then
				root.CFrame = HumanoidCFrame:Lerp(newCFrame, i / steps)
			else
				break
			end
			task.wait(1 / FPS)
		end
	end

	if enemyModel then
		enemyModel:Destroy()

		setmetatable(self, nil)
	end
end


return Normal

Sorry if it’s messy or bad, it’s my first time making a tower defense game by myself and didn’t really have an idea on how to make it.

For clarification, yes I’ve used a bit of ChatGPT for this part of the code:

local timeToReach = Distance / speed
		local steps = math.ceil(timeToReach / (1 / FPS))

		for i = 0, steps do
			if enemyModel:FindFirstChild("HumanoidRootPart") then
				root.CFrame = HumanoidCFrame:Lerp(newCFrame, i / steps)
			else
				break
			end
			task.wait(1 / FPS)
		end
	end
1 Like

is this being run on the client? it should be. what is FPS? what sets that?
image
this is the FPS im talking about
i saw you also get this
image
what is server info

2 Likes

image
It’s a module that basically stores and returns values like FPS, only FPS for now.
Oh and its not being run on the client it’s on the server, I’m planning on making it run on the client later on and making it update only 5 times a second on the server.

2 Likes

okay so this is kinda weird.

the way you want it to work, is that the enemies are first made on the server, but they would be like, an invisible part, and the server would tween them. this way the player doesnt see it. then, to make it smooth on the client, you would detect a remote event everytime a enemy is created. then you would spawn the model on the client, and you can either tween in, your lerp it. if you want to lerp it you can see the post i linked at the bottom so you can see how to make it frame independent, so it would be the same speed on every framerate. the server would run the attacking of the enemies, and when the server wants to shoot a bullet or smthn, it would use the invisible part.

1 Like

I’ll try that later when I have time, thank you for the detailed explanation though :smiley:

yeah no problem, right now if you dont feel like doing that. just lerp the zombie on the server. the performance may not be that good but it would look better than what you have right now :blush:

2 Likes

This is one weird code.

It should look like this instead of you are going for constant speed.

2 Likes

I’ve realized that.
Like, from the start I knew that fraction of the code was weird.
I’m quite young and not that good at math so thank you for linking this post, I’ll try learning from it.

1 Like

Hey, so I did everything in that post and this is my code:

		local startTime = tick()
		
		while true do
			
			local dt = task.wait()
			local dist = (nextPoint.Position - root.Position).Magnitude
			local speed = humanoid.WalkSpeed*dt
			local timeToReach = speed / dist
			
			if tick() - startTime >= timeToReach then break end
			
			local adjustedLerpAlpha = math.min(timeToReach,1)
			
			print(adjustedLerpAlpha, timeToReach, speed, dist, dt)
			
			root.CFrame = HumanoidCFrame:Lerp(nextPoint.CFrame, adjustedLerpAlpha)
		end

The problem is, this is what I’m getting in the console from printing all the values:

The adjustedAlpha is always 1, any way to fix this?

The walkspeed was 1000 when I’ve made the screenshot but at lower values it just doesnt print anything at all and doesn’t get past the if statement.

Nevermind I’ve found the issue myself no help needed. Sorry.