I’m having trouble figuring out how the heck I’m supposed to properly make a ledge grab script, it’s been taking multiple days just to get something working, and what I currently have right now for a ledge grab is very bare bones, what do I do to fix this script?
local char = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
local humanoid = char:WaitForChild("Humanoid")
local root = char.PrimaryPart
local origion = root.Position
local direction = root.CFrame.LookVector * 1.5
local vizual = Instance.new("Part", workspace) --< Vizual is a part that is created to visually show the ray's location
vizual.Anchored = true
vizual.CFrame = root.CFrame
vizual.CanCollide = false
vizual.Name = "Visualizer"
vizual.BrickColor = BrickColor.random()
vizual.Transparency = .25
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.IgnoreWater = true
params.FilterDescendantsInstances = {char, vizual}
function nearestNormalId(part, direction) -- This finds the nearest Normal, so that I can find out the face of the part.
local maxDot, maxNormal = 0, nil --Exclude results <= 0
for _, normalId in ipairs(Enum.NormalId:GetEnumItems()) do
local normal = part.CFrame:VectorToWorldSpace(Vector3.fromNormalId(normalId))
local dot = normal:Dot(direction) --Greater the more similar the vectors are
if dot > 0 and dot > maxDot then
maxDot = dot
maxNormal = normal
end
end
return maxNormal
end
game:GetService("RunService").RenderStepped:Connect(function()
local midpoint = origion + direction/2
origion = root.CFrame.Position direction = root.CFrame.LookVector * 1.5
vizual.Size = Vector3.new(1, 1, direction.Magnitude)
vizual.CFrame = CFrame.new(midpoint, origion)
local rayPart = Instance.new("Part")
local ray = workspace:Raycast(origion, direction, params) if ray then
rayPart.Position = ray.Position
if ray.Instance ~= nil and ray.Instance.Name == "ledgePart" and root.Position.Y < ray.Instance.Position.Y then
local normal = nearestNormalId(ray.Instance, direction)
if normal == Vector3.new(0, 0, -1) then
task.wait(.2)
root.Anchored = true
root.CFrame = rayPart.CFrame - Vector3.new(0, 2.5, -2)
task.wait(2)
root.Anchored = false
elseif normal == Vector3.new(0, 0, 1) then
task.wait(.2)
root.CFrame = ray.Instance.CFrame * CFrame.Angles(0, math.rad(-180), 0) - Vector3.new(0, 2.5, 2)
task.wait(2)
root.Anchored = false
elseif normal == Vector3.new(1, 0, 0) then
task.wait(.2)
root.CFrame = ray.Instance.CFrame * CFrame.Angles(0, math.rad(-90), 0) - Vector3.new(2, 2.5, 0)
task.wait(2)
root.Anchored = false
elseif normal == Vector3.new(-1, 0, 0) then
task.wait(.2)
root.CFrame = ray.Instance.CFrame * CFrame.Angles(0, math.rad(90), 0) - Vector3.new(-2, 2.5, 0)
task.wait(2)
root.Anchored = false
end
end
end
end)
Here are some potential areas to improve on in the current script:
Refactoring: Instead of having multiple if statements with the same code, you can create a function to handle the common code and call that function with different arguments to handle each case.
Performance: You are checking the raycast every frame, which may cause performance issues. You could consider checking only when the player is jumping or pressing a certain button to perform the ledge grab.
Readability: The code could benefit from using more descriptive variables names and breaking it up into multiple functions for better readability.
Ledge Detection: Currently, the code is only checking for a ledgePart part and assumes it’s a ledge. Consider adding a way to detect and validate that a part is actually a ledge, such as checking its size, shape, and/or properties.
Here’s a refactored version of the code that addresses these areas:
local char = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
local humanoid = char:WaitForChild("Humanoid")
local root = char.PrimaryPart
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.IgnoreWater = true
raycastParams.FilterDescendantsInstances = {char}
local visualizer = Instance.new("Part", workspace)
visualizer.Anchored = true
visualizer.CanCollide = false
visualizer.Name = "Visualizer"
visualizer.BrickColor = BrickColor.random()
visualizer.Transparency = .25
local function grabLedge(ledgerCFrame)
root.Anchored = true
root.CFrame = ledgerCFrame
wait(2)
root.Anchored = false
end
local function ledgeGrabCheck()
local origin = root.Position
local direction = root.CFrame.LookVector * 1.5
local midpoint = origin + direction/2
visualizer.Size = Vector3.new(1, 1, direction.Magnitude)
visualizer.CFrame = CFrame.new(midpoint, origin)
local rayResult = workspace:Raycast(origin, direction, raycastParams)
if rayResult then
local hitPart = rayResult.Instance
if hitPart.Name == "ledgePart" and root.Position.Y < hitPart.Position.Y then
local normal = hitPart.CFrame:VectorToWorldSpace(Vector3.FromNormalId(hitPart.TopSurface))
if normal == Vector3.new(0, 0, -1) then
local ledgerCFrame = hitPart.CFrame - Vector3.new(0, 2.5, -2)
grabLedge(ledgerCFrame)
elseif normal == Vector3.new(0, 0, 1) then
local ledgerCFrame = hitPart.CFrame * CFrame.Angles(0, math.rad(-180), 0) - Vector3.new(0, 2.5, 2)
grabLedge(ledgerCFrame)
elseif normal == Vector3.new(1
I think the note is right thought, I don’t believe renderstepped is the way to go with this or atleast the most performant way, why not use some sort of block where player enters a zone, it triggers the ledge grab?