- What do you want to achieve?:
I’d like to figure out why my enemy NPC units which follow the path in my Tower Defense game spin 360 degrees and or look at the node behind them / act buggy.
- What is the issue?:
It’s spinning weirdly as it moves up the path, perhaps it looks back at previous nodes or the wrong nodes? Keeping the wrong track of nodes travelled?
https://i.imgur.com/qIO6xow.gif
- What solutions have you tried so far?:
I tried adding the correct node / next node the enemy is supposed to travel to, distance checks, direction checks, but I can’t seem to figure it out.
After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!
Here’s the Server Side Code:
local TweenService = game:GetService("TweenService")
-- Remote Events and Remote Functions
local SpawnedEnemy = game.ReplicatedStorage:WaitForChild("SpawnedEnemy")
local RequestPaths = game.ReplicatedStorage:WaitForChild("RequestPaths")
-- Spline Calculations
local tableOfPaths = {}
function DrawPath()
local NodesTable = {}
local tensionMain = math.random(75, 100) / 100
local counter = 0
local Points = {}
for i = 1, #game.Workspace.Points:GetChildren() do
Points[i] = game.Workspace.Points[i].Position
end
local NumPoints = #Points
for i = 1, NumPoints - 1 do
local p0 = Points[i - 1] or Points[1]
local p1 = Points[i]
local p2 = Points[i + 1]
local p3 = Points[i + 2] or Points[NumPoints]
for j = 0, 1, 0.05 do
local tension = tensionMain
local t = j
local t2 = t * t
local t3 = t2 * t
local p = (
(-tension * t + 2 * tension * t2 - tension * t3) * p0 +
(2 + (tension - 6) * t2 + (4 - tension) * t3) * p1 +
(tension * t - 2 * (tension - 3) * t2 + (tension - 4) * t3) * p2 +
(-tension * t2 + tension * t3) * p3
) * 0.5
counter = counter + 1
NodesTable[counter] = { Position = p }
end
end
return NodesTable
end
for i = 1, 100 do
tableOfPaths[i] = DrawPath()
end
function RequestPath()
return tableOfPaths
end
RequestPaths.OnServerInvoke = RequestPath
task.wait(5)
local Precision = 10^2
local ServerTableOfEnemies = {}
local EnemyId = 1
function SpawnNPC()
local EnemyInfo = {
["EnemyId"] = EnemyId,
["Node"] = 1,
["Speed"] = 11, -- Constant speed
["Path"] = math.random(1, 100),
["CFrame"] = CFrame.lookAt(game.Workspace.Points["1"].Position, game.Workspace.Points["2"].Position),
["Done"] = false,
["SyncTime"] = workspace.DistributedGameTime -- Initialized to current server time
}
ServerTableOfEnemies[EnemyInfo.EnemyId] = EnemyInfo
EnemyId = EnemyId + 1
local ClientEnemyInfo = {
[1] = EnemyInfo.EnemyId,
[2] = Vector3int16.new(EnemyInfo.CFrame.X * Precision, EnemyInfo.CFrame.Y * Precision, EnemyInfo.CFrame.Z * Precision),
[3] = EnemyInfo.Node,
[4] = EnemyInfo.Speed,
[5] = EnemyInfo.Path,
[6] = workspace.DistributedGameTime,
}
SpawnedEnemy:FireAllClients(ClientEnemyInfo)
end
-- Function to handle NPC movement
function HandleMovement(dt)
for i, Enemy in pairs(ServerTableOfEnemies) do
if Enemy.Done then
print("Server is Done ", game.Workspace.DistributedGameTime)
ServerTableOfEnemies[i] = nil
else
local path = tableOfPaths[Enemy.Path]
if path then
local currentNode = path[Enemy.Node]
local nextNode = path[Enemy.Node + 1]
local nodeAfterNext = path[Enemy.Node + 2]
if currentNode and nextNode then
local direction = (nextNode.Position - Enemy.CFrame.Position).Unit
local distanceToNextNode = (nextNode.Position - Enemy.CFrame.Position).Magnitude
-- Move NPC by constant speed
local moveDistance = Enemy.Speed * dt
local newPosition = Enemy.CFrame.Position + (direction * moveDistance)
-- Smooth transition and rotation
if distanceToNextNode > 0.01 then -- Check for small movements to avoid unstable rotations
local targetCFrame = CFrame.lookAt(newPosition, nextNode.Position)
Enemy.CFrame = Enemy.CFrame:Lerp(targetCFrame, 1) -- Use alpha = 1 for smooth transitions
end
-- Calculate how much distance remains after reaching the next node
local remainingDistance = moveDistance - distanceToNextNode
-- Check if the NPC has moved past the next node
if remainingDistance > 0 and nodeAfterNext then
local distanceToNodeAfterNext = (nodeAfterNext.Position - nextNode.Position).Magnitude
if remainingDistance >= distanceToNodeAfterNext then
-- Move to node after next
remainingDistance = remainingDistance - distanceToNodeAfterNext
Enemy.Node = Enemy.Node + 2
if path[Enemy.Node] and path[Enemy.Node + 1] then
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
Enemy.CFrame = CFrame.new(currentNode.Position)
print("Server: Moved to node after next: " .. Enemy.Node)
else
Enemy.Done = true
end
else
-- Move to next node
Enemy.Node = Enemy.Node + 1
if path[Enemy.Node] and path[Enemy.Node + 1] then
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
Enemy.CFrame = CFrame.new(currentNode.Position + (nextNode.Position - currentNode.Position).Unit * remainingDistance)
print("Server: Moved to next node: " .. Enemy.Node)
else
Enemy.Done = true
end
end
elseif remainingDistance > 0 then
-- Move to next node
Enemy.Node = Enemy.Node + 1
if path[Enemy.Node] and path[Enemy.Node + 1] then
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
Enemy.CFrame = CFrame.new(currentNode.Position + (nextNode.Position - currentNode.Position).Unit * remainingDistance)
print("Server: Moved to next node: " .. Enemy.Node)
else
Enemy.Done = true
end
end
-- Debug information
print("Server: Enemy ID: " .. Enemy.EnemyId .. " Position: " .. tostring(Enemy.CFrame.Position) .. " Node: " .. Enemy.Node)
else
print("Server: Error: currentNode or nextNode is nil for Enemy ID:", Enemy.EnemyId)
end
else
print("Server: Error: Path not found for Enemy ID:", Enemy.EnemyId)
end
end
end
end
-- Main Routine
local enemyCount = 1
task.spawn(function()
for _ = 1, enemyCount do
task.wait(0.1) -- Spawn delay between each unit
SpawnNPC()
end
end)
while true do
local dt = task.wait()
HandleMovement(dt)
end
Here’s the Client Side Code:
local Precision = 10^2
local function Lerp(a, b, alpha)
return math.clamp(a + ((b - a) * alpha), -500, 500)
end
local tableOfPaths = game.ReplicatedStorage:WaitForChild("RequestPaths"):InvokeServer()
local tableOfEnemies = {}
local function SyncMoveEnemy(EnemyPart, EnemyData)
local currentTime = workspace.DistributedGameTime
local path = EnemyData.Path
if path then
local currentNode = path[EnemyData.Node]
local nextNode = path[EnemyData.Node + 1]
local nodeAfterNext = path[EnemyData.Node + 2]
if currentNode and nextNode then
local direction = (nextNode.Position - EnemyPart.CFrame.Position).Unit
local distanceToNextNode = (nextNode.Position - EnemyPart.CFrame.Position).Magnitude
-- Move NPC by constant speed
local elapsedTime = currentTime - EnemyData.SyncTime -- Calculate time elapsed
local moveDistance = EnemyData.Speed * elapsedTime -- Multiplying by SyncTime
local newPosition = EnemyPart.CFrame.Position + (direction * moveDistance)
local newOrientation = CFrame.lookAt(newPosition, nextNode.Position)
EnemyPart.CFrame = newOrientation
-- Calculate how much distance remains after reaching the next node
local remainingDistance = moveDistance - distanceToNextNode
-- Check if the NPC has moved past the next node
if remainingDistance > 0 and nodeAfterNext then
local distanceToNodeAfterNext = (nodeAfterNext.Position - nextNode.Position).Magnitude
if remainingDistance >= distanceToNodeAfterNext then
-- Move to node after next
remainingDistance = remainingDistance - distanceToNodeAfterNext
EnemyData.Node = EnemyData.Node + 2
if path[EnemyData.Node] and path[EnemyData.Node + 1] then
currentNode = path[EnemyData.Node]
nextNode = path[EnemyData.Node + 1]
EnemyPart.CFrame = CFrame.new(currentNode.Position)
print("Client: Moved to node after next: " .. EnemyData.Node)
else
EnemyData.Done = true
end
else
-- Move to next node
EnemyData.Node = EnemyData.Node + 1
if path[EnemyData.Node] and path[EnemyData.Node + 1] then
currentNode = path[EnemyData.Node]
nextNode = path[EnemyData.Node + 1]
EnemyPart.CFrame = CFrame.new(currentNode.Position + (nextNode.Position - currentNode.Position).Unit * remainingDistance)
print("Client: Moved to next node: " .. EnemyData.Node)
else
EnemyData.Done = true
end
end
elseif remainingDistance > 0 then
-- Move to next node
EnemyData.Node = EnemyData.Node + 1
if path[EnemyData.Node] and path[EnemyData.Node + 1] then
currentNode = path[EnemyData.Node]
nextNode = path[EnemyData.Node + 1]
EnemyPart.CFrame = CFrame.new(currentNode.Position + (nextNode.Position - currentNode.Position).Unit * remainingDistance)
print("Client: Moved to next node: " .. EnemyData.Node)
else
EnemyData.Done = true
end
end
-- Debug information
print("Client: Enemy ID: " .. EnemyData.EnemyId .. " Position: " .. tostring(EnemyPart.Position) .. " Node: " .. EnemyData.Node)
else
print("Client: Error: currentNode or nextNode is nil for Enemy ID:", EnemyData["EnemyId"])
end
else
print("Client: Error: Path not found for Enemy ID:", EnemyData["EnemyId"])
end
end
function Decompile(EnemyData)
return {
["EnemyId"] = EnemyData[1],
["EnemyPosition"] = EnemyData[2],
["Node"] = EnemyData[3],
["Speed"] = EnemyData[4],
["Path"] = tableOfPaths[EnemyData[5]],
["SyncTime"] = EnemyData[6],
["Done"] = false,
}
end
game.ReplicatedStorage.SpawnedEnemy.OnClientEvent:Connect(function(EnemyData)
local ClientTableEnemyInfo = Decompile(EnemyData)
local EnemyId = ClientTableEnemyInfo["EnemyId"]
tableOfEnemies[EnemyId] = ClientTableEnemyInfo
local CFrameNoOrientation = CFrame.new(ClientTableEnemyInfo["EnemyPosition"].X / Precision, ClientTableEnemyInfo["EnemyPosition"].Y / Precision, ClientTableEnemyInfo["EnemyPosition"].Z / Precision)
local cloneClientSide = game.ReplicatedStorage.ClientSide:Clone()
cloneClientSide.Name = ClientTableEnemyInfo["EnemyId"]
cloneClientSide.Parent = game.Workspace.Mobs
cloneClientSide.Position = game.Workspace.Points["1"].Position
cloneClientSide:PivotTo(CFrame.new(cloneClientSide.Position, CFrameNoOrientation.Position))
SyncMoveEnemy(cloneClientSide, ClientTableEnemyInfo)
end)
function HandleMovement(dt)
for i, Enemy in pairs(tableOfEnemies) do
local clientTarget = game.Workspace.Mobs[Enemy["EnemyId"]]
if Enemy.Done then
print("Client is Done ", game.Workspace.DistributedGameTime)
tableOfEnemies[i] = nil
game.Debris:AddItem(clientTarget, 0)
else
local path = Enemy.Path
if path then
local currentNode = path[Enemy.Node]
local nextNode = path[Enemy.Node + 1]
local nodeAfterNext = path[Enemy.Node + 2]
if currentNode and nextNode then
local direction = (nextNode.Position - clientTarget.CFrame.Position).Unit
local distanceToNextNode = (nextNode.Position - clientTarget.CFrame.Position).Magnitude
-- Move NPC by constant speed
local moveDistance = Enemy.Speed * dt
local newPosition = clientTarget.CFrame.Position + (direction * moveDistance)
-- Smooth transition and rotation
if distanceToNextNode > 0.01 then -- Check for small movements to avoid unstable rotations
local targetCFrame = CFrame.lookAt(newPosition, nextNode.Position)
clientTarget.CFrame = clientTarget.CFrame:Lerp(targetCFrame, 1) -- Use alpha = 1 for smooth transitions
end
-- Calculate how much distance remains after reaching the next node
local remainingDistance = moveDistance - distanceToNextNode
-- Check if the NPC has moved past the next node
if remainingDistance > 0 and nodeAfterNext then
local distanceToNodeAfterNext = (nodeAfterNext.Position - nextNode.Position).Magnitude
if remainingDistance >= distanceToNodeAfterNext then
-- Move to node after next
remainingDistance = remainingDistance - distanceToNodeAfterNext
Enemy.Node = Enemy.Node + 2
if path[Enemy.Node] and path[Enemy.Node + 1] then
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
clientTarget.CFrame = CFrame.new(currentNode.Position)
print("Client: Moved to node after next: " .. Enemy.Node)
else
Enemy.Done = true
end
else
-- Move to next node
Enemy.Node = Enemy.Node + 1
if path[Enemy.Node] and path[Enemy.Node + 1] then
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
clientTarget.CFrame = CFrame.new(currentNode.Position + (nextNode.Position - currentNode.Position).Unit * remainingDistance)
print("Client: Moved to next node: " .. Enemy.Node)
else
Enemy.Done = true
end
end
elseif remainingDistance > 0 then
-- Move to next node
Enemy.Node = Enemy.Node + 1
if path[Enemy.Node] and path[Enemy.Node + 1] then
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
clientTarget.CFrame = CFrame.new(currentNode.Position + (nextNode.Position - currentNode.Position).Unit * remainingDistance)
print("Client: Moved to next node: " .. Enemy.Node)
else
Enemy.Done = true
end
end
-- Debug information
print("Client: Enemy ID: " .. Enemy.EnemyId .. " Position: " .. tostring(clientTarget.Position) .. " Node: " .. Enemy.Node)
else
print("Client: Error: currentNode or nextNode is nil for Enemy ID:", Enemy.EnemyId)
end
else
print("Client: Error: Path not found for Enemy ID:", Enemy.EnemyId)
end
end
end
end
while true do
local dt = task.wait()
HandleMovement(dt)
end
I’ll really appreciate it if someone could help me figure out what’s causing this issue, I’ve attached a copy of the place as well just in case you want to test and or see the logs of the units being spawned. Thank you.
prototypeMain27_WorksButSpinsWeirdly_1.rbxl (103.0 KB)