September discount week
Tento týden až 80% sleva na e-learning týkající se jazyka C
50 % bodů zdarma na online výuku díky naší Slevové akci!

Lekce 4 - Zpět a Vpřed

V minulém díle, Stav hry, jsem ukazoval, co to je stav aplikace a jak ho držet.

V tomto díle ukáži, jaké výhody nám přináší zachování stavu aplikace do URL.

Webová hra

Logování

Ve chvíli, kdy držíme stav aplikace na jednom místě, můžeme všechny jeho změny velmi elegantně logovat a tím vidět, co se v aplikaci děje. Vytvoříme si funkci wrapReducer():

function wrapReducer(reducer){
    // Kontrola pro případ, že bych byl v prostředí node.js
    if('groupCollapsed' in console){
        return function (oldState,action) {
            console.groupCollapsed(`==[${action.type}]==>`);
            console.log(oldState);
            console.log('||');
            console.log('||');

            console.log(`[${action.type}]`, action);
            const newState = reducer(oldState, action);

            console.log('||');
            console.log('||');
            console.log('\\/');
            console.log(newState);

            console.groupEnd();
            return newState;
        }
    }else{

        return reducer;
    }
}

Potom vytváříme store tak, že reducer nejdříve "obalíme" touto logovací funkcí:

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

Nyní se po každé akci do konzole prohlížeče vypíše, co se stalo + stav předtím a potom.

URL

Ve webové aplikaci by bylo dobré, aby se každá změna stavu zaznamenávala do URL adresy v prohlížeči. Do URL však nemůžeme uložit příliš mnoho informací a tak můžeme stav ukládat do LocalStorage pod unikátním IDčkem a dané ID dávat do URL adresy.

function loadState(key){
    try {
        const serializedState = localStorage.getItem(key);
        if (serializedState === null) {
            return null;
        }
        return JSON.parse(serializedState);
    } catch (err) {
        return null;
    }
}
saveState(state){
    const key = uuid.v4();
    const serializedState = JSON.stringify(state);
    localStorage.setItem(key,serializedState);
    return key;
}
function createStateFromUri(uri){
    const key = uri.split('#',2)[1];
    const state = loadState(key);
    if(state){
        return state;
    }else{
        return defaultState;
    }
}
function createUriFromState(state){
    var key = saveState(state);
    return `#${key}`;
}

Nyní budeme poslouchat změny na store nejen kvůli vykreslování, ale i proto, abychom si nový stav uložili do LocalStorage a URL:

const store = Redux.createStore(stateReducer, createStateFromUri(document.location.toString()));

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

function render() {
    updateScene(scene, store.getState());
}

store.subscribe(render);
render();


store.subscribe(()=> {
    const state = store.getState();
    const uri = createUriFromState(state);
    const title = createTitleFromState(state);
    document.title = title;
    history.pushState({}, title, uri);
});

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


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

Zpět a vpřed

Nyní ukládáme stav do URL adresy a při načtení hry ho z něj načítáme. Pokud chceme plně využít potenciál webové aplikace, musíme umožnit uživateli klikat na tlačítka zpět a vpřed v prohlížeči.

Ve stavu aplikace budeme ukládat poslední akci a přidáme akci CHANGE_STATE, která změní stav podle toho, co ji přijde:

function stateReducer(state, action) {
    switch (action.type) {
        case 'CHANGE_STATE':
            return  Object.assign({},action.state, {lastAction:action});

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

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

        default:
            return state;
    }
}

Do URL budeme ukládat změny stavu pouze pokud poslední akce není CHANGE_STATE:

const store = Redux.createStore(wrapReducer(stateReducer), createStateFromUri(document.location.toString()));
const root = document.getElementById("root");
const canvas = document.getElementById("scene");
const engine = new BABYLON.Engine(canvas, true);
const scene = createScene(canvas, engine, store);
engine.runRenderLoop(function () {
    scene.render();
});
window.addEventListener("resize", function () {
    engine.resize();
});

function render() {
    updateScene(scene, store.getState());
}
store.subscribe(()=>{
    const state = store.getState();
    if(state.lastAction.type!=='CHANGE_STATE'){
        const uri = createUriFromState(state);
        history.pushState({}, '', uri);
    }
});
store.subscribe(render);
render();

// Vždy když uživatel klikne na zpět nebo vpřed, odešle se akce CHANGE_STATE.
window.onpopstate = function () {
    const state = createStateFromUri(document.location.toString());
    store.dispatch(createAction.CHANGE_STATE(state));
};

Titulek

Na to, aby naše hra naplno využívala historii prohlížeče, už stačí měnit titulek podle aktuálního stavu. Vytvoříme proto funkci, která vyrábí ze stavu titulek stránky:

const WEB_NAME = 'Simple web game';
const TITLE_SEPARATOR = ' | ';

function createTitleFromState(state) {
    let titleParts = [];

    if (state.blocks.length> 1) {
        titleParts.push(state.blocks.length + ' blocks world');
    }
    titleParts.push(WEB_NAME);

    return titleParts.join(TITLE_SEPARATOR);
}

A tuto funkci při každé změně stavu využijeme:

store.subscribe(()=>
    const state = store.getState();
    if(state.lastAction.type!=='CHANGE_STATE'){
        const uri = createUriFromState(state);
        const title = createTitleFromState(state);
        document.title = title;
        history.pushState({}, title, uri);
    }
});

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.

V dalším díle, TypeScript, ukáži, jak jednotlivé Javascriptové soubory spojit pomocí nástroje WebPack.


 

Stáhnout

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

 

Předchozí článek
Stav hry
Všechny články v sekci
WebGL a BabylonJS - 3D webová hra
Článek pro vás napsal Pavol Hejný
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
/^(web )?(app )?developer$/
Aktivity (10)

 

 

Komentáře

Avatar
slezak-petr
Člen
Avatar
slezak-petr:2.5.2019 16:26

ještě jsem narazil na to, že v Opeře to funguje jak má,
ale v I Exploreru a v Chrome ne, přestože dám "povolit blokovaný obsah"

Odpovědět
2.5.2019 16:26
Čím víc se učím, tím víc mi přijde, že toho ještě mnoho neumím
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 1 zpráv z 1.