Making a zombie controller for multiple zombies!

Hello Devforum! Recently, I have been working on a small zombie project of my own and I am working on making one zombie controller script that controls all the zombies in the game. I used this video: Roblox - Controlling Multiple AI with One Script (advanced) - Scripting Tutorial - YouTube to help me make it, but after I finished, nothing was working and no print statements were printed.

Here is a single zombie script inside a zombie:

local zombie = script.Parent
local zomhumroot = zombie.HumanoidRootPart
local zomanoid = zombie.Zombie
local anim = zomanoid.Walk
local anim2 = zomanoid.Attack
local anim3 = zomanoid.Idle
local loadanim = zomanoid:LoadAnimation(anim)
local loadanim2 = zomanoid:LoadAnimation(anim2)
local loadanim3 = zomanoid:LoadAnimation(anim3)

function findtarget()
	local dist = 1000
	local target = nil
	for i, human in pairs(game.Workspace:GetChildren()) do
		if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
			local humanoid = human:FindFirstChild("Humanoid")
			local humroot = human:FindFirstChild("HumanoidRootPart")
			if humanoid and humroot and human ~= zombie then
				if humanoid.Health > 0 then 
					if (zomhumroot.Position - humroot.Position).magnitude < dist then
						dist = (zomhumroot.Position - humroot.Position).magnitude
						target = humroot
					end
				end
			end
		end
	end
	return target
end

while wait(1) do
	local humroot = findtarget()
	if humroot then
		zomanoid:MoveTo(humroot.Position, humroot)
		loadanim:Play()
		if (zomhumroot.Position - humroot.Position).magnitude < 5 then
			loadanim2:Play()
			humroot.Parent.Humanoid:TakeDamage(15)
		end
	else
		zomanoid:MoveTo(zomhumroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
		loadanim:Stop()
		loadanim3:Play()
		wait(3)
		loadanim3:Stop()
		loadanim:Play()
	end
end

And here is the current controller script that I am working on:

local collectionservice = game:GetService("CollectionService")

local zombies = {}

function spawner(func, param)
	local co = coroutine.wrap(func)
	co(param)
end

function findtarget()
	local humans = collectionservice:GetTagged("Human")
	
	for i, zombie in pairs(zombies) do
		local dist = 1000
		local target = nil
		local zomanoid = zombie.Humanoid
		local zomhumroot = zombie.HumanoidRootPart
		for i, human in pairs(humans) do
			if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
				local humanoid = human:FindFirstChild("Humanoid")
				local humroot = human:FindFirstChild("HumanoidRootPart")
				if humanoid and humroot and human ~= zombie then
					if humanoid.Health > 0 then 
						if (zomhumroot.Position - humroot.Position).magnitude < dist then
							dist = (zomhumroot.Position - humroot.Position).magnitude
							target = humroot
						end
					end
				end
			end
		end
		return target
	end
end

function movehandler(zombie)
	local zomanoid = zombie.humanoid
	local zomhumroot = zombie.humroot
	local anim1 = zombie.walkanim
	local anim2 = zombie.attackanim
	local anim3 = zombie.idleanim
	spawner(function()
		while wait(1) do
			local humroot = findtarget()
			if humroot then
				zomanoid:MoveTo(humroot.Position, humroot)
				anim1:Play()
				if (zomhumroot.Position - humroot.Position).magnitude < 5 then
					anim2:Play()
					humroot.Parent.Humanoid:TakeDamage(15)
				end
			else
				zomanoid:MoveTo(zomhumroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
				anim1:Stop()
				anim3:Play()
				wait(3)
				anim3:Stop()
				anim1:Play()
			end
		end
	end)
end

function taghuman(obj)
	local human = obj:FindFirstChildWhichIsA("Humanoid")
	if human then
		collectionservice:AddTag(human, "Human")
	end
	print("tagged "..obj)
end

function zombiebodyremoval(zombie)
	local index = table.find(zombies, zombie)
	table.remove(zombies, index)
	wait(2)
	zombie.char:Destroy()
end

function addzomb(zomanoid)
	table.insert(zombies, {
		char = zomanoid.Parent,
		humanoid = zomanoid,
		humroot = zomanoid.Parent.HumanoidRootPart,
		target = nil,
		walkanim = zomanoid:LoadAnimation(zomanoid.Walk),
		attackanim = zomanoid:LoadAnimation(zomanoid.Attack),
		idleanim = zomanoid:LoadAnimation(zomanoid.Idle)
	})
	for i, zombie in pairs(zombies) do
		if zombie.humanoid == zomanoid then
			zombie.humanoid.Died:Connect(function() zombiebodyremoval(zombie) end)
			for i, part in pairs(zombie.char:GetDescendants()) do
				if part:IsA("BasePart") and part:SetNetworkOwnership() then
					part:SetNetworkOwner(nil)
				end
			end
			spawner(movehandler, zombie)
			break
		end
	end
end

game.Workspace.ChildAdded:Connect(taghuman)

collectionservice:GetInstanceAddedSignal("Zombie"):Connect(function(zomanoid)
	addzomb(zomanoid)
	print("Added zomb tag to zomanoid")
end)

function initialize()
	for i, zomtag in pairs(collectionservice:GetTagged("Zombie")) do
		local found = false
		for i, zombie in pairs(zombies) do
			if zombie.humanoid == zomtag then
				found = true
				print("Found true")
			end
		end
		if not found then
			addzomb(zomtag)
			print("Added to zombie")
		end
	end
end

initialize()

I am not sure what I did wrong because no print statements are printing. Please help!

2 Likes

See if the script is running at all by putting a single print statement at the top of the script like: print("Hello World!")

The script is running. It did print the statement.

Put a print statement at the beginning of every function to see which ones are calling. Also, determine which functions should be calling at what time, in what order, so you can walk through the script’s logical steps and see what’s going wrong. Whenever there are no errors but something isn’t running, there’s usually some code you need to add or a piece of logic that needs to be changed.

I’m actually working on a fairly complex system myself. I might have to check this video out.

Except mine uses Object Oriented Programming to assign every Zombie it’s own controller and set of unique stats shared under a single concept.

It allows a ton more flexibility. For instance, I can replace one, or even a group, of zombies behavior with a new behavior. Like if a Queen Zombie walks past a Zombie it will cause the Zombie to stay near her and follow her.

And causing this unique behavior is as simple as replacing the AiOrder of the Zombie with a different AiOrder.

Whenever you’re doing something like you are currently doing, it’s highly recommended to use OOP concepts.

Anyways, did you set up your tags correctly? Like, did you give zombies the “Zombie” tag?

so your Initalize code is this

function initialize()
	for i, zomtag in pairs(collectionservice:GetTagged("Zombie")) do

You Probably want to switch collectionservice:GetTagged("Zombie") to a zombie folder.
You should also make sure you have at least one model tagged as a Zombie.
I use Tag Editor By Sweetheartichoke to view and edit tags

Notice how if there are no tagged items it wont execute the code:

for k,v in pairs(game:GetService("CollectionService"):GetTagged("unusedtag")) do 
   print("test")
end 

print("Fin") 

Output:
10:45:43.243  Fin

Ok, so I created a function in my script that tags the zombies.


function tagzomb(obj)
	if obj.Name == "Zombie" then
		collectionservice:AddTag(obj, "Zombie")
	end
	print("tagged "..obj.Name)
end

It prints that it tags the zombie, but I am not sure what to do next.

You can call the function outside of the loop and then the zombies should be tagged.

Ok so I have now…

Here is my current script. It still does not work, but there are more print statements printing.

local collectionservice = game:GetService("CollectionService")

local zombies = {}

function spawner(func,...)
	local co = coroutine.wrap(func)
	co(...)
end

function updatetarget()
	local humans = collectionservice:GetTagged("Human")
	for i, zombie in pairs(zombies) do
		local dist = 1000
		local target = nil
		local zomhumroot = zombie.HumanoidRootPart
		for i, human in pairs(humans) do
			if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
				local humanoid = human:FindFirstChild("Humanoid")
				local humroot = human:FindFirstChild("HumanoidRootPart")
				if humanoid and humroot and human ~= zombie then
					if humanoid.Health > 0 then 
						if (zomhumroot.Position - humroot.Position).magnitude < dist then
							dist = (zomhumroot.Position - humroot.Position).magnitude
							target = humroot
						end
					end
				end
			end
		end
		zombie.target = target
		print("targeted")
		return target
	end
end

spawner(function()
	while wait(1) do
		updatetarget()
		print("Updating target")
	end
end)

function walktotarget(zombie)
	local humroot = updatetarget()
	print("k")
	if humroot then
		print("Found humroot")
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(humroot.Position, humroot)
			zombie.walkanim:Play()
		end)
		print("Walking")
	else
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(zombie.humroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
			zombie.walkanim:Stop()
			zombie.idleanim:Play()
			wait(3)
			zombie.idleanim:Stop()
			zombie.walkanim:Play()			
		end)
		print("Random walking")
	end
end

function movementhandler(zombie)
	print("Movement")
	while wait(1) do
		if zombie.humanoid.Health <= 0 then
			print("noooooooo")
			break
		end
		print("no")
		if zombie.target then
			walktotarget(zombie)
			print("Movement controleld")
		end
	end
end

function attack(zombie)
	local human = zombie.target.Parent.Huamnoid
	human:TakeDamage(15)
	zombie.attackanim:Play()
end

spawner(function()
	while wait(0.5) do
		for i, zombie in pairs(zombies) do
			local human = updatetarget()
			if zombie.target then
				if (zombie.HumanoidRootPart.Position - human.Position).magnitude < 5 then
					attack(zombie)
					print("attacked")
				end 
			end
		end
	end
end)

function taghuman(instance)
	local human = instance:FindFirstChild("Humanoid")
	if human then
		collectionservice:AddTag(human, "Human")
	end
end

function tagzomb(instance)
	if instance.Name == "Zombie" then
		local zombie = instance.Zombie
		if zombie then
			collectionservice:AddTag(zombie, "Zombie")
		end
		print("done")
	end
end

function removezomb(zombie)
	local index = table.find(zombies, zombie)
	table.remove(zombies, index)
	wait(2)
	zombie.char:Destroy()
end

function addzomb(zomanoid)
	table.insert(zombies, {
		char = zomanoid.Parent,
		humroot = zomanoid.Parent.HumanoidRootPart,
		humanoid = zomanoid,
		target = nil,
		attackanim = zomanoid:LoadAnimation(zomanoid.Attack),
		walkanim = zomanoid:LoadAnimation(zomanoid.Walk),
		idleanim = zomanoid:LoadAnimation(zomanoid.Idle)
	})
	for i, zombie in pairs(zombies) do
		if zombie.humanoid == zomanoid then
			zombie.humanoid.Died:Connect(function() removezomb(zombie) end)
			for i, part in pairs(zombie.char:GetDescendants()) do
				if part:IsA("BasePart") and part:CanSetNetworkOwnership() then
					part:SetNetworkOwner(nil)
				end
			end
			spawner(movementhandler, zombie)
			print("walking")
			break
		end
	end
end

game.Workspace.ChildAdded:Connect(taghuman)
game.Workspace.ChildAdded:Connect(tagzomb)

collectionservice:GetInstanceAddedSignal("Zombie"):Connect(function(zomanoid)
	addzomb(zomanoid)
end)

function initialize()
	for i, tag in pairs(collectionservice:GetTagged("Zombie")) do
		local found = false
		for i, zomb in pairs(zombies) do
			if zomb.humanoid == tag then
				found = true
			end
		end
		if not found then
			addzomb(tag)
		end
	end
	for i, human in pairs(game.Workspace:GetChildren()) do
		taghuman(human)
	end
end

initialize()

I recognise the format of this code to be similar to this:
https://www.roblox.com/library/4694469710/Multi-NPC-Control

I don’t know if kangerujack is the true originator of this code, but it certainly works and is a good base to build from.

Howevre, the code expects all NPCs to be present at the startup of the script (as does yours) and doesn’t cater for incremental loading of them.

Do you have all your NPCs in the workspace at startup? If not, you need to have a game loop to Clone an NPC from storage, then use CollectionService to tag it with a “Zombie” tag. Your script above, will then see the newly tagged NPC and start controlling it.

I see. I do have a spawner script inside of a zombie spawner I made and added the little code to tag the zombies humanoid. It tags the zombies, but the zombies still do not move.

Spawner script:

local collectionservice = game:GetService("CollectionService")

for i, spawner in pairs(script.Parent:GetChildren()) do
	if spawner.Name == "Spawner" then
		while true do
			local zombieclone = game.ReplicatedStorage.Zombie:Clone()
			zombieclone.UpperTorso.CFrame = spawner.CFrame
			zombieclone.Parent = game.Workspace
			collectionservice:AddTag(zombieclone.Zombie, "Zombie")
			wait(3)
		end
	end
end

Controller script now:

local collectionservice = game:GetService("CollectionService")

local zombies = {}

function spawner(func,...)
	local co = coroutine.wrap(func)
	co(...)
end

function updatetarget()
	local humans = collectionservice:GetTagged("Human")
	for i, zombie in pairs(zombies) do
		local dist = 1000
		local target = nil
		local zomhumroot = zombie.HumanoidRootPart
		for i, human in pairs(humans) do
			if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
				local humanoid = human:FindFirstChild("Humanoid")
				local humroot = human:FindFirstChild("HumanoidRootPart")
				if humanoid and humroot and human ~= zombie then
					if humanoid.Health > 0 then 
						if (zomhumroot.Position - humroot.Position).magnitude < dist then
							dist = (zomhumroot.Position - humroot.Position).magnitude
							target = humroot
						end
					end
				end
			end
		end
		zombie.target = target
		print("targeted")
		return target
	end
end

spawner(function()
	while wait(1) do
		updatetarget()
		print("Updating target")
	end
end)

function walktotarget(zombie)
	local humroot = updatetarget()
	print("k")
	if humroot then
		print("Found humroot")
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(humroot.Position, humroot)
			zombie.walkanim:Play()
		end)
		print("Walking")
	else
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(zombie.humroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
			zombie.walkanim:Stop()
			zombie.idleanim:Play()
			wait(3)
			zombie.idleanim:Stop()
			zombie.walkanim:Play()			
		end)
		print("Random walking")
	end
end

function movementhandler(zombie)
	print("Movement")
	while wait(1) do
		if zombie.humanoid.Health <= 0 then
			print("noooooooo")
			break
		end
		print("no")
		if zombie.target then
			walktotarget(zombie)
			print("Movement controleld")
		end
	end
end

function attack(zombie)
	local human = zombie.target.Parent.Huamnoid
	human:TakeDamage(15)
	zombie.attackanim:Play()
end

spawner(function()
	while wait(0.5) do
		for i, zombie in pairs(zombies) do
			local human = updatetarget()
			if zombie.target then
				if (zombie.HumanoidRootPart.Position - human.Position).magnitude < 5 then
					attack(zombie)
					print("attacked")
				end 
			end
		end
	end
end)

function taghuman(instance)
	local human = instance:FindFirstChild("Humanoid")
	if human then
		collectionservice:AddTag(human, "Human")
	end
end

function removezomb(zombie)
	local index = table.find(zombies, zombie)
	table.remove(zombies, index)
	wait(2)
	zombie.char:Destroy()
end

function addzomb(zomanoid)
	table.insert(zombies, {
		char = zomanoid.Parent,
		humroot = zomanoid.Parent.HumanoidRootPart,
		humanoid = zomanoid,
		target = nil,
		attackanim = zomanoid:LoadAnimation(zomanoid.Attack),
		walkanim = zomanoid:LoadAnimation(zomanoid.Walk),
		idleanim = zomanoid:LoadAnimation(zomanoid.Idle)
	})
	for i, zombie in pairs(zombies) do
		if zombie.humanoid == zomanoid then
			zombie.humanoid.Died:Connect(function() removezomb(zombie) end)
			for i, part in pairs(zombie.char:GetDescendants()) do
				if part:IsA("BasePart") and part:CanSetNetworkOwnership() then
					part:SetNetworkOwner(nil)
				end
			end
			spawner(movementhandler, zombie)
			print("walking")
			break
		end
	end
end

game.Workspace.ChildAdded:Connect(taghuman)

collectionservice:GetInstanceAddedSignal("Zombie"):Connect(function(zomanoid)
	addzomb(zomanoid)
end)

function initialize()
	for i, tag in pairs(collectionservice:GetTagged("Zombie")) do
		local found = false
		for i, zomb in pairs(zombies) do
			if zomb.humanoid == tag then
				found = true
			end
		end
		if not found then
			addzomb(tag)
		end
	end
	for i, human in pairs(game.Workspace:GetChildren()) do
		taghuman(human)
	end
end

initialize()
print("initialized")

P.S. nothing from the walktotarget function prints. I tried adjusting my code more to the script inside of the multiple ai controller model.

The main NPC control script expects the “zombie” tag to be in the Humanoid itself. Change you tagging to this:

-- TAG NPC	
	local human = zombieClone:FindFirstChildWhichIsA("Humanoid")
	if human then
		collectionservice:AddTag(human,"NPC")
	end

You do have a Humanoid in your NPCs don’t you?

Yes I do have humanoids in my npcs.

So like this?

Script:

local collectionservice = game:GetService("CollectionService")

for i, spawner in pairs(script.Parent:GetChildren()) do
	if spawner.Name == "Spawner" then
		while true do
			local zombieclone = game.ReplicatedStorage.Zombie:Clone()
			zombieclone.UpperTorso.CFrame = spawner.CFrame
			zombieclone.Parent = game.Workspace
			local humanoid = zombieclone:FindFirstChildWhichIsA("Humanoid")
			if humanoid then
				collectionservice:AddTag(humanoid, "Zombie")
			end
			wait(3)
		end
	end
end

Yeah, that should be good. Does it work?

If it doesn’t work, can you confirm that the Zombie clones are being cloned in to the workspace. Do you see the print statement appear from this line?

collectionservice:GetInstanceAddedSignal("Zombie"):Connect(function(zomanoid)
	addzomb(zomanoid)
	print("Added zomb tag to zomanoid")
end)

Yes it does print!! Thank you!

Now the problem now is the code doesn’t work. I am not sure what is wrong with my code because I implemented it from the single zombie code. I feel like there is something wrong with the walking function though because it does not execute.

local collectionservice = game:GetService("CollectionService")

local zombies = {}

function spawner(func,...)
	local co = coroutine.wrap(func)
	co(...)
end

function updatetarget()
	local humans = collectionservice:GetTagged("Human")
	for i, zombie in pairs(zombies) do
		local dist = 1000
		local target = nil
		local zomhumroot = zombie.HumanoidRootPart
		for i, human in pairs(humans) do
			if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
				local humanoid = human:FindFirstChild("Humanoid")
				local humroot = human:FindFirstChild("HumanoidRootPart")
				if humanoid and humroot and human ~= zombie then
					if humanoid.Health > 0 then 
						if (zomhumroot.Position - humroot.Position).magnitude < dist then
							dist = (zomhumroot.Position - humroot.Position).magnitude
							target = humroot
						end
					end
				end
			end
		end
		zombie.target = target
		print("targeted")
		return target
	end
end

spawner(function()
	while wait(1) do
		updatetarget()
		print("Updating target")
	end
end)

function walktotarget(zombie)
	local humroot = updatetarget()
	print("k")
	if humroot then
		print("Found humroot")
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(humroot.Position, humroot)
			zombie.walkanim:Play()
		end)
		print("Walking")
	else
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(zombie.humroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
			zombie.walkanim:Stop()
			zombie.idleanim:Play()
			wait(3)
			zombie.idleanim:Stop()
			zombie.walkanim:Play()			
		end)
		print("Random walking")
	end
end

function movementhandler(zombie)
	print("Movement")
	while wait(1) do
		if zombie.humanoid.Health <= 0 then
			print("noooooooo")
			break
		end
		print("no")
		if zombie.target then
			walktotarget(zombie)
			print("Movement controleld")
		end
	end
end

function attack(zombie)
	local human = zombie.target.Parent.Huamnoid
	human:TakeDamage(15)
	zombie.attackanim:Play()
end

spawner(function()
	while wait(0.5) do
		for i, zombie in pairs(zombies) do
			local human = updatetarget()
			if zombie.target then
				if (zombie.HumanoidRootPart.Position - human.Position).magnitude < 5 then
					attack(zombie)
					print("attacked")
				end 
			end
		end
	end
end)

function taghuman(instance)
	local human = instance:FindFirstChild("Humanoid")
	if human then
		collectionservice:AddTag(human, "Human")
	end
end

function removezomb(zombie)
	local index = table.find(zombies, zombie)
	table.remove(zombies, index)
	wait(2)
	zombie.char:Destroy()
end

function addzomb(zomanoid)
	table.insert(zombies, {
		char = zomanoid.Parent,
		humroot = zomanoid.Parent.HumanoidRootPart,
		humanoid = zomanoid,
		target = nil,
		attackanim = zomanoid:LoadAnimation(zomanoid.Attack),
		walkanim = zomanoid:LoadAnimation(zomanoid.Walk),
		idleanim = zomanoid:LoadAnimation(zomanoid.Idle)
	})
	for i, zombie in pairs(zombies) do
		if zombie.humanoid == zomanoid then
			zombie.humanoid.Died:Connect(function() removezomb(zombie) end)
			for i, part in pairs(zombie.char:GetDescendants()) do
				if part:IsA("BasePart") and part:CanSetNetworkOwnership() then
					part:SetNetworkOwner(nil)
				end
			end
			spawner(movementhandler, zombie)
			print("walking")
			break
		end
	end
end

game.Workspace.ChildAdded:Connect(taghuman)

collectionservice:GetInstanceAddedSignal("Zombie"):Connect(function(zomanoid)
	addzomb(zomanoid)
	print("Added zomanoid")
end)

function initialize()
	for i, tag in pairs(collectionservice:GetTagged("Zombie")) do
		local found = false
		for i, zomb in pairs(zombies) do
			if zomb.humanoid == tag then
				found = true
			end
		end
		if not found then
			addzomb(tag)
		end
	end
	for i, human in pairs(game.Workspace:GetChildren()) do
		taghuman(human)
	end
end

initialize()
print("initialized")

It does not print k or found humroot or Walking.

There’s a couple of possible issues in the code but you will need to work through them. We know that CollectionService is seeing the NPC added event and that addzomb() is firing as you see the print statement which comes after it.

Next step is to debug the addzomb(zomanoid) function. I would add a few print statements in the ```for`` loop as I think this is the place where it is falling over next.

for i, zombie in pairs(zombies) do
	print("Zombie added to Table, checking Humanoid")
	if zombie.humanoid == zomanoid then
		print("Zombie Humanoid found")
		zombie.humanoid.Died:Connect(function() removezomb(zombie) end)
		for i, part in pairs(zombie.char:GetDescendants()) do
			if part:IsA("BasePart") and part:CanSetNetworkOwnership() then
				print("Set NetOwnership", part)
				part:SetNetworkOwner(nil)
			end
		end
		spawner(movementhandler, zombie)
		print("walking")
		break
	end
end

You can see that each point that the script makes a decision, I add a following print statement. It’s a great way to see if the script is making the decisions you expect and need it to do. I follow exactly the same procedure when debugging my own work when things look chaotic, it’s just an easy way to do it.
See how you get on and report back.
I could give you a working version of the script, but that would not help you to learn, so you can work out these things in the future. Sorry for being a pita.

1 Like

Every print statement is actually printing. It is probably the movement functions that aren’t working, but I am not sure where I went wrong.

If you paste the current code here then I will have a look later tonight and try and see what is wrong.

Okay thank you. I have been working on different ways I could do this, so it would be nice if you could look over the code. I will provide both the code that I have for a single zombie and the multiple zombie controller script that doesn’t work.

Single Zombie Script:

local zombie = script.Parent
local zomhumroot = zombie.HumanoidRootPart
local zomanoid = zombie.Zombie
-- do not worry about the animations
local anim = zomanoid.Walk
local anim2 = zomanoid.Attack
local anim3 = zomanoid.Idle
local loadanim = zomanoid:LoadAnimation(anim)
local loadanim2 = zomanoid:LoadAnimation(anim2)
local loadanim3 = zomanoid:LoadAnimation(anim3)

function findtarget()
	local dist = 1000
	local target = nil
	for i, human in pairs(game.Workspace:GetChildren()) do
		if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
			local humanoid = human:FindFirstChild("Humanoid")
			local humroot = human:FindFirstChild("HumanoidRootPart")
			if humanoid and humroot and human ~= zombie then
				if humanoid.Health > 0 then 
					if (zomhumroot.Position - humroot.Position).magnitude < dist then
						dist = (zomhumroot.Position - humroot.Position).magnitude
						target = humroot
					end
				end
			end
		end
	end
	return target
end

while wait(1) do
	local humroot = findtarget()
	if humroot then
		zomanoid:MoveTo(humroot.Position, humroot)
		loadanim:Play()
		if (zomhumroot.Position - humroot.Position).magnitude < 5 then
			loadanim2:Play()
			humroot.Parent.Humanoid:TakeDamage(15)
		end
	else
		zomanoid:MoveTo(zomhumroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
		loadanim:Stop()
		loadanim3:Play()
		wait(3)
		loadanim3:Stop()
		loadanim:Play()
	end
end

Multiple zombie controller:

local collectionservice = game:GetService("CollectionService")

local zombies = {}

function spawner(func,...)
	local co = coroutine.wrap(func)
	co(...)
end

function updatetarget()
	local humans = collectionservice:GetTagged("Human")
	for i, zombie in pairs(zombies) do
		local dist = 1000
		local target = nil
		local zomhumroot = zombie.HumanoidRootPart
		for i, human in pairs(humans) do
			if human:FindFirstChild("Humanoid") and human:FindFirstChild("HumanoidRootPart") then
				local humanoid = human:FindFirstChild("Humanoid")
				local humroot = human:FindFirstChild("HumanoidRootPart")
				if humanoid and humroot and human ~= zombie then
					if humanoid.Health > 0 then 
						if (zomhumroot.Position - humroot.Position).magnitude < dist then
							dist = (zomhumroot.Position - humroot.Position).magnitude
							target = humroot
						end
					end
				end
			end
		end
		zombie.target = target
		print("targeted")
		return target
	end
end

spawner(function()
	while wait(1) do
		updatetarget()
		print("Updating target")
	end
end)

function walktotarget(zombie)
	local humroot = updatetarget()
	print("k")
	if humroot then
		print("Found humroot")
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(humroot.Position, humroot)
			zombie.walkanim:Play()
		end)
		print("Walking")
	else
		spawner(function()
			wait(0.5)
			zombie.humanoid:MoveTo(zombie.humroot.Position + Vector3.new(math.random(-50,50),0, math.random(-50,50)), game.Workspace.Part)
			zombie.walkanim:Stop()
			zombie.idleanim:Play()
			wait(3)
			zombie.idleanim:Stop()
			zombie.walkanim:Play()			
		end)
		print("Random walking")
	end
end

function movementhandler(zombie)
	print("Movement")
	while wait(1) do
		if zombie.humanoid.Health <= 0 then
			print("noooooooo")
			break
		end
		print("no")
		if zombie.target then
			walktotarget(zombie)
			print("Movement controleld")
		end
	end
end

function attack(zombie)
	local human = zombie.target.Parent.Huamnoid
	human:TakeDamage(15)
	zombie.attackanim:Play()
end

spawner(function()
	while wait(0.5) do
		for i, zombie in pairs(zombies) do
			local human = updatetarget()
			if zombie.target then
				if (zombie.HumanoidRootPart.Position - human.Position).magnitude < 5 then
					attack(zombie)
					print("attacked")
				end 
			end
		end
	end
end)

function taghuman(instance)
	local human = instance:FindFirstChild("Humanoid")
	if human then
		collectionservice:AddTag(human, "Human")
	end
end

function removezomb(zombie)
	local index = table.find(zombies, zombie)
	table.remove(zombies, index)
	wait(2)
	zombie.char:Destroy()
end

function addzomb(zomanoid)
	table.insert(zombies, {
		char = zomanoid.Parent,
		humroot = zomanoid.Parent.HumanoidRootPart,
		humanoid = zomanoid,
		target = nil,
		attackanim = zomanoid:LoadAnimation(zomanoid.Attack),
		walkanim = zomanoid:LoadAnimation(zomanoid.Walk),
		idleanim = zomanoid:LoadAnimation(zomanoid.Idle)
	})
	for i, zombie in pairs(zombies) do
		print("Zombie added to table")
		if zombie.humanoid == zomanoid then
			print("Zomanoid found")
			zombie.humanoid.Died:Connect(function() removezomb(zombie) end)
			for i, part in pairs(zombie.char:GetDescendants()) do
				if part:IsA("BasePart") and part:CanSetNetworkOwnership() then
					print("Set ownership", part)
					part:SetNetworkOwner(nil)
				end
			end
			spawner(movementhandler, zombie)
			print("walking")
			break
		end
	end
end

game.Workspace.ChildAdded:Connect(taghuman)

collectionservice:GetInstanceAddedSignal("Zombie"):Connect(function(zomanoid)
	addzomb(zomanoid)
	print("Added zomanoid")
end)

function initialize()
	for i, tag in pairs(collectionservice:GetTagged("Zombie")) do
		local found = false
		for i, zomb in pairs(zombies) do
			if zomb.humanoid == tag then
				found = true
			end
		end
		if not found then
			addzomb(tag)
		end
	end
	for i, human in pairs(game.Workspace:GetChildren()) do
		taghuman(human)
	end
end

initialize()
print("initialized")

Thank you for your help so far @BadDad2004 it really helps!

Sorry it took a while as I was busy on other things. Your script as it stands fails to find a valid Player target.

I have reworked it a bit (a lot) as I was testing. Put this updated script into ServerScriptService and copy your Zombie to ServerStorage:

-- SERVICES
local serverstorage = game:GetService("ServerStorage")	-- Needed for Zombie Cloning
local CollectionService = game:GetService("CollectionService")

--VARIABLES
local zombieClone = serverstorage.Zombie	-- Zombie NPC Clone
local zombieFolder = workspace.NPCs			-- Workspace folder for Zombies in game - don't keep everything in Workspace
local zombies = {}							-- Table used to keep track of all alive zombies

--Used to create new threads easily while avoid spawn()
function spawner(func,...)
	local co = coroutine.wrap(func)
	co(...)
end

--------------------------
----Zombie AI Handling----
--------------------------
--Simple function for getting the distance between 2 points
function checkDist(part1,part2)
	if typeof(part1) ~= Vector3 then part1 = part1.Position end
	if typeof(part2) ~= Vector3 then part2 = part2.Position end
	return (part1 - part2).Magnitude 
end

--Loops through the human tag to find the closest valid target
function updateTarget()
	local humans = CollectionService:GetTagged("Human")
	for _,zombie in pairs(zombies) do
		local target = nil
		local dist = 200
		for _,human in pairs(humans) do
			local root = human.RootPart
			if root and human.Health > 0 and checkDist(root,zombie.root) < dist and human.Parent.Name ~= zombie.char.Name then
				dist = checkDist(root,zombie.root)
				target = root
			end
		end
		zombie.target = target
	end
end

--Target updating
spawner(function()
	while wait(1) do
		updateTarget()
	end
end)

--Called to have the zombie path towards it's current target
function pathToTarget(zombie)
	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(zombie.root.Position, zombie.target.Position)
	local waypoints = path:GetWaypoints()
	local currentTarget = zombie.target
	for i,v in pairs(waypoints) do
		if v.Action == Enum.PathWaypointAction.Jump then
			zombie.human.Jump = true
		else
			zombie.human:MoveTo(v.Position)
			spawner(function()
				wait(0.5)
				if zombie.human.WalkToPoint.Y > zombie.root.Position.Y then
					zombie.human.Jump = true
				end
			end)
			zombie.human.MoveToFinished:Wait()
			if not zombie.target then
				break
			elseif checkDist(currentTarget,waypoints[#waypoints]) > 10 or currentTarget ~= zombie.target then
				pathToTarget(zombie)
				break
			end
		end
	end
end

--Simple loop to handle the pathfinding function
function movementHandler(zombie)
	while wait(1) do
		if zombie.human.Health <= 0 then
			break
		end
		if zombie.target then
			pathToTarget(zombie)
		end
	end
end

function attack(zombie)
	local human = zombie.target.Parent.Humanoid
	human:TakeDamage(10)
	zombie.grabAnim:Play()
end

spawner(function()
	while wait(0.5) do
		for _,zombie in pairs(zombies) do
			if zombie.target then
				if checkDist(zombie.target,zombie.root) < 5 then
					attack(zombie)
				end
			end
		end
	end
end)

-------------------------------
----Zombie Table Management----
-------------------------------
-- Create new Zombie instance
local function cloneZombie()
	local newZombie = zombieClone:Clone()
	newZombie.PrimaryPart.Position = Vector3.new(math.random(-100, 100), math.random(-100, 100), math.random(-100, 100))
	newZombie.Parent = zombieFolder
	tagNewZombie(newZombie)
end

--Simple function to check instances for humanoid then tag them if found
function tagHuman(instance)
	local human = instance:FindFirstChildWhichIsA("Humanoid")
	if human then
		CollectionService:AddTag(human,"Human")
	end
end

--Simple function to check instances for humanoid then tag them if found
function tagNewZombie(instance)
	local human = instance:FindFirstChildWhichIsA("Humanoid")
	if human then
		CollectionService:AddTag(human,"Zombie")
	end
end

--Respawning that is tied the to the .Died event
function removeZombie(zombie)
	-- You could add stuff in here to reward points to the Player with leaderstats
	local index = table.find(zombies,zombie)
	table.remove(zombies,index)
	wait(1)
	zombie.char:Destroy()
end

--Adds Zombies to Zombies table, sets up respawning,
--and spawns a pathing thread for each zombie.
function addZombie(zombieHumanoid)
	table.insert(zombies,{
		char = zombieHumanoid.Parent,
		root = zombieHumanoid.RootPart,
		human = zombieHumanoid,
		target = nil,
		clone = zombieHumanoid.Parent:Clone(),
		grabAnim = zombieHumanoid:LoadAnimation(zombieHumanoid.Parent.Grab)
	})
	for _,zombie in pairs(zombies) do
		if zombie.human == zombieHumanoid then
			zombie.human.Died:Connect(function() removeZombie(zombie) end)
			for i,v in pairs(zombie.char:GetDescendants()) do
				if v:IsA("BasePart") and v:CanSetNetworkOwnership() then
					v:SetNetworkOwner(nil)
				end
			end
			spawner(movementHandler,zombie)
			break
		end
	end
end

--Checking each object in the workspace for a humanoid as it enters/respawns
workspace.ChildAdded:Connect(tagHuman)

--Whenever something is tagged as a zombie then we add it to our table of alive zombies
CollectionService:GetInstanceAddedSignal("Zombie"):Connect(function(zombieHumanoid)
	addZombie(zombieHumanoid)
end)

--Ran one time to add all current zombies in the workspace on run
function intialize()
	for _,v in pairs(CollectionService:GetTagged("Zombie")) do
		local found = false
		for _,x in pairs(zombies) do
			if x.human == v then
				found = true
			end
		end
		if not found then
			addZombie(v)
		end
	end
	for i,v in pairs(workspace:GetChildren()) do
		tagHuman(v)
	end
end

intialize()


-- Have constantly spawning Zombies from Server Storage
-- Just copy your Zombie to ServerStorage
wait(10)	 -- Let the server settle down as checks & tags the Players
-- ZOMBIE CLONE LOOP - Spawns a new Zombie every 10 seconds from ServerStorage
while true do
	cloneZombie()
	wait(5)
end

Let us know how you get on.

1 Like