Vehicles cars and drivable physics
Use this for
Not for
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):
- Chassis:
project_add_componenta dynamicRigidBodyComponentplus a solid boxColliderComponent. SetRigidBodyComponent.lockRotations{x:true, y:false, z:true}so wheel-torque reaction cannot flip it; yaw (y) stays free to steer. - Wheels: four entities, each a dynamic
RigidBodyComponent, a solid sphereColliderComponent(friction near 1), and aJointComponent{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. - Throttle and steer: bind actions (see input-mapping-actions-bindings). In the handler (
addActionHandler, body viareplaceScriptFromGessaScript) set each wheel'smotor.targetVelocitywithctx.entity.patchComponenton 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):
- One chassis: a dynamic
RigidBodyComponentplus a solid boxColliderComponent, samelockRotations(free yaw only). - Bind throttle and steer actions; in the resolved-action handler (
replaceScriptFromGessaScript,onActionResolved) callctx.physics.commandwith{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
JointComponentplusRigidBodyComponentplusColliderComponent. - A motor or limits on a
fixed,spherical,spring, orropejoint is rejected by the schema (Rapier unit joints); motors live only onrevoluteandprismatic. - 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
VelocityComponentorForceComponentdirectly (both hidden and runtime-internal); drive motion throughmotor.targetVelocityorctx.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.
collideConnecteddefaults 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 nophysics.joint_detachedcorrections.project_get_graph_snapshotto confirm the chassis, the four wheels, and each revoluteJointComponentwith 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.