Как писать игры на React, TypeScript?
1. Преимущества React
-
управляемый данными, в соответствии с изменением состояния или свойств => изменение представления, предыдущий метод часто заключается в непосредственном управлении реализацией DOM, вызывая событие, чтобы код перемещения элемента был похож на:
=> this.moveRight = () => { this.left += 8; this.draw(); } this.draw = () => { if(this.ele === null){ this.ele = document.createElement('img'); this.ele.src = this.url; this.ele.style.width = this.width + 'px'; this.ele.style.height = this.height + 'px'; this.ele.style.position = 'absolute'; app.appendChild(this.ele); } this.ele.style.left = this.left + 'px'; this.ele.style.top = this.top + 'px'; };
Теперь намного дружелюбнее
=> this.moveRight = () => { this.setState( preState => ( { left: preState.left + 8 } )); } <ContraBG left={left} top={top} status={status} toward={toward}> </ContraBG>
-
более четкая структура, Напишите компоненты, которые необходимо отображать один за другим, чтобы люди могли с первого взгляда узнать, какие компоненты загружены в запущенную игру, старый способ стиля кода для отображения такого элемента, как
=> const plane = new ourplane(); plane.draw();
Если рендеринг более сложный и структура сложная, читать будет очень сложно. Текущий стиль кода позволяет сразу увидеть все запущенные компоненты.
=> @observer class InGame extends React.PureComponent<InGameProps, {}> { render() { const { store } = this.props; return ( <InGameBG // 包裹组件负责渲染背景变化相关 store={store}> <Contra // 玩家控制的角色组件 store={store}/> <BulletsMap // 负责渲染子弹 store={store}/> <EnemiesMap // 负责渲染敌方角色 store={store}/> </InGameBG> ); } }
2. Недостатки React
-
гибкость
Наследование между прежним классом и классами будет гораздо более гибким, например飞机继承至飞行物 => 飞行物继承至动态物 => 动态物继承至某一特性物体
Среди них пули также могут наследоваться от летающих объектов, так что у летающих объектов может быть больше подклассов. Каждый компонент в React может наследоваться только от React.Component, а идея компонентов более высокого порядка HOC может использоваться для рендеринга ряда компонентов с похожими свойствами. Например, в игре Super Mario много стен, у них схожая логика рендеринга и некоторые методы, которые будут использоваться, их можно сгенерировать, написав компонент более высокого порядка статического блока, который может более эффективно управлять кодом.
=> function WithStaticSquare<TOwnProps>(options: StaticSquareOption):ComponentDecorator<TOwnProps> { return Component => class HocSquare extends React.Component<TOwnProps, HocSquareState> { // xxx render() { const { styles, className } = this.state; const passThroughProps: any = this.props; const classNames = className ? `staticHocWrap ${className}` : "staticHocWrap"; const staticProps: WrappedStaticSquareUtils = { changeBackground: this.changeBackground, toTopAnimate: this.toTopAnimate }; // 提供一些可能会用到的改变背景图的方法以及被撞时调用向上动画的方法 return ( <div className={classNames} style={styles}> <Component hoc={staticProps} {...passThroughProps}/> </div> ); } } }
3. Проблемы с производительностью
-
Избегайте КатонаПервый напрямую манипулирует рендерингом DOM без особых задержек.
Когда React использует Mobx, Redux и т. д. для управления всеми игровыми данными, если рендеринг не оптимизирован, когда значение свойства хранилища изменяется, все компоненты, связанные с реквизитом, перерисовываются, и стоимость огромна!
-
Некоторые компоненты, использующие PureComponent, должны быть написаны так
=> class Square extends React.PureComponent<SquareProps, {}> { // xxx }
Среди них вам нужно понять PureComponent. React.PureComponent был выпущен в React 15.3 29.06.2016.
PureComponent изменяет метод жизненного цикла shouldComponentUpdate и автоматически проверяет необходимость повторного рендеринга компонента. В настоящее время PureComponent будет вызывать метод рендеринга только тогда, когда PureComponent обнаружит, что состояние или свойства изменились, но эта проверка поверхностна, что означает, что вложенные объекты и массивы не будут сравниваться.Больше информации -
Используйте компоненты для большего рендеринга, сравните два метода
=> // 方法1. <InGameBG // 包裹组件负责渲染背景变化相关 store={store}> <Contra // 玩家控制的角色组件 store={store}/> <BulletsMap // 负责渲染子弹 store={store}/> <EnemiesMap // 负责渲染敌方角色 store={store}/> </InGameBG> //方法2. <InGameBG store={store}> <Contra store={store}/> <div> { bulletMap.map((bullet, index) => { if ( bullet ) { return ( <Bullet key={`Bullet-${index}`} {...bullet} index={index} store={store}/> ); } return null; }) } </div> <EnemiesMap store={store}/> </InGameBG>
Разница между этими двумя методами заключается в том, следует ли визуализировать пулю через рендеринг компонента или непосредственно в родительском компоненте.Производительность метода 2 будет иметь большую проблему.Когда пуля изменяется, самый большой контейнер будет повторно визуализирован, а все дочерние элементы Компонент также решит, нужно ли его перерендерить, чтобы интерфейс застрял. С другой стороны, метод 1 отображает только те маркеры, данные которых изменяются.
4. На заметку
-
Вовремя снять мониторинг, когда компонент удален, необходимо удалить прослушиватель событий, функцию времени и т. д. компонента. например компонент запуска игры
=> class GameStart extends React.Component<GameStartProps, {}> { constructor(props) { super(props); this.onkeydownHandle = this.onkeydownHandle.bind(this); } componentDidMount() { this.onkeydown(); } componentWillUnmount() { this.destroy(); } destroy(): void { console.log("游戏开始! GameStart Component destroy ...."); window.removeEventListener("keydown", this.onkeydownHandle); } onkeydownHandle(e: KeyboardEvent): void { const keyCode: KeyCodeType = e.keyCode; const { store } = this.props; const { updateGameStatus } = store; switch ( keyCode ) { case 72: updateGameStatus(1); break; } } onkeydown(): void { window.addEventListener("keydown", this.onkeydownHandle); } render() { return ( <div className="gameStartWrap"> </div> ); } }
5. Недавно написанный эффект Super Contra с GitHub
GitHub.com/Маленький сюрприз/…