Detecting/Counting Humanoids through Raycasts

Hey there! I’m trying to detect when humanoids are detected! My hope is that the humanoids are tallied up when within the ray, and removed when they are no longer detected. This is what I’ve got so far, but it doesn’t seem to be detecting the humanoids correctly. Any help would be appreciated!

local Tool = Clip.Parent
local Mode = Tool.Values.Mode
local Detected = Clip.Detected

local Light = Clip.Light

local CheckScan = false

function Scan (start)
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {start}
	params.FilterType = Enum.RaycastFilterType.Blacklist

	local vector = start.CFrame.LookVector
	local ray = workspace:Raycast(start.Position, vector * Light.Attachment.SpotLight.Range, params)

	if ray and ray.Instance:FindFirstAncestorOfClass("Model") then
		local Humanoid = ray.Instance:FindFirstChildOfClass("Humanoid", true)
		if Humanoid then
			print('Found')
			return
		end
	end
end

Clip.Scanning.Changed:Connect(function()
	if (not Clip.Scanning.Value) == true then
		return
	end
	while Clip.Scanning.Value == true do
		wait(0.05)
		print('Scanning')
		local part = Scan(Clip.Light.Scan)
		if part then
			print('Found Humanoid!')
			Detected = Detected.Value + 1
		end
	CheckScan = false
end
end)

Could you describe what exactly you want to use this for?

It’s for a multipurpose tool, in this particular case the script is meant to detect all humanoids infront of the tool and change a text label to match the number of humanoids it detects

You could use linear algebra to find everyone infront of the player at a certain radius. Probably would be faster/more reliable too

Because Raycasts take the first item it hits, you will need to update the blacklist each time it finds a humanoid.

You can do this by calling the Scan function recursively, adding on the found character to the blacklist each time until nothing is found.

Here’s some pseudocode to explain what I mean:

Scan(blacklist, hit)
     hit = hit or 0
     target = Raycast(pos, dir, blacklist)
     humanoid = target.Parent:FindFirstChild("Humanoid") or target.Parent.Parent:FindFirstChild("Humanoid")
     if humanoid then
          hit = hit + 1
          addCharacter to blacklist
          Scan(blacklist, hit)
     else
          return hit
     end
end
3 Likes

That would totally work! I’m kind of unfamiliar with adding and removing things to a blacklist for raycasting, would it be with arrays or with the parameters tidbit?

@devauros The linear algebra method would look something like this:

	--Checks the angle
	local function _clampToAngle(inProximityResult : Array<BasePart>, originCFrame : CFrame) : Array<BasePart>
		local directionToPlayer : Vector3
		local angleQueryResult : Array<BasePart> = {}
	
		for _ : number, entity : BasePart in inProximityResult do
			directionToPlayer = (entity.Position - originCFrame.Position).Unit
			if originCFrame.LookVector:Dot(directionToPlayer) > 0.5 then
				table.insert(angleQueryResult, entity)
			end
		end
		
		return angleQueryResult
	end

	--Checks the radius
	local function _radiusCheck(originCFrame : CFrame, radius : number, queryTable : Array<BasePart>) : Array<BasePart>
		local inProximityResult : Array<BasePart> = {} 

		--First checks if the player is in proximity
		for _ : number, entity : BasePart in queryTable do
			if (entity.Position - originCFrame.Position).Magnitude <= radius then
				table.insert(inProximityResult, entity)
			end
		end

		--Checks angle. 0.5 = 45 degrees infront of the player
		local angleQueryResult : Array<BasePart> = _clampToAngle(inProximityResult, originCFrame)
		return angleQueryResult
	end

	--START THE FUNCTION FROM HERE
	local queryTable : Array<BasePart> = {}
	for _ : number, entity : Model in workspace.Entities:GetChildren() do
		if character == entity then --Skips over yourself
			continue
		end

		--Insert all the parts you want to check against in the table
		table.insert(queryTable, entity.HumanoidRootPart)
	end

	local queryResult : Array<BasePart> = _radiusCheck(character:GetPivot(), 10, queryTable)
	
	if #queryResult > 1 then
		print("something is infront of the player")
	else
		print("nothing detected")
	end
3 Likes

So, I’ve basically, copy and pasted it into the script here.
Does the variable character need to be pointed to the player’s character, or a particular part inside their character?

1 Like

On local queryResult : Array<BasePart> = _radiusCheck(character:GetPivot(), 10, queryTable),
the variable character in character:GetPivot() is pointing to the player’s character. Then :GetPivot() is an easy way to return the CFrame of a models primary part without having to index it.

So basically:
character:GetPivot() == character.PrimaryPart.CFrame
But it can be any CFrame

:GetPivot() documentation:

Like this?

local Tool = Clip.Parent
local Mode = Tool.Values.Mode
local Detected = Clip.Detected

local Light = Clip.Light

local player = script:FindFirstAncestorWhichIsA"Player" or game:GetService"Players":GetPlayerFromCharacter(script.Parent.Parent)
local CheckScan = false

function Scan()
	--Checks the angle
	local function _clampToAngle(inProximityResult : Array<BasePart>, originCFrame : CFrame) : Array<BasePart>
		local directionToPlayer : Vector3
		local angleQueryResult : Array<BasePart> = {}

		for _ : number, entity : BasePart in inProximityResult do
			directionToPlayer = (entity.Position - originCFrame.Position).Unit
			if originCFrame.LookVector:Dot(directionToPlayer) > 0.5 then
				table.insert(angleQueryResult, entity)
			end
		end

		return angleQueryResult
	end

	--Checks the radius
	local function _radiusCheck(originCFrame : CFrame, radius : number, queryTable : Array<BasePart>) : Array<BasePart>
		local inProximityResult : Array<BasePart> = {} 

		--First checks if the player is in proximity
		for _ : number, entity : BasePart in queryTable do
			if (entity.Position - originCFrame.Position).Magnitude <= radius then
				table.insert(inProximityResult, entity)
			end
		end

		--Checks angle. 0.5 = 45 degrees infront of the player
		local angleQueryResult : Array<BasePart> = _clampToAngle(inProximityResult, originCFrame)
		return angleQueryResult
	end

	--START THE FUNCTION FROM HERE
	local queryTable : Array<BasePart> = {}
	for _ : number, entity : Model in workspace.Entities:GetChildren() do
		if player.Character == entity then --Skips over yourself
			continue
		end

		--Insert all the parts you want to check against in the table
		table.insert(queryTable, entity.HumanoidRootPart)
	end

	local queryResult : Array<BasePart> = _radiusCheck(player.Character:GetPivot(), 10, queryTable)

	if #queryResult > 1 then
		print("something is infront of the player")
	else
		print("nothing detected")
	end
end

Clip.Scanning.Changed:Connect(function()
	if (not Clip.Scanning.Value) == true then
		return
	end
	while Clip.Scanning.Value == true do
		wait(0.05)
		Scan()
		end
	CheckScan = false
end)

Well idk does it work?

It doesn’t appear to be. I noticed you had entities as workspace.Entities:GetChildren(), so I made a folder and included a dummy with a humanoid. Was this the intended setup?

Yes, In my game when a player joins their character is added to a folder in workspace called “Entities”. And from there I can easily loop through the folder to get everyone

Is it necessary for the player to be included in this folder?

No, but in my game the player is in the folder (it adds everyone) so I’m skipping them because there’s no reason to check against themselves yk

Well now I’m just not sure why it isn’t detecting this humanoid then

Could be here:

Clip.Scanning.Changed:Connect(function()
	if (not Clip.Scanning.Value) == true then
		return
	end
	while Clip.Scanning.Value == true do
		wait(0.05)
		Scan()
		end
	CheckScan = false
end)

I’m assuming Clip.Scanning.Changed is fired every time the player activates the tool?

Yeah, it’s turned true whenever the conditions are met by another script

So It’s basically just an on and off switch?

Pretty much! It’s like a multimode thing, so I need to ensure this scanning function is only done when it’s active.