IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Stav hry

V minulém díle, Vylepšování základní scény, jsem ukazoval, jak se dají mazat krychle.

V tomto díle ukážu, jak můžeme držet stav celé aplikace na jednom místě.

Před čtením tohoto článku je dobré znát základy MVC architektury a něco o frameworku Redux.

Webová hra - WebGL a BabylonJS - 3D webová hra

Stav aplikace

Stav aplikace jsou data, která představují podstatu toho, co uživatel vidí na obrazovce.

V běžné webové aplikaci - např. Wikipedii, je stav aplikace to, na jaké stránce + odstavci jsem. V Eshopu je stav aplikace obvykle jaký produkt si prohlížím + co mám v košíku. V naší hře bude stav aplikace jaké krychle jsou na scéně.

Redux je Framework pro držení stavu aplikace a manipulaci s ním. V single page aplikacích využívajících Redux se stav aplikace obvykle ukládá do čistých javascriptových objektů nebo Immutable objektů. Já využiji čisté JS objekty. Takto bude vypadat defaultní stav hry:

const defaultState = {
    blocks: [{id:'My first block!!!',position:{x:0,y:0,z:0}}]
};

Poznámka: Natočení a vzdálenost kamery by také měla být součástí stavu aplikace. To je však trochu obtížnější na implementaci a tím pádem tomu věnuji celý článek někdy v budoucnu.

Na to aby nám dnešní ukázka fungovala, musíme přidat knihovny Redux a knihovnu pro generování uuid. V dnešním díle to udělám v hlavičce html. V některém z dalších dílů použiji balíčkovací systém npm.

<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js"></script>

Stavem aplikace se manipuluje pomocí funkce zvané reducer. Do ní předáme původní stav + akci a ven vypadne stav nový. Je velmi důležité vyrobit stav nový a neupravovat stav původní, abychom nad aplikací neztratili kontrolu. Akce je prostý JS objekt, který má klíč type. Ten pojmenovává podstatu toho, jak se stav změní. Pro naši hru budeme mít zatím 2 typy akcí: přidávání nové krychle a smazání krychle:

function stateReducer(state, action) {
    switch (action.type) {

        case 'BLOCK_ADD':
            return {
                blocks: state.blocks.concat([action.newBlock])
            };

        case 'BLOCK_DELETE':
            return {
                blocks: state.blocks.filter((block)=>block.id!==action.blockId)
            };

        default:
            return state;
    }
}

Nyní můžeme z reduceru a defaultního stavu vyrobit Redux store. To je "centrální úložiště" stavu naší hry.

const store = Redux.createStore(stateReducer,defaultState);

Tam, kde jsem původně zasahoval přímo do Babylon scény, budu odesílat akce do Redux uložiště:

function onPointerUp(event) {
    const pickInfo = scene.pick(scene.pointerX, scene.pointerY);
    if (pickInfo.hit) {
        const currentMesh = pickInfo.pickedMesh;
        switch (event.button) {
            case 0:
                const diff = currentMesh.position.subtract(pickInfo.pickedPoint);
                const position = currentMesh.position.clone();

                ['x', 'y', 'z'].forEach((dimension) => {
                    if (diff[dimension] >= 0.5 - 0.001) {
                        position[dimension]--;
                    } else if (diff[dimension] <= -0.5 + 0.001) {
                        position[dimension]++;
                    }
                });

                store.dispatch({
                    type: 'BLOCK_ADD', newBlock: {
                        id: uuid.v4(),// Pro nový blok generuji nové id pomocí knihovny uuid https://www.npmjs.com/package/uuid
                        position: {
                            x: Math.floor(position.x),
                            y: Math.floor(position.y),
                            z: Math.floor(position.z)
                        }
                    }
                });
                break;

            case 2:
                store.dispatch({type: 'BLOCK_DELETE', blockId: currentMesh.name});
                break;
        }
    }
}

Musím však zaručit, aby se krychle vytvořila či smazala i ve scéně, ne pouze v datovém modelu. Proto si funkci createScene rozdělím na dvě:

  • createScene - Připraví mi prázdnou Babylon scénu. To docílím tak, že z createScene odstraním část, kdy vytvářím první krychli. Tu spustím při startu hry.
  • updateScene - Vytvoří ve scéně krychle přesně podle aktuálního stavu aplikace.
function updateScene(scene, state) {
    // Nejdříve vyčistím scénu.
    scene.meshes.forEach((mesh) => {
        mesh.dispose();
    });
    scene.meshes = [];

    // Vyhledám základní materiál.
    const materialNormal = scene.materials.find(material=>material.name==='material-normal');

    // Potom ji naplním podle aktuálního stavu.
    state.blocks.forEach(block=>{
        const newBox = BABYLON.Mesh.CreateBox(block.id, 1, scene);
        newBox.material = materialNormal;
        newBox.position = new BABYLON.Vector3(block.position.x, block.position.y, block.position.z);
    });
}

Tu spustím po vytvoření store a vždy, když se mi změní stav aplikace:

const store = Redux.createStore(stateReducer, defaultState);

var canvas = document.getElementById("scene");
var engine = new BABYLON.Engine(canvas, true);
var scene = createScene(canvas, engine);

// Vytvořím pomocnou funkci render.
function render() {
    updateScene(scene, store.getState());
}

store.subscribe(render);
render();

engine.runRenderLoop(function () {
    scene.render();
});

// Resize
window.addEventListener("resize", function () {
    engine.resize();
});

Poznámka: Není úplně efektivní po každé změně stavu celou scénu přemazávat. V tomhle případě jsem tento postup zvolil proto, že za mírné plýtvání pamětí, získám mnohem lepší kontrolu nad tím, co se v aplikace děje. Později můžeme funkci updateScene() optimalizovat.

Soubory

Vzhledem k tomu, že se nám začíná projekt rozrůstat, rozdělil jsem ho do několika souborů:

src
    script
        scene
            create-scene.ts
            update-scene.ts
        state
            default-state.js
        state-reducers
            state-reducer.js
        browser.tsx
    style
        index.css
index.html

Rozdělanou hru si můžeš stáhnout pod článkem, nebo jít do Git repozitáře, kde najdeš nejnovější verzi zdrojových kódů. Nebo si ji rovnou můžeš vyzkoušet na webappgames.git­hub.io/web-game.

WebGL a BabylonJS - 3D webová hra

V dalším díle, Zpět a Vpřed, ukáži, jaké výhody nám přináší zachování stavu aplikace do URL.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 71x (3.59 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

Předchozí článek
Vylepšování základní scény
Všechny články v sekci
WebGL a BabylonJS - 3D webová hra
Přeskočit článek
(nedoporučujeme)
Zpět a Vpřed
Článek pro vás napsal Pavol Hejný
Avatar
Uživatelské hodnocení:
4 hlasů
/^(web )?(app )?developer$/
Aktivity