Hi there, I’ve been working on a ledge climbing system. I want to detect ledges of a part in order to position the Character’s RootPart to it.
I’ve successfully done this by detecting a part’s normal vector from a Raycast - and then move the RootPart to that ledge. It works, but
The Problem
It only works if the orientation is at 0. It doesn’t respect a part’s orientation. The ledge detection remains the same no matter the orientation and hence the RootPart moves to the opposite / wrong / unwanted offset ledge.
What works
What doesn’t
As you can see, the rotated part still thinks the orientation is at 0.
I did a lot of digging around in the DevForums regarding part CFrame vectors (LookVectors, UpVectors) to connect other parts - but I found little documentation about how to implement Raycast normals and surfaces, or even orientation. How can I achieve this?
Here’s a snippet for the ledge detection. This is part of a bigger segment in RunService.Heartbeat.
local ray = Ray.new(Head.CFrame.Position, Head.CFrame.LookVector * 3)
local part, position, hitNormal = workspace:FindPartOnRay(ray,Character)
if part then
local absoluteNormal = Vector3.new(math.abs(hitNormal.X), math.abs(hitNormal.Y), math.abs(hitNormal.Z)) -- the absolute value makes sure I'm not multiplying negatives with negatives (otherwise will detected 2 ledges on a 4 ledged object)
local yVector = (part.CFrame.UpVector) * ((part.Size.Y) / 2)
local xzVector = hitNormal * ((part.Size * absoluteNormal) / 2)
local ledgeOffset = part.CFrame + yVector + xzVector
ledgeOffset = CFrame.lookAt(ledgeOffset.Position, ledgeOffset.Position - hitNormal) -- this makes the detected ledge face the Part
local ledgePart = Instance.new("Part") -- just a debug to visualize the ledge
ledgePart.Parent = workspace
ledgePart.Anchored = true
ledgePart.Size = Vector3.one
ledgePart.CFrame = ledgeOffset
ledgePart.CanQuery = false
ledgePart.CanCollide = false
ledgePart.CanTouch = false
end
I think you need to rotate absoluteNormal to match the rotation of the part in order to multiply it with the Size and get the result you are looking for. I think that might be as easy as
The way I would get the position of the ledge for a simple block setup like this is:
Take the raycast’s hit position and translate it to local coordinates of the part. In your code, and also in mine, the racast’s hit position is called position.
local localPos = part.CFrame:PointToObjectSpace(position)
Now in local coordinates of the object, set the y component to the top of the part.
local localLedgePos = Vector3.new(localPos.x, part.Size.y/2, localPos.z)
Convert back to world coordinates.
local ledgePos = part.CFrame:PointToWorldSpace(localLedgePos)
Then construct a CFrame using CFrame.lookAt and the hitNormal like you are already doing.
There are a few limitations of this approach worth mentioning.
Upside down Parts will cause an upside down ledge to be found
Unusual geometry that is not a Part (MeshParts, Unions) will find a ledge somewhere at the top of their bounding box, acting as if they have the same geometry as a plain Part.
Parts stacked on top of one another will find a ledge where they connect even though it is a solid wall to the player
If you are able to work around these limitations / are okay with them, this should be a decent approach to finding ledges.