Gessa Docs
Recipes

Recipe

Vehicles cars and drivable physics

The player drives a car, tank, kart, or rover. There is no VehicleComponent and no WheelComponent; a vehicle is composed from the physics atoms. Pick one of two proven paths.
engine v1.0.234since script-semantic-patch-ops.v1, action-catalog.v1.0.232, physics.v1Copy for LLM

Use this for

a car, tank, kart, or rover the player drives; wheels that spin and propel a chassis; steering and acceleration; a joint-driven physics vehicle or a lighter arcade car

Not for

a first-person or third-person character on foot (see first-person-controls, third-person-and-platformer-movement); a moving platform or elevator on a fixed path (see moving-platforms-and-elevators); a projectile launched from a weapon (see projectiles-and-ranged-weapons)

Pairs with: Physics colliders rigid bodies triggers and joints, Input mapping actions bindings keyboard mouse gamepad touch, Camera feel field of view smoothing tone mapping and post, Third-person follow camera and platformer jump movement

The recipe

Add components with project_add_component; author behavior with project_apply_script_semantic_patch (ai_safe pack).

Approach A, joint drivetrain (a true physics vehicle):

  1. Chassis: project_add_component a dynamic RigidBodyComponent plus a solid box ColliderComponent. Set RigidBodyComponent.lockRotations {x:true, y:false, z:true} so wheel-torque reaction cannot flip it; yaw (y) stays free to steer.
  2. Wheels: four entities, each a dynamic RigidBodyComponent, a solid sphere ColliderComponent (friction near 1), and a JointComponent {jointType:"revolute", connectedEntityId:<chassis>, axis:<lateral>, connectedAnchor:<wheel offset in chassis space>, motor:{enabled:true, mode:"velocity", targetVelocity:<spin>, damping:<factor>}}. The runtime builds a real Rapier hinge with that motor; spinning wheels plus ground friction drive the chassis. Motors exist only on revolute and prismatic joints.
  3. Throttle and steer: bind actions (see input-mapping-actions-bindings). In the handler (addActionHandler, body via replaceScriptFromGessaScript) set each wheel's motor.targetVelocity with ctx.entity.patchComponent on the throttle action; steer by driving the left and right wheels at different target speeds (skid or tank steer).

Approach B, arcade car (script plus physics command; no wheels, robust):

  1. One chassis: a dynamic RigidBodyComponent plus a solid box ColliderComponent, same lockRotations (free yaw only).
  2. Bind throttle and steer actions; in the resolved-action handler (replaceScriptFromGessaScript, onActionResolved) call ctx.physics.command with {type:"impulse"} forward for throttle and {type:"torqueImpulse"} about the up axis for steering. Name it an arcade car honestly: it is force-driven, not a simulated drivetrain.

Pitfalls

  • There is no VehicleComponent, no WheelComponent, no wheel-raycast suspension, and no Ackermann steering; compose from JointComponent plus RigidBodyComponent plus ColliderComponent.
  • A motor or limits on a fixed, spherical, spring, or rope joint is rejected by the schema (Rapier unit joints); motors live only on revolute and prismatic.
  • Unbalanced wheel-motor reaction torque hops or flips a free chassis; lock pitch and roll on the chassis so only yaw is free.
  • Never patch VelocityComponent or ForceComponent directly (both hidden and runtime-internal); drive motion through motor.targetVelocity or ctx.physics.command.
  • Each wheel is its own rigid body and collider, so one four-wheel car is five bodies; many cars approach the 1000-collider budget.
  • collideConnected defaults false, so wheels do not collide with their own chassis; leave it false.

Verify

  • simulation_run (qa.run.start) or the playtest driver: possess the chassis, drive throttle then steer, assert the chassis moved and its yaw heading changed, and assert no physics.joint_detached corrections.
  • project_get_graph_snapshot to confirm the chassis, the four wheels, and each revolute JointComponent with an enabled motor.

What the engine supports (honest note)

The engine supports a true joint-based physics vehicle TODAY, with zero engine additions: JointComponent revolute plus a velocity motor is wired to real Rapier ImpulseJoints (server/src/modules/runtime/physics/componentSync.ts syncJointState). Proven in server/tests/vehiclesComposed.test.ts: motorized wheels move a chassis about 1.86 units forward, an opposed left/right drive yaws it, and the arcade chassis drives and turns on input through the W2-L playtest driver. What is NOT present is a dedicated vehicle primitive: no wheel-raycast suspension, no anti-roll or Ackermann steering, no drivetrain component. Those would be the next engine primitive; until then a car is the composition above.

Was this helpful?Report an issueContact support

On this page