How can I make my zombies move easier in a Tower Defense game?

So I’m a new scripter and I’ve always wanted to make a tower defense game. Something I’m struggling with is making an easier/efficient way to make the zombies walk along the paths.

  1. What do you want to achieve?
    Use a more efficient way to make my zombies move around in a tower defense game i’m trying to making.

  2. What is the issue?
    The code I use sort of works but it’s not that good. I feel like I could do something that’s a lot more efficient. Here’s the code for example.

local hum = script.Parent:WaitForChild("Humanoid")
local torso = script.Parent:WaitForChild("Torso")


hum:MoveTo(game.Workspace.endingpart1.Position)
wait(6.8)
hum:MoveTo(game.Workspace.endingpart2.Position)
wait(5.8)
hum:MoveTo(game.Workspace.endingpart3.Position)
wait(7.3)
hum:MoveTo(game.Workspace.endingpart4.Position)
wait(15)
hum:MoveTo(game.Workspace.endingpart5.Position)

Here’s what the map is like.

  1. What solutions have you tried so far?
    I’ve looked at TheDevKing’s pathfinding video and saw you can make NPCs travel to another part’s location. I’ve also looked around the forum posts but they say stuff a little confusing to me because I’m a new scripter.

I accept constructive criticism

5 Likes

I would cframe them because Walking relies on physics which causes more lag,

and I would use AnimationControllers rather than Humanoids because Humanoids have extra details that you don’t need to work with, like Health, and the Head and HumanoidRootPart requirements

3 Likes

Yes, you can use a loop to automatically go through every node in the path. It could look something like this:

local nodes = {
    game.Workspace.endingpart1,
    game.Workspace.endingpart2,
    game.Workspace.endingpart3,
    game.Workspace.endingpart4,
    game.Workspace.endingpart5,
}

for _, node in ipairs(nodes) do
    hum:MoveTo(node.Position)
    local distance = (torso.Position - node.Position).Magnitude
    wait (distance / hum.WalkSpeed)
end

You can also generate the nodes table at run time instead of typing that out manually, e.g. by putting all the node parts in a Folder and using code like this:

local nodes = game.Workspace.Path1:GetChildren()
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)

This uses sorting and string pattern matching to find the number at the end of the part names, but that can be a lot to take in for a beginner. You can avoid the need for pattern matching by storing the number of each node separately, either in an Attribute or an IntValue. If you go with attributes, the code would look like this instead:

local nodes = game.Workspace.Path1:GetChildren()
table.sort(nodes, function(nodeA, nodeB)
     return nodeA:GetAttribute("PathNumber") < nodeB:GetAttribute("PathNumber")
end)

You could also get rid of the need to sort the list in the first place by using a different data structure, this comment is a bit long tho so I won’t go into detail with that unless you’re interested! :slight_smile:

Hope this helps, let me know if you’ve any questions

3 Likes

Where exactly would I put the script in? I currently have my own in the npc and whenever it gets cloned into the workspace from replicated storage that’s when the code starts.

Edit: for some reason every time a zombie gets to one of the ending parts, they wait like half a second then move to the next one. They also stutter a little when walking. I can show I video if you want me to

Would it be something like setting the primary cframe part then moving that part to where I want them to move?

I’d set the CFrame of the HumanoidRootPart or the root of the rigged model before the physics step
e.g.,

speed = 5 -- the dummy will move 5 studs per second
root =  workspace.Dummy.HumanoidRootPart.CFrame -- in humanoid rigs, HumanoidRootPart is the root because everything is rigged onto it, non-humanoid rigs may have a different root

-- Stepped fires before each physics step
-- timeSinceLastStep is the time in seconds since the last time .Stepped fired
game:GetService("RunService").Stepped:Connect(function(currentTime, timeSinceLastStep)
   -- this moves the root in the direction it's facing by (5 * timeSinceLastStep)
   root.CFrame = root.CFrame + (root.CFrame.LookVector * (timeSinceLastStep * speed))
   -- when the physics step triggers after this, the other parts of the rig will follow the root
end)
1 Like

“CFrame is not a valid member of CFrame” Line 8. I put the script in serverscriptservice by the way if that’s where it’s supposed to go.

mb, I meant root = workspace.Dummy.HumanoidRootPart without the .CFrame at the end
but also, read through and understand the script, it’s just an example of how to move a Dummy 5 studs per second in the direction it’s facing

How would I use it with the script I have currently?

local hum = script.Parent:WaitForChild("Humanoid")
local torso = script.Parent:WaitForChild("Torso")

local nodes = game.Workspace.Path1:GetChildren()
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
	hum:MoveTo(node.Position)
	local distance = (torso.Position - node.Position).Magnitude
	wait (distance / hum.WalkSpeed)
end

instead of hum:MoveTo(node.Position), you’d simulate the movement by moving the root of the rig until it passes node.Position

I think the general idea would be this

function MoveTo(part, start, target)
   startTime = os.clock()
   while part has not passed target
      local currentTime, timeSinceLastStep = RunService.Stepped:Wait()
      cframe part to where it should be from the start towards the target given the time since startTime
   cframe the part to the target because the loop made the part pass it by a bit
end

for _, node in ipairs(nodes) do
	MoveTo(root, node.Position)
end

It’s underlining the startTime in orange and says that its only used in MoveTo and I should consider changing it to local. Isn’t it already in MoveTo?

yes, it should be local
the error is saying that since it’s only used inside that function, we should make it local so it doesn’t exist outside the function

Soon I’ll be releasing my controller module, which allows you to compute and follow paths with the default humanoids without writing your own controller. It’ll make what you are trying to write take 2 lines instead of 50+. You can learn more here:

2 Likes

Is this supposed to be used on client or server?

it works on serverside

but if you really want to lower lag, you’d handle cframing the models on clientside and have serverside treat the npcs as data
the npcs could just be stored as a table that has {distance along track, health, associated model, etc}