3D arcade in the browser: how we made the game on React + Redux

Hello, Habr! In those not so distant years, in the first year of the "programmer" faculty, I liked to ask fellow students the question: "Why did you even go here to study?" Of course, I did not keep accurate statistics of answers, but I remember for certain: more than half wanted to make games. Most of those who answered this way were not ready for the abundance of different types of “mathematicians” and “physicist” with whom we were overwhelmed in the first two years of study. Not all survived - by the end of the second year of the five overcrowded groups there were three incomplete.


Not so long ago, our front-end team had the opportunity to try themselves in the role of gamedev. Very briefly, the task is this: to make a real 3D game, so that you can play just by opening the browser. Even mobile. Even in webview.



, , , — React + Redux, « », , , .



RnD , : « CodeFest — . — ». , , , . Gods in the sky.


Photo from the CodeFest group on VK / https://vk.com/codefest.  The source of the photo is here.
CodeFest .


MMO TPS , , . , , , . CodeFest. , .



2 , , 2 . , , .


: -, — , . , N , — 1 . , — 15 . , , 16-. , , :


  1. 30 . CodeFest, — , «». , .
  2. «» . , :
    • 90 CPU;
    • 120 ;
    • 270 /c — , 675 /c — .
  3. 4,2 /c. , . — UDP WebRTC, . .
  4. . 2 , , , . , - Node.js , .

, , ( ?), , , , . .


, . , «». — .


:


, , , . , , 5 , , 40 .


-. , , — . React Redux. c 3D-, , Gods in the sky, three.js.


, .



:



, Node.js. :


  • , - ( , IE11);
  • access-;
  • html- .

SSR . kubernetes , . , . , , , , ( ). . Nginx.



. TS, AppState, :


interface AppState {
    gameState: GameState;
    gameObjects: GameObjects;
    data: Data;
    requests: RequestsState;
}

requests — , — . , — . .


data — , , , , «». .


GameState :


  1. stage: 'factoids' | 'citySelect' | 'flyShow' | 'game' | 'results'; — . :


    • ;
    • ;
    • ;
    • ;
    • .

  2. elapsedTime: number; — , , .



, factoids flyShow, , — elapsedTime. 0, . , . , , , . , . … - , .


, , :


interface GameObjects {
    user: UserInGame;
    gifts: { [id: string]: GiftInGame; };
    boosts: { [id: string]: Boost; };
    bombs: { [id: string]: Bomb; };
}

, : , , . — .


GameObjects — . , FPS . GPU 100 . Redmi Note 7 40 FPS ( ). MI 5S 30 FPS, 20 , .


, . .



- Redux, — . Redux , . ? Redux, .


, . , . «» ( . behavior — ). , .


. — tick, . , . tick . :


  • , , , . , , , .
  • ( Redux-), .
    , «» .
  • , gameObjects, . , «», .

25 ( ).


. ( ), React, . - , «- » ( , , ).


Another team member that day.  Photo taken from pixabay.com
. pixabay.com


, FPS- 13. , , - MI 5S, FPS- — 5-6, . .


: performance — , . , , .


FPS=30 , :


  • Redux FPS * ~[ ] ~= 750 , , .
  • React’ FPS * ~2 * ~[ ] .
  • three.js , , WebGL . , .
  • (.. 150 ) 4-6 , .. .
  • CSS- .
  • , FPS 2-4 ( , «» ).
  • React.memo, - , .

React :


  • * ~2 , connect. connect — - HOC, , .
  • , React – (render pass), CPU, GPU.


: React.memo , , «», , DOM-, .


, React + Redux, .


Redux


Redux — . ~1 , . ~1, - — . -, , GameObjects , :


export function setNextGameObjects(payload: GameObjects) {
    return {
        type: 'SET_NEXT_GAME_OBJECTS' as const,
        payload,
    };
}

GameObjects :


  1. , . SET_NEXT_GAME_OBJECTS.
  2. , , GameObjects. Redux , - . SET_NEXT_GAME_OBJECTS.

:


let state = store.getState().gameObjects;
for (const action of this.collectedActions) {
    state = gameObjectsFakeStateReducer(state, action);
}
store.dispatch(setNextGameObjects(state));

collectedActions — , . — , .


— ~25 10 : ~2 . , gameObjectsFakeStateReducer . 25 . , .


? , perf-. 3 :


  1. ES2018, ~50 < ES2018 (, Firefox — ). , spread.
  2. Object.assign 300 .
  3. 500 , « » .

, , , — ES2018, . Firefox.


№3 , … . .


Object.assign. gameObjectsFakeStateReducer gameObjectsFastStateReducer, - :


switch (action.type) {
    case 'PARTIAL_GIFT_STATE_PATCH':
        if (!state.gifts[action.payload.id]) {
            return state;
        }
        Object.assign(state.gifts[action.payload.id], action.payload);
        break;
    // case, default ...
}

? , . , . 25 1 2-4 ( CPU). :


image


«» , , Redux 10 . , - , , connect gameObjects. , .


React


React . — DOM. , , — reselect, React.memo/React.PureComponent, shouldComponentUpdate . .


, , ~2, — 5. , React , .. DOM .


batch React + Redux. , — . , ?


, , connect . , .


. , <ScoreBoard />,


  1. ;
  2. , .

React.PureComponent, connect mapStateToProps. elapsedTime score. Score , elapsedTime — . elapsedTime - render. , FPS, shouldComponentUpdate. HOC’a connect — shouldComponentUpdate .


, «» , :


  1. shouldComponentUpdate, elapsedTime ( ). , HOC’a connect.
  2. mapStateToProps . , , . connect’a.
  3. useSelector. , .. . , , , .
  4. — connect, — . , . ScoreBoard .

— ! ScoreBoard’a connect, , . mapStateToProps , .


, connect. , — - - canvas-, three.js (, , .). render , :


return <div id="map" className={s.map} ref={mapRef} />;


return <canvas id="scene" className={s.scene} ref={sceneRef} />;

, DOM — . , React . , componentDidUpdate, . , componentDidUpdate :


public componentDidUpdate() {
    const { geoPos, orientedRotationQuanternion } = this.props;
    const { map } = this.state;
    map.setQuat(orientedRotationQuanternion);
    map.setCenter([geoPos[0], geoPos[1]], { animate: false });
    map.setZoom(getMapZoomFromHeight(geoPos[2]), { animate: false });
}

, componentDidUpdate . :


  1. connect. , mapStateToProps;
  2. public shouldComponentUpdate() { return false; } — «» «»;
  3. Redux ( ) useStore ;
  4. (store.subscribe(() => { /* … */}));
  5. ;
  6. !

:


store.subscribe(() => {
    const state = store.getState();
    const { map } = this.state;
    const { geoPos, orientedRotationQuanternion } = state.gameObjects.user;

    map.setQuat(orientedRotationQuanternion);
    map.setCenter([geoPos[0], geoPos[1]], { animate: false });
    map.setZoom(getMapZoomFromHeight(geoPos[2]), { animate: false });
}); 

, , .


, - , .



. — React Tree Reconcilation + Commit



React Tree Reconcilation + Commit



React + Redux , :


  • , , , «» React , ;
  • ;
  • « », , ;
  • , .

, . , . , . ? .


:


  1. , , , , . , .
  2. , React’ 30 . : .
  3. , .
  4. . . , .
  5. .
  6. Performance — . CPU — , Intel i9.
  7. - : 12–20 – . , .
  8. , . , , . , .
  9. , , . IE11 , . 70- Edge 18- ( ).
  10. WebGL . - , , -, Firefox linux, . . — . -, . , , « », .

, :


  1. Firefox. . , WebGL, — spread. FPS Firefox , Chrome. , , .
  2. IPhone. Safari IE6 220 . , , . , , . . — - .
  3. . . . , , (, , ). .
  4. . , , . , , , «» .
  5. ( ), – , . .

, , . iOS, Android Web .


All Articles