i’m working on a tower defense game, and i just finished writing a custom NPC handler.
what you’re seeing right now is 50 npcs being rendered and lerped on the client. NPCs can only move when their position changes and if they’re in view of the camera. there are no instances on the server; everything is pure data.
right now i’m trying to make it so npcs can move through these nodes (ascending to descending). however, i encountered a problem:
what the NPC is supposed to do is to first spawn on that red part and then traverses through the white parts (nodes). instead, it spawns and moves to the first node and completely stops.
(all the provided code is server-side)
here is my custom MoveTo function:
function Enemy:MoveTo(finishPosition, speed)
local duration = (finishPosition - self.Properties.Position).Magnitude / speed
local durationMultiplier = (1 / duration)
while ((finishPosition - self.Properties.Position).Magnitude > 0) do
local alpha = (duration * durationMultiplier)
self.Properties.Position = self.Properties.Position:Lerp(finishPosition, alpha)
--we set this to true to let the server know the enemy is moving
--and replicate the enemy's data to all clients
self.States.PositionChanged = true
task.wait()
end
end
and here is how i handle the nodes and enemy movement:
local speed = self.Properties.EnemyStats.WanderSpeed
local nodes = {}
for _, node in ipairs(workspace.Nodes:GetChildren()) do
nodes[#nodes + 1] = node
end
table.sort(nodes, function(nodeA, nodeB)
local nodeNumberA = string.match(nodeA.Name, "%d+")
local nodeNumberB = string.match(nodeB.Name, "%d+")
return tonumber(nodeNumberA) < tonumber(nodeNumberB)
end)
task.spawn(function()
for _, node in ipairs(nodes) do
local nodePosition = V3(node.Position.X, 0, node.Position.Z)
local distance = (self.Properties.Position - nodePosition).Magnitude
local duration = (distance / speed)
self:MoveTo(nodePosition, speed)
task.wait(duration)
end
end)
What is going on with your duration calculations?? You calculate duration, then divide 1 by it, then times that product by the original sum which will always return 1.
I’m not sure if thats the issue with why your code is not running, its ambiguous as to what task.wait is, but if you’re calling task.wait() inside the task itself, its looks like the code is waiting on itself to finish.
Failing that, spam prints to see where its failing. It seems to me like the code is yielding before the next node
after some quick edits, it works for the most part.
however, i want the movement to be instantaneous. when i tried to implement it, this happens:
based on what i could gather from the above clip (and a bunch of tests), i think the enemy “silently” moves through the nodes until the server tracks its position using the self.States.PositionChanged, and replicates the enemy’s position to all clients.
here is my updated code:
--from my custom MoveTo() function
local distance = (finishPosition - self.Properties.Position).Magnitude
local duration = (distance / speed)
duration /= 1 * duration
while ((finishPosition - self.Properties.Position).Magnitude > 0) do
self.Properties.Position = self.Properties.Position:Lerp(finishPosition, duration)
self:SetState("PositionChanged", true)
task.wait()
end
if self.States.PositionChanged then
self:SetState("PositionChanged", false)
end
--main code
local speed = self.Properties.EnemyStats.WanderSpeed
self.States.IsWandering = true
local nodes = {}
for _, node in ipairs(workspace.Nodes:GetChildren()) do
nodes[#nodes + 1] = node
end
table.sort(nodes, function(nodeA, nodeB)
local nodeNumberA = string.match(nodeA.Name, "%d+")
local nodeNumberB = string.match(nodeB.Name, "%d+")
return tonumber(nodeNumberA) < tonumber(nodeNumberB)
end)
for _, node in ipairs(nodes) do
local nodePosition = V3(node.Position.X, 0, node.Position.Z)
local distance = (self.Properties.Position - nodePosition).Magnitude
local duration = (distance / speed)
duration /= 1 * duration
--print(node)
--print(duration)
self:MoveTo(nodePosition, speed)
end
I don’t know how exactly you’re replicating the enemy position to client, the only thing I can think of at the moment is that you shouldn’t set PositionChanged to false in MoveTo function after while but after for loop because you want enemy to move without stopping.
Like this:
--from my custom MoveTo() function
local distance = (finishPosition - self.Properties.Position).Magnitude
local duration = (distance / speed)
duration /= 1 * duration
while ((finishPosition - self.Properties.Position).Magnitude > 0) do
self.Properties.Position = self.Properties.Position:Lerp(finishPosition, duration)
self:SetState("PositionChanged", true)
task.wait()
end
--main code
local speed = self.Properties.EnemyStats.WanderSpeed
self.States.IsWandering = true
local nodes = {}
for _, node in ipairs(workspace.Nodes:GetChildren()) do
nodes[#nodes + 1] = node
end
table.sort(nodes, function(nodeA, nodeB)
local nodeNumberA = string.match(nodeA.Name, "%d+")
local nodeNumberB = string.match(nodeB.Name, "%d+")
return tonumber(nodeNumberA) < tonumber(nodeNumberB)
end)
for _, node in ipairs(nodes) do
local nodePosition = V3(node.Position.X, 0, node.Position.Z)
local distance = (self.Properties.Position - nodePosition).Magnitude
local duration = (distance / speed)
duration /= 1 * duration
--print(node)
--print(duration)
self:MoveTo(nodePosition, speed)
end
if self.States.PositionChanged then
self:SetState("PositionChanged", false)
end