Geek tričko zdarma Geek tričko zdarma
Tričko zdarma! Stačí před dobitím bodů použít kód TRIKO15. Více informací zde

Lekce 4 - Zpět a Vpřed

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

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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 ukáži, jak jednotlivé Javascriptové soubory spojit pomocí nástroje WebPack.


 

Stáhnout

Staženo 41x (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í!
/^(web )?(app )?developer$/
Předchozí článek
Stav hry
Všechny články v sekci
Vytvoř si vlastní 3D webovou hru
Miniatura
Následující článek
TypeScript
Aktivity (7)

 

 

Komentáře

Avatar
slezak-petr
Člen
Avatar
slezak-petr:2. května 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. května 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.