Hello, this post is an addition to my previous post here.
I am attempting to create a ledge climbing system in my game. The player attaches to the ledge by jumping up, then can use a combination of keybinds to jump to other ledges. For example, if the player jumps onto a ledge, they can use “spacebar+a” to jump to the ledge on the left or use just “a” to move slightly over to the left. This kind of works, however I am having problems with the raycasting aspect. The raycast (or spherecast) is not picking up any nearby ledges despite there being many around.
If anyone would like to playtest this in studio, simply create a part named “Ledge” and anchor it at about character height and jump up to it. At the moment, I am sticking lots of small Ledge parts together to make bigger ledges (as moving side to side puts you onto the nearest ledge part that you are not already attached to) but if anyone has a better way to do this, let me know.
I would really appreciate it if you could take a look at my code below and suggest any relevant changes whether about my raycasting issue or anything else. Any and all help is appreciated!
Thank you
Code:
-- Wait for humanoidRootPart to exist
local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart
repeat
humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
wait(0.1)
until humanoidRootPart
print("HumanoidRootPart found")
local camera = game.Workspace.CurrentCamera
-- Ensure the camera is valid
if not camera then
error("Camera not found")
return
end
print("Camera found")
local isGrabbing = false
local ledgePart = nil
local isJumping = false
local canAttach = true -- New variable to track if attaching is allowed
-- Create a folder for debug rays
local debugFolder = Instance.new("Folder")
debugFolder.Name = "Excluded"
debugFolder.Parent = game.Workspace
print("Debug folder created")
-- Function to create a debug ray
local function createDebugRay(origin, direction, length)
local rayPart = Instance.new("Part")
rayPart.Anchored = true
rayPart.CanCollide = false
rayPart.Size = Vector3.new(0.2, 0.2, length)
rayPart.Position = origin + direction * (length / 2)
rayPart.BrickColor = BrickColor.new("Bright red")
rayPart.Transparency = 0.5
rayPart.Parent = debugFolder
return rayPart
end
-- Function to check for nearby ledges
local function findLedge(excludeTable)
local characterPosition = character.Head.CFrame.Position
local characterDirection = humanoidRootPart.CFrame.LookVector
local rayLength = 5
local rayHeightOffset = 5
-- Create a debug ray for visualization
--local debugRay = createDebugRay(characterPosition + Vector3.new(0, rayHeightOffset, 0), characterDirection, rayLength)
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {character, debugFolder}
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local rRay = workspace:Raycast(characterPosition, characterDirection * rayLength, rayParams)
-- Remove the debug ray after raycasting
--debugRay:Destroy()
if rRay then
print("ray")
end
if rRay and rRay.Instance.Name == "Ledge" then
print("Found Ledge: " .. rRay.Instance.Name)
return rRay.Instance
else
return nil
end
end
-- Function to attach the player to a ledge
local function attachToLedge(ledge, rayInfo)
if not isGrabbing then
isGrabbing = true
ledgePart = ledge
-- Play the grab animation (replace "AnimationId" with your animation ID)
local humanoid = character:FindFirstChild("Humanoid")
-- Calculate the position on the ledge where the player should grab
local ledgeAttachPosition
if rayInfo then
ledgeAttachPosition = ledge.CFrame.Position + rayInfo.Normal
else
ledgeAttachPosition = CFrame.new(ledge.Position, character.Head.Position).Position
end
humanoid.AutoRotate = false
local bodyPos = Instance.new("BodyPosition")
bodyPos.MaxForce = Vector3.new(50000, 50000, 50000)
bodyPos.Position = ledgeAttachPosition + Vector3.new(0, -2, 0)
bodyPos.Name = "LedgeGrabBPos"
bodyPos.Parent = character.HumanoidRootPart
end
end
-- Function to release from the ledge
local function releaseFromLedge()
if isGrabbing then
isGrabbing = false
-- Stop the grab animation (if playing)
local humanoid = character:FindFirstChild("Humanoid")
-- Remove the attachment and weld
local ledgeAttachment = humanoidRootPart:FindFirstChild("Attachment")
if ledgeAttachment then
ledgeAttachment:Destroy()
end
if ledgePart then
if character.HumanoidRootPart:FindFirstChild("LedgeGrabBPos") then
character.HumanoidRootPart["LedgeGrabBPos"]:Destroy()
humanoid.AutoRotate = true
end
end
ledgePart = nil
end
end
-- Function to attach or detach from the ledge when "E" is pressed
local function toggleLedgeGrab()
if isGrabbing then
releaseFromLedge()
elseif canAttach then
local nearbyLedge = findLedge()
if nearbyLedge then
attachToLedge(nearbyLedge)
end
end
end
-- Listen for "E" key press to toggle ledge grab
game:GetService("UserInputService").InputBegan:Connect(function(input, gameProcessed)
if not gameProcessed and input.KeyCode == Enum.KeyCode.E then
toggleLedgeGrab()
end
end)
-- Function to jump to another ledge or move slightly
local function jumpOrMove(direction, excludeTable)
print('event running')
if isGrabbing then
local rayParams = RaycastParams.new()
local radius = 5
rayParams.FilterType = Enum.RaycastFilterType.Exclude
if excludeTable then
rayParams.FilterDescendantsInstances = {character, debugFolder, excludeTable}
else
rayParams.FilterDescendantsInstances = {character, debugFolder}
end
local rRay = workspace:Spherecast((character.Head.CFrame.Position + Vector3.new(0, 1.5, 0)) + direction, radius, humanoidRootPart.CFrame.LookVector * 5, rayParams)
if rRay and rRay.Instance.Name == "Ledge" then
print("i did find a ledge!")
releaseFromLedge()
attachToLedge(rRay.Instance, rRay)
print("woohoo")
else
local moveAmount = direction * 2
local cFPos = humanoidRootPart.CFrame * CFrame.new(moveAmount)
print('i didnt find a ledge :(')
--if character.HumanoidRootPart:FindFirstChild("LedgeGrabBPos") then
-- character.HumanoidRootPart.LedgeGrabBPos.Position = cFPos.Position
-- character.HumanoidRootPart.LedgeGrabBPos.P = 5000
-- releaseFromLedge()
--end
end
end
end
-- Listen for input to grab, release, or perform actions while grabbing
game:GetService("UserInputService").InputBegan:Connect(function(input, gameProcessed)
if not gameProcessed then
if input.KeyCode == Enum.KeyCode.Space then
--if isGrabbing then
-- releaseFromLedge()
if not isJumping then
isJumping = true
local humanoid = character:FindFirstChild("Humanoid")
if humanoid then
humanoid:Move(Vector3.new(0, 10, 0))
end
end
elseif isGrabbing then
print("p is grabbing")
if input.KeyCode == Enum.KeyCode.A then
jumpOrMove(Vector3.new(3, 0, 0))
elseif input.KeyCode == Enum.KeyCode.D then
jumpOrMove(Vector3.new(-3, 0, 0))
elseif input.KeyCode == Enum.KeyCode.W then
jumpOrMove(Vector3.new(0, 0, -3))
elseif input.KeyCode == Enum.KeyCode.S then
local ledgeBelow = findLedge()
if not ledgeBelow then
releaseFromLedge()
jumpOrMove(Vector3.new(0, -3, 0))
end
end
end
end
end)
-- Heartbeat for ledge detection
local function onHeartbeat()
if not isGrabbing then
local nearbyLedge = findLedge()
if nearbyLedge then
attachToLedge(nearbyLedge)
end
end
end
game:GetService("RunService").Heartbeat:Connect(onHeartbeat)