How do I detect if a player is under a part?

What do I want to achieve?
I’m trying to make a snow script in StarterCharacterScripts that will only show the snow particles when a player is not under a part

What’s the problem?
I have no idea how I would do that at all

3 Likes

Use RayCasting.

1 Like

Here’s a UnderPartDetector class that you could use that would send you events whenever a part (like the character’s HumanoidRootObject) goes under any other part. It uses the same raycasting suggestion that @Katrist mentioned, and encapsulates it on a resuable way:

The module script:

local RunService = game:GetService("RunService")

-- Config
local CHECK_DELAY_IN_S = .5
local HEIGHT_ABOVE_PART = 100

-- UnderPartDetector class
local UnderPartDetector = {}
UnderPartDetector.__index = UnderPartDetector

function UnderPartDetector.new(part, descendantsToFilter)
	local self = {}
	setmetatable(self, UnderPartDetector)
	
	self.part = part
	self.underPartEvent = Instance.new("BindableEvent")
	self.outsideEvent = Instance.new("BindableEvent")
	self.isUnderPart = nil
	self.descendantsToFilter = descendantsToFilter
	
    -- Private state
	self._lastCheckTime = 0

	RunService.Heartbeat:Connect(function()
		self:onHeartbeat()
	end)
	
	return self
end

function UnderPartDetector:onHeartbeat()
	local now = tick()
	if now - self._lastCheckTime < CHECK_DELAY_IN_S then
		return
	end
	
	self._lastCheckTime = now
	
	local hitObject = self:castRayUp()
	
	if hitObject and not self.isUnderPart then
		self.underPartEvent:Fire(hitObject)
		self.isUnderPart = true
	end
	
	if not hitObject and self.isUnderPart then
		self.outsideEvent:Fire()
		self.isUnderPart = false
	end		
end

function UnderPartDetector:castRayUp()
	-- Set an origin and directional vector
	local rayOrigin = self.part.Position
	local rayDirection = Vector3.new(0, HEIGHT_ABOVE_PART, 0)
	
	-- Build a "RaycastParams" object and cast the ray
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {self.descendantsToFilter}
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

	if raycastResult then
		return raycastResult.Instance
	end
	
	return nil
end

return UnderPartDetector

and here’s how you’d use the class in a Script or LocalScript:

local ServerScripts = game:GetService("ServerScriptService")
local Players = game:GetService("Players")
local UnderPartDetector = require(ServerScripts:WaitForChild("Utils"):WaitForChild("UnderPartDetector"))

Players.PlayerAdded:Connect(function(player)
	local character = player.Character or player.CharacterAdded:Wait()
    -- add character as the second arg to filter the raycast from hitting the Head, etc.
	local underPartDetector = UnderPartDetector.new(character.HumanoidRootPart, character)

	underPartDetector.underPartEvent.Event:Connect(function(part)
		print("Under part", part)
	end)

	underPartDetector.outsideEvent.Event:Connect(function()
		print("Outside!")
	end)
end)

3 Likes

Alright, that should work, i’ll test it later.