--[[
██████╗ █████╗ ██████╗ ██╗ ██╗ ██████╗ ██╗ ██╗██████╗
██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝██╔═══██╗██║ ██║██╔══██╗
██████╔╝███████║██████╔╝█████╔╝ ██║ ██║██║ ██║██████╔╝
██╔═══╝ ██╔══██║██╔══██╗██╔═██╗ ██║ ██║██║ ██║██╔══██╗
██║ ██║ ██║██║ ██║██║ ██╗╚██████╔╝╚██████╔╝██║ ██║
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
Advanced Parkour System v3.0
Motor6D CFrame animation (no server APIs needed)
Keybinds:
[LEFT SHIFT] while sprinting = Slide
[SPACE] near wall + moving = Wall Run
[SPACE] near low obstacle = Vault
[LEFT SHIFT] + moving = Sprint
]]
-- // Services
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")
local Player = Players.LocalPlayer
local Camera = workspace.CurrentCamera
-- // CONFIG
local CFG = {
WallRunSpeed = 26,
WallRunDuration = 1.3,
WallRunGravity = 0.4,
WallRunRayDist = 3.5,
WallRunCooldown = 0.4,
WallRunCamTilt = 14,
SlideSpeed = 36,
SlideDuration = 0.9,
SlideCooldown = 0.5,
SlideCamTilt = -7,
SlideFriction = 0.93,
SlideMinSpeed = 8,
VaultRayDist = 4.5,
VaultMaxHeight = 7,
VaultMinHeight = 1.2,
VaultDuration = 0.28,
VaultCooldown = 0.35,
MaxStamina = 100,
StaminaRegen = 20,
WallRunDrain = 28,
SlideCost = 12,
VaultCost = 8,
WalkSpeed = 16,
SprintSpeed = 22,
}
-- // CHARACTER SETUP
local Character, Humanoid, RootPart
local Motors = {} -- Store Motor6D references + their original C0
local function CacheMotors()
Motors = {}
if not Character then return end
for _, desc in ipairs(Character:GetDescendants()) do
if desc:IsA("Motor6D") then
Motors[desc.Name] = {
Motor = desc,
OriginalC0 = desc.C0,
}
end
end
end
local function SetupCharacter(char)
Character = char
Humanoid = char:WaitForChild("Humanoid")
RootPart = char:WaitForChild("HumanoidRootPart")
char:WaitForChild("Head") -- ensure loaded
task.wait(0.1)
CacheMotors()
end
if Player.Character then
SetupCharacter(Player.Character)
end
Player.CharacterAdded:Connect(function(char)
SetupCharacter(char)
end)
-- // STATE
local State = {
isWallRunning = false,
wallSide = nil,
wallTime = 0,
wallCD = 0,
isSliding = false,
slideTime = 0,
slideDir = Vector3.zero,
slideCD = 0,
isVaulting = false,
vaultCD = 0,
stamina = CFG.MaxStamina,
sprinting = false,
shiftHeld = false,
spaceHeld = false,
}
------------------------------------------------------------------------
-- MOTOR6D ANIMATION SYSTEM (client-side, no server APIs)
------------------------------------------------------------------------
local AnimTargets = {} -- {motorName = targetCFrame}
local AnimAlpha = 0
local AnimSpeed = 8
local function ResetAnimTargets()
AnimTargets = {}
end
-- Smoothly blend Motor6D C0 toward targets each frame
local function UpdateMotorAnimation(dt)
for name, data in pairs(Motors) do
local target = AnimTargets[name]
if target then
data.Motor.C0 = data.Motor.C0:Lerp(data.OriginalC0 * target, math.min(dt * AnimSpeed, 1))
else
-- Return to original
data.Motor.C0 = data.Motor.C0:Lerp(data.OriginalC0, math.min(dt * AnimSpeed, 1))
end
end
end
-- Pose presets
local function SetWallRunPose(side, phase)
local tilt = side == "Right" and -30 or 30
local legSwing = math.sin(phase * math.pi * 2) * 50
AnimTargets = {
["RootJoint"] = CFrame.Angles(0, 0, math.rad(tilt * 0.4)),
["Left Hip"] = CFrame.Angles(math.rad(-legSwing), 0, 0),
["Right Hip"] = CFrame.Angles(math.rad(legSwing), 0, 0),
["Left Shoulder"] = CFrame.Angles(math.rad(legSwing * 0.6), 0, math.rad(-10)),
["Right Shoulder"] = CFrame.Angles(math.rad(-legSwing * 0.6), 0, math.rad(10)),
["Neck"] = CFrame.Angles(math.rad(5), math.rad(side == "Right" and -15 or 15), 0),
-- R15 equivalents
["Root"] = CFrame.Angles(0, 0, math.rad(tilt * 0.4)),
["LeftHip"] = CFrame.Angles(math.rad(-legSwing), 0, 0),
["RightHip"] = CFrame.Angles(math.rad(legSwing), 0, 0),
["LeftShoulder"] = CFrame.Angles(math.rad(legSwing * 0.6), 0, math.rad(-10)),
["RightShoulder"] = CFrame.Angles(math.rad(-legSwing * 0.6), 0, math.rad(10)),
}
AnimSpeed = 12
end
local function SetSlidePose()
AnimTargets = {
-- R6
["RootJoint"] = CFrame.Angles(math.rad(-15), 0, 0),
["Left Hip"] = CFrame.Angles(math.rad(60), 0, 0),
["Right Hip"] = CFrame.Angles(math.rad(10), 0, math.rad(8)),
["Left Shoulder"] = CFrame.Angles(math.rad(25), 0, math.rad(-30)),
["Right Shoulder"] = CFrame.Angles(math.rad(25), 0, math.rad(30)),
["Neck"] = CFrame.Angles(math.rad(15), 0, 0),
-- R15
["Root"] = CFrame.Angles(math.rad(-15), 0, 0),
["LeftHip"] = CFrame.Angles(math.rad(60), 0, 0),
["RightHip"] = CFrame.Angles(math.rad(10), 0, math.rad(8)),
["LeftShoulder"] = CFrame.Angles(math.rad(25), 0, math.rad(-30)),
["RightShoulder"] = CFrame.Angles(math.rad(25), 0, math.rad(30)),
["Waist"] = CFrame.Angles(math.rad(-25), 0, 0),
}
AnimSpeed = 14
end
local function SetVaultPose(phase)
local armAngle = phase < 0.5 and -110 or 40
local legAngle = phase < 0.5 and -35 or 45
local torsoAngle = phase < 0.5 and 12 or -18
AnimTargets = {
-- R6
["RootJoint"] = CFrame.Angles(math.rad(torsoAngle), 0, 0),
["Left Hip"] = CFrame.Angles(math.rad(legAngle), 0, 0),
["Right Hip"] = CFrame.Angles(math.rad(legAngle), 0, 0),
["Left Shoulder"] = CFrame.Angles(math.rad(armAngle), 0, math.rad(-12)),
["Right Shoulder"] = CFrame.Angles(math.rad(armAngle), 0, math.rad(12)),
-- R15
["Root"] = CFrame.Angles(math.rad(torsoAngle), 0, 0),
["LeftHip"] = CFrame.Angles(math.rad(legAngle), 0, 0),
["RightHip"] = CFrame.Angles(math.rad(legAngle), 0, 0),
["LeftShoulder"] = CFrame.Angles(math.rad(armAngle), 0, math.rad(-12)),
["RightShoulder"] = CFrame.Angles(math.rad(armAngle), 0, math.rad(12)),
["Waist"] = CFrame.Angles(math.rad(torsoAngle * 0.5), 0, 0),
}
AnimSpeed = 18
end
------------------------------------------------------------------------
-- RAYCAST HELPERS
------------------------------------------------------------------------
local RayParams = RaycastParams.new()
RayParams.FilterType = Enum.RaycastFilterType.Exclude
local function UpdateFilter()
if Character then
RayParams.FilterDescendantsInstances = {Character}
end
end
local function Cast(origin, dir)
UpdateFilter()
return workspace:Raycast(origin, dir, RayParams)
end
local function CheckWall(side)
if not RootPart or not RootPart.Parent then return nil end
local dir = side == "Right" and RootPart.CFrame.RightVector or -RootPart.CFrame.RightVector
local hit = Cast(RootPart.Position, dir * CFG.WallRunRayDist)
if hit and hit.Instance and hit.Instance.CanCollide then
local dot = math.abs(hit.Normal:Dot(Vector3.yAxis))
if dot < 0.3 then return hit end
end
return nil
end
local function CheckVault()
if not RootPart or not RootPart.Parent then return nil, nil end
local look = RootPart.CFrame.LookVector
local waist = Cast(RootPart.Position + Vector3.new(0, -1, 0), look * CFG.VaultRayDist)
if not waist or not waist.Instance or not waist.Instance.CanCollide then return nil, nil end
local topOrigin = waist.Position + look * 1.5 + Vector3.new(0, CFG.VaultMaxHeight, 0)
local top = Cast(topOrigin, Vector3.new(0, -CFG.VaultMaxHeight * 2, 0))
if top then
local h = top.Position.Y - (RootPart.Position.Y - 3)
if h >= CFG.VaultMinHeight and h <= CFG.VaultMaxHeight then
return waist, top
end
end
return nil, nil
end
local function IsGrounded()
if not RootPart or not RootPart.Parent then return false end
return Cast(RootPart.Position, Vector3.new(0, -3.5, 0)) ~= nil
end
local function MoveDir()
if not Humanoid then return Vector3.zero end
return Humanoid.MoveDirection
end
local function IsMoving()
return MoveDir().Magnitude > 0.1
end
------------------------------------------------------------------------
-- HUD (Minecraft-style, blocky, Code font only)
------------------------------------------------------------------------
local function CreateHUD()
local old = Player.PlayerGui:FindFirstChild("ParkourHUD")
if old then old:Destroy() end
local Gui = Instance.new("ScreenGui")
Gui.Name = "ParkourHUD"
Gui.ResetOnSpawn = false
Gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
Gui.Parent = Player.PlayerGui
-- Bottom bar container
local Bar = Instance.new("Frame")
Bar.Name = "Bar"
Bar.Size = UDim2.new(0, 280, 0, 58)
Bar.Position = UDim2.new(0.5, -140, 1, -82)
Bar.BackgroundColor3 = Color3.fromRGB(28, 28, 28)
Bar.BorderSizePixel = 0
Bar.Parent = Gui
local stroke = Instance.new("UIStroke")
stroke.Thickness = 3
stroke.Color = Color3.fromRGB(10, 10, 10)
stroke.Parent = Bar
-- Inner panel
local Inner = Instance.new("Frame")
Inner.Size = UDim2.new(1, -8, 1, -8)
Inner.Position = UDim2.new(0, 4, 0, 4)
Inner.BackgroundColor3 = Color3.fromRGB(42, 42, 42)
Inner.BorderSizePixel = 0
Inner.Parent = Bar
Instance.new("UIStroke", Inner).Color = Color3.fromRGB(55, 55, 55)
-- Stamina text
local sLabel = Instance.new("TextLabel")
sLabel.Size = UDim2.new(0, 60, 0, 12)
sLabel.Position = UDim2.new(0, 8, 0, 3)
sLabel.BackgroundTransparency = 1
sLabel.Text = "STAMINA"
sLabel.TextColor3 = Color3.fromRGB(170, 170, 170)
sLabel.TextSize = 10
sLabel.Font = Enum.Font.Code
sLabel.TextXAlignment = Enum.TextXAlignment.Left
sLabel.Parent = Inner
-- Stamina bar bg
local sBG = Instance.new("Frame")
sBG.Size = UDim2.new(1, -16, 0, 12)
sBG.Position = UDim2.new(0, 8, 0, 16)
sBG.BackgroundColor3 = Color3.fromRGB(18, 18, 18)
sBG.BorderSizePixel = 0
sBG.Parent = Inner
Instance.new("UIStroke", sBG).Color = Color3.fromRGB(8, 8, 8)
-- Stamina fill
local sFill = Instance.new("Frame")
sFill.Name = "Fill"
sFill.Size = UDim2.new(1, 0, 1, 0)
sFill.BackgroundColor3 = Color3.fromRGB(50, 205, 50)
sFill.BorderSizePixel = 0
sFill.Parent = sBG
-- Shine
local shine = Instance.new("Frame")
shine.Size = UDim2.new(1, 0, 0.3, 0)
shine.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
shine.BackgroundTransparency = 0.82
shine.BorderSizePixel = 0
shine.Parent = sFill
-- Action label
local aLabel = Instance.new("TextLabel")
aLabel.Name = "Action"
aLabel.Size = UDim2.new(1, -16, 0, 16)
aLabel.Position = UDim2.new(0, 8, 0, 30)
aLabel.BackgroundTransparency = 1
aLabel.Text = "[ IDLE ]"
aLabel.TextColor3 = Color3.fromRGB(150, 150, 150)
aLabel.TextSize = 12
aLabel.Font = Enum.Font.Code
aLabel.Parent = Inner
-- Crosshair
local cross = Instance.new("TextLabel")
cross.Size = UDim2.new(0, 20, 0, 20)
cross.Position = UDim2.new(0.5, -10, 0.5, -10)
cross.BackgroundTransparency = 1
cross.Text = "+"
cross.TextColor3 = Color3.fromRGB(255, 255, 255)
cross.TextSize = 20
cross.Font = Enum.Font.Code
cross.TextStrokeTransparency = 0.4
cross.Parent = Gui
-- Keybind hints
local hints = Instance.new("Frame")
hints.Size = UDim2.new(0, 210, 0, 70)
hints.Position = UDim2.new(0, 10, 0, 10)
hints.BackgroundColor3 = Color3.fromRGB(18, 8, 28)
hints.BackgroundTransparency = 0.2
hints.BorderSizePixel = 0
hints.Parent = Gui
local hs = Instance.new("UIStroke")
hs.Color = Color3.fromRGB(75, 35, 115)
hs.Thickness = 2
hs.Parent = hints
local ht = Instance.new("TextLabel")
ht.Size = UDim2.new(1, -10, 1, -6)
ht.Position = UDim2.new(0, 5, 0, 3)
ht.BackgroundTransparency = 1
ht.RichText = true
ht.Text = '<font color="#FFFF55">[SHIFT]</font> Slide / Sprint\n<font color="#FFFF55">[SPACE]</font> Wall Run / Vault'
ht.TextColor3 = Color3.fromRGB(190, 190, 190)
ht.TextSize = 12
ht.Font = Enum.Font.Code
ht.TextXAlignment = Enum.TextXAlignment.Left
ht.TextYAlignment = Enum.TextYAlignment.Top
ht.Parent = hints
task.delay(6, function()
pcall(function()
TweenService:Create(hints, TweenInfo.new(1.5), {BackgroundTransparency = 1}):Play()
TweenService:Create(hs, TweenInfo.new(1.5), {Transparency = 1}):Play()
TweenService:Create(ht, TweenInfo.new(1.5), {TextTransparency = 1}):Play()
end)
end)
return {Fill = sFill, Action = aLabel}
end
local HUD = CreateHUD()
------------------------------------------------------------------------
-- CAMERA TILT
------------------------------------------------------------------------
local camTiltCurrent = 0
local camTiltTarget = 0
------------------------------------------------------------------------
-- WALL RUN
------------------------------------------------------------------------
local wrBV = nil
local wrConn = nil
local function StopWallRun()
if not State.isWallRunning then return end
State.isWallRunning = false
State.wallCD = tick()
if wrBV then pcall(function() wrBV:Destroy() end) wrBV = nil end
if wrConn then wrConn:Disconnect() wrConn = nil end
-- Wall jump boost
if RootPart and RootPart.Parent then
local bv = Instance.new("BodyVelocity")
bv.MaxForce = Vector3.new(30000, 30000, 30000)
bv.Velocity = RootPart.CFrame.LookVector * 16 + Vector3.new(0, 26, 0)
bv.Parent = RootPart
Debris:AddItem(bv, 0.15)
end
camTiltTarget = 0
ResetAnimTargets()
end
local function StartWallRun(side, wallHit)
if State.isWallRunning or State.isSliding or State.isVaulting then return end
if State.stamina < 8 then return end
if tick() - State.wallCD < CFG.WallRunCooldown then return end
State.isWallRunning = true
State.wallSide = side
State.wallTime = 0
local wallNormal = wallHit.Normal
local wallFwd = wallNormal:Cross(Vector3.yAxis).Unit
if RootPart.CFrame.LookVector:Dot(wallFwd) < 0 then wallFwd = -wallFwd end
wrBV = Instance.new("BodyVelocity")
wrBV.MaxForce = Vector3.new(50000, 50000, 50000)
wrBV.P = 10000
wrBV.Parent = RootPart
camTiltTarget = side == "Right" and CFG.WallRunCamTilt or -CFG.WallRunCamTilt
local start = tick()
local phase = 0
wrConn = RunService.Heartbeat:Connect(function(dt)
if not State.isWallRunning then return end
State.wallTime = tick() - start
State.stamina = math.max(0, State.stamina - CFG.WallRunDrain * dt)
local t = State.wallTime / CFG.WallRunDuration
local up = CFG.WallRunSpeed * (1 - t * CFG.WallRunGravity * 2.5)
local fwd = CFG.WallRunSpeed * (1 - t * 0.3)
if wrBV and wrBV.Parent then
wrBV.Velocity = wallFwd * fwd + Vector3.new(0, math.max(up, -12), 0)
end
-- Leg cycling animation
phase = phase + dt * 4
SetWallRunPose(side, phase)
local wall = CheckWall(side)
if State.wallTime >= CFG.WallRunDuration
or State.stamina <= 0
or not wall
or (IsGrounded() and State.wallTime > 0.2)
or not State.spaceHeld then
StopWallRun()
end
end)
end
------------------------------------------------------------------------
-- SLIDE
------------------------------------------------------------------------
local slBV = nil
local slConn = nil
local origHipHeight = nil
local function StopSlide()
if not State.isSliding then return end
State.isSliding = false
State.slideCD = tick()
if slBV then pcall(function() slBV:Destroy() end) slBV = nil end
if slConn then slConn:Disconnect() slConn = nil end
if origHipHeight and Humanoid then
Humanoid.HipHeight = origHipHeight
origHipHeight = nil
end
camTiltTarget = 0
ResetAnimTargets()
end
local function StartSlide()
if State.isSliding or State.isWallRunning or State.isVaulting then return end
if not IsGrounded() or not IsMoving() then return end
if State.stamina < CFG.SlideCost then return end
if tick() - State.slideCD < CFG.SlideCooldown then return end
local vel = RootPart.AssemblyLinearVelocity
local hSpeed = Vector3.new(vel.X, 0, vel.Z).Magnitude
if hSpeed < CFG.SlideMinSpeed then return end
State.isSliding = true
State.slideTime = 0
State.slideDir = MoveDir().Unit
State.stamina = math.max(0, State.stamina - CFG.SlideCost)
origHipHeight = Humanoid.HipHeight
Humanoid.HipHeight = origHipHeight * 0.45
slBV = Instance.new("BodyVelocity")
slBV.MaxForce = Vector3.new(40000, 0, 40000)
slBV.P = 8000
slBV.Velocity = State.slideDir * CFG.SlideSpeed
slBV.Parent = RootPart
SetSlidePose()
camTiltTarget = CFG.SlideCamTilt
local start = tick()
slConn = RunService.Heartbeat:Connect(function(dt)
if not State.isSliding then return end
State.slideTime = tick() - start
if slBV and slBV.Parent then
slBV.Velocity = slBV.Velocity * CFG.SlideFriction
end
if State.slideTime >= CFG.SlideDuration or not State.shiftHeld then
StopSlide()
end
end)
end
------------------------------------------------------------------------
-- VAULT
------------------------------------------------------------------------
local function StartVault()
if State.isVaulting or State.isWallRunning or State.isSliding then return end
if not IsMoving() then return end
if State.stamina < CFG.VaultCost then return end
if tick() - State.vaultCD < CFG.VaultCooldown then return end
local _, topHit = CheckVault()
if not topHit then return end
State.isVaulting = true
State.stamina = math.max(0, State.stamina - CFG.VaultCost)
local startPos = RootPart.Position
local topPos = topHit.Position + Vector3.new(0, 3.5, 0)
local endPos = topPos + RootPart.CFrame.LookVector * 4
local bg = Instance.new("BodyGyro")
bg.MaxTorque = Vector3.new(1e5, 1e5, 1e5)
bg.CFrame = RootPart.CFrame
bg.Parent = RootPart
local bv = Instance.new("BodyVelocity")
bv.MaxForce = Vector3.new(1e5, 1e5, 1e5)
bv.P = 15000
bv.Parent = RootPart
local elapsed = 0
local conn
conn = RunService.Heartbeat:Connect(function(dt)
if not State.isVaulting then
if conn then conn:Disconnect() end
return
end
elapsed = elapsed + dt
local a = math.min(elapsed / CFG.VaultDuration, 1)
SetVaultPose(a)
-- Quadratic bezier
local p0, p1, p2 = startPos, topPos + Vector3.new(0, 2, 0), endPos
local pos = (1 - a)^2 * p0 + 2 * (1 - a) * a * p1 + a^2 * p2
if bv and bv.Parent and RootPart and RootPart.Parent then
bv.Velocity = (pos - RootPart.Position) * 30
end
if a >= 1 then
State.isVaulting = false
State.vaultCD = tick()
pcall(function() bv:Destroy() end)
pcall(function() bg:Destroy() end)
ResetAnimTargets()
conn:Disconnect()
end
end)
end
------------------------------------------------------------------------
-- INPUT
------------------------------------------------------------------------
UserInputService.InputBegan:Connect(function(input, gpe)
if gpe then return end
if input.KeyCode == Enum.KeyCode.LeftShift then
State.shiftHeld = true
if IsMoving() and IsGrounded() then
local vel = RootPart.AssemblyLinearVelocity
local hSpeed = Vector3.new(vel.X, 0, vel.Z).Magnitude
if hSpeed >= CFG.SlideMinSpeed then
StartSlide()
else
State.sprinting = true
if Humanoid then Humanoid.WalkSpeed = CFG.SprintSpeed end
end
else
State.sprinting = true
if Humanoid then Humanoid.WalkSpeed = CFG.SprintSpeed end
end
end
if input.KeyCode == Enum.KeyCode.Space then
State.spaceHeld = true
if not IsGrounded() and not State.isWallRunning then
local rWall = CheckWall("Right")
local lWall = CheckWall("Left")
if rWall and IsMoving() then
StartWallRun("Right", rWall)
elseif lWall and IsMoving() then
StartWallRun("Left", lWall)
end
elseif IsGrounded() and IsMoving() then
StartVault()
end
end
end)
UserInputService.InputEnded:Connect(function(input, gpe)
if input.KeyCode == Enum.KeyCode.LeftShift then
State.shiftHeld = false
State.sprinting = false
if not State.isSliding and not State.isWallRunning and Humanoid then
Humanoid.WalkSpeed = CFG.WalkSpeed
end
end
if input.KeyCode == Enum.KeyCode.Space then
State.spaceHeld = false
end
end)
------------------------------------------------------------------------
-- MAIN LOOP
------------------------------------------------------------------------
RunService.RenderStepped:Connect(function(dt)
if not Character or not Character.Parent then return end
if not Humanoid or Humanoid.Health <= 0 then return end
-- Stamina regen
if not State.isWallRunning and not State.isSliding and not State.isVaulting then
State.stamina = math.min(CFG.MaxStamina, State.stamina + CFG.StaminaRegen * dt)
end
-- Speed guard
if not State.isSliding and not State.isWallRunning and not State.isVaulting then
Humanoid.WalkSpeed = State.sprinting and CFG.SprintSpeed or CFG.WalkSpeed
end
-- Motor6D animation blending
UpdateMotorAnimation(dt)
-- HUD update
pcall(function()
local pct = State.stamina / CFG.MaxStamina
HUD.Fill.Size = UDim2.new(math.max(pct, 0), 0, 1, 0)
if pct > 0.5 then
HUD.Fill.BackgroundColor3 = Color3.fromRGB(50, 205, 50)
elseif pct > 0.25 then
HUD.Fill.BackgroundColor3 = Color3.fromRGB(255, 200, 40)
else
HUD.Fill.BackgroundColor3 = Color3.fromRGB(220, 50, 50)
end
local txt, col = "[ IDLE ]", Color3.fromRGB(150, 150, 150)
if State.isWallRunning then
txt = ">> WALL RUN [" .. (State.wallSide or "?") .. "] <<"
col = Color3.fromRGB(85, 255, 255)
elseif State.isSliding then
txt = ">> SLIDE <<"
col = Color3.fromRGB(255, 170, 50)
elseif State.isVaulting then
txt = ">> VAULT <<"
col = Color3.fromRGB(170, 85, 255)
elseif State.sprinting and IsMoving() then
txt = ">> SPRINT <<"
col = Color3.fromRGB(255, 255, 85)
elseif IsMoving() then
txt = "[ MOVING ]"
col = Color3.fromRGB(200, 200, 200)
end
HUD.Action.Text = txt
HUD.Action.TextColor3 = col
end)
-- Camera tilt
camTiltCurrent = camTiltCurrent + (camTiltTarget - camTiltCurrent) * math.min(dt * 10, 1)
Camera.CFrame = Camera.CFrame * CFrame.Angles(0, 0, math.rad(camTiltCurrent))
end)
------------------------------------------------------------------------
-- DEATH CLEANUP
------------------------------------------------------------------------
local function OnDeath()
StopWallRun()
StopSlide()
State.isVaulting = false
camTiltTarget = 0
ResetAnimTargets()
end
if Humanoid then
Humanoid.Died:Connect(OnDeath)
end
Player.CharacterAdded:Connect(function(char)
task.wait(0.5)
if Humanoid then
Humanoid.Died:Connect(OnDeath)
end
HUD = CreateHUD()
end)
print("[PARKOUR v3] Loaded! SHIFT=Slide/Sprint | SPACE=WallRun/Vault")