So, I was trying to make a pipe / ladder climbing system like “DECAY” or parkour reborn, but I can’t get the hooking up right. I also can’t make it work for console (and mobile for now isn’t added, will try to add later.)
I will give the script so you guys can help me inspect it:
local Players = game:GetService(“Players”)
local UserInputService = game:GetService(“UserInputService”)
local RunService = game:GetService(“RunService”)
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild(“Humanoid”)
local root = character:WaitForChild(“HumanoidRootPart”)
local climbSpeed = 6
local isClimbing = false
local climbDirection = 0
local touchingTrigger = false
local currentTrigger = nil
local currentRail = nil
local verticalOffset = 0
local att0, att1, alignPos, alignOri
local function debug(msg)
print("[CLIMB]: " … msg)
end
local function createAttachment(parent, name, position)
local attachment = Instance.new(“Attachment”)
attachment.Name = name
attachment.Position = position or Vector3.new()
attachment.Parent = parent
return attachment
end
local function setupAligns()
att0 = createAttachment(root, “ClimbAttachment”)
att1 = createAttachment(currentRail, “ClimbTarget”, Vector3.new())
alignPos = Instance.new("AlignPosition")
alignPos.Attachment0 = att0
alignPos.Attachment1 = att1
alignPos.RigidityEnabled = true
alignPos.Responsiveness = 50
alignPos.MaxForce = 10000
alignPos.Parent = root
alignOri = Instance.new("AlignOrientation")
alignOri.Attachment0 = att0
alignOri.Attachment1 = att1
alignOri.RigidityEnabled = true
alignOri.Responsiveness = 50
alignOri.MaxTorque = 10000
alignOri.Parent = root
end
local function cleanupAligns()
if att0 then att0:Destroy() att0 = nil end
if att1 then att1:Destroy() att1 = nil end
if alignPos then alignPos:Destroy() alignPos = nil end
if alignOri then alignOri:Destroy() alignOri = nil end
end
local function startClimbing(trigger)
if isClimbing then return end
currentTrigger = trigger
currentRail = trigger.Parent:FindFirstChild("Rail")
if not currentRail then
debug("No rail found in model.")
return
end
isClimbing = true
climbDirection = 0
-- Disable auto rotate
humanoid.AutoRotate = false
local railBottomY = currentRail.Position.Y - currentRail.Size.Y / 2
local playerY = root.Position.Y
-- Clamp vertical offset with a small margin so not snapped on the top
local margin = 0.1
local playerOffset = playerY - railBottomY
verticalOffset = math.clamp(playerOffset, 0, currentRail.Size.Y - margin)
setupAligns()
att0.Position = Vector3.new(0, -currentRail.Size.Y / 2 + verticalOffset, 0)
debug("Started climbing.")
end
local function stopClimbing()
if not isClimbing then return end
isClimbing = false
climbDirection = 0
currentTrigger = nil
currentRail = nil
verticalOffset = 0
humanoid.AutoRotate = true
cleanupAligns()
debug("Stopped climbing.")
end
– Input handling for keyboard and controller buttons
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
-- Jump inside trigger to start climbing
if (input.KeyCode == Enum.KeyCode.Space or input.KeyCode == Enum.KeyCode.ButtonA) and touchingTrigger then
debug("Jump pressed inside trigger.")
startClimbing(currentTrigger)
end
end)
UserInputService.InputEnded:Connect(function(input, gameProcessed)
if gameProcessed then return end
if isClimbing then
– Keyboard up/down key releases reset climbDirection
if (input.KeyCode == Enum.KeyCode.W or input.KeyCode == Enum.KeyCode.Up) and climbDirection == -1 then
climbDirection = 0
elseif (input.KeyCode == Enum.KeyCode.S or input.KeyCode == Enum.KeyCode.Down) and climbDirection == 1 then
climbDirection = 0
end
end
end)
– Detect climbables in workspace
local climbables = workspace:FindFirstChild(“Climbables”)
if climbables then
for _, model in pairs(climbables:GetChildren()) do
if model:IsA(“Model”) then
local trigger = model:FindFirstChild(“Trigger”)
if trigger and trigger:IsA(“BasePart”) then
trigger.Touched:Connect(function(part)
if part:IsDescendantOf(character) then
touchingTrigger = true
currentTrigger = trigger
end
end)
trigger.TouchEnded:Connect(function(part)
if part:IsDescendantOf(character) then
touchingTrigger = false
currentTrigger = nil
if isClimbing then
stopClimbing()
end
end
end)
end
end
end
else
debug(“No Climbables folder found in workspace.”)
end
– Heartbeat: update climbing & detect controller thumbstick input
RunService.Heartbeat:Connect(function(dt)
– If not climbing, confirm if player still touching trigger
if not isClimbing and currentTrigger and root then
local touchingParts = currentTrigger:GetTouchingParts()
touchingTrigger = false
for _, part in pairs(touchingParts) do
if part:IsDescendantOf(character) then
touchingTrigger = true
break
end
end
end
if isClimbing and att0 and currentRail then
-- Handle keyboard keys W/S for climbDirection if no controller input detected
-- But better to handle controller here:
-- Get thumbstick Y axis for controller
local gamepads = Enum.UserInputType.Gamepad1
local state = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
local stickY = 0
for _, input in pairs(state) do
if input.KeyCode == Enum.KeyCode.Thumbstick1 then
stickY = input.Position.Y
break
end
end
-- Deadzone for stick
local deadzone = 0.2
if math.abs(stickY) > deadzone then
climbDirection = -stickY -- invert so stick forward = climb up (negative Y)
else
-- If no stick input, keep current climbDirection only if keyboard keys pressed; else zero
if not UserInputService:IsKeyDown(Enum.KeyCode.W) and not UserInputService:IsKeyDown(Enum.KeyCode.Up) and
not UserInputService:IsKeyDown(Enum.KeyCode.S) and not UserInputService:IsKeyDown(Enum.KeyCode.Down) then
climbDirection = 0
end
end
-- Update verticalOffset
verticalOffset = verticalOffset + climbDirection * climbSpeed * dt
verticalOffset = math.clamp(verticalOffset, 0, currentRail.Size.Y)
att0.Position = Vector3.new(0, -currentRail.Size.Y / 2 + verticalOffset, 0)
end
end)
Any help would be highly appreciated!
(btw yes, some parts have gotten modified by AI)