‹›
JoltPhysics

4. Uchopování předmětů Grabberem

Označte bednu jako uchopitelnou a propojte Carry s hráčem i kamerou.

Postup#

  • Nahraďte stejných šest událostí touto závěrečnou verzí. Počkejte, až bedna dopadne, stiskněte Tab, namiřte středový kříž na bednu a klikněte levým tlačítkem.
  • Levé tlačítko přepíná uchopení/puštění. Pravé držte pro otáčení drženého předmětu; kamera tehdy nespotřebuje pohyb myši. Před stiskem R pro srovnání orientace pusťte pravé tlačítko; F odhodí a kolečko mění vzdálenost držení. Tab přes update_controls také pustí předmět.
  • set_distances(6,2,1,3,8) znamená: uchopit do 6 m, držet ve 2 m, dovolit držení od 1 do 3 m, pustit při překročení meze 8 m. Mez puštění musí být větší než dosah uchopení i maximální vzdálenost držení.
  • set_camera(id) odkazuje na tuto instanci controlleru. Automatické ovládání vyžaduje mouse_locked a camera.Position/Target/Up se složkami X/Y/Z velkými písmeny; carry_mouse_input povoluje otáčení myší. refresh_camera a controls_use_mouse zde tento kontrakt zajišťují.
  • Bedna váží 2 kg, je pod limitem max_mass=20 kg a výslovně povoluje uchopení. Statickou podlahu nosit nelze. Neúspěšný pokus vysvětluje Grab reason v GUI; miřte na povrch bedny v dosahu a zkontrolujte, že Tab zamkl myš.

Pořadí událostí#

Create vytvoří zdroje. Step načte vstup a připraví kameru. End Step aktualizuje Grabber, zavolá jediný jolt_update a obnoví kameru po simulaci. Draw kreslí 3D debug; Draw GUI kreslí text. Clean Up uvolní pomocníky, Character, tělesa a nakonec svět.

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);

player = jolt_character();
if (is_undefined(player)) {
    show_error(jolt_get_error_message(), true);
    exit;
}
player.set_radius(0.4);
player.set_height(1.8);
player.set_position(0, -2, 0.1);
player.set_speed(4);
player.set_jump_speed(5);
jolt_debug_set(JOLT_DEBUG_CHARACTERS, true);

crate.set_grab_enabled(true);
carry = jolt_grabber();
if (is_undefined(carry)) {
    show_error(jolt_get_error_message(), true);
    exit;
}
carry.set_holder(player);
carry.set_camera(id);
carry.set_distances(6, 2, 1, 3, 8);
carry.set_max_mass(20);
carry.set_grab_button(mb_left);
carry.set_rotate_button(mb_right);
carry.set_realign_key(ord("R"));
carry.set_throw_key(ord("F"));
carry.set_throw_impulse(10);
carry.set_throw_max_speed(6);
carry.set_debug_labels(false);
jolt_debug_set(JOLT_DEBUG_GRABBER, true);

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);

if (keyboard_check_pressed(vk_tab)) {
    mouse_locked = !mouse_locked;
    window_mouse_set_locked(mouse_locked);
}
if (!window_has_focus()) {
    mouse_locked = false;
    window_mouse_set_locked(false);
}
window_set_cursor(mouse_locked ? cr_none : cr_default);
carry_mouse_input = !is_undefined(carry) && carry.controls_use_mouse(id);
if (mouse_locked && !carry_mouse_input) {
    yaw += window_mouse_get_delta_x() * sensitivity;
    pitch = clamp(pitch - window_mouse_get_delta_y() * sensitivity, -85, 85);
}

var _forward = 0;
var _side = 0;
if (mouse_locked) {
    _forward = keyboard_check(ord("W")) - keyboard_check(ord("S"));
    _side = keyboard_check(ord("D")) - keyboard_check(ord("A"));
    if (keyboard_check_pressed(vk_space)) player.jump();
}
var _mx = _forward * dcos(yaw) + _side * dcos(yaw - 90);
var _my = _forward * dsin(yaw) + _side * dsin(yaw - 90);
var _length = point_distance(0, 0, _mx, _my);
if (_length > 1) { _mx /= _length; _my /= _length; }
player.set_move(_mx, _my);
refresh_camera();

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();

Vyzkoušejte krok za krokem#

Akce v okně hryOčekávaný výsledek
Po dopadu bedny klikněte do okna, stiskněte Tab, zamiřte křížem na bednu a jednou klikněte LMB.Holding se změní na 1. LMB nemusíte držet; druhé kliknutí bednu pustí.
Znovu uchopte bednu. Držte RMB a přitom pomalu pohybujte myší.Otáčí se držená bedna a kamera během RMB nepřebírá pohyb myši. Samotné držení RMB bez pohybu myši nic neotáčí.
Pusťte RMB a teprve potom stiskněte R.Spustí se srovnání orientace. Při drženém RMB se R podle současného ovládání ignoruje.
Při držení použijte kolečko, potom stiskněte F.Kolečko mění vzdálenost v rozmezí 1–3 m. F odhodí bednu a Holding se vrátí na 0.
Znovu uchopte bednu a stiskněte Tab nebo přepněte do jiného okna.Bedna se pustí a kurzor se uvolní. Po návratu znovu stiskněte Tab.
Zkuste uchopit podlahu.Statickou podlahu nelze nosit. Neúspěch zde není chyba Grabberu.

Jde o kontrolní postup pro čtenáře, nikoliv o tvrzení, že byly všechny ruční kroky úspěšně odzkoušeny. Skutečný rozsah ověření je uveden na stránce Ověření příkladů.

Bedna je symetrická krychle bez textury. Otočení přesně o 90° může vypadat stejně jako původní orientace; při kontrole RMB používejte malé průběžné pohyby a sledujte hrany. Grab reason popisuje poslední výsledek operace, není to průběžná informace o objektu pod zaměřovačem. Aktuální držení poznáte podle Holding.

Ověření výsledku#

V GUI musí být World: 1. Bedna dopadne a její střed zůstane blízko Z=0.5. Hráč chodí a po skoku znovu dopadne. Holding se po uchopení změní na 1; po puštění, odhození nebo odemčení myši zpět na 0. Při ukončení nesmí vzniknout chyba neplatného handle.

Když to nefunguje#

  • Neznámá jolt_* funkce nebo BBMOD typ: chybí součást importu, nikoliv další nastavení objektu.
  • World: 0 nebo chyba DLL: ověřte cílovou platformu x64 a odpovídající DLL podle Instalace.
  • World: 1, ale nic nevidíte: ověřte Draw událost, oba debug shadery, viditelnost objektu a vypnuté viewport kamery. Fyzikální těleso samo nevytváří herní model.
  • Příliš rychlý pohyb: v místnosti nesmí běžet druhý controller s jolt_update; delta_time se převádí z mikrosekund na sekundy.
  • Chyba při odchodu z místnosti: uvolňujte Grabber před Characterem a svět jako poslední. Nepoužívejte zde persistent controller.

Soubory příkladu#

Rozsah ověření#

Tyto stejné soubory používá oddělený testovací projekt. Stav kompilace a runtime kontrol je uveden na stránce Ověření praktických příkladů. Automatický test nenahrazuje ruční posouzení pocitu z myši a vzhledu scény.