React, TypeScript 寫遊戲探索

如何用 React, TypeScript 寫遊戲?

image
image

1. React的優點

  • 數據驅動, 根據state或者props的變化 => 視圖的變化, 之前的方式每每是直接操做 DOM 實現, 觸發某事件使得元素移動代碼相似如:html

    =>
      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';
      };複製代碼

    如今就友好不少react

    =>
      this.moveRight = () => {
          this.setState( preState => (
              {
                  left: preState.left + 8
              }
          ));
      }
    
      <ContraBG
          left={left}
          top={top}
          status={status}
          toward={toward}>
      </ContraBG>複製代碼
  • 結構更清晰, 逐個書寫須要渲染的組件, 能讓人一目瞭然的知道遊戲運行中加載的組件, 老的方式代碼風格去渲染一個元素如git

    =>
      const plane = new ourplane();
      plane.draw();複製代碼

    若是渲染的多告終構複雜了,閱讀就會十分困難。如今的代碼風格就可以一目瞭然的看到全部運行的組件github

    =>
      @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的劣勢

  • 靈活性
    前者類與類之間繼承會靈活不少, 如typescript

    飛機繼承至飛行物 => 飛行物繼承至動態物 => 動態物繼承至某一特性物體複製代碼

    其中子彈也能夠繼承至飛行物使得飛行物等能夠衍生更多子類。React中各組件只能繼承至React.Component,可採用HOC高階組件思想去渲染一系列具備類似性質的組件。如超級瑪麗遊戲中有許多的牆,它們具備類似的渲染邏輯,以及一些都會須要用到的方法, 能夠經過寫一個靜態方塊的高階組件去生成, 可以更高效的管理代碼。數組

    =>
      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等進行整個遊戲數據控制時, 若是不對渲染進行優化, 當store某個屬性值變化致使全部接入props的組件都從新渲染一次代價是巨大的!
  1. 採用PureComponent某些組件須要這樣寫bash

    =>
     class Square extends React.PureComponent<SquareProps, {}> {
         // xxx
     }複製代碼

    其中就須要瞭解PureComponent。React.PureComponent是2016.06.29 React 15.3中發佈。app

    image
    image

    PureComponent改變了生命週期方法shouldComponentUpdate,而且它會自動檢查組件是否須要從新渲染。這時,只有PureComponent檢測到state或者props發生變化時,PureComponent纔會調用render方法,可是這種檢查只是淺計較這就意味着嵌套對象和數組是不會被比較的 更多信息

  2. 多采用組件去渲染, 對比兩種方法函數

    =>
     // 方法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. 最近寫的 超級魂鬥羅 效果與GitHub

超級魂鬥羅
超級魂鬥羅

超級魂鬥羅
超級魂鬥羅

github.com/xiaoxiaojx/…

Thank You

相關文章
相關標籤/搜索