How do I detect the nearest character / humanoid next to the player?

I’m creating a push system similar to the one in Fall Guys for a king of the hill game. I’m wondering how I can detect the closest player to the character, and run a function if it’s in arms reach.

Something like

mouse.Button1Up:Connect(function()
       local closestplayer = -- find who's closest to the player who fired the event
	   local Distance = (closestplayer - player).Magnitude
       if distance < maxdistance then
end)

I would recommend using a ray cast from the player’s humanoid root part to detect if there is a character in front of them and you would need to multiply the ray’s direction (ray.direction) by the max distance a player can be from the player who clicked.

This should be useful:
Raycast Documentation

1 Like

You do not need to raycast to find the nearest player. The easiest way is a standard iteration:

local function FindNearestPlayer(Player)
	local Character = Player.Character or Player.CharacterAdded:Wait()
	local Nearest, NearestDistance = nil, math.huge
	for i, OtherPlayer in ipairs(game:GetPlayers()) do
		local OtherCharacter = OtherPlayer.Character
		local Distance = (Character.Position - OtherCharacter.Position).Magnitude
		if Distance < NearestDistance then
			Nearest, NearestDistance = OtherPlayer, Distance
		end
	end
	return Nearest, NearestDistance
end
3 Likes

While standard iteration is the simplest approach, iteration + raycasting should be more effective for your needs, that is, an actual arm’s reach and not only proximity.

Raycasting should eliminate all cases where the player is close enough, but is concealed behind an object.

All of this is covered in the comments in the appended script, along with replaced mouse event for UserInputService.InputBegan (you could also replace it with ContextActionService for easier mobile support).

local PlayerService = game:GetService("Players")
local UIS = game:GetService("UserInputService")

local ARM_REACH = 10

local localPlayer = PlayerService.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()
local hrp = character:WaitForChild("HumanoidRootPart")

-- Ignore player's own character while casting rays.
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {character}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local function FindNearestReachablePlayer(): (Player, number)
	local reachablePlayer: Player
	local distance = math.huge
	
	for _,otherPlayer in PlayerService:GetPlayers() do
		
		-- Skip all players without characters/root parts.
		local primaryPart = otherPlayer.Character and otherPlayer.Character.PrimaryPart
		if not primaryPart then continue end
		
		-- Find the distance between the primary parts.
		local mag = (hrp.CFrame.Position - primaryPart.CFrame.Position).Magnitude
		
		-- If the distance is out of arm's reach, skip.
		-- If there's a closer reachable player already, skip as well.
		if mag > ARM_REACH or mag > distance then continue end
		
		-- Raycast towards the nearby player.
		local raycastResult = workspace:Raycast(
			hrp.CFrame.Position, primaryPart.CFrame.Position - hrp.CFrame.Position, raycastParams
		)
		
		if raycastResult and raycastResult.Instance then
			-- Have we hit something other than that player?
			-- That means the player is possibly behind an obstacle.
			if PlayerService:GetPlayerFromCharacter(raycastResult.Instance.Parent) ~= otherPlayer then
				continue
			end
			-- Update the variables with the new information.
			distance = mag
			reachablePlayer = otherPlayer
		end
	end
	
	return reachablePlayer, distance
end

UIS.InputBegan:Connect(function(inputObj, gameProcessed)
	if gameProcessed then return end
	if inputObj.UserInputType == Enum.UserInputType.MouseButton1 then
		print(FindNearestReachablePlayer())
	end
end)

P.S. Although the code works, it has not extensively tested. You should look for various edge cases like raycasting through water.

2 Likes