New to using CollectionService and only 1 instance responding

Hello! I’m messing around with collection service trying to make NPCs for my game. Thing is, i dont know what the hell im doing :skull:
Im trying to make the NPCs face the nearest player at all times, however, when i run the script i wrote only 1 of the NPCs follows the player while the others just stand there as if to mock me.

Heres evidence of the NPCs bullying me

and heres the code i put in ServerScriptService

local collectionService = game:GetService("CollectionService")
local players = game:GetService("Players")

local maxDistance = 30

for _, funnyMan in pairs(collectionService:GetTagged("Test")) do
	local humanoid = funnyMan:WaitForChild("Humanoid") or funnyMan.Humanoid
	
	while true do
		wait(0.01)
		local nearestPlayer, nearestDistance
		for _, player in pairs(players:GetPlayers()) do
			local character = player.Character
			local distance = player:DistanceFromCharacter(funnyMan.HumanoidRootPart.Position)
			if not character or 
				distance > maxDistance or
				(nearestDistance and distance >= nearestDistance)
			then
				continue
			end
			nearestDistance = distance
			nearestPlayer = player
		end
		if nearestPlayer then
			funnyMan.PrimaryPart.CFrame = CFrame.new(funnyMan.PrimaryPart.Position, Vector3.new(nearestPlayer.Character.PrimaryPart.Position.X, funnyMan.PrimaryPart.Position.Y, nearestPlayer.Character.PrimaryPart.Position.Z))
		end
	end
end

can someone tell me whats wrong with my script? thank.

The issue is not collection services in this script but the while loop preventing the rest of the for loop from running.

Put the while loop inside a function in task.spawn() to bypass this issue.

1 Like

To add on what @dthecoolest said, you can wrap the NPC look at logic in a local function without the while loop and connect it to RunService.Stepped instead. Sure, you can no longer use wait or task.wait, but it’ll still run per NPC without stopping the initial for loop.

Actually that would be less performant, as Stepped fires 1/x times per seconds where x is the user’s frame rate so typically it would run 1/60 times per seconds which is really expensive for NPC logic especially if you were to add more NPCs. On the other hand, a while loop which runs every 0.01 seconds i.e 1/10th of second is much more reasonable for this use case. Nvm I just realized that the while loop runs every 1/100th 1/30th of second which is overkill, but ye anyways OP change the frequency of the while loop to run every 0.1 seconds

The while loop runs every 1/30 of a second because they’re using wait()

Yep, someone already told me that in forums, i forgot to edit the post

do a

task.wait(1)

before you loop

or take everything from in the loop. Make it a function.
then put that function in the loop.

after the loop you do:

CollectionService:TagAdded("tag"):Connect(yourfunction)

this will result in the code running through all tagged and running for all new tags too.
So if its initialized later on the TagAdded wil run.

EDIT:

i see now you got while loop in there. Put that while loop in a

task.Spawn(function()|
  while task.wait() do
  end
end)

Like this?

local collectionService = game:GetService("CollectionService")
local players = game:GetService("Players")


local maxDistance = 30

local function funnyFunc(funnyMan)
	local nearestPlayer, nearestDistance
	for _, player in pairs(players:GetPlayers()) do
		local character = player.Character
		local distance = player:DistanceFromCharacter(funnyMan.HumanoidRootPart.Position)
		if not character or 
			distance > maxDistance or
			(nearestDistance and distance >= nearestDistance)
		then
			continue
		end
		nearestDistance = distance
		nearestPlayer = player
	end
	if nearestPlayer then
		funnyMan.PrimaryPart.CFrame = CFrame.new(funnyMan.PrimaryPart.Position, Vector3.new(nearestPlayer.Character.PrimaryPart.Position.X, funnyMan.PrimaryPart.Position.Y, nearestPlayer.Character.PrimaryPart.Position.Z))
	end
end

for _, funnyMan in pairs(collectionService:GetTagged("Test")) do
	local humanoid = funnyMan:WaitForChild("Humanoid") or funnyMan.Humanoid
	while task.wait() do
		funnyFunc(funnyMan)
	end
end

collectionService:TagAdded("tag"):Connect(funnyFunc())

how exactly would i do that? im very new to all of this

nope,

you copy everything thats is inside the for loop and put that in a function. let the function recieve the npc in parameters.

then call the function in the toop and give it as parameter your npc

then under it you do this:

collectionService:TagAdded("tag"):Connect(funnyFunc)
local collectionService = game:GetService("CollectionService")
local players = game:GetService("Players")


local maxDistance = 30

local function funnyFunc(funnyMan)
	local nearestPlayer, nearestDistance
	for _, player in pairs(players:GetPlayers()) do
		local character = player.Character
		local distance = player:DistanceFromCharacter(funnyMan.HumanoidRootPart.Position)
		if not character or 
			distance > maxDistance or
			(nearestDistance and distance >= nearestDistance)
		then
			continue
		end
		nearestDistance = distance
		nearestPlayer = player
	end
	if nearestPlayer then
		funnyMan.PrimaryPart.CFrame = CFrame.new(funnyMan.PrimaryPart.Position, Vector3.new(nearestPlayer.Character.PrimaryPart.Position.X, funnyMan.PrimaryPart.Position.Y, nearestPlayer.Character.PrimaryPart.Position.Z))
	end
end

for _, funnyMan in pairs(collectionService:GetTagged("Test")) do
	funnyFunc(funnyMan)
end

collectionService:TagAdded("Test"):Connect(funnyFunc)

I wrote this but its returning 09:25:25.489 TagAdded is not a valid member of CollectionService "CollectionService" - Server - Script:30. Is that how we use TagAdded?

Oh I see what went wrong you have to use a single dot and not :

EDIT: and not use its as a function but event,
Like this:

CollectionService.TagAdded:Connect(function(tag)
	warn(tag)
end)