<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      炸彈人游戲開發系列(5):控制炸彈人移動,引入狀態模式

      前言

      上文中我們實現了炸彈人顯示和左右移動。本文開始監聽鍵盤事件,使玩家能控制炸彈人移動。然后會在重構的過程中會引入狀態模式。大家會看到我是如何在開發的過程中通過重構來提出設計模式,而不是在初步設計階段提出設計模式的。

      本文目的

      實現“使用鍵盤控制玩家移動”

      完善炸彈人移動,增加上下方向的移動

      本文主要內容

      回顧上文更新后的領域模型

      開發策略

      首先進行性能優化,使用雙緩沖技術顯示地圖。接著考慮到“增加上下移動”的功能與上文實現的“左右移動”功能類似,實現起來沒有難度,因此優先實現“使用鍵盤控制玩家移動”,再實現“增加上下移動”。

      性能優化

      雙緩沖

      什么是雙緩沖

      當數據量很大時,繪圖可能需要幾秒鐘甚至更長的時間,而且有時還會出現閃爍現象,為了解決這些問題,可采用雙緩沖技術來繪圖。
      雙緩沖即在內存中創建一個與屏幕繪圖區域一致的對象,先將圖形繪制到內存中的這個對象上,再一次性將這個對象上的圖形拷貝到屏幕上,這樣能大大加快繪圖的速度。雙緩沖實現過程如下:
      1、在內存中創建與畫布一致的緩沖區
      2、在緩沖區畫圖
      3、將緩沖區位圖拷貝到當前畫布上
      4、釋放內存緩沖區

      為什么要用雙緩沖

      因為顯示地圖是這樣顯示的:假設地圖大小為40*40,每個單元格是一個bitmap,則有40*40個bitmap。使用canvas的drawImage繪制每個bitmap,則要繪制40*40次才能繪制完一張完整的地圖,開銷很大。

      那么應該如何優化呢?

      • 每次只繪制地圖中變化的部分。
      • 當變化的范圍也很大時(涉及到多個bitmap),則可用雙緩沖,減小頁面抖動的現象。

      因此,使用“分層渲染”可以實現第1個優化,而使用“雙緩沖”則可實現第2個優化。

      實現

      在MapLayer中創建一個緩沖畫布,在繪制地圖時先在緩沖畫布上繪制,繪制完成后再將緩沖畫布拷貝到地圖畫布中。

      MapLayer

      (function () {
          var MapLayer = YYC.Class(Layer, {
              Init: function () {
                  //*雙緩沖
      
                  //創建緩沖canvas
                  this.___createCanvasBuffer();
                  //獲得緩沖context
                  this.___getContextBuffer();
              },
              Private: {
                  ___canvasBuffer: null,
                  ___contextBuffer: null,
      
                  ___createCanvasBuffer: function () {
                      this.___canvasBuffer = $("<canvas/>", {
                          width: bomberConfig.canvas.WIDTH.toString(),
                          height: bomberConfig.canvas.HEIGHT.toString()
                      })[0];
                  },
                  ___getContextBuffer: function () {
                      this.___contextBuffer = this.___canvasBuffer.getContext("2d");
                  },
                  ___drawBuffer: function (img) {
                      this.___contextBuffer.drawImage(img.img, img.x, img.y, img.width, img.height);
                  }
              },
              Protected: {
                  P__createCanvas: function () {
                      var canvas = $("<canvas/>", {
                          width: bomberConfig.canvas.WIDTH.toString(),
                          height: bomberConfig.canvas.HEIGHT.toString(),
                          css: {
                              "position": "absolute",
                              "top": bomberConfig.canvas.TOP,
                              "left": bomberConfig.canvas.LEFT,
                              "border": "1px solid blue",
                              "z-index": 0
                          }
                      });
                      $("body").append(canvas);
      
                      this.P__canvas = canvas[0];
                  }
              },
              Public: {
                  draw: function () {
                      var i = 0,
                          len = 0,
                          imgs = null;
      
                      imgs = this.getChilds();
      
                      for (i = 0, len = imgs.length; i < len; i++) {
                          this.___drawBuffer(imgs[i]);
                      }
                      this.P__context.drawImage(this.___canvasBuffer, 0, 0);
                  },
                  clear: function () {
                      this.___contextBuffer.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
                      this.base();
                  },
                  render: function () {
                      if (this.P__isChange()) {
                          this.clear();
                          this.draw();
                          this.P__setStateNormal();
                      }
                  }
              }
          });
      
          window.MapLayer = MapLayer;
      }());

      控制炸彈人移動

      現在,讓我們來實現“使用鍵盤控制炸彈人家移動” 。

      分離出KeyEventManager類

      因為玩家是通過鍵盤事件來控制炸彈人的,所以考慮提出一個專門處理事件的KeyEventManager類,它負責鍵盤事件的綁定與移除。

      提出按鍵枚舉值

      因為控制炸彈人移動的方向鍵可以為W、S、A、D,也可以為上、下、左、右方向鍵。也就是說,具體的方向鍵可能根據個人喜好變化,可以提供幾套方向鍵方案,讓玩家自己選擇。

      為了實現上述需求,需要使用枚舉值KeyCodeMap來代替具體的方向鍵。這樣有以下好處:

      • 使用抽象隔離具體變化。當具體的方向鍵變化時,只要改變枚舉值對應的value即可,而枚舉值不會變化
      • 增加可讀性。枚舉值如Up一看就知道表示向上走,而87(W鍵的keycode)則看不出來是什么意思。

      增加keystate

      如果在KeyEventManager綁定的鍵盤事件中直接操作PlayerSprite:

      • 耦合太重。PlayerSprite變化時也會影響到KeyEventManager
      • 不夠靈活。如果以后增加多個玩家的需求,那么就需要修改KeyEventManager,使其直接操作多個玩家精靈類,這樣耦合會更中,第一點的情況也會更嚴重。

      因此,我增加按鍵狀態keyState。這是一個空類,用于存儲當前的按鍵狀態。

      當觸發鍵盤事件時,KeyEventManager類改變keyState。然后在需要處理炸彈人移動的地方(如PlayerSprite),判斷keyState,就可以知道當前按下的是哪個鍵,進而控制炸彈人進行相應方向的移動。

      領域模型

      相關代碼

      KeyCodeMap

      var keyCodeMap = {
          Left: 65, // A鍵
          Right: 68, // D鍵
          Down: 83, // S鍵
          Up: 87 // W鍵
      };

      KeyEventManager、KeyState

      (function () {
          //枚舉值
          var keyCodeMap = {
              Left: 65, // A鍵
              Right: 68, // D鍵
              Down: 83, // S鍵
              Up: 87 // W鍵
          };
          //按鍵狀態
          var keyState = {};
      
      
          var KeyEventManager = YYC.Class({
              Private: {
                  _keyDown: function () { },
                  _keyUp: function () { },
                  _clearKeyState: function () {
                      window.keyState = {};
                  }
              },
              Public: {
                  addKeyDown: function () {
                      var self = this;
      
                      this._keyDown = YYC.Tool.event.bindEvent(this, function (e) {
                          self._clearKeyState();
      
                          window.keyState[e.keyCode] = true;
                      });
      
                      YYC.Tool.event.addEvent(document, "keydown", this._keyDown);
                  },
                  removeKeyDown: function(){
                      YYC.Tool.event.removeEvent(document, "keydown", this._keyDown);
                  },
                  addKeyUp: function () {
                      var self = this;
      
                      this._keyUp = YYC.Tool.event.bindEvent(this, function (e) {
                          self._clearKeyState();
      
                          window.keyState[e.keyCode] = false;
                      });
      
                      YYC.Tool.event.addEvent(document, "keyup", this._keyUp);
                  },
                  removeKeyUp: function () {
                      YYC.Tool.event.removeEvent(document, "keyup", this._keyUp);
                  },
              }
          });
      
          window.keyCodeMap = keyCodeMap;
          window.keyState = keyState;
          window.keyEventManager = new KeyEventManager();
      }());

      PlayerSprite

                  handleNext: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.speedX = -this.speedX;
                          this.setAnim("walk_left");
                      }
                      else if (window.keyState[keyCodeMap.D] === true) {
                          this.speedX = this.speedX;
                          this.setAnim("walk_right");
                      }
                      else {
                          this.speedX = 0;
                          this.setAnim("stand_right");
                      }
                  }

      在游戲初始化時綁定事件:

      Game

              _initEvent: function () {
                  keyEventManager.addKeyDown();
                  keyEventManager.addKeyUp();
              }
              ...
              init: function () {
                  ...
                  this._initEvent();
              },

      引入狀態模式

      發現“炸彈人移動”中,存在不同狀態,且狀態可以轉換的現象

      在上一篇博文中,我實現了顯示和移動炸彈人,炸彈人可以在畫布上左右走動。

      我發現在游戲中,炸彈人是處于不同的狀態的:站立、走動。又可以將狀態具體為:左站、右站、左走、右走。

      炸彈人處于不同狀態時,它的行為是不一樣的(如處于左走狀態時,炸彈人移動方向為向左;處于右走狀態時,炸彈人移動方向為向右),且不同狀態之間可以轉換。

      狀態圖

      根據上面的分析,讓我萌生了可以使用狀態模式的想法。 狀態模式介紹詳見Javascript設計模式之我見:狀態模式

      為什么在此處用狀態模式

      其實此處炸彈人的狀態數并不多,且每個狀態的邏輯也不復雜,完全可以直接在PlayerState中使用if else來實現狀態的邏輯和狀態切換。

      那為什么我要用狀態模式了?

      1、做這個游戲是為了學習,狀態模式我之前沒有實際應用過,因此可以在此處練手

      2、此處也符合狀態模式的應用場景:一個對象的行為取決于它的狀態, 并且它必須在運行時刻根據狀態改變它的行為

      3、擴展方便。目前實現了炸彈人左右移動,后面還會實現炸彈人上下移動。如果用狀態模式的話,只需要增加四個狀態:上走、上站、下走、下站,再對應修改Context和客戶端即可。

      應用狀態模式的領域模型

       

      狀態模式具體實現 

      因為有右走、右站、左走、左站四個狀態類,因此就要創建4個具體狀態類,分別對應這四個狀態類。 

      PlayerSprite

      (function () {
          var PlayerSprite = YYC.Class(Sprite, {
              Init: function (data) {
                  this.x = data.x;
                  this.speedX = data.speedX;
                  this.walkSpeed = data.walkSpeed;
                  this.minX = data.minX;
                  this.maxX = data.maxX;
                  this.defaultAnimId = data.defaultAnimId;
                  this.anims = data.anims;
      
                  this.setAnim(this.defaultAnimId);
      
                  this.__context = new Context(this);
      
                  this.__context.setPlayerState(this.__getCurrentState());
              },
              Private: {
                  __context: null,
      
                  _getCurrentState: function () {
                      var currentState = null;
      
                      switch (this.defaultAnimId) {
                          case "stand_right":
                              currentState = Context.standRightState;
                              break;
                          case "stand_left":
                              currentState = Context.standLeftState;
                              break;
                          case "walk_right":
                              currentState = Context.walkRightState;
                              break;
                          case "walk_left":
                              currentState = Context.walkLeftState;
                              break;
                          default:
                              throw new Error("未知的狀態");
                              break;
                      }
                  }
              },
              Public: {
                  //精靈的速度
                  speedX: 0,
                  speedY: 0,
                  //定義sprite走路速度的絕對值
                  walkSpeed: 0,
      
                  // 更新精靈當前狀態
                  update: function (deltaTime) {
                      //每次循環,改變一下繪制的坐標
                      this.__setCoordinate(deltaTime);
      
                      this.base(deltaTime);
                  },
                  draw: function (context) {
                      var frame = null;
      
                      if (this.currentAnim) {
                          frame = this.currentAnim.getCurrentFrame();
      
                          context.drawImage(this.currentAnim.getImg(), frame.x, frame.y, frame.width, frame.height, this.x, this.y, frame.imgWidth, frame.imgHeight);
                      }
                  },
                  clear: function (context) {
                      var frame = null;
      
                      if (this.currentAnim) {
                          frame = this.currentAnim.getCurrentFrame();
      
                          //要加上圖片的寬度/高度
                          context.clearRect(0, 0, this.maxX + frame.imgWidth, this.maxY + frame.imgHeight);
                      }
                  },
                  handleNext: function () {
                      this.__context.walkLeft();
                      this.__context.walkRight();
                      this.__context.stand();
                  }
              }
          });
      
          window.PlayerSprite = PlayerSprite;
      }());
      View Code

      Context

      (function () {
          var Context = YYC.Class({
              Init: function (sprite) {
                  this.sprite = sprite;
              },
              Private: {
                  _state: null
              },
              Public: {
                  sprite: null,
      
                  setPlayerState: function (state) {
                      this._state = state;
                      //把當前的上下文通知到當前狀態類對象中
                      this._state.setContext(this);
                  },
                  walkLeft: function () {
                      this._state.walkLeft();
                  },
                  walkRight: function () {
                      this._state.walkRight();
                  },
                  stand: function () {
                      this._state.stand();
                  }
              },
              Static: {
                  walkLeftState: new WalkLeftState(),
                  walkRightState: new WalkRightState(),
                  standLeftState: new StandLeftState(),
                  standRightState: new StandRightState()
              }
          });
      
          window.Context = Context;
      }());
      View Code

       

      PlayerState

      (function () {
          var PlayerState = YYC.AClass({
              Protected: {
                  P_context: null
              },
              Public: {
                  setContext: function (context) {
                      this.P_context = context;
                  }
              },
              Abstract: {
                  stand: function () { },
                  walkLeft: function () { },
                  walkRight: function () { }
              }
          });
      
          window.PlayerState = PlayerState;
      }());
      View Code

      WalkLeftState

      (function () {
          var WalkLeftState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      if (window.keyState[keyCodeMap.A] === false) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.standLeftState);
                      }
                  },
                  walkLeft: function () {
                      var sprite = null;
      
                      if (window.keyState[keyCodeMap.A] === true) {
                          sprite = this.P_context.sprite;
                          sprite.speedX = -sprite.walkSpeed;
                          sprite.speedY = 0;
                          sprite.setAnim("walk_left");
                      }
                  },
                  walkRight: function () {
                  }
              }
          });
      
          window.WalkLeftState = WalkLeftState;
      }());
      View Code

      StandLeftState

      (function () {
          var StandLeftState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      var sprite = null;
                      
                      if (window.keyState[keyCodeMap.A] === false) {
                          sprite = this.P_context.sprite;
                          sprite.speedX = 0;
                          sprite.setAnim("stand_left");
                      }
                  },
                  walkLeft: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkLeftState);
                      }
                  },
                  walkRight: function () {
                      if (window.keyState[keyCodeMap.D] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkRightState);
                      }
                  }
              }
          });
      
          window.StandLeftState = StandLeftState;
      }());
      View Code

      WalkRightState

      (function () {
          var WalkRightState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      if (window.keyState[keyCodeMap.D] === false) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.standRightState);
                      }
                  },
                  walkLeft: function () {
                  },
                  walkRight: function () {
                      var sprite = null;
      
                      if (window.keyState[keyCodeMap.D] === true) {
                          sprite = this.P_context.sprite;
                          sprite.speedX = sprite.walkSpeed;
                          sprite.speedY = 0;
                          sprite.setAnim("walk_right");
                      }
                  }
              }
          });
      
          window.WalkRightState = WalkRightState;
      }());
      View Code

       

      StandRightState

      (function () {
          var StandRightState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      var sprite = null;
      
                      if (window.keyState[keyCodeMap.D] === false) {
                          sprite = this.P_context.sprite;
                          sprite.speedX = 0;
                          sprite.setAnim("stand_right");
                      }
                  },
                  walkLeft: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkLeftState);
                      }
                  },
                  walkRight: function () {
                      if (window.keyState[keyCodeMap.D] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkRightState);
                      }
                  }
              }
          });
      
          window.StandRightState = StandRightState;
      }());
      View Code

      重構PlayerSprite

      PlayerSprite重構前相關代碼

              Init: function (data) {
                  this.x = data.x;
                  this.speedX = data.speedX;
                  this.walkSpeed = data.walkSpeed;
                  this.minX = data.minX;
                  this.maxX = data.maxX;
                  this.defaultAnimId = data.defaultAnimId;
                  this.anims = data.anims;
      this.setAnim(this.defaultAnimId); this.__context = new Context(this);
      this.__context.setPlayerState(this.__getCurrentState()); },

      從構造函數中分離出init

      現在構造函數Init看起來有4個職責:

      • 讀取參數
      • 設置默認動畫
      • 創建Context實例,且因為狀態類需要獲得PlayerSprite類的成員,因此在創建Context實例時,將PlayerSprite的實例注入到Context中。
      • 設置當前默認狀態。

      在測試PlayerSprite時,發現難以測試。這是因為構造函數職責太多,造成了互相的干擾。

      從較高的層面來看,現在構造函數做了兩件事:

      • 讀取參數
      • 初始化

      因此,我將“初始化”提出來,形成init方法。

      構造函數保留“創建Context實例”職責

      這里比較難決定的是“創建Context實例”這個職責應該放到哪里。

      考慮到PlayerSprite與Context屬于組合關系,Context只屬于PlayerSprite,它應該在創建PlayerSprite時而創建。因此,將“創建Context實例”保留在PlayerSprite的構造函數中。

      重構后的PlayerSprite

      Init: function (data) {
          this.x = data.x;
          this.speedX = data.speedX;
          this.walkSpeed = data.walkSpeed;
          this.minX = data.minX;
          this.maxX = data.maxX;
          this.defaultAnimId = data.defaultAnimId;
          this.anims = data.anims;
      
          this._context = new Context(this);
      },
      ...
          init: function () {
              this._context.setPlayerState(this._getCurrentState());
      
              this.setAnim(this.defaultAnimId);
          },
      ... 

      增加炸彈人上下方向的移動

      增加狀態類

      增加WalkUpState、WalkDownState、StandUpState、StandDownState類,并對應修改Context即可。

      關于“為什么要有四個方向的Stand狀態類”的思考

      看到這里,有朋友可能會說,為什么用這么多的Stand狀態類,直接用一個StandState類豈不是更簡潔?

      原因在于,上站、下站、左站、右站的行為是不一樣的,這具體體現在顯示的動畫不一樣(炸彈人站立的方向不一樣)。

      領域模型

      相關代碼

      WalkUpState

      (function () {
          var WalkUpState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      if (window.keyState[keyCodeMap.W] === false) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.standUpState);
                      }
                  },
                  walkLeft: function () {
                  },
                  walkRight: function () {
                  },
                  walkUp: function () {
                      var sprite = null;
      
                      if (window.keyState[keyCodeMap.W] === true) {
                          sprite = this.P_context.sprite;
                          sprite.speedX = 0;
                          sprite.speedY = -sprite.walkSpeed;
                          sprite.setAnim("walk_up");
                      }
                  },
                  walkDown: function () {
                  }
              }
          });
      
          window.WalkUpState = WalkUpState;
      }());
      View Code

      WalkDownState

      (function () {
          var WalkDownState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      if (window.keyState[keyCodeMap.S] === false) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.standDownState);
                      }
                  },
                  walkLeft: function () {
                  },
                  walkRight: function () {
                  },
                  walkUp: function () {
                  },
                  walkDown: function () {
                      var sprite = null;
      
                      if (window.keyState[keyCodeMap.S] === true) {
                          sprite = this.P_context.sprite;
                          sprite.speedX = 0;
                          sprite.speedY = sprite.walkSpeed;
                          sprite.setAnim("walk_down");
                      }
                  }
              }
          });
      
          window.WalkDownState = WalkDownState;
      }());
      View Code

      StandUpState

      (function () {
          var StandUpState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      var sprite = null;
                      if (window.keyState[keyCodeMap.W] === false) {
                          sprite = this.P_context.sprite;
                          
                          sprite.speedY = 0;
                          sprite.setAnim("stand_up");
                      }
                  },
                  walkLeft: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkLeftState);
                      }
                  },
                  walkRight: function () {
                      if (window.keyState[keyCodeMap.D] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkRightState);
                      }
                  },
                  walkUp: function () {
                      if (window.keyState[keyCodeMap.W] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkUpState);
                      }
                  },
                  walkDown: function () {
                      if (window.keyState[keyCodeMap.S] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkDownState);
                      }
                  }
              }
          });
      
          window.StandUpState = StandUpState;
      }());
      View Code

      StandDownState

      (function () {
          var StandDownState = YYC.Class(PlayerState, {
              Public: {
                  stand: function () {
                      var sprite = null;
                      if (window.keyState[keyCodeMap.S] === false) {
                          sprite = this.P_context.sprite;
                          sprite.speedY = 0;
                          sprite.setAnim("stand_down");
                      }
                  },
                  walkLeft: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkLeftState);
                      }
                  },
                  walkRight: function () {
                      if (window.keyState[keyCodeMap.D] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkRightState);
                      }
                  },
                  walkUp: function () {
                      if (window.keyState[keyCodeMap.W] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkUpState);
                      }
                  },
                  walkDown: function () {
                      if (window.keyState[keyCodeMap.S] === true) {
                          this.P_context.sprite.resetCurrentFrame(0);
                          this.P_context.setPlayerState(Context.walkDownState);
                      }
                  }
              }
          });
      
          window.StandDownState = StandDownState;
      }());
      View Code

      Context

                  walkUp: function () {
                      this._state.walkUp();
                  },
                  walkDown: function () {
                      this._state.walkDown();
                  },
      ...
              Static: {
                  walkUpState: new WalkUpState(),
                  walkDownState: new WalkDownState(),
      ...
                  standUpState: new StandUpState(),
                  standDownState: new StandDownState()
              }

      解決問題

      解決“drawImage中的dx、dy和clearRect中的x、y按比例縮放

      現在我需要解決在第3篇博文中提到的問題

      問題描述

      如果把PlayerSprite.js -> draw -> drawImage:

      context.drawImage(this.currentAnim.getImg(), frame.x, frame.y, frame.width, frame.height, this.x, this.y, frame.imgWidth, frame.imgHeight);

      中的this.x、this.y設定成260、120:

      context.drawImage(this.currentAnim.getImg(), frame.x, frame.y, frame.width, frame.height, 260, 120, frame.imgWidth, frame.imgHeight);

      則不管畫布canvas的width、height如何設置,玩家人物都固定在畫布的右下角!!!

      照理說,坐標應該為一個固定值,不應該隨畫布的變化而變化。即如果canvas.width = 300, drawImage的dx=300,則圖片應該在畫布右側邊界處;如果canvas.width 變為600,則圖片應該在畫布中間!而不應該還在畫布右側邊界處!

      問題分析

      這是因為我在PlayerLayer的創建canvas時,使用了css設置畫布的大小,因此導致了畫布按比例縮放的問題。

      PlayerLayer

      P__createCanvas: function () {
          var canvas = $("<canvas/>", {
              //id: id,
              width: bomberConfig.canvas.WIDTH.toString(),
              height: bomberConfig.canvas.HEIGHT.toString(),
              css: {
                  "position": "absolute",
                  "top": bomberConfig.canvas.TOP,
                  "left": bomberConfig.canvas.LEFT,
                  "border": "1px solid red",
                  "z-index": 1
              }
          });
          $("body").append(canvas);
      
          this.P__canvas = canvas[0];
      }

      詳見關于使用Css設置Canvas畫布大小的問題

      解決方案

      通過HTML創建canvas,并在Html中設置它的width和height:

      <canvas width="500" height="500">
      </canvas>

      本文最終領域模型

      查看大圖

      高層劃分

      新增包

      • 事件管理包
        KeyState、KeyEventManager

      分析

      狀態類應該放到哪個包?

      狀態類與玩家精靈類PlayerSprite互相依賴且共同重用,因此應該都放到“精靈”這個包中。

      本文層、包

      對應領域模型

      • 輔助操作層
        • 控件包
          PreLoadImg
        • 配置包
          Config
      • 用戶交互層
        • 入口包
          Main
      • 業務邏輯層
        • 輔助邏輯
          • 工廠包
            BitmapFactory、LayerFactory、SpriteFactory
          • 事件管理包
            KeyState、KeyEventManager
        • 游戲主邏輯
          • 主邏輯包
            Game
        • 層管理
          • 層管理實現包
            PlayerLayerManager、MapLayerManager
          • 層管理抽象包
          • LayerManager
          • 層實現包
            PlayerLayer、MapLayer
          • 層抽象包
            Layer
          • 集合包
            Collection
        • 精靈
          • 精靈包
            PlayerSprite、Context、PlayerState、WalkLeftState、WalkRightState、WalkUpState、WalkDownState、StandLeftState、StandRightState、StandUpState、StandDownState
          • 動畫包
            Animation、GetSpriteData、SpriteData、GetFrames、FrameData
      • 數據操作層
        • 地圖數據操作包
          MapDataOperate
        • 路徑數據操作包
          GetPath
        • 圖片數據操作包
          Bitmap
      • 數據層
        • 地圖包
          MapData
        • 圖片路徑包
          ImgPathData

      本文參考資料

      HTML5超級瑪麗小游戲源代碼

      完全分享,共同進步——我開發的第一款HTML5游戲《驢子跳》

      歡迎瀏覽上一篇博文:炸彈人游戲開發系列(4):炸彈人顯示與移動

      歡迎瀏覽下一篇博文:炸彈人游戲開發系列(6):實現碰撞檢測,設置移動步長 

      posted @ 2013-10-19 22:32  楊元超  閱讀(2264)  評論(4)    收藏  舉報
      主站蜘蛛池模板: 丰满无码人妻热妇无码区| 色天天天综合网色天天| 四虎www永久在线精品| 安徽省| 久久精品午夜视频| 国产精品黄在线观看免费| 无码精品国产VA在线观看DVD | 中文字幕av国产精品| 成年午夜免费韩国做受视频| 国产精品一二二区视在线| 97在线视频人妻无码| 国产激情电影综合在线看| 辽中县| 国产精品女生自拍第一区| 国产精品午夜剧场免费观看| 久久久午夜精品福利内容 | 成人午夜免费一区二区三区| 女人色熟女乱| 99久久精品国产一区二区暴力| 国产片av在线观看国语| 无码成人午夜在线观看| 昂仁县| 久久精品国产清自在天天线| 国产亚洲精品AA片在线播放天| 香蕉在线精品一区二区| 91中文字幕一区在线| 韩国无码AV片午夜福利| 亚洲天堂一区二区成人在线| 亚洲男人的天堂久久香蕉| 成人国产av精品免费网| 好男人官网资源在线观看| 兴化市| 亚洲一区二区三成人精品| 精品免费看国产一区二区 | 国产精品中文字幕在线看| 欧美人伦禁忌dvd放荡欲情 | 人人妻人人做人人爽夜欢视频| 又爽又黄又无遮挡的视频 | 亚洲乱码国产乱码精品精大量| 我要看亚洲黄色太黄一级黄| 亚洲 日本 欧洲 欧美 视频|