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 6 - Uživatelské rozhraní

V minulém díle, TypeScript, jsem ukazoval, jaké výhody má TypeScript oproti JavaScriptu.

V tomto díle ukáži, jak vytvořím uživatelské rozhraní pomocí Reactu a Material UI.

Nebudu vytvářet nic složitého. V rámci uživatelského rozhraní chci zobrazit výběr barvy bloku a zobrazený text s počtem kostiček. To vše v levé vysouvací liště vedle scény.

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

Stav

Abychom mohli všechny nové funkčnosti implementovat, musíme změnit strukturu stavu hry. Kromě bloků a poslední akce musíme držet stav uživatelského rozhraní. To budou dvě hodnoty navíc:

  • ui.drawer říká, zda je levá lišta vysunutá.
  • ui.color je barva, kterou máme aktuálně vybranou.

Takto např. bude vypadat stav hry:

{
    "blocks": [
        {
            "id": "00dc4850-98cc-43ed-9965-1f6b08cb93da",
            "position": {
                "x": 1,
                "y": 1,
                "z": 0
            },
            "color": "#d74040"
        },
        {
            "id": "d5a0191d-1c7f-495d-9aa0-65236441f513",
            "position": {
                "x": 0,
                "y": 1,
                "z": 0
            },
            "color": "#d74040"
        }
    ],
    "ui": {
        "drawer": true,
        "color": "#d74040"
    },
    "lastAction": {
        "type": "BLOCK_ADD",
        "newBlock": {
            "id": "d5a0191d-1c7f-495d-9aa0-65236441f513",
            "position": {
                "x": 0,
                "y": 1,
                "z": 0
            },
            "color": "#d74040"
        }
    }
}

K akcím, co máme, přibudou ještě:

  • UI_DRAWER_TOGGLE - zobrazí či skryje levou lištu.
  • UI_COLOR_SET - nastaví barvu.

Spravovat takto složitý stav pouze pomocí jednoho reduceru je velmi nepřehledné. Proto využiji funkci Reduxu combineReducers.

index.ts

import { combineReducers } from 'redux'
import * as _ from "lodash";
import blocks from './blocks';
import ui from './ui';


function lastAction(previousAction,action){
    return action;
}


const stateReducerInner = combineReducers({
    blocks,
    ui,
    lastAction
});

export enum ActionTypes{
    CHANGE_STATE='CHANGE_STATE',
}

export const createAction = {
    CHANGE_STATE: (state)=>({type:ActionTypes.CHANGE_STATE,state}),
};

export function stateReducer(state, action) {
    if (action.type === ActionTypes.CHANGE_STATE) {
        return _.assign({},action.state, {lastAction:action});
    } else {
        return stateReducerInner(state, action);
    }
};

blocks.ts

const defaultBlocks = [
    {
        id:'My first block!!!',
        position:{x:0,y:0,z:0},
        color:'#cccccc'
    }
];

export default function blocks(blocks = defaultBlocks, action) {
    switch (action.type) {

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


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

        default:
            return blocks;
    }
}

ui.ts

const defaultUi = {
    drawer:false,
    color: '#cccccc',
};


export default function ui(ui=defaultUi, action) {
    return {
        drawer: action.type==='UI_DRAWER_TOGGLE'?(!ui.drawer):ui.drawer,
        color:  action.type==='UI_COLOR_SET'?action.value:ui.color,
    };
}

React

React je Javascriptový framework pro psaní uživatelských rozhraní. Dále v tomto článku budu předpokládat základní znalost Reactu, JSX syntaxe a react-redux. Pokud o tomto frameworku slyšíš poprvé, můžeš si přečíst např. tento článek. Také budu využívat Material UI , abych se nezdržoval stylováním komponent.

Naše React komponenty budou:

  • Root bude obsahovat levou lištu, kde budou komponenty Heading a BlockColor. Tato lišta bude vysouvací.
  • Heading bude informace o počtu kostiček.
  • BlockColor zobrazí aktuální barvu bloku a umožní její změnu.

root.tsx

import * as React from "react";
import {connect} from 'react-redux';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import RaisedButton from 'material-ui/RaisedButton';
import Divider from 'material-ui/Divider';
import * as FontAwesome from 'react-fontawesome';
import BlockColor from './block-color';
import Heading from './heading';


function mapStateToProps(state){
    return {
        drawer: state.ui.drawer
    };
}

function mapDispatchToProps(dispatch){
    return {
        onMenu: ()=>dispatch({type:'UI_DRAWER_TOGGLE'}),
    }
}

function Root({drawer,onMenu}){
    return (
        <div>
            <RaisedButton
                onTouchTap={onMenu}
                style={{
                    position: 'fixed',
                    zIndex: 3,
                    top: 0,
                    left: 0,
                }}>
                <FontAwesome name="bars"/>
            </RaisedButton>


            <Drawer style={{
                zIndex: 5
            }} open={drawer}>


                <MenuItem onTouchTap={onMenu} leftIcon={<FontAwesome name="times"/>}>Close</MenuItem>

                <Heading/>
                <Divider />
                <BlockColor/>


            </Drawer>

        </div>
    )
}

export default connect(mapStateToProps, mapDispatchToProps)(Root);

heading.tsx

import * as React from "react";
import {connect} from 'react-redux';
import MenuItem from 'material-ui/MenuItem';

function mapStateToProps(state){
    return {
        size: state.blocks.length
    };
}

function Heading({size}){
    return (
        <div>
            <MenuItem>
                <h2>{size} blocks world</h2>
            </MenuItem>
        </div>
    )
}


export default connect(mapStateToProps)(Heading);

block-color.tsx

import * as React from "react";
import {connect} from 'react-redux';
import Subheader from 'material-ui/Subheader';
import MenuItem from 'material-ui/MenuItem';

function mapStateToProps(state){
    return {
        color: state.ui.color
    };
}

function mapDispatchToProps(dispatch){
    return {
        colorChange: (event)=>dispatch({type:'UI_COLOR_SET',value:event.target.value}),
    }
}

function BlockColor({color,colorChange}){
    return (
        <div>
            <Subheader>Block color</Subheader>
            <MenuItem>
                <input type="color" value={color} onChange={colorChange}/>
            </MenuItem>
        </div>
    )
}

export default connect(mapStateToProps, mapDispatchToProps)(BlockColor);

Nakonec Root komponentu vyrendrujeme a napojíme na store pomocí react-redux

ReactDOM.render(
    <Provider store={store}>
        <MuiThemeProvider>
            <Root />
        </MuiThemeProvider>
    </Provider>,
    root
);

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, Nasazení na server, ukáži nasazovaní celého projektu na server.


 

Stáhnout

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

Staženo 513x (8.61 kB)

 

Předchozí článek
TypeScript
Všechny články v sekci
WebGL a BabylonJS - 3D webová hra
Přeskočit článek
(nedoporučujeme)
Nasazení na server
Článek pro vás napsal Pavol Hejný
Avatar
Uživatelské hodnocení:
1 hlasů
/^(web )?(app )?developer$/
Aktivity