Fast-paced movement mechanics like wall running, sliding, and ledge mantling define modern competitive action games. Attempting to build fluid parkour using default Humanoid state changes often results in sticky collision glitches, erratic momentum loss, and frustrating falls.
By leveraging modern Luau physics constraints—specifically `LinearVelocity`, `VectorForce`, and directional raycasting—developers can construct responsive, server-authoritative custom movement controllers that deliver AAA platforming feel across PC, console, and mobile.
1. Overriding Default Humanoid States for Fluid Parkour
Foundations of custom movement state machines in Roblox Studio:
- Disabling Conflicting Humanoid States: When wall running initiates, immediately invoke
Humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)and disableFreefallto prevent the default falling animation from overriding procedural physics. - RootPart Network Ownership: Ensure the client maintains explicit network ownership of their character's
HumanoidRootPartviarootPart:SetNetworkOwner(player)to eliminate input-to-physics latency. - State Machine Modularity: Architecture movement using a finite state machine (Walking, Sprinting, WallRunning, LedgeHanging, Vaulting) with distinct entry, tick, and exit hooks.
2. Directional Raycasting & Surface Normal Alignment
Detecting runnable geometry and calculating traversal vectors:
- Multi-Raycast Probes: Cast horizontal raycasts from the character's hip and shoulder positions to the left and right (typically 2.5–3.5 studs) to detect vertical wall surfaces.
- Surface Normal Evaluation: Extract the hit surface normal vector. Ensure the surface angle is perpendicular (within 75° to 105° relative to the world up vector) to prevent running on shallow ramps.
- Forward Vector Projection: Calculate the wall-run direction using the cross product:
wallNormal:Cross(Vector3.yAxis), orienting traversal parallel to the wall contour.
3. Modern Physics Constraints: LinearVelocity & VectorForce
Replacing deprecated BodyMovers with constraint-based forces:
- LinearVelocity for Constant Speed: Attach a
LinearVelocityconstraint to HumanoidRootPart. SetVelocityConstraintModeto Vector and configureMaxForcehigh enough to counteract lateral friction while maintaining a constant forward pace. - VectorForce for Anti-Gravity: Counteract the workspace gravity acceleration (typically 196.2 studs/s²) with an upward
VectorForcecalculated ascharacterMass * workspace.Gravity * antiGravityFactor. - Controlled Vertical Drift: Gradually decay the upward force over 1.5–2.5 seconds to create a realistic downward arc, establishing a skill-based time limit on continuous wall runs.
-- Modern Wall Running Physics Controller (Client-Side)
local RunService = game:GetService("RunService")
local WallRun = {}
WallRun.__index = WallRun
function WallRun.new(character)
local self = setmetatable({}, WallRun)
self.Char = character
self.RootPart = character:WaitForChild("HumanoidRootPart")
self.Humanoid = character:WaitForChild("Humanoid")
-- Setup LinearVelocity Constraint
local att = Instance.new("Attachment", self.RootPart)
local lv = Instance.new("LinearVelocity")
lv.Attachment0 = att
lv.MaxForce = 15000
lv.RelativeTo = Enum.ActuatorRelativeTo.World
lv.Enabled = false
lv.Parent = self.RootPart
self.Attachment = att
self.LinearVelocity = lv
self.IsRunning = false
return self
end
function WallRun:CheckWall(side)
local rayDir = (side == "Left" and -self.RootPart.CFrame.RightVector or self.RootPart.CFrame.RightVector) * 3.5
local params = RaycastParams.new()
params.FilterDescendantsInstances = {self.Char}
params.FilterType = Enum.RaycastFilterType.Exclude
return workspace:Raycast(self.RootPart.Position, rayDir, params)
end
function WallRun:Start(hitNormal, forwardDir)
self.IsRunning = true
self.LinearVelocity.VectorVelocity = forwardDir * 32 + Vector3.new(0, 4, 0)
self.LinearVelocity.Enabled = true
self.Humanoid:ChangeState(Enum.HumanoidStateType.Physics)
end
function WallRun:Stop()
self.IsRunning = false
self.LinearVelocity.Enabled = false
self.Humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
end
return WallRun
4. Procedural Ledge Grabbing & Mantle Transitions
Detecting ledges and executing seamless climb-up animations:
- Two-Tier Ledge Detection: Cast a chest-height ray forward to confirm wall contact, combined with a downward ray cast from above the wall to find the exact ledge top edge.
- RootPart Snapping: Anchor or clamp character position using an
AlignPositionconstraint when the ledge is grabbed, allowing the player to hang or shimmy. - Tweened Mantle Curve: When the jump key is pressed while hanging, play a climb animation while smoothly interpolating the RootPart position up and over the ledge edge using a quadratic bezier curve.
5. Mobile Touch Controls & Server Validation
Ensuring accessibility across devices while preventing teleport exploits:
- ContextActionService Touch Binding: Bind wall-jump and mantle inputs to dynamic on-screen action buttons with generous touch hitboxes (minimum 48x48 pixels).
- Server-Side Speed Clamping: The server must calculate velocity deltas between replication ticks. Allow higher velocity thresholds exclusively during verified wall-run states.
- Sanity Raycasts on Server: Re-verify that a valid wall existed near the player's position during the reported wall-run window before confirming client positions.
Frequently Asked Questions
Why should developers use LinearVelocity instead of BodyVelocity in Roblox?
BodyVelocity is a legacy BodyMover deprecated by Roblox. LinearVelocity uses the modern constraint solver, offering superior physical stability, proper integration with mass properties, and predictable multi-platform replication.
How do you prevent a character from sliding down a wall while wall running?
Apply an upward force via VectorForce or configure the Y component of LinearVelocity to cancel workspace gravity (mass * 196.2). To balance gameplay, decay this upward force over 1.5–2 seconds.
How does surface normal raycasting work for wall running?
Raycasts cast horizontally from the character detect the wall and return a hit surface normal. The cross product of the normal with the global up vector (0, 1, 0) yields a forward traversal vector parallel to the wall.
How can developers prevent wall-running exploits on the server?
Maintain an authoritative server state check that casts rays from the player's replicated position to ensure a physical wall exists within 4 studs, and clamp maximum velocity to prevent fly-hacking.