Really trying to figure out how I can clean up this messy ledge grab code

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:

  1. 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.
  2. 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.
  3. Readability: The code could benefit from using more descriptive variables names and breaking it up into multiple functions for better readability.
  4. 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

Are you a human? If so the ChatGPT answer is not complete and it stops a this line:

elseif normal == Vector3.new(1
4 Likes

I was also a bit confused their, although their was a bit of a nicer way you could’ve said that

Thanks for pointing it out though, didn’t check the post for a bit

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?

1 Like

that’s technically what I’m doing here, I’m a bit confused on what you mean?