IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

6. diel - Užívateľské rozhranie

V minulom diele som ukazoval, aké výhody má TypeScript oproti JavaScriptu. V tomto diele ukážem, ako vytvorím užívateľské rozhranie pomocou React a Material UI.

Nebudem vytvárať nič zložité. V rámci používateľského rozhrania chcem zobraziť výber farby bloku a zobrazený text s počtom kociek. To všetko v ľavej vysúvací lište vedľa scény.

webová hra - Vytvor si vlastné 3D webovú hru

Stav

Aby sme mohli všetky nové funkčnosti implementovať, musíme zmeniť štruktúru stavu hry. Okrem blokov a poslednej akcie musíme držať stav používateľského rozhrania. To budú dve hodnoty navyše:

  • ui. drawer hovorí, či je ľavá lišta vysunutá.
  • ui. color je farba, ktorú máme aktuálne vybratú.

Takto napr. Bude vyzerať 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 akciám, čo máme, pribudnú ešte:

  • UI_DRAWER_TOGGLE zobrazí či skryje ľavú lištu.
  • UI_COLOR_SET nastaví farbu.

Spravovať takto zložitý stav iba pomocou jedného reducer je veľmi neprehľadné. Preto využijem funkciu Redux 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 pre písanie užívateľských rozhraní. Ďalej v tomto článku budem predpokladať základná znalosť React, JSX syntaxe a react-redux. Pokiaľ o tomto frameworku počuješ prvýkrát, môžeš si prečítať napr. Tento článok. Tiež budem využívať Material UI, aby som sa nezdržiaval stylovaním komponentov.

Naše React komponenty budú:

  • Root bude obsahovať ľavú lištu, kde budú komponenty Heading a BlockColor. Táto lišta bude vysúvacia.
  • Heading bude informácie o počte kociek.
  • BlockColor zobrazí aktuálny farbu bloku a umožní jej zmenu.

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);

Nakoniec Root komponent vyrendrujeme a napojíme na store pomocou react-redux

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

Rozrobenú hru si môžeš stiahnuť pod článkom, alebo ísť do Git repozitára, kde nájdeš najnovšiu verziu zdrojových kódov. Alebo si ju rovno môžeš vyskúšať na webappgames.git­hub.io/web-game. V ďalšom diele ukážem nasadzovaní celého projektu na server.


 

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 554x (8.61 kB)

 

Predchádzajúci článok
TypeScript
Všetky články v sekcii
Vytvor si vlastné 3D webovú hru
Preskočiť článok
(neodporúčame)
Nasadenie na server
Článok pre vás napísal Pavol Hejný
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
/^(web )?(app )?developer$/
Aktivity