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.
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.github.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 podmienkamiStiahnuté 554x (8.61 kB)