ParkourSystem_v2

Run Settings
LanguageLua
Language Version
Run Command
--[[ ██████╗ █████╗ ██████╗ ██╗ ██╗ ██████╗ ██╗ ██╗██████╗ ██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝██╔═══██╗██║ ██║██╔══██╗ ██████╔╝███████║██████╔╝█████╔╝ ██║ ██║██║ ██║██████╔╝ ██╔═══╝ ██╔══██║██╔══██╗██╔═██╗ ██║ ██║██║ ██║██╔══██╗ ██║ ██║ ██║██║ ██║██║ ██╗╚██████╔╝╚██████╔╝██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ Advanced Parkour System v2.0 - Wall Run (Left/Right detection + gravity curve) - Slide (Speed burst + hitbox shrink + camera tilt) - Vault (Obstacle detection + lerp over) - Custom procedural animations (no imports) - Minecraft-style HUD (blocky stamina bar + move indicator) Keybinds: [LEFT SHIFT] while moving = Slide [SPACE] near wall + moving = Wall Run [SPACE] near low obstacle = Vault ]] -- // Services local Players = game:GetService("Players") local RunService = game:GetService("RunService") local UserInputService = game:GetService("UserInputService") local TweenService = game:GetService("TweenService") local Player = Players.LocalPlayer local Camera = workspace.CurrentCamera -- // Wait for character local Character = Player.Character or Player.CharacterAdded:Wait() local Humanoid = Character:WaitForChild("Humanoid") local RootPart = Character:WaitForChild("HumanoidRootPart") local Animator = Humanoid:WaitForChild("Animator") -- // Respawn handler Player.CharacterAdded:Connect(function(char) Character = char Humanoid = char:WaitForChild("Humanoid") RootPart = char:WaitForChild("HumanoidRootPart") Animator = Humanoid:WaitForChild("Animator") end) -- // Config local CONFIG = { -- Wall Run WallRunSpeed = 28, WallRunDuration = 1.4, WallRunGravity = 0.35, WallRunRayDist = 3.2, WallRunCooldown = 0.5, WallRunMinSpeed = 8, WallRunCameraTilt = 15, -- Slide SlideSpeed = 38, SlideDuration = 1.0, SlideCooldown = 0.6, SlideHitboxScale = 0.5, SlideCameraTilt = -8, SlideMinSpeed = 10, SlideFriction = 0.92, -- Vault VaultRayDist = 4.5, VaultMaxHeight = 7.5, VaultMinHeight = 1.5, VaultSpeed = 0.3, VaultCooldown = 0.4, -- Stamina MaxStamina = 100, StaminaRegen = 18, WallRunStaminaCost = 30, SlideStaminaCost = 15, VaultStaminaCost = 10, -- General DefaultWalkSpeed = 16, SprintSpeed = 24, } -- // State local State = { isWallRunning = false, wallRunSide = nil, -- "Left" or "Right" wallRunTime = 0, wallRunCooldown = 0, isSliding = false, slideTime = 0, slideCooldown = 0, slideDir = nil, isVaulting = false, vaultCooldown = 0, stamina = CONFIG.MaxStamina, isSprinting = false, shiftHeld = false, spaceHeld = false, } ------------------------------------------------------------------------ -- MINECRAFT-STYLE HUD ------------------------------------------------------------------------ local function CreateHUD() -- Destroy old GUI if re-running local old = Player.PlayerGui:FindFirstChild("ParkourHUD") if old then old:Destroy() end local ScreenGui = Instance.new("ScreenGui") ScreenGui.Name = "ParkourHUD" ScreenGui.ResetOnSpawn = false ScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling ScreenGui.Parent = Player.PlayerGui -- Main container (bottom center) local Container = Instance.new("Frame") Container.Name = "Container" Container.Size = UDim2.new(0, 280, 0, 64) Container.Position = UDim2.new(0.5, -140, 1, -90) Container.BackgroundColor3 = Color3.fromRGB(30, 30, 30) Container.BorderSizePixel = 0 Container.Parent = ScreenGui -- Blocky border (MC style - 3px dark outline) local Border = Instance.new("UIStroke") Border.Thickness = 3 Border.Color = Color3.fromRGB(12, 12, 12) Border.Parent = Container -- Inner border highlight local InnerBorder = Instance.new("Frame") InnerBorder.Name = "InnerBorder" InnerBorder.Size = UDim2.new(1, -6, 1, -6) InnerBorder.Position = UDim2.new(0, 3, 0, 3) InnerBorder.BackgroundColor3 = Color3.fromRGB(48, 48, 48) InnerBorder.BorderSizePixel = 0 InnerBorder.Parent = Container local InnerStroke = Instance.new("UIStroke") InnerStroke.Thickness = 1 InnerStroke.Color = Color3.fromRGB(60, 60, 60) InnerStroke.Parent = InnerBorder -- Stamina label (pixely look using Code font) local StaminaLabel = Instance.new("TextLabel") StaminaLabel.Name = "StaminaLabel" StaminaLabel.Size = UDim2.new(0, 70, 0, 16) StaminaLabel.Position = UDim2.new(0, 10, 0, 6) StaminaLabel.BackgroundTransparency = 1 StaminaLabel.Text = "STAMINA" StaminaLabel.TextColor3 = Color3.fromRGB(180, 180, 180) StaminaLabel.TextSize = 11 StaminaLabel.Font = Enum.Font.Code StaminaLabel.TextXAlignment = Enum.TextXAlignment.Left StaminaLabel.Parent = InnerBorder -- Stamina bar background local StaminaBG = Instance.new("Frame") StaminaBG.Name = "StaminaBG" StaminaBG.Size = UDim2.new(1, -20, 0, 14) StaminaBG.Position = UDim2.new(0, 10, 0, 22) StaminaBG.BackgroundColor3 = Color3.fromRGB(20, 20, 20) StaminaBG.BorderSizePixel = 0 StaminaBG.Parent = InnerBorder local BarStroke = Instance.new("UIStroke") BarStroke.Thickness = 2 BarStroke.Color = Color3.fromRGB(10, 10, 10) BarStroke.Parent = StaminaBG -- Stamina bar fill local StaminaFill = Instance.new("Frame") StaminaFill.Name = "StaminaFill" StaminaFill.Size = UDim2.new(1, 0, 1, 0) StaminaFill.BackgroundColor3 = Color3.fromRGB(50, 205, 50) StaminaFill.BorderSizePixel = 0 StaminaFill.Parent = StaminaBG -- Pixelated shine on stamina bar (MC hearts style) local Shine = Instance.new("Frame") Shine.Name = "Shine" Shine.Size = UDim2.new(1, 0, 0.35, 0) Shine.BackgroundColor3 = Color3.fromRGB(255, 255, 255) Shine.BackgroundTransparency = 0.8 Shine.BorderSizePixel = 0 Shine.Parent = StaminaFill -- Move indicator label local MoveLabel = Instance.new("TextLabel") MoveLabel.Name = "MoveLabel" MoveLabel.Size = UDim2.new(1, -20, 0, 18) MoveLabel.Position = UDim2.new(0, 10, 0, 38) MoveLabel.BackgroundTransparency = 1 MoveLabel.Text = "[ IDLE ]" MoveLabel.TextColor3 = Color3.fromRGB(255, 255, 85) MoveLabel.TextSize = 13 MoveLabel.Font = Enum.Font.Code MoveLabel.TextXAlignment = Enum.TextXAlignment.Center MoveLabel.Parent = InnerBorder -- Crosshair (MC style - small + in center) local Crosshair = Instance.new("TextLabel") Crosshair.Name = "Crosshair" Crosshair.Size = UDim2.new(0, 20, 0, 20) Crosshair.Position = UDim2.new(0.5, -10, 0.5, -10) Crosshair.BackgroundTransparency = 1 Crosshair.Text = "+" Crosshair.TextColor3 = Color3.fromRGB(255, 255, 255) Crosshair.TextSize = 22 Crosshair.Font = Enum.Font.Code Crosshair.TextStrokeTransparency = 0.5 Crosshair.TextStrokeColor3 = Color3.fromRGB(0, 0, 0) Crosshair.Parent = ScreenGui -- Keybind hint (top left, MC-style tooltip) local HintFrame = Instance.new("Frame") HintFrame.Name = "HintFrame" HintFrame.Size = UDim2.new(0, 220, 0, 80) HintFrame.Position = UDim2.new(0, 12, 0, 12) HintFrame.BackgroundColor3 = Color3.fromRGB(20, 10, 30) HintFrame.BackgroundTransparency = 0.25 HintFrame.BorderSizePixel = 0 HintFrame.Parent = ScreenGui local HintStroke = Instance.new("UIStroke") HintStroke.Thickness = 2 HintStroke.Color = Color3.fromRGB(80, 40, 120) HintStroke.Parent = HintFrame local HintText = Instance.new("TextLabel") HintText.Size = UDim2.new(1, -12, 1, -8) HintText.Position = UDim2.new(0, 6, 0, 4) HintText.BackgroundTransparency = 1 HintText.RichText = true HintText.Text = '<font color="#FFFF55">[SHIFT]</font> Slide\n<font color="#FFFF55">[SPACE]</font> Wall Run / Vault\n<font color="#FFFF55">[W+SHIFT]</font> Sprint' HintText.TextColor3 = Color3.fromRGB(200, 200, 200) HintText.TextSize = 12 HintText.Font = Enum.Font.Code HintText.TextXAlignment = Enum.TextXAlignment.Left HintText.TextYAlignment = Enum.TextYAlignment.Top HintText.Parent = HintFrame -- Fade hint after 8 seconds task.delay(8, function() local tw = TweenService:Create(HintFrame, TweenInfo.new(1.5), {BackgroundTransparency = 1}) local tw2 = TweenService:Create(HintStroke, TweenInfo.new(1.5), {Transparency = 1}) local tw3 = TweenService:Create(HintText, TweenInfo.new(1.5), {TextTransparency = 1}) tw:Play() tw2:Play() tw3:Play() end) return { StaminaFill = StaminaFill, MoveLabel = MoveLabel, } end local HUD = CreateHUD() ------------------------------------------------------------------------ -- CUSTOM PROCEDURAL ANIMATIONS (no imported anims) ------------------------------------------------------------------------ local AnimationModule = {} -- Create a KeyframeSequence programmatically, register as animation local function BuildAnimation(name, length, looped, keyframes) local kfs = Instance.new("KeyframeSequence") kfs.Name = name kfs.Loop = looped for _, kfData in ipairs(keyframes) do local kf = Instance.new("Keyframe") kf.Time = kfData.Time for _, poseData in ipairs(kfData.Poses) do local pose = Instance.new("Pose") pose.Name = poseData.Name pose.CFrame = poseData.CFrame pose.EasingStyle = poseData.EasingStyle or Enum.PoseEasingStyle.Linear pose.EasingDirection = poseData.EasingDirection or Enum.PoseEasingDirection.InOut -- Handle sub-poses (limb hierarchy) if poseData.SubPoses then for _, subData in ipairs(poseData.SubPoses) do local subPose = Instance.new("Pose") subPose.Name = subData.Name subPose.CFrame = subData.CFrame subPose.EasingStyle = subData.EasingStyle or Enum.PoseEasingStyle.Linear subPose.Parent = pose end end pose.Parent = kf end kf.Parent = kfs end -- Register with animator local animId = Animator:LoadAnimation( (function() local anim = Instance.new("Animation") local reg = game:GetService("KeyframeSequenceProvider"):RegisterKeyframeSequence(kfs) anim.AnimationId = reg return anim end)() ) return animId end -- WALL RUN ANIMATION (body tilts sideways, legs cycling) local function CreateWallRunAnim(side) local tiltAngle = side == "Right" and math.rad(-35) or math.rad(35) local armReach = side == "Right" and math.rad(-60) or math.rad(60) return BuildAnimation("WallRun_" .. side, 0.6, true, { { Time = 0, Poses = { { Name = "HumanoidRootPart", CFrame = CFrame.Angles(0, 0, tiltAngle * 0.3), SubPoses = { { Name = "LowerTorso", CFrame = CFrame.Angles(math.rad(10), 0, 0), } } }, { Name = "LeftUpperLeg", CFrame = CFrame.Angles(math.rad(-45), 0, 0), }, { Name = "RightUpperLeg", CFrame = CFrame.Angles(math.rad(35), 0, 0), }, { Name = "LeftUpperArm", CFrame = CFrame.Angles(math.rad(20), 0, math.rad(-15)), }, { Name = "RightUpperArm", CFrame = CFrame.Angles(armReach, 0, math.rad(15)), }, } }, { Time = 0.3, Poses = { { Name = "HumanoidRootPart", CFrame = CFrame.Angles(0, 0, tiltAngle * 0.3), }, { Name = "LeftUpperLeg", CFrame = CFrame.Angles(math.rad(35), 0, 0), }, { Name = "RightUpperLeg", CFrame = CFrame.Angles(math.rad(-45), 0, 0), }, { Name = "LeftUpperArm", CFrame = CFrame.Angles(math.rad(-30), 0, math.rad(-15)), }, { Name = "RightUpperArm", CFrame = CFrame.Angles(math.rad(10), 0, math.rad(15)), }, } }, }) end -- SLIDE ANIMATION (crouched, one leg forward, arms back) local SlideAnim = BuildAnimation("Slide", 0.4, false, { { Time = 0, Poses = { { Name = "HumanoidRootPart", CFrame = CFrame.Angles(math.rad(-5), 0, 0), SubPoses = { { Name = "LowerTorso", CFrame = CFrame.Angles(math.rad(-25), 0, 0), } } }, { Name = "LeftUpperLeg", CFrame = CFrame.Angles(math.rad(70), 0, 0), }, { Name = "RightUpperLeg", CFrame = CFrame.Angles(math.rad(15), 0, math.rad(10)), }, { Name = "LeftUpperArm", CFrame = CFrame.Angles(math.rad(30), 0, math.rad(-25)), }, { Name = "RightUpperArm", CFrame = CFrame.Angles(math.rad(30), 0, math.rad(25)), }, { Name = "UpperTorso", CFrame = CFrame.Angles(math.rad(-30), 0, 0), }, { Name = "Head", CFrame = CFrame.Angles(math.rad(20), 0, 0), }, } }, }) -- VAULT ANIMATION (hands reach forward, body lifts up and over) local VaultAnim = BuildAnimation("Vault", 0.35, false, { { Time = 0, Poses = { { Name = "HumanoidRootPart", CFrame = CFrame.Angles(math.rad(15), 0, 0), }, { Name = "LeftUpperArm", CFrame = CFrame.Angles(math.rad(-120), 0, math.rad(-10)), }, { Name = "RightUpperArm", CFrame = CFrame.Angles(math.rad(-120), 0, math.rad(10)), }, { Name = "LeftUpperLeg", CFrame = CFrame.Angles(math.rad(-40), 0, 0), }, { Name = "RightUpperLeg", CFrame = CFrame.Angles(math.rad(-40), 0, 0), }, } }, { Time = 0.2, Poses = { { Name = "HumanoidRootPart", CFrame = CFrame.Angles(math.rad(-20), 0, 0), }, { Name = "LeftUpperArm", CFrame = CFrame.Angles(math.rad(40), 0, math.rad(-20)), }, { Name = "RightUpperArm", CFrame = CFrame.Angles(math.rad(40), 0, math.rad(20)), }, { Name = "LeftUpperLeg", CFrame = CFrame.Angles(math.rad(50), 0, 0), }, { Name = "RightUpperLeg", CFrame = CFrame.Angles(math.rad(50), 0, 0), }, } }, }) -- Pre-build wall run anims local WallRunLeftAnim = CreateWallRunAnim("Left") local WallRunRightAnim = CreateWallRunAnim("Right") local currentAnim = nil local function PlayAnim(track) if currentAnim and currentAnim.IsPlaying then currentAnim:Stop(0.15) end currentAnim = track track:Play(0.15) end local function StopAnim() if currentAnim and currentAnim.IsPlaying then currentAnim:Stop(0.2) currentAnim = nil end end ------------------------------------------------------------------------ -- RAYCAST HELPERS ------------------------------------------------------------------------ local RayParams = RaycastParams.new() RayParams.FilterType = Enum.RaycastFilterType.Exclude local function UpdateRayFilter() RayParams.FilterDescendantsInstances = {Character} end UpdateRayFilter() Player.CharacterAdded:Connect(function() task.wait(0.5) UpdateRayFilter() end) local function CastRay(origin, direction) return workspace:Raycast(origin, direction, RayParams) end -- Check for wall on a specific side 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 result = CastRay(RootPart.Position, dir * CONFIG.WallRunRayDist) if result and result.Instance and result.Instance.CanCollide then -- Verify it's a roughly vertical surface local dot = math.abs(result.Normal:Dot(Vector3.new(0, 1, 0))) if dot < 0.3 then return result end end return nil end -- Check for vaultable obstacle ahead local function CheckVault() if not RootPart or not RootPart.Parent then return nil, nil end local lookDir = RootPart.CFrame.LookVector -- Cast forward at waist height local waistResult = CastRay( RootPart.Position + Vector3.new(0, -1, 0), lookDir * CONFIG.VaultRayDist ) if not waistResult or not waistResult.Instance or not waistResult.Instance.CanCollide then return nil, nil end -- Cast downward from above the obstacle to find the top local topOrigin = waistResult.Position + lookDir * 1.5 + Vector3.new(0, CONFIG.VaultMaxHeight, 0) local topResult = CastRay(topOrigin, Vector3.new(0, -CONFIG.VaultMaxHeight * 2, 0)) if topResult then local obstacleHeight = topResult.Position.Y - (RootPart.Position.Y - 3) if obstacleHeight >= CONFIG.VaultMinHeight and obstacleHeight <= CONFIG.VaultMaxHeight then return waistResult, topResult end end return nil, nil end -- Check if character is grounded local function IsGrounded() if not RootPart or not RootPart.Parent then return false end local result = CastRay(RootPart.Position, Vector3.new(0, -3.5, 0)) return result ~= nil end -- Get movement direction local function GetMoveDirection() if not Humanoid then return Vector3.zero end return Humanoid.MoveDirection end local function IsMoving() return GetMoveDirection().Magnitude > 0.1 end ------------------------------------------------------------------------ -- CAMERA TILT ------------------------------------------------------------------------ local currentTilt = 0 local targetTilt = 0 local function SetCameraTilt(degrees) targetTilt = math.rad(degrees) end ------------------------------------------------------------------------ -- WALL RUN SYSTEM ------------------------------------------------------------------------ local wallRunBV = nil local function StartWallRun(side, wallResult) if State.isWallRunning or State.isSliding or State.isVaulting then return end if State.stamina < CONFIG.WallRunStaminaCost * 0.3 then return end if tick() - State.wallRunCooldown < CONFIG.WallRunCooldown then return end State.isWallRunning = true State.wallRunSide = side State.wallRunTime = 0 -- Calculate wall-parallel direction (upward + forward along wall) local wallNormal = wallResult.Normal local wallForward = wallNormal:Cross(Vector3.new(0, 1, 0)).Unit if RootPart.CFrame.LookVector:Dot(wallForward) < 0 then wallForward = -wallForward end -- Create BodyVelocity for wall run movement wallRunBV = Instance.new("BodyVelocity") wallRunBV.MaxForce = Vector3.new(50000, 50000, 50000) wallRunBV.P = 10000 wallRunBV.Parent = RootPart -- Play animation local animTrack = side == "Right" and WallRunRightAnim or WallRunLeftAnim PlayAnim(animTrack) -- Camera tilt SetCameraTilt(side == "Right" and CONFIG.WallRunCameraTilt or -CONFIG.WallRunCameraTilt) -- Wall run update loop local startTick = tick() local conn conn = RunService.Heartbeat:Connect(function(dt) if not State.isWallRunning then if conn then conn:Disconnect() end return end State.wallRunTime = tick() - startTick State.stamina = math.max(0, State.stamina - CONFIG.WallRunStaminaCost * dt) -- Gravity curve: starts going up, gradually arcs down local t = State.wallRunTime / CONFIG.WallRunDuration local upForce = CONFIG.WallRunSpeed * (1 - t * CONFIG.WallRunGravity * 2.5) local forwardForce = CONFIG.WallRunSpeed * (1 - t * 0.3) if wallRunBV and wallRunBV.Parent then wallRunBV.Velocity = wallForward * forwardForce + Vector3.new(0, math.max(upForce, -10), 0) end -- Re-check wall local newWall = CheckWall(side) -- End conditions if State.wallRunTime >= CONFIG.WallRunDuration or State.stamina <= 0 or not newWall or IsGrounded() and State.wallRunTime > 0.2 or not State.spaceHeld then StopWallRun() if conn then conn:Disconnect() end end end) end function StopWallRun() if not State.isWallRunning then return end State.isWallRunning = false State.wallRunCooldown = tick() if wallRunBV then wallRunBV:Destroy() wallRunBV = nil end -- Small upward boost on release (wall jump) if RootPart and RootPart.Parent then local jumpBV = Instance.new("BodyVelocity") jumpBV.MaxForce = Vector3.new(30000, 30000, 30000) jumpBV.Velocity = RootPart.CFrame.LookVector * 18 + Vector3.new(0, 28, 0) jumpBV.Parent = RootPart game:GetService("Debris"):AddItem(jumpBV, 0.15) end SetCameraTilt(0) StopAnim() end ------------------------------------------------------------------------ -- SLIDE SYSTEM ------------------------------------------------------------------------ local slideBV = nil local originalHipHeight = nil 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 < CONFIG.SlideStaminaCost * 0.5 then return end if tick() - State.slideCooldown < CONFIG.SlideCooldown then return end local moveDir = GetMoveDirection() if moveDir.Magnitude < 0.1 then return end -- Check minimum speed for slide if RootPart.AssemblyLinearVelocity.Magnitude < CONFIG.SlideMinSpeed then return end State.isSliding = true State.slideTime = 0 State.slideDir = moveDir.Unit State.stamina = math.max(0, State.stamina - CONFIG.SlideStaminaCost) -- Shrink hitbox originalHipHeight = Humanoid.HipHeight Humanoid.HipHeight = originalHipHeight * CONFIG.SlideHitboxScale -- Slide velocity slideBV = Instance.new("BodyVelocity") slideBV.MaxForce = Vector3.new(40000, 0, 40000) slideBV.P = 8000 slideBV.Velocity = State.slideDir * CONFIG.SlideSpeed slideBV.Parent = RootPart -- Animation + camera PlayAnim(SlideAnim) SetCameraTilt(CONFIG.SlideCameraTilt) local startTick = tick() local conn conn = RunService.Heartbeat:Connect(function(dt) if not State.isSliding then if conn then conn:Disconnect() end return end State.slideTime = tick() - startTick -- Apply friction if slideBV and slideBV.Parent then local speed = slideBV.Velocity.Magnitude * CONFIG.SlideFriction slideBV.Velocity = State.slideDir * speed end -- End conditions if State.slideTime >= CONFIG.SlideDuration or not State.shiftHeld then StopSlide() if conn then conn:Disconnect() end end end) end function StopSlide() if not State.isSliding then return end State.isSliding = false State.slideCooldown = tick() if slideBV then slideBV:Destroy() slideBV = nil end if originalHipHeight and Humanoid then Humanoid.HipHeight = originalHipHeight originalHipHeight = nil end SetCameraTilt(0) StopAnim() end ------------------------------------------------------------------------ -- VAULT SYSTEM ------------------------------------------------------------------------ local function StartVault() if State.isVaulting or State.isWallRunning or State.isSliding then return end if not IsMoving() then return end if State.stamina < CONFIG.VaultStaminaCost then return end if tick() - State.vaultCooldown < CONFIG.VaultCooldown then return end local waistHit, topHit = CheckVault() if not waistHit or not topHit then return end State.isVaulting = true State.stamina = math.max(0, State.stamina - CONFIG.VaultStaminaCost) -- Play vault animation PlayAnim(VaultAnim) -- Calculate vault path local startPos = RootPart.Position local topPos = topHit.Position + Vector3.new(0, 3.5, 0) local endPos = topPos + RootPart.CFrame.LookVector * 4 -- Disable default physics during vault local bg = Instance.new("BodyGyro") bg.MaxTorque = Vector3.new(100000, 100000, 100000) bg.CFrame = RootPart.CFrame bg.Parent = RootPart local bv = Instance.new("BodyVelocity") bv.MaxForce = Vector3.new(100000, 100000, 100000) bv.P = 15000 bv.Parent = RootPart -- Smooth vault lerp local elapsed = 0 local duration = CONFIG.VaultSpeed 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 alpha = math.min(elapsed / duration, 1) -- Bezier curve: start -> top -> end local t = alpha local p0 = startPos local p1 = topPos + Vector3.new(0, 2, 0) -- control point (arc peak) local p2 = endPos local pos = (1 - t)^2 * p0 + 2 * (1 - t) * t * p1 + t^2 * p2 if bv and bv.Parent and RootPart and RootPart.Parent then bv.Velocity = (pos - RootPart.Position) * 30 end if alpha >= 1 then State.isVaulting = false State.vaultCooldown = tick() if bv then bv:Destroy() end if bg then bg:Destroy() end StopAnim() if conn then conn:Disconnect() end end end) end ------------------------------------------------------------------------ -- INPUT HANDLING ------------------------------------------------------------------------ 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 -- If already moving fast, slide; otherwise sprint if RootPart and RootPart.AssemblyLinearVelocity.Magnitude >= CONFIG.SlideMinSpeed then StartSlide() else State.isSprinting = true Humanoid.WalkSpeed = CONFIG.SprintSpeed end else State.isSprinting = true Humanoid.WalkSpeed = CONFIG.SprintSpeed end end if input.KeyCode == Enum.KeyCode.Space then State.spaceHeld = true if not IsGrounded() and not State.isWallRunning then -- Try wall run local rightWall = CheckWall("Right") local leftWall = CheckWall("Left") if rightWall and IsMoving() then StartWallRun("Right", rightWall) elseif leftWall and IsMoving() then StartWallRun("Left", leftWall) end elseif IsGrounded() and IsMoving() then -- Try vault StartVault() end end end) UserInputService.InputEnded:Connect(function(input, gpe) if input.KeyCode == Enum.KeyCode.LeftShift then State.shiftHeld = false State.isSprinting = false if not State.isSliding and not State.isWallRunning then Humanoid.WalkSpeed = CONFIG.DefaultWalkSpeed end end if input.KeyCode == Enum.KeyCode.Space then State.spaceHeld = false end end) ------------------------------------------------------------------------ -- MAIN UPDATE LOOP (Stamina, HUD, Camera Tilt) ------------------------------------------------------------------------ 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 (when not doing parkour moves) if not State.isWallRunning and not State.isSliding and not State.isVaulting then State.stamina = math.min(CONFIG.MaxStamina, State.stamina + CONFIG.StaminaRegen * dt) end -- Walk speed reset guard if not State.isSliding and not State.isWallRunning and not State.isVaulting then if State.isSprinting then Humanoid.WalkSpeed = CONFIG.SprintSpeed else Humanoid.WalkSpeed = CONFIG.DefaultWalkSpeed end end -- Update HUD if HUD.StaminaFill and HUD.StaminaFill.Parent then local pct = State.stamina / CONFIG.MaxStamina HUD.StaminaFill.Size = UDim2.new(math.max(pct, 0), 0, 1, 0) -- Color shift: green -> yellow -> red if pct > 0.5 then HUD.StaminaFill.BackgroundColor3 = Color3.fromRGB(50, 205, 50) elseif pct > 0.25 then HUD.StaminaFill.BackgroundColor3 = Color3.fromRGB(255, 200, 40) else HUD.StaminaFill.BackgroundColor3 = Color3.fromRGB(220, 50, 50) end end -- Move label if HUD.MoveLabel and HUD.MoveLabel.Parent then local label = "[ IDLE ]" local color = Color3.fromRGB(150, 150, 150) if State.isWallRunning then label = ">> WALL RUN [" .. (State.wallRunSide or "?") .. "] <<" color = Color3.fromRGB(85, 255, 255) elseif State.isSliding then label = ">> SLIDE <<" color = Color3.fromRGB(255, 170, 50) elseif State.isVaulting then label = ">> VAULT <<" color = Color3.fromRGB(170, 85, 255) elseif State.isSprinting and IsMoving() then label = ">> SPRINT <<" color = Color3.fromRGB(255, 255, 85) elseif IsMoving() then label = "[ MOVING ]" color = Color3.fromRGB(200, 200, 200) end HUD.MoveLabel.Text = label HUD.MoveLabel.TextColor3 = color end -- Smooth camera tilt currentTilt = currentTilt + (targetTilt - currentTilt) * math.min(dt * 12, 1) Camera.CFrame = Camera.CFrame * CFrame.Angles(0, 0, currentTilt) end) ------------------------------------------------------------------------ -- CLEANUP ON DEATH ------------------------------------------------------------------------ Humanoid.Died:Connect(function() StopWallRun() StopSlide() State.isVaulting = false StopAnim() SetCameraTilt(0) end) print("[PARKOUR] System loaded! | SHIFT=Slide/Sprint | SPACE=WallRun/Vault")
Editor Settings
Theme
Key bindings
Full width
Lines