How do I make my script work with multiple NPCs instead of just one?

  1. What do you want to achieve? Keep it simple and clear!
    I am trying to make it so that multiple NPCs are able to detect the player. I’ve made it so these NPCs get a tag from the collection service and it’s these NPCs that should be able to do this. I’ve also stored them all in one folder to make it easier.

  2. What is the issue?
    When trying to define the npc variable as all the tagged NPCs in the folder (mentioned above), I either get errors where it says “attempt to index nil with .Position” at line 53, OR there are no errors but there is only one NPC that’s able to detect you instead of all of them.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I have tried to write the npc variable as a table and store the NPC models in it as an array, but it throws back the position error I described above. I’ve tried just doing a simple :GetChildren() method for the variable but that also doesn’t work and gives the same error. At the moment, I just defined npc as the currently tagged npc which explains why it works only on one NPC but I don’t know what else to do.

Here’s the script. It’s a local script and is located in StarterPlayerScripts.

Please note that the rest of the script works as intended, I just need it to be able to work with multiple NPCs and that’s what I can’t figure out.

-- Get necessary services
local RunService = game:GetService("RunService")
local PlayersService = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")


-- Give all Detector NPCs the detector tags so they can detect the player
for i,v in pairs (game.Workspace.Room2.DetectorNPCs:GetChildren()) do
	CollectionService:AddTag(v, "DetectorNPC")
	npc = v
end


-- Set NPC and character, along with their sight and range properties

local npcSight = 0.7
local npcRange = 100

-- GUI elements and reaction time
local player = PlayersService.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local head = character:WaitForChild("Head")
local detectedevent = ReplicatedStorage:WaitForChild("PlayerDetected")

local playerGui = player.PlayerGui
local DetectionBar = playerGui:WaitForChild("DetectionGUI")
local detectionFrame = DetectionBar:WaitForChild("detectionFrame")
local reactionTime = 0.8

-- Create a table to hold TweenInfo
local tweenInfo = TweenInfo.new(
	0,  -- Time
	Enum.EasingStyle.Quad,  -- EasingStyle
	Enum.EasingDirection.Out,  -- EasingDirection
	0,  -- RepeatCount (0 implies the tween will not repeat)
	false,  -- Reverses (should the tween reverse once reaching the endpoint)
	0  -- DelayTime
)

local characterIsInFov = false
local detectionProgress = 0 -- Variable to keep track of detection progress
local hitPlayer = nil -- Move hitPlayer variable outside the if statement

RunService.Heartbeat:Connect(function(dt)
	if character and npc then -- Check if the character and the NPC are not null

		-- Calculate the vector between the NPC and the character
		local npcToCharacter = (head.Position - npc.Head.Position).Unit
		local npcLook = npc.Head.CFrame.LookVector
		local dotProduct = npcToCharacter:Dot(npcLook)
		
		if dotProduct > npcSight then
			--character is in field of view        
			characterIsInFov = true
		else
			-- character is not in field of view so we do not need to take any action
			characterIsInFov = false
		end

		-- Prepare parameters for raycasting
		local raycastParams = RaycastParams.new()
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude
		raycastParams.FilterDescendantsInstances = {npc}
		raycastParams.IgnoreWater = true

		-- Perform a raycast to check if the character is in sight
		local ray = workspace:Raycast(npc.PrimaryPart.Position, (character.PrimaryPart.Position - npc.PrimaryPart.Position).Unit * npcRange, raycastParams)

		-- Check if the character is in range and in field of view
		if characterIsInFov and (head.Position - npc.PrimaryPart.Position).Magnitude <= npcRange and CollectionService:HasTag(npc, "DetectorNPC") then
			if ray and ray.Instance.Parent == character then
				hitPlayer = PlayersService:GetPlayerFromCharacter(ray.Instance.Parent) -- This will now update the variable declared earlier
				if hitPlayer then
					-- Increase the detection progress
					detectionProgress = math.min(detectionProgress + dotProduct * 2 * dt, 1)
				end
			else
				-- Decrease the detection bar when not in sight
				detectionProgress = math.max(detectionProgress - 0.08 * dt, 0) -- Decrease the detection progress
			end
		else
			-- Decrease the detection bar when not in sight
			detectionProgress = math.max(detectionProgress - 0.08 * dt, 0) -- Decrease the detection progress
		end

		-- Animate the detection bar according to detection progress
		local alpha = TweenService:GetValue(detectionProgress / 1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
		DetectionBar.detectionFrame.detectionBar.Size = UDim2.new(1, 0, 0, 0):Lerp(UDim2.new(1, 0, 1, 0), alpha)
		DetectionBar.detectionFrame.detectionBar.BackgroundColor3 = Color3.fromRGB(255, 255, 255)

		-- Check if the detection bar is filled completely before triggering the NPC detection
		if detectionProgress >= 1 then
			task.wait(reactionTime)
			if characterIsInFov and (head.Position - npc.PrimaryPart.Position).Magnitude <= npcRange and hitPlayer then -- Make sure hitPlayer is not nil
				print('Player detected: ', hitPlayer.Name)
				
				
				DetectionBar.detectionFrame.detectionBar.BackgroundColor3 = Color3.fromRGB(255, 0, 0)
				detectedevent:FireServer(game.Players.LocalPlayer)
			end
		end
	end
end)

Any help would be hugely appreciated :grin:. I’m fairly new to scripting and not that good at it so please bare with me if my code has been written badly or something like that.

4 Likes

Sorry to bump but I still haven’t found a solution to this

Try this:

-- Get necessary services
local RunService = game:GetService("RunService")
local PlayersService = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")

-- Give all Detector NPCs the detector tags so they can detect the player
-- Store all Detector NPCs
local allNpcs = {}
for _, npc in pairs(game.Workspace.Room2.DetectorNPCs:GetChildren()) do
	CollectionService:AddTag(npc, "DetectorNPC")
	
	table.insert(allNpcs, {
		npc = npc,
		detectionProgress = 0 -- You will store detection progress for each NPC here
	})
end

-- Set NPC and character, along with their sight and range properties

local npcSight = 0.7
local npcRange = 100

-- GUI elements and reaction time
local player = PlayersService.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local head = character:WaitForChild("Head")
local detectedevent = ReplicatedStorage:WaitForChild("PlayerDetected")

local playerGui = player.PlayerGui
local DetectionBar = playerGui:WaitForChild("DetectionGUI")
local detectionFrame = DetectionBar:WaitForChild("detectionFrame")
local reactionTime = 0.8

-- Create a table to hold TweenInfo
local tweenInfo = TweenInfo.new(
	0,  -- Time
	Enum.EasingStyle.Quad,  -- EasingStyle
	Enum.EasingDirection.Out,  -- EasingDirection
	0,  -- RepeatCount (0 implies the tween will not repeat)
	false,  -- Reverses (should the tween reverse once reaching the endpoint)
	0  -- DelayTime
)

local characterIsInFov = false
local detectionProgress = 0 -- Variable to keep track of detection progress
local hitPlayer = nil -- Move hitPlayer variable outside the if statement

RunService.Heartbeat:Connect(function(dt)
	local maxDetectionProgress = 0 -- Used to track the highest detection level among all NPCs

	for _, npcData in pairs(allNpcs) do
		local npc = npcData.npc
		
		if CollectionService:HasTag(npc, "DetectorNPC") then
			local detectionProgress = npcData.detectionProgress
			
			local characterIsInFov = false
			local hitPlayer = nil

			-- Calculate the vector between the NPC and the character
			local npcToCharacter = (head.Position - npc.Head.Position).Unit
			local npcLook = npc.Head.CFrame.LookVector
			local dotProduct = npcToCharacter:Dot(npcLook)

			if dotProduct > npcSight then
				--character is in field of view        
				characterIsInFov = true
			else
				-- character is not in field of view so we do not need to take any action
				characterIsInFov = false
			end

			-- Prepare parameters for raycasting
			local raycastParams = RaycastParams.new()
			raycastParams.FilterType = Enum.RaycastFilterType.Exclude
			raycastParams.FilterDescendantsInstances = {npc}
			raycastParams.IgnoreWater = true

			-- Perform a raycast to check if the character is in sight
			local ray = workspace:Raycast(npc.PrimaryPart.Position, (character.PrimaryPart.Position - npc.PrimaryPart.Position).Unit * npcRange, raycastParams)

			-- Check if the character is in range and in field of view
			if characterIsInFov and (head.Position - npc.PrimaryPart.Position).Magnitude <= npcRange then
				if ray and ray.Instance.Parent == character then
					hitPlayer = PlayersService:GetPlayerFromCharacter(ray.Instance.Parent)
					if hitPlayer then
						-- Increase the detection progress
						detectionProgress = math.min(detectionProgress + dotProduct * 2 * dt, 1)
					end
				else
					-- Decrease the detection bar when not in sight
					detectionProgress = math.max(detectionProgress - 0.08 * dt, 0)
				end
			else
				-- Decrease the detection bar when not in sight
				detectionProgress = math.max(detectionProgress - 0.08 * dt, 0)
			end

			-- Save updated detectionProgress back to the table
			npcData.detectionProgress = detectionProgress

			-- Check for the maximum detection progress
			if detectionProgress > maxDetectionProgress then
				maxDetectionProgress = detectionProgress
			end
		end
	end

	-- Animate the detection bar according to the highest detection progress
	local alpha = TweenService:GetValue(maxDetectionProgress / 1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
	DetectionBar.detectionFrame.detectionBar.Size = UDim2.new(1, 0, 0, 0):Lerp(UDim2.new(1, 0, 1, 0), alpha)
	DetectionBar.detectionFrame.detectionBar.BackgroundColor3 = maxDetectionProgress >= 1 and Color3.fromRGB(255, 0, 0) or Color3.fromRGB(255, 255, 255)

	-- Check if the detection bar is filled completely before triggering the NPC detection
	if maxDetectionProgress >= 1 then
		task.wait(reactionTime)
		detectedevent:FireServer(game.Players.LocalPlayer)
		-- Here, you can add additional logic to determine which NPC detected the player if necessary.
	end
end)
1 Like

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