Player movement abilities dash sprint and double jump
CharacterMovementComponent motor, and the burst itselfUse this for
Not for
Pairs with: Third-person follow camera and platformer jump movement, First-person camera movement and mouselook controls, Input mapping actions bindings keyboard mouse gamepad touch, Physics colliders rigid bodies triggers and joints, Game feel juice hit flash camera kick and impact
When to use
The player already walks and jumps (see third-person-and-platformer-movement or first-person-controls) and now needs an extra move: a dash burst, a sprint that speeds them up while a key is held, or a second jump in the air. The engine has no DashComponent, SprintComponent, or DoubleJumpComponent; each ability is a script layered on the real CharacterMovementComponent motor, and the burst itself is the same ctx.physics.command launch that jump pads use.
The recipe
Prerequisite: the player carries CharacterMovementComponent (enableKcc: true, a maxSpeed, a jumpSpeed, a groundY) and an InputProfileComponent. Add one action per ability (see input-mapping-actions-bindings): a sprint boolean and a dash (or doubleJump) button. Author the behavior with project_apply_script_semantic_patch using the ai_safe op pack.
Sprint (faster while held)
addActionHandler{ "actionKey":"sprint", "handlerKey":"onSprint" }.- In the handler, branch on the
InputActionEventphase: onpressed,ctx.entity.patchComponentthe player'sCharacterMovementComponentto a highermaxSpeed(for example 11); onreleased, patch it back to the walk value (for example 7). Read the walk value once withctx.entity.readComponentand stash it in state if you want it dynamic. - Optional stamina: seed a
staminanumber withwriteTypedStateValue, drain it each tick while sprinting withincrementNumericState{ "by": -1 }, and forcereleasedbehavior when it hits 0.
Dash (a burst in the facing direction)
addActionHandler{ "actionKey":"dash", "handlerKey":"onDash" }.- Keep the current facing fresh:
addTickHandler{ "handlerKey":"onMoveSample" }that, in areplaceScriptFromGessaScriptbody, reads themoveaction value (theInputActionEventvalue, avector2d) and, when it is non-zero,writeTypedStateValueafacingvector. This gives the dash a direction even at a standstill (last-moved). - In
onDash(whole-body GessaScript), readfacing, scale it to a dash speed (for example 18), and firectx.physics.command(playerId, { type: "launch", velocity: { x: fx*18, y: 0, z: fz*18 } })whereplayerIdis the entity the handler runs on (ctx.entityis the self-accessor for reads and patches; pass the player's own id to the command).launchSETS the kinematic character's velocity for this step, which is the one sanctioned way to fling aCharacterMovementComponentbody; do not patchTransformComponentposition to fake the dash. - Cooldown:
setBooleanState{ "field":"dashReady", "value": false }on dash, and flip it back after a delay with aTimerComponent(see timers-countdowns-and-waves). Gate the dash ondashReadyso it cannot be spammed.
Double jump (a second jump before landing)
- Track jumps used: seed
jumpsUsedto 0 withwriteTypedStateValue. - Ground reset: in an
addTickHandlerbody,ctx.entity.readComponenttheTransformComponentposition.yand theCharacterMovementComponentgroundY; when the player is at (or near)groundY,writeTypedStateValuejumpsUsedback to 0. This is the grounded check without a hidden velocity lane. - Jump action:
addActionHandler{ "actionKey":"jump", "handlerKey":"onJump" }.readTypedStateValuejumpsUsed; if it is less than the allowed count (2 for a double jump),ctx.physics.command(playerId, { type:"launch", velocity:{ x:0, y: jumpSpeed, z:0 } })andincrementNumericStatejumpsUsed{ "by": 1 }. WhenjumpsUsedreaches the cap, ignore the press.
Pitfalls
- The launch
yfor a jump should match the movementjumpSpeedso the double jump feels like the first; read it withctx.entity.readComponentrather than hardcoding a second number that can drift from the motor. launchREPLACES velocity, it does not add to it: a dash issued mid-fall wipes the fall speed for that step. That is usually what you want for a crisp dash; for a momentum-preserving dash, compose the desired resultant yourself.- Do NOT author a
VelocityComponentorForceComponentto move the character; both are hidden internal runtime lanes. Every burst goes throughctx.physics.command, every speed change throughCharacterMovementComponent.maxSpeed. - Ground the double-jump reset on position vs
groundY, not on a landing event you cannot subscribe to; there is no grounded-state flag on the movement component to read. - Sprint changes
maxSpeed; it does not multiply an existing velocity. If sprint feels like it does nothing, confirm you patched the sameCharacterMovementComponentthe mover reads and thatenableKccis true.
Verify
engine_get_component_schemaonCharacterMovementComponentto confirmmaxSpeed/jumpSpeed/groundYbefore patching.project_get_graph_snapshotto confirm the sprint/dash/jump handlers and theScriptComponentbinding exist on the player.simulation_run(qa.run.start) to prove sprint raises speed while held, the dash launches in the move direction, and a second jump fires in the air but a third does not.