Can't get mob to move toward player

Hi, essentially I can’t get a mob to move toward a player (for whatever reason). I’ve tried looking for sources and debugging but nothing has worked. The mob wont move, it just stands there. There are no errors.

Main.script:

local Players = game:GetService("Players")

local mob = require(script.Mob)
local map = workspace.DeadCenter

for wave=1, 5 do
	print("wave:", wave)
	if wave < 5 then
		mob.Spawn("Zombie", 3 * wave, map)
	end
	
	repeat
		task.wait(1)
	until #map.Mob:GetChildren() == 0
	
	print("wave", wave, "ended")
	task.wait(1)
end

game.Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function()
		mob.moveToPlayer(player, map)
	end)
end)--

Mob.modulescript: (inside of Main.script)

local ServerStorage = game:GetService("ServerStorage")
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local mob = {}

-- Spawning 
function mob.Spawn(name, quantity, map)
	local mobExists = ServerStorage.Mobs:FindFirstChild(name)

	if mobExists then
		for i=1, quantity do
			task.wait(0.5)
			local newMob = mobExists:Clone()
			newMob.HumanoidRootPart.CFrame = map.Start.CFrame
			newMob.Parent = map.Mob
		end
	else
		warn("Requested mob doesn't exist:", name)
	end
end

function mob.moveToPlayer(player, map)
	local mobs = game.Workspace.Mob:GetChildren()
	for _, mob in ipairs(mobs) do
		local humanoid = mob:WaitForChild("Humanoid")
		local playerCharacter = player.Character
		if playerCharacter then
			local humanoidRootPart = playerCharacter:FindFirstChild("HumanoidRootPart")
			if humanoidRootPart then
				local path = PathfindingService:CreatePath({
					AgentRadius = 1,
					AgentHeight = 3,
					AgentCanJump = true,
					AgentCanClimb = true,
				})
				path:ComputeAsync(mob.PrimaryPart.Position, humanoidRootPart.Position)
				path:MoveTo(mob.HumanoidRootPart)
			end
		end
	end
end

return mob

Firstly you are literally calling MoveTo on a path which will cause an error. Along with that you are telling it to stay in place by moving to it’s own HumanoidRootPart. Try to do this instead:

for i, waypoint in pairs(path:GetWaypoints()) do --get path to move to player
humanoid:MoveTo(waypoint.Position) -- move mob to each waypoint
end

Secondly CharacterAdded may sometimes not fire due to the script running after the character spawned, add this line before it:

if player.Character then -- if character existed already then move mobs to player
mob.moveToPlayer(player, map)
end

Last but not least these aren’t even the same destination, causing the script to not find any mobs

1 Like

Grassperson has already answered your question and this is more of a suggestion, however I would recommend you listen for the ChildRemoved event on your mob folder and check if every mob has been removed then instead of every second. That way you can avoid unnecessary script runtime and there won’t be a delay between the last mob being removed and the wave ending.

1 Like

‘player’ is an unknown global in Main.Script, even after

local mob = require(script.Mob)

How do I tell Main.Script that the function argument already exists?

Have you put the code inside PlayerAdded and before CharacterAdded?

Can you clarify on this, I don’t really understand what you’re asking

The problem lies within the following lines.

game.Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function()
		mob.moveToPlayer(player, map)
	end)
end)

By the time player.CharacterAdded fires the player parameter given by .PlayerAdded is going to be undefined since the Lua garbage collector removes it. You should instead take the character parameter passed by .CharacterAdded and pass it to game.Players:GetPlayerFromCharacter().

This is actually incorrect, I just noticed the compiler does not actually garbage collect the player parameter.

I’ve followed your steps and the mobs are still standing still. I’ll post the code-

main.script:

local Players = game:GetService("Players")

local mob = require(script.Mob)
local map = workspace.DeadCenter

for wave=1, 5 do
	print("wave:", wave)
	if wave < 5 then
		mob.Spawn("Zombie", 3 * wave, map)
	end
	
	repeat
		task.wait(1)
	until #map.Mob:GetChildren() == 0 --onChildRemoved
	
	print("wave", wave, "ended")
	task.wait(1)
end
	
game.player.PlayerAdded:Connect(function(player)
	if player.Character then -- if character existed already then move mobs to player
		mob.moveToPlayer(player, map)
	end
	player.CharacterAdded:Connect(function() -- character connected
		mob.moveToPlayer(player, map)
	end)
end)

mob.script:

local ServerStorage = game:GetService("ServerStorage")
local PathfindingService = game:GetService("PathfindingService")
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local mob = {}

-- Spawning 
function mob.Spawn(name, quantity, map)
	local mobExists = ServerStorage.Mobs:FindFirstChild(name)

	if mobExists then
		for i=1, quantity do
			task.wait(0.5)
			local newMob = mobExists:Clone()
			newMob.HumanoidRootPart.CFrame = map.Start.CFrame
			newMob.Parent = map.Mob
		end
	else
		warn("Requested mob doesn't exist:", name)
	end
end

function mob.moveToPlayer(player, map)
	local mobs = map.Mob:GetChildren()
	for _, mob in ipairs(mobs) do
		local humanoid = mob:WaitForChild("Humanoid")
		local playerCharacter = player.Character
		if playerCharacter then
			local humanoidRootPart = playerCharacter:FindFirstChild("HumanoidRootPart")
			if humanoidRootPart then
				local path = PathfindingService:CreatePath({ -- pathfind
					AgentRadius = 1,
					AgentHeight = 3,
					AgentCanJump = true,
					AgentCanClimb = true,
				})
				path:ComputeAsync(mob.PrimaryPart.Position, humanoidRootPart.Position)
				--path:MoveTo(mob.HumanoidRootPart)
				for i, waypoint in pairs(path:GetWaypoints()) do --get path to move to player
					humanoid:MoveTo(waypoint.Position) -- move mob to each waypoint
				end
			end
		end
	end
end

return mob

Looking at your code you are actually calling your .moveToPlayer() function once.

1 Like

moveToPlayer is being called in main.script, or am I incorrect?

Yeah it is but your mobs will only walk towards the player once and then stop moving since you’re only calling the function once.

1 Like

Oh, ok. If that’s the problem, what would be an optimised way of calling the function constantly? I don’t want to constantly do loops to keep calling the function, I presume that would cause performance issues.

I would recommend you only call your function every few frames to lower the amount of computations. You will have to find a balance between performance and update speed. The only way to update a path is to call :ComputeAsync() again.

1 Like

Also with ChildRemoved, I presume I will have to make a brand new function and connect it with checkChildren?

Yeah you will have to make a function to check if a wave has ended. You should also refactor your code to use recursion. When the wave ended function fires from the event it should fire a function to start the next wave.

1 Like

Okay I will make my own ChildRemoved later then, thanks
What would an example code be for updating :ComputeAsync() every few frames?