4. díl - Zpět a Vpřed

JavaScript 3D webová hra Zpět a Vpřed

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulém díle jsem ukazoval, co to je stav aplikace a jak ho držet. V tomto díle ukáži, jaké výhody nám to přináší.

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 hejny.github.io/web-game. V dalším díle ukáži, jak jednotlivé Javascriptové soubory spojit pomocí nástroje WebPack.


 

Stáhnout

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

 

 

Článek pro vás napsal Pavol Hejný
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se věnuje vývoji mnoha www aplikací - https://www.pavolhejny.com/
Miniatura
Předchozí článek
Stav hry
Miniatura
Všechny články v sekci
Vytvoř si vlastní 3D webovou hru
Miniatura
Následující článek
TypeScript
Aktivity (6)

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!