NPC Immediately stops moving when another one spawns

Heya there.

I’m rewriting an OOP NPC System. There’s however an issue with the system. The issue being that whenever a new NPC spawns in when one NPC is in the workspace, the first NPC immediately stops all movement.

--[[SERVICES]]--
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")

--[[NPC MODEL FOLDERS]]--
local NPCFolder = game:GetService("ServerScriptService").NPCS
local NoobsFolder = NPCFolder.Noobs

--[[MODULE]]--
local BaseNPCModule = {}
BaseNPCModule.__index = BaseNPCModule

function BaseNPCModule:CreateNPC(UnitName, UnitTeam, Weapon, Special)
	--//Checking to see if the unit exists.
	for _, FindUnit in pairs(NoobsFolder:GetChildren()) do
		if FindUnit:IsA("Model") and FindUnit.Name == UnitName then

			--//Creating the unit.
			self.Unit = FindUnit:Clone()
			self.Humanoid = self.Unit:FindFirstChildWhichIsA("Humanoid")
			self.Unit.Parent = game.Workspace
			self.UnitTeam = game:GetService("CollectionService"):AddTag(self.Unit, UnitTeam)
			self.Is_Special = Special

			--//Giving it functionality
			if not self.Is_Special then
				--//Assuming it's just a basic NPC (E.g. Noob/Zombie Sworder)
				--//Otherwise, the NPC is going to inheritate only the FindNearestTargets and Pathfind functions.
				task.spawn(function()
					while task.wait(0.05) do
						task.spawn(function()
							local Target = BaseNPCModule:FindNearestTargets()

							if Target then
								BaseNPCModule:Pathfind(Target)
							end
						end)
					end
				end)
				
			end
		end
	end
end

function BaseNPCModule:FindNearestTargets()
	local Targets = {}
	local Nearest = math.huge
	local Target = nil

	for _, PossibleTargets in ipairs(workspace:GetChildren()) do
		if PossibleTargets:IsA("Model") and PossibleTargets:FindFirstChildWhichIsA("Humanoid") then
			local Humanoid = PossibleTargets:FindFirstChildWhichIsA("Humanoid")
			if Humanoid.Health <= 1 then
				--//They're dead.
				return
			end

			--//If they aren't dead, check the distance.
			local Distance = (PossibleTargets.PrimaryPart.Position - self.Unit.PrimaryPart.Position).Magnitude
			if Distance <= Nearest then

				if PossibleTargets:HasTag("Zombies") and self.Unit:HasTag("Noobs") then
					table.insert(Targets,{
						Magnitude = Nearest,
						FoundTarget = PossibleTargets
					})
				elseif PossibleTargets:HasTag("Noobs") and self.Unit:HasTag("Zombies") then
					table.insert(Targets,{
						Magnitude = Nearest,
						FoundTarget = PossibleTargets
					})
				end

			end
		end
	end
	
	for _, Entry in pairs(Targets) do
		local magnitude = Entry.Magnitude
		local target = Entry.FoundTarget
		
		if magnitude <= Nearest then
			Nearest = magnitude
			Target = target
		end
	end

	return Target
end

function BaseNPCModule:Pathfind(Target)
	local NewPath = PathfindingService:CreatePath({
		AgentRadius = 3,
		AgentHeight = 5,
		AgentCanJump = true,
	})
	
	NewPath:ComputeAsync(self.Unit.PrimaryPart.Position, Target.PrimaryPart.Position)
	if NewPath.Status == Enum.PathStatus.Success then
		local Waypoints = NewPath:GetWaypoints()

		for i, Waypoint in ipairs(Waypoints) do
			if i <= 2 then continue end
			if Waypoints[4] then
				self.Humanoid:MoveTo(Waypoints[4].Position)

				local TimeOut = self.Humanoid.MoveToFinished:Wait()
				if not TimeOut then
					warn("NPC got stuck... Recalculating!")
					BaseNPCModule:Pathfind(Target)
				end
			end
		end
	end
end

return BaseNPCModule

When you create an NPC, you’re not creating a new object profile for the new npc you’re creating.

function BaseNPCModule:CreateNPC(UnitName, UnitTeam, Weapon, Special)
	--//Checking to see if the unit exists.
	for _, FindUnit in pairs(NoobsFolder:GetChildren()) do
		if FindUnit:IsA("Model") and FindUnit.Name == UnitName then

			--// Creating the unit.
			-- // Still using the meta methods table as it's obj profile
			-- // So you're just replacing the last NPC you just created with this new one
			self.Unit = FindUnit:Clone()

Vs

function BaseNPCModule:CreateNPC(UnitName, UnitTeam, Weapon, Special)
	--// Checking to see if the unit exists.
	local unit = NoobsFolder:FindFirstChild(UnitName)
	if not unit then return end
	local self = setmetatable({}, BaseNPCModule) -- // The obj/profile
	--//Creating the unit.
	self.Unit = unit:Clone()
	-- ...
	return self
1 Like

Sorry for the late response, but I’ve tried implementing your solution to the code, now it keeps saying Attempt to index nil with "PrimaryPart" on the FindNearestTarget code.

For any direct calls you have like BaseNPCModule:FindNearestTargets(), BaseNPCModule:Pathfind(Target) and BaseNPCModule:Pathfind(Target), should replace BaseNPCModule with self.

If you’re making a methods table that’s for a class, typically you’d want to use self:foo(...) over methods:foo(...) even within the function declared.

function methods:Foo(a, b, c): any
	if not self.Target then
		self:Chase(self.Target)

---
obj:Foo()
--- // and not
methods:Foo()
-- // Unless you want to do
methods.foo(obj, a, b, c)

I recommend doing methods.foo(obj, a, b, c) on objs that you might have cleaned up before a process is finished. Otherwise use that method similar to how you’d do compositional programming.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.