‹›
JoltPhysics

2. A floor and a falling crate

Add two collision boxes. The crate falls onto the floor and stays there.

Steps#

  • Keep the room and the single o_tutorial from lesson 1. Replace all six event bodies with this lesson's complete versions; do not append a second Create or physics update.
  • The floor centre is Z=-0.25 and its full height is 0.5 m, so its top is Z=0. The 1 m crate starts at centre Z=3. All box dimensions are full lengths.
  • Run the room. The debug camera shows the floor and falling crate without importing a mesh. Crate Z should settle near 0.5 m; allow small contact tolerances.

Event order#

Create allocates resources. Step reads input and prepares the camera. End Step updates Grabber, calls the single jolt_update and refreshes the camera after simulation. Draw renders 3D debug; Draw GUI renders text. Clean Up releases helpers, Character, bodies and finally the world.

o_tutorial — Create#

GML
game_set_speed(60, gamespeed_fps);
floor_body = undefined;
crate = undefined;
player = undefined;
carry = undefined;
frame_seconds = 1 / 60;
mouse_locked = false;
carry_mouse_input = false;
yaw = 90;
pitch = -17;
sensitivity = 0.12;

if (!jolt_start()) {
    show_error(jolt_get_error_message(), true);
    exit;
}
jolt_debug_enable(true);
jolt_debug_set(0);
jolt_debug_set(JOLT_DEBUG_SHAPES, true);

floor_body = jolt_box_create_static(0, 0, -0.25, 12, 12, 0.5);
crate = jolt_box_create_dynamic(0, 2, 3, 1, 1, 1);
if (is_undefined(floor_body) || is_undefined(crate)) {
    show_error(jolt_get_error_message(), true);
    exit;
}
crate.set_mass(2);
crate.set_friction(0.6);
crate.set_restitution(0.1);

camera = {
    Position: {X:8, Y:-10, Z:7},
    Target: {X:0, Y:0, Z:1},
    Up: {X:0, Y:0, Z:1}
};
refresh_camera = function() {
    if (is_undefined(player)) return;
    var _feet = player.get_position();
    camera.Position = {X:_feet.x, Y:_feet.y, Z:_feet.z + 1.6};
    camera.Target = {
        X:camera.Position.X + dcos(yaw) * dcos(pitch),
        Y:camera.Position.Y + dsin(yaw) * dcos(pitch),
        Z:camera.Position.Z + dsin(pitch)
    };
};
refresh_camera();

o_tutorial — Step#

GML
frame_seconds = clamp(delta_time / 1000000, 0, 0.05);

o_tutorial — Step → End Step#

GML
if (!is_undefined(carry)) carry.update_controls();
if (jolt_update(frame_seconds) < 0) {
    show_error(jolt_get_error_message(), true);
    exit;
}
refresh_camera();

o_tutorial — Draw#

GML
var _world = matrix_get(matrix_world);
var _view = matrix_get(matrix_view);
var _projection = matrix_get(matrix_projection);
var _p = camera.Position;
var _t = camera.Target;
matrix_set(matrix_world, matrix_build_identity());
matrix_set(matrix_view, matrix_build_lookat(
    _p.X, _p.Y, _p.Z, _t.X, _t.Y, _t.Z, 0, 0, 1));
matrix_set(matrix_projection, matrix_build_projection_perspective_fov(
    70, room_width / room_height, 0.05, 100));
jolt_debug_draw();
matrix_set(matrix_world, _world);
matrix_set(matrix_view, _view);
matrix_set(matrix_projection, _projection);

o_tutorial — Draw → Draw GUI#

GML
draw_set_colour(c_white);
draw_text(16, 16, "World: " + string(jolt_is_started()));
if (!is_undefined(crate)) {
    draw_text(16, 40, "Crate Z: " + string_format(crate.get_position().z, 1, 2));
}
if (!is_undefined(player)) {
    draw_text(16, 64, "Tab: mouse | WASD: move | Space: jump");
    var _cx = display_get_gui_width() * 0.5;
    var _cy = display_get_gui_height() * 0.5;
    draw_line(_cx - 5, _cy, _cx + 5, _cy);
    draw_line(_cx, _cy - 5, _cx, _cy + 5);
}
if (!is_undefined(carry)) {
    draw_text(16, 88, "LMB: grab/release | RMB: rotate | R: realign | F: throw");
    draw_text(16, 112, "Holding: " + string(carry.is_holding()));
    draw_text(16, 136, "Grab reason: " + carry.get_last_reason());
}

o_tutorial — Clean Up#

GML
window_mouse_set_locked(false);
window_set_cursor(cr_default);
if (is_struct(carry)) carry.destroy();
if (is_struct(player)) player.destroy();
if (is_struct(crate)) crate.destroy();
if (is_struct(floor_body)) floor_body.destroy();
jolt_stop();

Try it step by step#

Action in the game windowExpected result
Run the new version and wait a few seconds.Debug outlines show the floor and crate. Crate Z falls from 3 to approximately 0.5; these are not textured models.
Move the mouse or press WASD.The view does not move in this lesson. The camera is fixed until lesson 3.

This is a reader checklist, not a claim that every manual step has been successfully tested. See Example Verification for the actual verification scope.

Check the result#

GUI must show World: 1. The crate lands and its centre stays near Z=0.5. Exiting must not produce an invalid-handle error.

Troubleshooting#

  • Unknown jolt_* function or BBMOD type: an imported resource is missing; this is not another object setting.
  • World: 0 or a DLL error: check the Windows x64 target and matching DLL using Installation.
  • World: 1 but nothing visible: check the Draw event, both debug shaders, object visibility and disabled viewport cameras. Physics bodies do not create visual game models.
  • Movement too fast: do not run a second jolt_update controller; convert delta_time from microseconds to seconds.
  • Error when leaving the room: release Grabber before Character and the world last. Do not use a persistent controller here.

Example files#

Verification scope#

The isolated test project uses these exact files. Compilation and runtime check results are listed on Practical Example Verification. Automated tests do not replace manual assessment of mouse feel and scene appearance.