Yeah its a concept applicable to any character controller really, If you want to look at how I did it, here is the function for checking for ledges itself, keep in mind this isn’t perfect, it has issues like being able to clip through roofs if specific things happen and more (I tried my best to account for all possible cases)
module.DetectionSettings = {
LedgeCheckDistance = 4,
LedgeCheckRange = 5,
LedgeCheckOffset = Vector3.new(0,3,0),
WallCheckOffset = Vector3.new(0,-0.001,0),
WallCheckDistanceOffset = 0.001,
VerifyCheckOffset = Vector3.new(0,1,0),
GrabOffset = Vector3.new(0,-2,0),
GrabDistance = 1,
}
function module.CheckLedge(simulation,cmd)
-- Build raycast parameters
local Whitelist = {}
if Server then
table.insert(Whitelist, Server.collisionRootFolder)
else
local CollisionRoot = Client:GetCollisionRoot()
if CollisionRoot then
table.insert(Whitelist, CollisionRoot)
end
end
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Whitelist
Params.FilterDescendantsInstances = Whitelist
-- Get
local PlayerAngle = -MathUtils:PlayerAngleToVec(simulation.state.targetAngle)
local LedgeCheckOrigin = (simulation.state.pos + PlayerAngle * module.DetectionSettings.LedgeCheckDistance)
+ module.DetectionSettings.LedgeCheckOffset
local LedgeDirection = MathUtils:AimPointToDir(LedgeCheckOrigin + Vector3.new(0,-1,0),LedgeCheckOrigin)
-- Cast ray to find ledge near our look direction
local LedgeResult = workspace:Raycast(LedgeCheckOrigin,LedgeDirection * module.DetectionSettings.LedgeCheckRange, Params)
if LedgeResult then
SeeQuery:Ray(true,LedgeCheckOrigin,LedgeResult,10).BrickColor = BrickColor.new("Lime green")
local WallCheckOrigin = Vector3.new(simulation.state.pos.X,LedgeResult.Position.Y,simulation.state.pos.Z)
+ module.DetectionSettings.WallCheckOffset
local WallCheckTarget = LedgeResult.Position
+ module.DetectionSettings.WallCheckOffset
local WallCheckDirection = MathUtils:AimPointToDir(WallCheckTarget,WallCheckOrigin)
local WallCheckDistance = (WallCheckOrigin - WallCheckTarget).Magnitude + module.DetectionSettings.WallCheckDistanceOffset
local WallResult = workspace:Raycast(WallCheckOrigin, WallCheckDirection * WallCheckDistance, Params)
if WallResult then
SeeQuery:Ray(true,WallCheckOrigin,WallResult,10).BrickColor = BrickColor.new("Lime green")
local VerifyCheckOrigin = WallCheckOrigin + module.DetectionSettings.VerifyCheckOffset
local VerifyResult = workspace:Raycast(VerifyCheckOrigin,WallCheckDirection * WallCheckDistance,Params)
if not VerifyResult then
VerifyResult = SeeQuery:RecreateRayResult(VerifyCheckOrigin,WallCheckDistance,WallCheckDirection)
SeeQuery:Ray(true,VerifyCheckOrigin,VerifyResult,10)
-- return the result
local pos = WallResult.Position
local normal = WallResult.Normal
return pos, normal
else
SeeQuery:Ray(true,VerifyCheckOrigin,VerifyResult,10).BrickColor = BrickColor.new("Lime green")
end
else
WallResult = SeeQuery:RecreateRayResult(WallCheckOrigin,WallCheckDistance,WallCheckDirection)
SeeQuery:Ray(true,WallCheckOrigin,WallResult,10)
end
else
LedgeResult = SeeQuery:RecreateRayResult(LedgeCheckOrigin,module.DetectionSettings.LedgeCheckRange,LedgeDirection)
SeeQuery:Ray(true,LedgeCheckOrigin,LedgeResult,10)
end
end