Character Attachment
Makes a CharacterVirtual follow a rigid body's full three-dimensional transform for seats, vehicles, elevators and moving machinery.
JoltCharacter.attachment_attach() switches an existing managed character from independent CharacterVirtual movement to a transform follower. The supplied position and rotation are stored in the target body's local space. Every fixed step derives the character's world position, orientation, up vector and point velocity from that body until the character is detached or the target is destroyed.
Syntax#
character.attachment_attach(body, options=undefined)
character.attachment_detach()
character.attachment_is_active()
character.attachment_get_body()Arguments#
| Argument | Type | Description |
|---|---|---|
| body | JoltBody | Live managed rigid body in the same world as the character. Static, Kinematic and Dynamic rigid bodies are valid targets. |
| options | Struct or undefined | Optional body-local position and orientation. Omitted values use the body origin and identity rotation. |
Options#
| Field | Default | Description |
|---|---|---|
| position | {x:0,y:0,z:0} | Finite body-local attachment position. It is not a world-space destination and is transformed by the target body's position and rotation. |
| rotation_euler | {x:0,y:0,z:0} | Finite body-local XYZ rotation in degrees. It uses the extension's qZ * qY * qX Euler convention. |
| rotation | undefined | Advanced body-local quaternion {x,y,z,w}. When supplied it takes precedence over rotation_euler, must be finite and non-zero, and is normalized internally. |
Transform model#
world_position = body_transform * local_position
world_rotation = body_rotation * local_rotation
world_up = world_rotation * {x:0, y:0, z:1}- The local position rotates with the body. A seat offset of {x:1,y:0,z:0} therefore follows the body's local X axis rather than remaining one world unit to the east.
- The local rotation is composed after the body rotation, so roll, pitch, yaw and upside-down orientations remain valid for the complete attached state.
- Use rotation_euler for normal authoring. Use rotation only when the project already has a normalized or normalizable quaternion.
- Attaching again replaces the old target and local transform immediately; a separate detach call is not required when changing seats.
Typical seat interaction#
// player is a live JoltCharacter; seat_body is a live JoltBody.
if (keyboard_check_pressed(ord("E"))) {
if (!player.attachment_is_active()) {
if (!player.attachment_attach(seat_body, {
position: {x:0, y:-0.7, z:1.2},
rotation_euler: {x:0, y:0, z:180}
})) {
show_debug_message(jolt_get_error().message);
}
} else {
player.attachment_detach();
}
}
// Position can use character.sync(); orientation must be applied explicitly.
player.sync();
if (player.attachment_is_active()) {
var facing = player.get_rotation_euler();
character_object.rotation_x = facing.x;
character_object.rotation_y = facing.y;
character_object.rotation_z = facing.z;
}What changes while attached#
| System | Attached behaviour |
|---|---|
| Position and rotation | The rigid body is authoritative. The attachment is sampled before and after every fixed physics step, avoiding a one-step render lag. |
| Walking and gravity | Normal CharacterVirtual movement, gravity, stair handling, slope solving and ground movement are suspended. |
| Jumping and crouching | Jump requests, jump buffering, coyote time and crouch transitions do not drive the attached character. |
| Collisions and pushing | Independent CharacterVirtual collision solving, character contacts and pushing are suspended so the seat or chassis cannot eject its own passenger. |
| Velocity | The character reports the target body's velocity at the exact attachment point, including the contribution from angular motion. |
| Reference frame | A previous character reference frame is cleared when the full attachment succeeds. |
Related methods#
| Method | Return and purpose |
|---|---|
| attachment_detach() | Returns Boolean. Preserves world position, inherits target point velocity and restores ordinary global Z-up movement. |
| attachment_is_active() | Returns Boolean native state. It also notices automatic detach after the target is destroyed. |
| attachment_get_body() | Returns the current live managed JoltBody, or undefined. |
| attachment_get_position() | Returns the stored body-local {x,y,z} position. |
| attachment_get_rotation() | Returns the stored body-local quaternion {x,y,z,w}. |
| get_rotation() | Returns the character's current world quaternion. |
| get_rotation_euler() | Returns the current world orientation as GameMaker Euler degrees for rendering. |
| get_up() | Returns the current world-space up vector, including the attached body's roll and pitch. |
Rendering and the physics loop#
- Create the character and rigid body only after jolt_start(), then call jolt_update(delta_time / 1000000) from the normal Step flow.
- The native attachment resamples after rigid-body simulation, so get_position(), get_rotation() and get_up() expose the current body pose after jolt_update().
- JoltCharacter.sync() copies position and character state to a bound GameMaker instance, but it does not apply model rotation. Read get_rotation() or get_rotation_euler() and update the visual orientation explicitly.
- Do not drive set_position(), set_velocity(), walking, jumping or crouching as competing authorities during the attached state.
Detaching safely#
- attachment_detach() is safe when already detached and returns true.
- The current world position is preserved. The character inherits the body's point velocity at the attachment location, so leaving a moving or rotating vehicle keeps physically coherent momentum.
- Ordinary controller movement is global Z-up. On detach the implementation preserves heading as far as possible while realigning a rolled or upside-down passenger to Z-up.
- Jump and forced-jump state is cleared. Normal collision registration resumes for subsequent physics steps.
Ownership and automatic detach#
- The character does not own the target body. Detaching or destroying the character does not destroy the body.
- Destroying the target body automatically detaches every attached character before its generational handle becomes stale.
- Automatic detach preserves the last character position; when possible, normal target destruction also transfers its current point velocity.
- A stale body wrapper, a body from another world or an invalid character is rejected. Handle-slot reuse cannot silently attach the character to a different later body.
- jolt_stop() cleans both resources through normal managed teardown; do not call their methods afterward.
Choosing the local pose#
- Author the seat offset relative to the body's public transform origin. For bodies with a centre-of-mass offset, this remains the exposed body transform rather than an arbitrary world point.
- Start with identity rotation and adjust one Euler axis at a time. This makes model-facing differences easier to diagnose.
- If the character appears in the wrong place when the vehicle turns, a world-space coordinate was probably supplied where body-local position was required.
- For enter-nearest-seat logic, convert or predefine each seat in local space; do not continuously rewrite the attachment pose from world coordinates.
Validation and errors#
- The character and target must both be valid managed resources in the same world.
- options.position must contain finite x, y and z values.
- options.rotation_euler must contain finite x, y and z degrees when rotation is omitted.
- options.rotation must contain finite x, y, z and w values and must not have near-zero length.
- attachment_attach(), attachment_detach() and state-changing methods return false on failure. Read jolt_get_error() immediately before another Jolt call replaces the diagnostic.
Common mistakes#
| Symptom | Likely cause and correction |
|---|---|
| attachment_attach is undefined | It was called globally. Use player.attachment_attach(body, options) on a JoltCharacter. |
| Passenger orbits from the wrong point | position contains world coordinates. Supply an offset in the body's local space. |
| Position follows but the model stays upright | sync() does not write visual rotation. Apply get_rotation() or get_rotation_euler() to the rendered instance/model. |
| Player cannot walk or jump | This is intentional during full attachment. Detach first, or use a reference frame for walkable moving geometry. |
| The player shoots away on exit | The inherited point velocity is physically real for a fast rotating attachment. Limit vehicle angular speed or apply a game-specific post-detach policy if required. |
Setter-based configuration#
var seat = jolt_character_attachment(seat_body);
seat.set_position(0, -0.7, 1.2);
seat.set_rotation_euler(0, 0, 180);
player.attachment_attach(seat);Editing settings does not mutate an existing attachment; call attachment_attach again. Settings setters return self; attachment_attach returns Boolean. Old names attach_to_body, detach_from_body and is_attached are not current methods.
Verified operation contracts#
These contracts distinguish units, return values, errors and lifetime according to the current implementation. Check results: false, 0 and undefined can mean different things for different methods.
| Symbol / owner | Signature | Operation contract |
|---|---|---|
| jolt_character_attachment | jolt_character_attachment(body=undefined) | Creates settings only, optional body (undefined); does not require a world or create physics. Returns JoltCharacterAttachmentSettings; use character.attachment_attach(settings). |
| JoltCharacter.attachment_attach | JoltCharacter.attachment_attach(body, options = undefined) | Live JoltBody in the same world + options, or JoltCharacterAttachmentSettings. Boolean. Local position defaults to {0, 0, 0} m; rotation XYZW takes precedence over rotation_euler XYZ° (default identity). Switches to host following, clears reference frame and pending jump; not an independently walking solver. |
| JoltCharacter.attachment_detach | JoltCharacter.attachment_detach() | Boolean; detaches the Character with host velocity inheritance according to native detach, does not destroy the host. |
| JoltCharacter.attachment_get_body | JoltCharacter.attachment_get_body() | Live managed host or undefined. |
| JoltCharacter.attachment_get_position | JoltCharacter.attachment_get_position() | Local offset {x, y, z} in m relative to the host, not a world hand/character position; undefined on read error. Check attachment state separately. |
| JoltCharacter.attachment_get_rotation | JoltCharacter.attachment_get_rotation() | Local quaternion {x, y, z, w} relative to the host, not world orientation; undefined on read error. |
| JoltCharacter.attachment_is_active | JoltCharacter.attachment_is_active() | Boolean current attachment state; also false on error. |
| JoltCharacter.get_rotation | JoltCharacter.get_rotation() | Character world orientation: quaternion {x, y, z, w}, or Euler {x, y, z} in degrees according to the name; undefined on error. |
| JoltCharacter.get_rotation_euler | JoltCharacter.get_rotation_euler() | Character world orientation: quaternion {x, y, z, w}, or Euler {x, y, z} in degrees according to the name; undefined on error. |
| JoltCharacterAttachmentSettings.set_body | JoltCharacterAttachmentSettings.set_body(body) | Returns self, changes settings only for the next attachment_attach. Position is local in m; rotation expects an XYZW struct and copies components; Euler XYZ degrees clears the previous quaternion. Validated at attachment_attach; an existing attachment is not reconfigured. |
| JoltCharacterAttachmentSettings.set_position | JoltCharacterAttachmentSettings.set_position(x,y,z) | Returns self, changes settings only for the next attachment_attach. Position is local in m; rotation expects an XYZW struct and copies components; Euler XYZ degrees clears the previous quaternion. Validated at attachment_attach; an existing attachment is not reconfigured. |
| JoltCharacterAttachmentSettings.set_rotation | JoltCharacterAttachmentSettings.set_rotation(rotation) | Returns self, changes settings only for the next attachment_attach. Position is local in m; rotation expects an XYZW struct and copies components; Euler XYZ degrees clears the previous quaternion. Validated at attachment_attach; an existing attachment is not reconfigured. |
| JoltCharacterAttachmentSettings.set_rotation_euler | JoltCharacterAttachmentSettings.set_rotation_euler(x,y,z) | Returns self, changes settings only for the next attachment_attach. Position is local in m; rotation expects an XYZW struct and copies components; Euler XYZ degrees clears the previous quaternion. Validated at attachment_attach; an existing attachment is not reconfigured. |