How to make a single script for npcs using collection service?

I new at using collection service, so i need help…

2 Likes
function setupNpc(npc: Model)
    --start AI loop for this NPC
    --other NPC stuff
end

function cleanupNpc(npc: Model)
    --free resources used by the NPC
end

for _, tagged in TagS:GetTagged(NPC_TAG) do
    setupNpc(tagged)
end
TagS:GetInstanceAddedSignal:Connect(setupNpc)
TagS:GetInstanceRemovedSignal:Connect(cleanupNpc)
1 Like

thank you, i will try. =) -------

i used this script but i got a problem. In theory, two NPCs should reach the point, but it turns out that one NPC reaches the middle of the path and stops, then the second goes to the middle of the path and stops, and the first goes to the point. How to fix it?(Used translator)

Script:

local Tags = game:GetService("CollectionService")
local PathfindingService = game:GetService("PathfindingService")
local plrinserver = false


game.Players.PlayerAdded:Connect(function() -- i use this thing because, script can`t find player, and my game is load +-10 seconds
	wait(10)
	plrinserver = true
end)

local distance = 999

function setupNpc(npc)
	print("settuping..")
	--start AI loop for this NPC
	--other NPC stuff
	local target
	
	-- detect distance between players and npc
	
	for i,p in ipairs(game.Players:GetChildren()) do
		if p:IsA("Player") then
			local dis = (p.Character:WaitForChild("HumanoidRootPart").Position - npc.HumanoidRootPart.Position).Magnitude
print(dis)
			if dis <= distance then 
				target = p
				print("nearest player: "..tostring(target))

			end
		end
	end
if target ~= nil then -- make npc walk to nearest player

		local path = PathfindingService:CreatePath()
		path:ComputeAsync(npc.HumanoidRootPart.Position, target.Character:WaitForChild("HumanoidRootPart").Position) -- Change "workspace.Part.Position" to your part
		local waypoints = path:GetWaypoints()

		for i, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				npc.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			end

			npc.Humanoid:MoveTo(waypoint.Position)
			npc.Humanoid.MoveToFinished:Wait()
		end
	end
end

function cleanupNpc(npc)
	--free resources used by the NPC
end


Tags:GetInstanceAddedSignal("NpcAI"):Connect(setupNpc)
Tags:GetInstanceRemovedSignal("NpcAI"):Connect(cleanupNpc)

while wait() do
	if plrinserver == true then
	for _, tagged in Tags:GetTagged("NpcAI") do
	setupNpc(tagged)
		end
		end
end

settuping isnt a real english word btw, its just “setting up” :]

Pathfinding service just tends to do this, I personally if you want good and responsive NPCs I would use Vector3s and :MoveTo() to move NPCs. Pathfinding in general isnt effective and many developers have complained about its poor functionality- but youre on the right track!

This is almost certainly not what you want. Consider what happens the second time the loop runs when there’s a player in the server: setupNpc gets called again per NPC… and again and again. It should only be called once per NPC in it’s entire lifetime. Every time you call it, some resources are taken up. So you use e.g. more and more memory over time. This is called a memory leak and will “mysteriously” make your game run slower after 10 minutes, or 10 hours, and can be really hard to debug so it’s important to be mindful not to introduce memory leaks.

A fix could look like

while wait() do
	if plrinserver == true then
	    for _, tagged in Tags:GetTagged("NpcAI") do
	        setupNpc(tagged)
    	end
	end
    break
end

or

while not plrinserver do
    wait()
end
for _, tagged in Tags:GetTagged("NpcAI") do
	setupNpc(tagged)
end

or

game.Players.PlayerAdded:Once(function()
    -- Because script can`t find player, and my game is load +-10 seconds
	wait(10)
	for _, tagged in Tags:GetTagged("NpcAI") do
	    setupNpc(tagged)
    end
end)

The last one is best IMO because you don’t need a global variable, and all the logic is contained in a single block and doesn’t “mix” very much with any other logic.

The idea is to create a logic loop for each NPC in the setup function, something like

local TagS = game:GetService("CollectionService")
local PathfindingS = game:GetService("PathfindingService")

local aggroRange = 999

function findPlayerCFrame(player: Player): CFrame?
	if not player.Character then
		return nil
	end
	
	return player.Character:GetPivot()
end

function findNearestPlayer(npc: Model): (Player, number)
	local nearest, nearestDistance = nil, math.huge
	local npcPosition = npc:GetPivot().Position
	
	for _, player in game.Players:GetPlayers() do
		local playerCF = findPlayerCFrame(player)
		if not playerCF then continue end
		
		local playerPos = playerCF.Position
		local playerDistance = (playerPos - npcPosition).Magnitude
		
		if playerDistance <= nearestDistance then 
			nearest, nearestDistance = player, playerDistance
		end
	end
	
	return nearest, nearestDistance
end

function followPath(npc: Model, path: Path)
	local humanoid = npc:FindFirstChildWhichIsA("Humanoid")
	local waypoints = path:GetWaypoints()
		
	for _, waypoint in waypoints do
		if waypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		end
		
		humanoid:MoveTo(waypoint.Position)
		humanoid.MoveToFinished:Wait()
	end
end

function setupNpc(npc)
	print(`settuping {npc:GetFullPath()}`)

	-- Start AI loop for this NPC
	while wait(1) do
		--"Clean up" this infinite loop when the NPC should be cleaned up
		if not TagS:HasTag(npc, "NpcAI") then break end

		-- Target the nearest player in range
		local target, targetDistance = findNearestPlayer(npc)
		local targetCF = findPlayerCFrame(target)
		if targetDistance > aggroRange then
			target = nil
		end
		
		if not target then continue end 
		if not targetCF then continue end

		-- Walk to target
		local pathToTarget = PathfindingS:CreatePath()
		pathToTarget:ComputeAsync(npc:GetPivot().Position, targetCF.Position)
		
		followPath(npc, pathToTarget)
	end
end

function cleanupNpc(npc)
	--free resources used by the NPC
end

TagS:GetInstanceAddedSignal("NpcAI"):Connect(setupNpc)
TagS:GetInstanceRemovedSignal("NpcAI"):Connect(cleanupNpc)

game.Players.PlayerAdded:Once(function()
	-- Because script can`t find player, and my game is load +-10 seconds
	wait(10)
	for _, tagged in TagS:GetTagged("NpcAI") do
		setupNpc(tagged)
	end
end)

I applied 3 loop options and one big script:

  1. NPCs don’t move at all
  2. worked 50%, they stop on the spot and I know why. The problem is that the script must always process the position of the player, player cannot always stand still.
  3. Same thing
    (applied on mine and on yours)

Big script: same
(Used translator)

I found a script and remake it, it works, but it have bugs.
When npcs touch parts that heigher than npc, they stops.
Script:

local Tags = game:GetService("CollectionService")
local pathfindingService = game:GetService("PathfindingService")



function findNearestTorso(pos,npc)
	local list = game.Workspace:GetChildren()
	local torso = nil
	local dist = 10000
	local temp = nil
	local human = nil
	local temp2 = nil
	for x = 1, #list do
		temp2 = list[x]
		if (temp2.className == "Model") and (temp2 ~= npc) and (temp2.Name ~= "Zombie")  then
			temp = temp2:findFirstChild("HumanoidRootPart")
			human = temp2:findFirstChild("Humanoid")
			if (temp ~= nil) and (human ~= nil) and (human.Health > 0)  then
				if (temp.Position - pos).Magnitude < dist then
					torso = temp
					dist = (temp.Position - pos).Magnitude
				end
			end
		end
	end
	return torso
end

function pathfind2(Target,Hum,hroot)
	local pfs = game:GetService("PathfindingService")

	local enemytorso = Target
	local human = Hum
	local direct = Vector3.FromNormalId(Enum.NormalId.Back)
	local ncf = hroot.CFrame * CFrame.new(direct)
	local connection;
	direct = ncf.p
	local path = pfs:CreatePath()
	path:ComputeAsync(direct,enemytorso.Position)
	local waypoint = path:GetWaypoints()

	for index, WayPoint in ipairs(waypoint) do

			human:MoveTo(WayPoint.Position);
		if index > 2 then return end


	end
end


function checkcanpath(torso2,hroot)
	local rute = pathfindingService:CreatePath()
	rute:ComputeAsync(hroot.Position, torso2.Position)
	local rutePoints = rute:GetWaypoints()
	if #rutePoints > 0 then
		return true
	else

		return false
	end	
end 


function GetPlayersBodyParts(t)
	local torso = t
	if torso then
		local figure = torso.Parent
		for _, v in pairs(figure:GetChildren()) do
			if v:IsA'Part' then
				return v.Name
			end
		end
	else
		return "HumanoidRootPart"
	end
end


function getHumanoid(model)
	for _, v in pairs(model:GetChildren()) do
		if v:IsA'Humanoid' then
			return v
		end
	end
end

function setupNpc(npc)
	npc.HumanoidRootPart:SetNetworkOwner(nil)
	local zombie = npc
	local human = getHumanoid(zombie)
	local hroot = zombie.HumanoidRootPart
	local target = findNearestTorso(npc.HumanoidRootPart.Position,npc)
	if target ~= nil then
		pathfind2(target,human,hroot)



	end
end

function cleanupNpc(npc)
	--free resources used by the NPC
end


--Tags:GetInstanceAddedSignal("NpcAI"):Connect(setupNpc)
--Tags:GetInstanceRemovedSignal("NpcAI"):Connect(cleanupNpc)


--for _, tagged in Tags:GetTagged("NpcAI") do
--			setupNpc(tagged)
--end








while true do
	wait()
	for _, tagged in Tags:GetTagged("NpcAI") do
			setupNpc(tagged)
	end
end

Bugs:


one thing is, you are trying to access the ‘ClassName’ property, however you tried to access it as ‘className’ which does not work.

second, why don’t you just iterate over the list than use a for loop for counting

i’ve cleaned up your code a bit

local collectionService = game:GetService("CollectionService")
local pathfindingService = game:GetService("PathfindingService")

function findNearestTorso(pos,npc)
	local list = workspace:GetChildren()
	local torso
	local dist = 10000
	local temp
	local human
	local temp2
	for _, temp2 in list do
		if not temp2:IsA("Model") or not (temp2 ~= npc) or not  (temp2.Name ~= "Zombie")  then continue end
			temp = temp2:FindFirstChild("HumanoidRootPart")
			human = getHumanoid(temp2)
			if (temp ~= nil) and (human ~= nil) and (human.Health > 0)  then
				if (temp.Position - pos).Magnitude < dist then
					torso = temp
					dist = (temp.Position - pos).Magnitude
				end
			end
		end
	return torso
end

function pathfind2(Target,Hum,hroot)
	local enemytorso = Target 
	local direct = Vector3.FromNormalId(Enum.NormalId.Back)
	local ncf = hroot.CFrame * CFrame.new(direct)
	direct = ncf.Position
	local path = pathfindingService:CreatePath()
	path:ComputeAsync(direct,enemytorso.Position)
	local waypoints = path:GetWaypoints()

	for index, waypoint in waypoints do

			Hum:MoveTo(waypoint.Position)
		if index > 2 then break return end


	end
end


function checkcanpath(torso2, hroot)
	local route = pathfindingService:CreatePath()
	route:ComputeAsync(hroot.Position, torso2.Position)
	local waypoints = route:GetWaypoints()
	return #waypoints > 0
end 


function GetPlayersBodyParts(torso)
	if torso then
		local figure = torso.Parent
		for _, v in figure:GetChildren() do
			if not v:IsA("BasePart") then continue end
			return v.Name
		end
	else
		return "HumanoidRootPart"
	end
end


function getHumanoid(model)
    if not model:FindFirstChildWhichIsA("Humanoid") then return nil end
    return model:FindFirstChildWhichIsA("Humanoid")
end

function setupNpc(npc)
	npc.HumanoidRootPart:SetNetworkOwner(nil)
	local zombie = npc
	local human = getHumanoid(zombie)
	local hroot = zombie.HumanoidRootPart
	local target = findNearestTorso(npc.HumanoidRootPart.Position,npc)
	if target then
		pathfind2(target,human,hroot)
	end
end

--function cleanupNpc(npc)
	--free resources used by the NPC
--end

--collectionService:GetInstanceAddedSignal("NpcAI"):Connect(setupNpc)
--collectionService:GetInstanceRemovedSignal("NpcAI"):Connect(cleanupNpc)

coroutine.wrap(function()
while task.wait(1) do
	for _, tagged in collectionService:GetTagged("NpcAI") do
		setupNpc(tagged)
	end
end
end)()
1 Like

Everything works, only they stop when they descend (for example, from a platform onto the grass)

Plan:

add a PathfindingModifier