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

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

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

      炸彈人游戲開(kāi)發(fā)系列(8):放炸彈

      前言

      上文中我們加入了1個(gè)敵人,使用A*算法尋路。本文會(huì)給我們的炸彈人增加放炸彈的能力。

      說(shuō)明

      名詞解釋

      • xx類族
        是指以xx為基類的繼承樹(shù)上的所有類。

      本文目的

      實(shí)現(xiàn)“放炸彈”功能

      增加1個(gè)敵人,即一共有2個(gè)敵人追蹤炸彈人

      本文主要內(nèi)容

       

      回顧上文更新后的領(lǐng)域模型

      查看大圖

      對(duì)領(lǐng)域模型進(jìn)行思考

      Layer類族的render方法改名為run

      Layer的render方法負(fù)責(zé)統(tǒng)一調(diào)用Layer的方法,在概念上屬于Actor,因此將其改名為run。

      開(kāi)發(fā)策略

      首先實(shí)現(xiàn)“放炸彈”功能。把這個(gè)功能分解成很多個(gè)子功能,一個(gè)一個(gè)地實(shí)現(xiàn)子功能。

      然后再加入1個(gè)敵人。實(shí)際上就是在Game中往EnemyLayer集合中再加入一個(gè)EnemySprite實(shí)例,SpriteData增加第2個(gè)敵人的數(shù)據(jù),SpriteFactory增加工廠方法createEnemy2。

      放炸彈流程

       

      功能分解

      顯示炸彈和火焰

      顯示炸彈

      首先來(lái)實(shí)現(xiàn)“地圖上顯示炸彈”的功能,目前最多顯示1個(gè)炸彈,玩家、敵人不能穿過(guò)炸彈。如果玩家處于炸彈方格中,則敵人會(huì)原地等待,玩家離開(kāi)后,敵人繼續(xù)追蹤。

      增加圖片

      增加圖片bomb.png:

      增加BomberSprite

      增加炸彈精靈類BomberSprite:

      (function () {
          var BombSprite = YYC.Class(Sprite, {
              Init: function (data, bitmap) {
                  this.base(null, bitmap);
              },
              Public: {
                  draw: function (context) {
                      context.drawImage(this.bitmap.img, this.x, this.y, this.bitmap.width, this.bitmap.height);
                  },
                  clear: function (context) {
                          context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
                  }
              }
          });
      
          window.BombSprite = BombSprite;
      }());

      增加BombLayer

      在畫(huà)布上增加炸彈層。同時(shí)增加對(duì)應(yīng)的BombLayer類,它的集合元素為BombSprite類的實(shí)例。

      將玩家、敵人畫(huà)布Canvas的zIndex設(shè)為3,炸彈畫(huà)布的zIndex設(shè)為1,使得,炸彈畫(huà)布位于地圖畫(huà)布(zIndex為0)之上,玩家和敵人畫(huà)布之下。

      BomberLayer

      (function () {
          var BombLayer = YYC.Class(Layer, {
              Private: {
                  ___hasBomb: function () {
                      return this.getChilds().length > 0;
                  },
                  ___render: function () {
                      if (this.___hasBomb()) {
                          this.clear();
                          this.draw();
                      }
                  }
              },
              Public: {
                  setCanvas: function () {
                      this.P__canvas = document.getElementById("bombLayerCanvas");
                      var css = {
                          "position": "absolute",
                          "top": bomberConfig.canvas.TOP,
                          "left": bomberConfig.canvas.LEFT,
                          "z-index": 1
                      };
      
                      $("#bombLayerCanvas").css(css);
                  },
                  draw: function () {
                      this.P__iterator("draw", this.P__context);
                  },
                  clear: function () {
                      this.P__iterator("clear", this.P__context);
                  },
                  run: function () {
                      this.___render();
                  }
              }
          });
      
          window.BombLayer = BombLayer;
      }());

      增加工廠方法

      SpriteFactory增加創(chuàng)建炸彈精靈類實(shí)例的工廠方法。

      LyaerFactory增加創(chuàng)建炸彈層實(shí)例的工廠方法。

      SpriteFactory

             createBomb: function (playerSprite) {
                  return new BombSprite(playerSprite, bitmapFactory.createBitmap({ img: window.imgLoader.get("bomb"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
              },

      LayerFactory

          createBomb: function () {
                  return new BombLayer();
              },

      修改PlayerSprite

      PlayerSprite增加createBomb方法:

                 bombNum: 0,
      ...
                  createBomb: function () {
                      if (this.moving || this.bombNum === 1) {
                          return null;
                      }
      
                      var bomb = spriteFactory.createBomb();
      
                      bomb.x = this.x;
                      bomb.y = this.y;
      
                      this.bombNum += 1;
      
                      return bomb;
                  }

      修改PlayerLayer

      PlayerLayer增加getBomb和createAndAddBomb方法:

                  bombLayer: null,
      ...
                  getBomb: function (bombLayer) {
                      this.bombLayer = bombLayer;
                  },
                  createAndAddBomb: function () {
                      var bomb = this.getChildAt(0).createBomb();
                      if (!bomb) {
                          return false;
                      }
                      this.bombLayer.appendChild(bomb);
                  }

      監(jiān)聽(tīng)空格鍵

      空格鍵用于炸彈人放炸彈。

      KeyCodeMap增加空格鍵枚舉值:

          var keyCodeMap = {
              Left: 65, // A鍵
              Right: 68, // D鍵
              Down: 83, // S鍵
              Up: 87, // W鍵
              Space: 32   //空格鍵
          };
      
          keyState[keyCodeMap.A] = false;
          keyState[keyCodeMap.D] = false;
          keyState[keyCodeMap.W] = false;
          keyState[keyCodeMap.S] = false;
          keyState[keyCodeMap.Space] = false;

      然后在PlayerLayer中對(duì)KeyState的空格鍵進(jìn)行判定:

                  run: function () {
                      if (keyState[keyCodeMap.Space]) {
                          this.createAndAddBomb();
                          keyState[keyCodeMap.Space] = false;
                      }
                      this.base();
                  }

      領(lǐng)域模型

        

      顯示火焰

      火力范圍設(shè)為1格,分為上下左右四個(gè)方向。地圖的墻對(duì)火焰有阻斷作用。

      增加圖片

      爆炸中心為圖片boom.png:

      火焰為圖片explode.png:

      增加FireSprite

      增加火焰精靈類。

      增加FireLayer

      在畫(huà)布上增加火焰畫(huà)布,同時(shí)對(duì)應(yīng)的FireLayer類。

      該畫(huà)布位于地圖和炸彈畫(huà)布之上,玩家和敵人畫(huà)布之下。

      增加工廠方法

      SpriteFactory增加創(chuàng)建爆炸中心火焰精靈類實(shí)例和創(chuàng)建火焰精靈類實(shí)例的工廠方法。

      LayerFactory增加創(chuàng)建火焰層實(shí)例的工廠方法。

      領(lǐng)域模型

       

      相關(guān)代碼

      Sprite

      (function () {
          var Sprite = YYC.AClass({
              Init: function (data, bitmap) {
                  this.bitmap = bitmap;
      
                  if (data) {
                      this.x = data.x;
                      this.y = data.y;
      
                      this.defaultAnimId = data.defaultAnimId;
                      this.anims = data.anims;
                  }
              },
              Private: {
                  //更新幀動(dòng)畫(huà)
                  _updateFrame: function (deltaTime) {
                      if (this.currentAnim) {
                          this.currentAnim.update(deltaTime);
                      }
                  }
              },
              Public: {
                  bitmap: null,
      
                  //精靈的坐標(biāo)
                  x: 0,
                  y: 0,
      
                  //精靈包含的所有 Animation 集合. Object類型, 數(shù)據(jù)存放方式為" id : animation ".
                  anims: null,
                  //默認(rèn)的Animation的Id , string類型
                  defaultAnimId: null,
      
                  //當(dāng)前的Animation.
                  currentAnim: null,
      
                  //設(shè)置當(dāng)前Animation, 參數(shù)為Animation的id, String類型
                  setAnim: function (animId) {
                      this.currentAnim = this.anims[animId];
                  },
                  //重置當(dāng)前幀
                  resetCurrentFrame: function (index) {
                      this.currentAnim && this.currentAnim.setCurrentFrame(index);
                  },
                  //取得精靈的碰撞區(qū)域,
                  getCollideRect: function () {
                      return {
                          x1: this.x,
                          y1: this.y,
                          x2: this.x + this.bitmap.width,
                          y2: this.y + this.bitmap.height
                      }
                  },
                  Virtual: {
                      //初始化方法
                      init: function () {
                          //設(shè)置當(dāng)前Animation
                          this.setAnim(this.defaultAnimId);
                      },
                      // 更新精靈當(dāng)前狀態(tài).
                      update: function (deltaTime) {
                          this._updateFrame(deltaTime);
                      },
                      //獲得坐標(biāo)對(duì)應(yīng)的方格坐標(biāo)
                      getCellPosition: function (x, y) {
                          return {
                              x: x / bomberConfig.WIDTH,
                              y: y / bomberConfig.HEIGHT
                          }
                      },
                      draw: function (context) {
                          context.drawImage(this.bitmap.img, this.x, this.y, this.bitmap.width, this.bitmap.height);
                      },
                      clear: function (context) {
                          //直接清空畫(huà)布區(qū)域
                          context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
                      }
                  }
              },
              Abstract: {
              }
          });
      
          window.Sprite = Sprite;
      }());
      View Code

      FireSprite

      (function () {
          var FireSprite = YYC.Class(Sprite, {
              Init: function (data, bitmap) {
                  this.base(null, bitmap);
              }
          });
      
          window.FireSprite = FireSprite;
      }());

      BombSprite

      (function () {
          var BombSprite = YYC.Class(Sprite, {
              Init: function (playerSprite, bitmap) {
                  this.playerSprite = playerSprite;
      
                  this.base(null, bitmap);
              },
              Protected: {
              },
              Private: {
                  __createFire: function () {
                      var fires = [],
      
                          up = null,
                          down = null,
                          left = null,
                          right = null;
      
                      this.__createCenter(fires);
                      this.__createUp(fires);
                      this.__createDown(fires);
                      this.__createLeft(fires);
                      this.__createRight(fires);
      
                      return fires;
                  },
                  __createCenter: function (fires) {
                      var center = spriteFactory.createExplode();
      
                      center.x = this.x;
                      center.y = this.y;
                      fires.push(center);
                  },
                  __createUp: function (fires) {
                      this.__createOneDir(fires, this.x, this.y - bomberConfig.HEIGHT);
                  },
                  __createDown: function (fires) {
                      this.__createOneDir(fires, this.x, this.y + bomberConfig.HEIGHT);
                  },
                  __createLeft: function (fires) {
                      this.__createOneDir(fires, this.x - bomberConfig.WIDTH, this.y);
                  },
                  __createRight: function (fires) {
                      this.__createOneDir(fires, this.x + bomberConfig.WIDTH, this.y);
                  },
                  __createOneDir: function (fires, x, y) {
                      var fire = null;
                      var position = this.getCellPosition(x, y);
      
                      if (this.__isNotBorder(position) && this.__isGround(position)) {
                          fire = spriteFactory.createFire();
                          fire.x = x;
                          fire.y = y;
                          fires.push(fire);
                      }
                  },
                  __isNotBorder: function (position) {
                      if (position.x < 0 || position.y < 0) {
                          return false;
                      }
      
                      if (position.x >= window.mapData[0].length || position.y >= window.mapData.length) {
                          return false;
                      }
      
                      return true;
                  },
                  __isGround: function (position) {
                      return window.mapData[position.y][position.x] === window.bomberConfig.map.type.GROUND;
                  },
                  __changeTerrainData: function () {
                      var pass = bomberConfig.map.terrain.pass,
                      position = this.getCellPosition(this.x, this.y);
      
                      window.terrainData[position.y][position.x] = pass;
                  }
              },
              Public: {
                  playerSprite: null,
      
                  explode: function () {
                      this.playerSprite.bombNum -= 1;
                      this.__changeTerrainData();
                      return this.__createFire();
                  }
              }
          });
      
          window.BombSprite = BombSprite;
      }());
      View Code

      PlayerSprite

      (function () {
          var PlayerSprite = YYC.Class(MoveSprite, {
              Init: function (data, bitmap) {
                  this.base(data, bitmap);
      
                  this.P__context = new Context(this);
              },
              Private: {
                  __allKeyUp: function () {
                      return window.keyState[keyCodeMap.A] === false && window.keyState[keyCodeMap.D] === false
                           && window.keyState[keyCodeMap.W] === false && window.keyState[keyCodeMap.S] === false;
                  },
                  __judgeAndSetDir: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.P__context.walkLeft();
                      }
                      else if (window.keyState[keyCodeMap.D] === true) {
                          this.P__context.walkRight();
                      }
                      else if (window.keyState[keyCodeMap.W] === true) {
                          this.P__context.walkUp();
                      }
                      else if (window.keyState[keyCodeMap.S] === true) {
                          this.P__context.walkDown();
                      }
                  },
                  __changeTerrainData: function () {
                      var stop = bomberConfig.map.terrain.stop,
                      position = this.getCurrentCellPosition();
      
                      window.terrainData[position.y][position.x] = stop;
                  }
              },
              Public: {
                  //已放置的炸彈數(shù)
                  bombNum: 0,
      
                  move: function () {
                      this.P__context.move();
                  },
                  setDir: function () {
                      if (this.moving) {
                          return;
                      }
      
                      if (this.__allKeyUp()) {
                          this.P__context.stand();
                      }
                      else {
                          this.__judgeAndSetDir();
                      }
                  },
                  createBomb: function () {
                      if (this.moving || this.bombNum === 1) {
                          return null;
                      }
      
                      var bomb = spriteFactory.createBomb(this);
      
                      bomb.x = this.x;
                      bomb.y = this.y;
      
                      this.bombNum += 1;
      
                      this.__changeTerrainData();
      
                      return bomb;
                  }
              }
          });
      
          window.PlayerSprite = PlayerSprite;
      }());
      View Code

      Layer

      //層類(抽象類)
      //職責(zé):
      ////負(fù)責(zé)層內(nèi)組件的統(tǒng)一draw
      (function () {
          var Layer = YYC.AClass(Collection, {
              Init: function () {
              },
              Private: {
                  __state: bomberConfig.layer.state.CHANGE,   //默認(rèn)為change
      
                  __getContext: function () {
                      this.P__context = this.P__canvas.getContext("2d");
                  }
              },
              Protected: {
                  //*共用的變量(可讀、寫(xiě))
      
                  P__canvas: null,
                  P__context: null,
      
                  //*共用的方法(可讀)
      
                  P__isChange: function () {
                      return this.__state === bomberConfig.layer.state.CHANGE;
                  },
                  P__isNormal: function () {
                      return this.__state === bomberConfig.layer.state.NORMAL;
                  },
                  P__setStateNormal: function () {
                      this.__state = bomberConfig.layer.state.NORMAL;
                  },
                  P__setStateChange: function () {
                      this.__state = bomberConfig.layer.state.CHANGE;
                  },
                  P__iterator: function (handler) {
                      var args = Array.prototype.slice.call(arguments, 1),
                          nextElement = null;
      
                      while (this.hasNext()) {
                          nextElement = this.next();
                          nextElement[handler].apply(nextElement, args);  //要指向nextElement
                      }
                      this.resetCursor();
                  },
                  P__render: function () {
                      if (this.P__isChange()) {
                          this.clear();
                          this.draw();
                          this.P__setStateNormal();
                      }
                  }
              },
              Public: {
                  addElements: function (elements) {
                      this.appendChilds(elements);
                  },
                  Virtual: {
                      init: function () {
                          this.__getContext();
                      },
                      //更改狀態(tài)
                      change: function () {
                          this.__state = bomberConfig.layer.state.CHANGE;
                      }
                  }
              },
              Abstract: {
                  setCanvas: function () {
                  },
                  clear: function () {
                  },
                  //統(tǒng)一繪制
                  draw: function () { },
                  //游戲主線程調(diào)用的函數(shù)
                  run: function () { }
              }
          });
      
          window.Layer = Layer;
      }());
      View Code

      FireLayer

      (function () {
          var FireLayer = YYC.Class(Layer, {
              Private: {
                  ___hasFire: function(){
                      return this.getChilds().length > 0;
                  }
              },
              Public: {
                  setCanvas: function () {
                      this.P__canvas = document.getElementById("fireLayerCanvas");
                      var css = {
                          "position": "absolute",
                          "top": bomberConfig.canvas.TOP,
                          "left": bomberConfig.canvas.LEFT,
      
                          "z-index": 2
                      };
      
                      $("#fireLayerCanvas").css(css);
                  },
                  draw: function () {
                      this.P__iterator("draw", this.P__context);
                  },
                  clear: function () {
                      this.P__iterator("clear", this.P__context);
                  },
                  change: function () {
                      if (this.___hasFire()) {
                          this.base();
                      }
                  },
                  run: function () {
                      this.P__render();
                  }
              }
          });
      
          window.FireLayer = FireLayer;
      }());

      BombLayer

      (function () {
          var BombLayer = YYC.Class(Layer, {
              Private: {
                  ___hasBomb: function(){
                      return this.getChilds().length > 0;
                  },
                  ___removeBomb: function (bomb) {
                      //*注意順序!
      
                      this.clear();
                      this.remove(bomb);
                  },
                  ___removeAllFire: function () {
                      //*注意順序!
      
                      this.fireLayer.clear();
                      this.fireLayer.removeAll();
                  }
              },
              Public: {
                  fireLayer: null,
      
                  setCanvas: function () {
                      this.P__canvas = document.getElementById("bombLayerCanvas");
                      var css = {
                          "position": "absolute",
                          "top": bomberConfig.canvas.TOP,
                          "left": bomberConfig.canvas.LEFT,
                          "z-index": 1
                      };
      
                      $("#bombLayerCanvas").css(css);
                  },
                  draw: function () {
                      this.P__iterator("draw", this.P__context);
                  },
                  clear: function () {
                      this.P__iterator("clear", this.P__context);
                  },
                  getFire: function (fireLayer) {
                      this.fireLayer = fireLayer;
                  },
                  explode: function (bomb) {
                      var self = this;
      
                      this.fireLayer.addElements(bomb.explode());
                      this.___removeBomb(bomb);
                      //定時(shí)清空f(shuō)ireLayer(火焰消失)
                      setTimeout(function () {
                          self.___removeAllFire();
                      }, 300);
                  },
                  change: function(){
                      if (this.___hasBomb()) {
                          this.base();
                      }
                  },
                  run: function () {
                      this.P__render();
                  }
              }
          });
      
          window.BombLayer = BombLayer;
      }());

      SpriteFactory

              createFire: function () {
                  return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("fire"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
              },
              createExplode: function () {
                  return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("explode"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
              }

      LayerFactory  

              createFire: function () {
                  return new FireLayer();
              }

      “顯示炸彈和火焰”演示

      演示地址

      使用觀察者模式

      觀察者模式介紹

      詳見(jiàn)Javascript設(shè)計(jì)模式之我見(jiàn):觀察者模式

      應(yīng)用場(chǎng)景

      墻被炸掉后,會(huì)變成空地。

      實(shí)現(xiàn)思路

      Maplayer的changeSpriteImg負(fù)責(zé)更改地圖圖片,BombSprite的explode負(fù)責(zé)處理爆炸邏輯。

      需要在explode中調(diào)用Maplayer的changeSpriteImg。

      因此,決定在Game中訂閱Maplayer的changeSpriteImg方法,然后在BombSprite的explode方法中發(fā)布。   

      為什么此處用觀察者模式

      因?yàn)镸apLayer的Layer類族在BombSprite的Sprite類族的上層,我不希望下層BombSprite與上層MapLayer耦合。

      因此,采用觀察者模式來(lái)解除兩者的耦合。

      領(lǐng)域模型

      使用觀察模式前

       

      使用觀察模式后

      相關(guān)代碼

      Subject

      (function () {
          if (!Array.prototype.forEach) {
              Array.prototype.forEach = function (fn, thisObj) {
                  var scope = thisObj || window;
                  for (var i = 0, j = this.length; i < j; ++i) {
                      fn.call(scope, this[i], i, this);
                  }
              };
          }
      
          if (!Array.prototype.filter) {
              Array.prototype.filter = function (fn, thisObj) {
                  var scope = thisObj || window;
                  var a = [];
                  for (var i = 0, j = this.length; i < j; ++i) {
                      if (!fn.call(scope, this[i], i, this)) {
                          continue;
                      }
                      a.push(this[i]);
                  }
                  return a;
              };
          }
      
          Subject = function () {
              this._events = [];
          }
      
          Subject.prototype = (function () {
      
              return {
                  //訂閱方法
                  subscribe: function (context, fn) {
                      if (arguments.length == 2) {
                          this._events.push({ context: arguments[0], fn: arguments[1] });
                      }
                      else {
                          this._events.push(arguments[0]);
                      }
                  },
                  //發(fā)布指定方法
                  publish: function (context, fn, args) {
                      var args = Array.prototype.slice.call(arguments, 2);    //獲得函數(shù)參數(shù)
                      var _context = null;
                      var _fn = null;
      
                      this._events.filter(function (el) {
                          if (el.context) {
                              _context = el.context;
                              _fn = el.fn;
                          }
                          else {
                              _context = context;
                              _fn = el;
                          }
      
                          if (_fn === fn) {
                              return _fn;
                          }
                      }).forEach(function (el) {  //指定方法可能有多個(gè)
                              el.apply(_context, args);       //執(zhí)行每個(gè)指定的方法
                          });
                  },
                  unSubscribe: function (fn) {
                      var _fn = null;
                      this._events = this._events.filter(function (el) {
                          if (el.fn) {
                              _fn = el.fn;
                          }
                          else {
                              _fn = el;
                          }
      
                          if (_fn !== fn) {
                              return el;
                          }
                      });
                  },
                  //全部發(fā)布
                  publishAll: function (context, args) {
                      var args = Array.prototype.slice.call(arguments, 1);    //獲得函數(shù)參數(shù)
                      var _context = null;
                      var _fn = null;
      
                      this._events.forEach(function (el) {
                          if (el.context) {
                              _context = el.context;
                              _fn = el.fn;
                          }
                          else {
                              _context = context;
                              _fn = el;
                          }
      
                          _fn.apply(_context, args);       //執(zhí)行每個(gè)指定的方法
                      });
                  },
                  dispose: function () {
                      this._events = [];
                  }
              }
          })();
      
          YYC.Pattern.Subject = Subject;
      })();
      View Code

      MapLayer

                  //改變指定精靈類的img對(duì)象
                  //參數(shù):
                  //x:x坐標(biāo)(方格對(duì)應(yīng)值);y:y坐標(biāo)(方格對(duì)應(yīng)值);img:要替換的img對(duì)象
                  changeSpriteImg: function (x, y, img) {
                      var index = y * window.bomberConfig.map.COL + x;
                      this.getChildAt(index).bitmap.img = img;
                  },

      BombSprite

               __destroyOneDir: function (x, y) {
      ...
                          window.observer.publishAll(null, position.x, position.y, groundImg);
      ...
                  },

      Game

          //觀察者全局實(shí)例
          window.observer = null
      
          var Game = YYC.Class({
              Init: function () {
                  window.observer = new YYC.Pattern.Observer();
              },
      ...
          init: function () {
      ...
              //觀察者模式 -> 訂閱                
              window.observer.subscribe(this.layerManager.getLayer("mapLayer"), this.layerManager.getLayer("mapLayer").changeSpriteImg);
          },

      重構(gòu)

      增加TerrainDataOperate

      增加TerrainData地形數(shù)據(jù)操作類TerrainDataOperate

      領(lǐng)域模型

      重構(gòu)前

      重構(gòu)后

      相關(guān)代碼

      TerrainDataOperate

      (function () {
          var terrainDataOperate = {
              getTerrainData: function () {
                  return YYC.Tool.array.clone(window.terrainData);
              },
              setTerrainData: function (x, y, data) {
                  window.terrainData[y][x] = data;
              }
          };
      
          window.terrainDataOperate = terrainDataOperate;
      }());

      增加火力范圍

      將范圍從1格改為2格,方便演示游戲。

      增加游戲全局狀態(tài)GameState

      在Game的run方法中,需要判斷敵人是否抓住了玩家(是否與玩家碰撞):

          run: function () {
              if (this.layerManager.getLayer("enemyLayer").collideWidthPlayer()) {
                  YYC.Tool.asyn.clearAllTimer(this.mainLoop);
                  alert("Game Over!");
                  return;
              }
      ...
          }

      這里注意到,Game需要知道EnemyLayer的collideWidthPlayer方法:

      但Game類只應(yīng)該知道LayerManager,而不應(yīng)該知道Layer(見(jiàn)“炸彈人游戲開(kāi)發(fā)系列(1):準(zhǔn)備工作”中的概念層次結(jié)構(gòu))。

      因此,增加游戲全局狀態(tài)GameState,在Game的run判斷GameState,然后把與炸彈人的碰撞檢測(cè)的任務(wù)放到EnemyLayer的run方法中:

      重構(gòu)后相關(guān)代碼

      Config

          game: {
              state: {
                  NORAML: 1,
                  OVER: 2
              }
          },

      Game

          //游戲全局狀態(tài)
          window.gameState = window.bomberConfig.game.state.NORMAL;
      ...
          run: function () {
              if (window.gameState === window.bomberConfig.game.state.OVER) {
                  this.gameOver();
                  return;
              }
      ...
          }
          ,
          gameOver: function () {
              YYC.Tool.asyn.clearAllTimer(this.mainLoop);
              alert("Game Over!");
          }

      EnemyLayer

                  run: function () {
                      if (this.collideWidthPlayer()) {
                          window.gameState = window.bomberConfig.game.state.OVER;
                          return;
                      }
      ...

      炸彈可以炸死炸彈人和敵人

      在炸彈爆炸時(shí),判斷與炸彈人、敵人是否碰撞并進(jìn)行相應(yīng)處理。

      領(lǐng)域模型

      相關(guān)代碼

      BombLayer

      ___collideFireWithPlayer: function (bomb) {
          if (bomb.collideFireWithCharacter(this.playerLayer.getChildAt(0))) {
              window.gameState = window.bomberConfig.game.state.OVER;
          }
      },
      ___collideFireWithEnemy: function (bomb) {
          var i = 0,
              len = 0,
              enemySprites = this.enemyLayer.getChilds();
      
          for (i = 0, len = enemySprites.length ; i < len; i++) {
              if (bomb.collideFireWithCharacter(enemySprites[i])) {
                  this.___removeEnemy(enemySprites[i]);
              }
          }
      },
      ___removeEnemy: function (enemy) {
          //*注意順序!
      
          this.enemyLayer.clear();
          this.enemyLayer.remove(enemy);
      },
      ___handleCollid: function (bomb) {
          //判斷與炸彈人碰撞
          this.___collideFireWithPlayer(bomb)
          //判斷與每個(gè)敵人碰撞
          this.___collideFireWithEnemy(bomb);
      }
      ...
      enemyLayer: null,
      playerLayer: null,
      ...
      explode: function (bomb) {
          var self = this,
              result = null;
      
          //處理碰撞
          this.___handleCollid(bomb);
      ...

      移動(dòng)時(shí)放炸彈

      因?yàn)檎◤椚艘苿?dòng)時(shí),根據(jù)炸彈人狀態(tài)的不同,炸彈放置的坐標(biāo)策略也不同(即如果炸彈人往上走,則炸彈放在炸彈人所在方格的上面相鄰方格;如果往左走,則炸彈放在炸彈人所在方格的左側(cè)相鄰方格)。所以將PlayerSprite的createBomb方法委托給狀態(tài)類處理。具體來(lái)說(shuō),就是把createBomb方法移到狀態(tài)類的WalkState類和Stand類中來(lái)分別處理。

      領(lǐng)域模型

      分析

      因?yàn)镻layerSprite、EnemySprite都使用了狀態(tài)類,因此兩者都與BombSprite耦合。但只有PlayerSprite需要使用createBomb方法,EnemySprite并不需要使用該方法。所以此處違反了迪米特法則。

      目前這種情況在可以接受的范圍之內(nèi)。如果在后面的開(kāi)發(fā)中EnemySprite與BombSprite耦合得很?chē)?yán)重,再來(lái)考慮解耦。

      放置多個(gè)炸彈

      可以最多放3個(gè)炸彈,炸彈爆炸時(shí)會(huì)引爆在火力范圍內(nèi)的炸彈。

      不能在一個(gè)方格疊加多個(gè)炸彈

      在狀態(tài)類WalkState類族、StandState類族的createBomb中判斷方格是否有炸彈(判斷地形數(shù)據(jù)TerrainData來(lái)實(shí)現(xiàn))。

      改變地圖

      炸掉墻

      如果墻處于火焰范圍內(nèi),則修改MapData,將墻的圖片換成空地圖片,同時(shí)對(duì)應(yīng)修改TerrainData,將墻所在的方格設(shè)成可通過(guò)。

      刷新地圖

      在炸掉墻后,在BombLayer中需要調(diào)用MapLayer的setStateChange方法,將MapLayer的state設(shè)為change,從而能夠在游戲的下一個(gè)主循環(huán)中,刷新地圖,從而顯示為空地。

      領(lǐng)域模型

      相關(guān)代碼

      BombLayer

      ___mapChange: function (mapChange) {
          if (mapChange) {
              this.mapLayer.setStateChange();
          }
      }

      小結(jié)

      現(xiàn)在我們就完成了“放炸彈”的功能,來(lái)看下成果吧~

      “放炸彈”演示

      演示地址

      相關(guān)代碼

      FireSprite

      (function () {
          var FireSprite = YYC.Class(Sprite, {
              Init: function (data, bitmap) {
                  this.base(null, bitmap);
              }
          });
      
          window.FireSprite = FireSprite;
      }());

      FireLayer

      (function () {
          var FireLayer = YYC.Class(Layer, {
              Private: {
                  ___hasFire: function(){
                      return this.getChilds().length > 0;
                  }
              },
              Public: {
                  setCanvas: function () {
                      this.P__canvas = document.getElementById("fireLayerCanvas");
                      var css = {
                          "position": "absolute",
                          "top": bomberConfig.canvas.TOP,
                          "left": bomberConfig.canvas.LEFT,
                          "z-index": 2
                      };
      
                      $("#fireLayerCanvas").css(css);
                  },
                  draw: function () {
                      this.P__iterator("draw", this.P__context);
                  },
                  clear: function () {
                      this.P__iterator("clear", this.P__context);
                  },
                  change: function () {
                      if (this.___hasFire()) {
                          this.setStateChange();
                      }
                  },
                  run: function () {
                      this.P__render();
                  }
              }
          });
      
          window.FireLayer = FireLayer;
      }());
      View Code

      PlayerSprite

      (function () {
          var PlayerSprite = YYC.Class(MoveSprite, {
              Init: function (data, bitmap) {
                  this.base(data, bitmap);
      
                  this.P__context = new Context(this);
              },
              Private: {
                  __allKeyUp: function () {
                      return window.keyState[keyCodeMap.A] === false && window.keyState[keyCodeMap.D] === false
                           && window.keyState[keyCodeMap.W] === false && window.keyState[keyCodeMap.S] === false;
                  },
                  __judgeAndSetDir: function () {
                      if (window.keyState[keyCodeMap.A] === true) {
                          this.P__context.walkLeft();
                      }
                      else if (window.keyState[keyCodeMap.D] === true) {
                          this.P__context.walkRight();
                      }
                      else if (window.keyState[keyCodeMap.W] === true) {
                          this.P__context.walkUp();
                      }
                      else if (window.keyState[keyCodeMap.S] === true) {
                          this.P__context.walkDown();
                      }
                  },
                  __changeTerrainData: function () {
                      var stop = bomberConfig.map.terrain.stop,
                      position = this.getCurrentCellPosition();
      
                      terrainDataOperate.setTerrainData(position.x, position.y, stop);
                  }
              },
              Public: {
                  //已放置的炸彈數(shù)
                  bombNum: 0,
      
                  move: function () {
                      this.P__context.move();
                  },
                  setDir: function () {
                      if (this.moving) {
                          return;
                      }
      
                      if (this.__allKeyUp()) {
                          this.P__context.stand();
                      }
                      else {
                          this.__judgeAndSetDir();
                      }
                  },
                  createBomb: function () {
                      if (this.bombNum === 3) {
                          return null;
                      }
      
                      return this.P__context.createBomb();
                  }
              }
          });
      
          window.PlayerSprite = PlayerSprite;
      }());
      View Code

      BomberSprite

      (function () {
          var BombSprite = YYC.Class(Sprite, {
              Init: function (playerSprite, bitmap) {
                  this.playerSprite = playerSprite;
      
                  this.base(null, bitmap);
              },
              Protected: {
              },
              Private: {
                  //返回火焰范圍
                  //返回順序?yàn)閇center、[up]、[down]、[left]、[right]]
                  __getFireAllRange: function () {
      
                      return [
                          { x: this.x, y: this.y },
                          [
                              { x: this.x, y: this.y - bomberConfig.HEIGHT },
                              { x: this.x, y: this.y - bomberConfig.HEIGHT * 2 }
                          ],
                          [
                              { x: this.x, y: this.y + bomberConfig.HEIGHT },
                              { x: this.x, y: this.y + bomberConfig.HEIGHT * 2 }
                          ],
                          [
                              { x: this.x - bomberConfig.WIDTH, y: this.y },
                              { x: this.x - bomberConfig.WIDTH * 2, y: this.y }
                          ],
                          [
                              { x: this.x + bomberConfig.WIDTH, y: this.y },
                              { x: this.x + bomberConfig.WIDTH * 2, y: this.y }
                          ]
                      ];
                  },
                  __getCenterEffectiveRange: function (effectiveRange, center) {
                      effectiveRange.center = { x: center.x, y: center.y };
                  },
                  __getFourDirEffectiveRange: function (effectiveRange, allRange) {
                      var i = 0,
                          j = 0,
                          len1 = 0,
                          len2 = 0,
                          firePos = null,
                          cellPos = null,
                          groundRange = [],
                          wallRange = [];
      
                      for (i = 0, len1 = allRange.length; i < len1; i++) {
                          for (j = 0, len2 = allRange[i].length; j < len2; j++) {
                              firePos = allRange[i][j];
                              cellPos = this.getCellPosition(firePos.x, firePos.y);
      
                              if (this.__isNotBorder(cellPos)) {
                                  if (this.__isGround(cellPos)) {
                                      groundRange.push(firePos);
                                  }
                                  else if (this.__isWall(cellPos)) {
                                      wallRange.push(firePos);
                                      break;
                                  }
                                  else {
                                      throw new Error("未知的地圖類型");
                                  }
                              }
                          }
                      }
                      effectiveRange.groundRange = groundRange;
                      effectiveRange.wallRange = wallRange;
                  },
                  __createFire: function (effectiveRange) {
                      var fires = [];
      
                      this.__createCenter(fires, effectiveRange);
                      this.__createFourDir(fires, effectiveRange);
      
                      return fires;
                  },
                  __createCenter: function (fires, effectiveRange) {
                      var center = spriteFactory.createExplode();
      
                      center.x = effectiveRange.center.x;
                      center.y = effectiveRange.center.y;
                      fires.push(center);
                  },
                  __createFourDir: function (fires, effectiveRange) {
                      var i = 0,
                          len = 0,
                          fire = null,
                          groundRange = effectiveRange.groundRange;
      
                      for (i = 0, len = groundRange.length; i < len; i++) {
                                  fire = spriteFactory.createFire();
                                  fire.x = groundRange[i].x;
                                  fire.y = groundRange[i].y;
                                  fires.push(fire);
                      }
                  },
                  __isNotBorder: function (position) {
                      if (position.x < 0 || position.y < 0) {
                          return false;
                      }
      
                      if (position.x >= window.mapData[0].length || position.y >= window.mapData.length) {
                          return false;
                      }
      
                      return true;
                  },
                  __isGround: function (position) {
                      return window.mapDataOperate.getMapData()[position.y][position.x] === window.bomberConfig.map.type.GROUND;
                  },
                  __bombPass: function () {
                      var pass = bomberConfig.map.terrain.pass,
                      position = this.getCellPosition(this.x, this.y);
      
                      terrainDataOperate.setTerrainData(position.x, position.y, pass);
                  },
                  __destroyWall: function (effectiveRange) {
                      var i = 0,
                      len = 0,
                      mapChange = false,
                      wallRange = effectiveRange.wallRange,
                      cellPos = null,
                            ground = bomberConfig.map.type.GROUND,
                          groundImg = window.imgLoader.get("ground"),
                          wall = bomberConfig.map.type.WALL,
                          pass = bomberConfig.map.terrain.pass,
                          stop = bomberConfig.map.terrain.stop;
      
                      for (i = 0, len = wallRange.length; i < len; i++) {
                          cellPos = this.getCellPosition(wallRange[i].x, wallRange[i].y);
                          window.mapDataOperate.setMapData(cellPos.x, cellPos.y, ground);
                                      window.terrainDataOperate.setTerrainData(cellPos.x, cellPos.y, pass);
                                      //觀察者模式 -> 發(fā)布
                                      //調(diào)用mapLayer.changeSpriteImg,改變地圖層對(duì)應(yīng)精靈類的img對(duì)象
                                      window.observer.publishAll(null, cellPos.x, cellPos.y, groundImg);
                                      if (!mapChange) {
                                          mapChange = true;
                                      }
                      }
      
                      return mapChange;
                  },
                  __isWall: function (position) {
                      return window.mapDataOperate.getMapData()[position.y][position.x] === window.bomberConfig.map.type.WALL;
                  },
                  __isInEffectiveRange: function (effectiveRange) {
                      var range = null;
      
                      range = effectiveRange.groundRange.concat(effectiveRange.wallRange);
                      range.push(effectiveRange.center);
      
                      if (this.__isInRange(range)) {
                          return true;
                      }
                      else {
                          return false;
                      }
                  },
                  __isInRange: function (range) {
                      var i = 0,
                          len = 0;
      
                      for (i = 0, len = range.length; i < len; i++) {
                          if (range[i].x === this.x && range[i].y === this.y) {
                              return true;
                          }
                      }
      
                      return false;
                  }
              },
              Public: {
                  playerSprite: null,
                  //是否已爆炸標(biāo)志
                  exploded: false,
      
                  explode: function () {
                      var fires = null,
                          mapChange = false,
                          effectiveRange = [];
      
                      this.playerSprite.bombNum -= 1;
                      this.exploded = true;
                      this.__bombPass();
                      effectiveRange = this.getFireEffectiveRange();
                      fires = this.__createFire(effectiveRange);
                      mapChange = this.__destroyWall(effectiveRange);
      
      
                      return {
                          fires: fires,
                          mapChange: mapChange
                      };
                  },
                  //檢測(cè)火焰與玩家人物、敵人的碰撞
                  collideFireWithCharacter: function (sprite) {
                      var effectiveRange = this.getFireEffectiveRange(),
                      range = [],
                      fire = {},
                      obj2 = {},
                      i = 0,
                      len = 0;
      
                      //放到數(shù)組中
                      range.push(effectiveRange.center);
                      range = range.concat(effectiveRange.groundRange, effectiveRange.wallRange);
      
                      for (i = 0, len = range.length; i < len; i++) {
                          fire = {
                              x: range[i].x,
                              y: range[i].y,
                              width: this.bitmap.width,
                              height: this.bitmap.height
                          };
                          obj2 = {
                              x: sprite.x,
                              y: sprite.y,
                              width: sprite.bitmap.width,
                              height: sprite.bitmap.height
                          };
                          if (YYC.Tool.collision.col_Between_Rects(fire, obj2)) {
                              return true;
                          }
                      }
      
                      return false;
                  },
                  //返回有效范圍。(考慮墻、邊界阻擋等問(wèn)題)
                  //返回值形如:{center: {x: 1,y: 1}}, {groundRange: [{{x: 1,y: 1}]}, {wallRange: [{{x: 1,y: 1}]}
                  getFireEffectiveRange: function () {
                      var effectiveRange = {},
                          allRange = this.__getFireAllRange();
      
                      this.__getCenterEffectiveRange(effectiveRange, allRange.shift());
                      this.__getFourDirEffectiveRange(effectiveRange, allRange);
      
                      return effectiveRange;
                  },
                  isInEffectiveRange: function (bomb) {
                      return this.__isInEffectiveRange(bomb.getFireEffectiveRange());
                  }
              }
          });
      
          window.BombSprite = BombSprite;
      }());
      View Code

      Sprite

      (function () {
          var Sprite = YYC.AClass({
              Init: function (data, bitmap) {
                  this.bitmap = bitmap;
      
                  if (data) {
                      this.x = data.x;
                      this.y = data.y;
      
                      this.defaultAnimId = data.defaultAnimId;
                      this.anims = data.anims;
                  }
              },
              Private: {
                  //更新幀動(dòng)畫(huà)
                  _updateFrame: function (deltaTime) {
                      if (this.currentAnim) {
                          this.currentAnim.update(deltaTime);
                      }
                  }
              },
              Public: {
                  bitmap: null,
      
                  //精靈的坐標(biāo)
                  x: 0,
                  y: 0,
      
                  //精靈包含的所有 Animation 集合. Object類型, 數(shù)據(jù)存放方式為" id : animation ".
                  anims: null,
                  //默認(rèn)的Animation的Id , string類型
                  defaultAnimId: null,
      
                  //當(dāng)前的Animation.
                  currentAnim: null,
      
                  //設(shè)置當(dāng)前Animation, 參數(shù)為Animation的id, String類型
                  setAnim: function (animId) {
                      this.currentAnim = this.anims[animId];
                  },
                  //重置當(dāng)前幀
                  resetCurrentFrame: function (index) {
                      this.currentAnim && this.currentAnim.setCurrentFrame(index);
                  },
                  //取得精靈的碰撞區(qū)域,
                  getCollideRect: function () {
                      var obj = {
                          x: this.x,
                          y: this.y,
                          width: this.bitmap.width,
                          height: this.bitmap.height
                      };
      
                      return YYC.Tool.collision.getCollideRect(obj);
                  },
                  Virtual: {
                      //初始化方法
                      init: function () {
                          //設(shè)置當(dāng)前Animation
                          this.setAnim(this.defaultAnimId);
                      },
                      // 更新精靈當(dāng)前狀態(tài).
                      update: function (deltaTime) {
                          this._updateFrame(deltaTime);
                      },
                      //獲得坐標(biāo)對(duì)應(yīng)的方格坐標(biāo)(向下取值)
                      getCellPosition: function (x, y) {
                          return {
                              x: Math.floor(x / bomberConfig.WIDTH),
                              y: Math.floor(y / bomberConfig.HEIGHT)
                          }
                      },
                      draw: function (context) {
                          context.drawImage(this.bitmap.img, this.x, this.y, this.bitmap.width, this.bitmap.height);
                      },
                      clear: function (context) {
                          //直接清空畫(huà)布區(qū)域
                          context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
                      }
                  }
              }
          });
      
          window.Sprite = Sprite;
      }());
      View Code

      PlayerLayer

      (function () {
          var PlayerLayer = YYC.Class(CharacterLayer, {
              Init: function (deltaTime) {
                  this.base(deltaTime);
              },
              Private: {
                  ___keyDown: function () {
                      if (keyState[keyCodeMap.A] === true || keyState[keyCodeMap.D] === true
                          || keyState[keyCodeMap.W] === true || keyState[keyCodeMap.S] === true) {
                          return true;
                      }
                      else {
                          return false;
                      }
                  },
                  ___spriteMoving: function () {
                      return this.getChildAt(0).moving
                  },
                  ___spriteStand: function () {
                      if (this.getChildAt(0).stand) {
                          this.getChildAt(0).stand = false;
                          return true;
                      }
                      else {
                          return false;
                      }
                  }
              },
              Public: {
                  bombLayer: null,
      
                  setCanvas: function () {
                      this.P__canvas = document.getElementById("playerLayerCanvas");
      
                      $("#playerLayerCanvas").css({
                          "position": "absolute",
                          "top": bomberConfig.canvas.TOP,
                          "left": bomberConfig.canvas.LEFT,
                          "border": "1px solid red",
                          "z-index": 3
                      });
                  },
                  init: function (layers) {
                      this.bombLayer = layers.bombLayer;
      
                      this.base();
                  },
                  change: function () {
                      if (this.___keyDown() || this.___spriteMoving() || this.___spriteStand()) {
                          this.base();
                      }
                  },
                  createAndAddBomb: function () {
                      var bomb = this.getChildAt(0).createBomb();
                      var self = this;
      
                      if (!bomb) {
                          return false;
                      }
      
                      this.bombLayer.appendChild(bomb);
                      //3s后炸彈爆炸
                      setTimeout(function () {
                          if (!bomb.exploded) {
                              self.bombLayer.explode(bomb);
                          }
                      }, 3000);
      
                      return bomb;
                  },
                  run: function () {
                      if (keyState[keyCodeMap.Space]) {
                          this.createAndAddBomb();
                          keyState[keyCodeMap.Space] = false;
                      }
                      this.base();
                  }
              }
          });
      
          window.PlayerLayer = PlayerLayer;
      }());
      View Code

      BomberLayer

      (function () {
          var BombLayer = YYC.Class(Layer, {
              Private: {
                  ___hasBomb: function(){
                      return this.getChilds().length > 0;
                  },
                  ___removeBomb: function (bomb) {
                      //*注意順序!
      
                      this.clear(bomb);
                      this.remove(bomb);
                  },
                  ___removeAllFire: function () {
                      //*注意順序!
      
                      this.fireLayer.clear();
                      this.fireLayer.removeAll();
                  },
                  ___removeEnemy: function (enemy) {
                      //*注意順序!
      
                      this.enemyLayer.clear(enemy);
                      this.enemyLayer.remove(enemy);
                  },
                  ___mapChange: function (mapChange) {
                      if (mapChange) {
                          this.mapLayer.setStateChange();
                      }
                  },
                  ___collideFireWithPlayer: function (bomb) {
                          if (bomb.collideFireWithCharacter(this.playerLayer.getChildAt(0))) {
                              window.gameState = window.bomberConfig.game.state.OVER;
                          }
                  },
                  ___collideFireWithEnemy: function (bomb) {
                          var i = 0,
                              len = 0,
                              enemySprites = this.enemyLayer.getChilds();
      
                          for (i = 0, len = enemySprites.length ; i < len; i++) {
                              if (bomb.collideFireWithCharacter(enemySprites[i])) {
                                  this.___removeEnemy(enemySprites[i]);
                              }
                          }
                  },
                  ___handleCollid: function (bomb) {
                      //判斷與玩家人物碰撞
                      this.___collideFireWithPlayer(bomb)
                      //判斷與每個(gè)敵人碰撞
                      this.___collideFireWithEnemy(bomb);
                  },
                  ___explodeInEffectiveRange: function (bomb) {
                      var eachBomb = null;
      
                      this.resetCursor();
                      while (this.hasNext()) {
                          eachBomb = this.next();
                          if (eachBomb.isInEffectiveRange.call(eachBomb, bomb)) {
                              this.explode(eachBomb);
                          }
                      }
                      this.resetCursor();
                  }
              },
              Public: {
                  fireLayer: null,
                  mapLayer: null,
                  playerLayer: null,
                  enemyLayer: null,
      
                  setCanvas: function () {
                      this.P__canvas = document.getElementById("bombLayerCanvas");
                      var css = {
                          "position": "absolute",
                          "top": bomberConfig.canvas.TOP,
                          "left": bomberConfig.canvas.LEFT,
                          "z-index": 1
                      };
      
                      $("#bombLayerCanvas").css(css);
                  },
                  init: function(layers){
                      this.fireLayer = layers.fireLayer;
                      this.mapLayer = layers.mapLayer;
                      this.playerLayer = layers.playerLayer;
                      this.enemyLayer = layers.enemyLayer;
      
                      this.base();
                  },
                  draw: function () {
                      this.P__iterator("draw", this.P__context);
                  },
                  explode: function (bomb) {
                      var self = this,
                          result = null;
      
                      //處理碰撞
                      this.___handleCollid(bomb);
      
                      result = bomb.explode();
                      this.fireLayer.addSprites(result.fires);
                      this.___mapChange(result.mapChange);
                      this.___removeBomb(bomb);
      
      
                      //炸彈爆炸時(shí)會(huì)引爆在火力范圍內(nèi)的炸彈。
                      this.___explodeInEffectiveRange(bomb);
      
                      //定時(shí)清空f(shuō)ireLayer(火焰消失)
                      setTimeout(function () {
                          self.___removeAllFire();
                      }, 300);
      
                  },
                  change: function(){
                      if (this.___hasBomb()) {
                          this.setStateChange();
                      }
                  },
                  run: function () {
                      this.P__render();
                  }
              }
          });
      
          window.BombLayer = BombLayer;
      }());
      View Code

      Layer

      (function () {
          var Layer = YYC.AClass(Collection, {
              Init: function () {
              },
              Private: {
                  __state: bomberConfig.layer.state.CHANGE,   //默認(rèn)為change
      
                  __getContext: function () {
                      this.P__context = this.P__canvas.getContext("2d");
                  }
              },
              Protected: {
                  //*共用的變量(可讀、寫(xiě))
      
                  P__canvas: null,
                  P__context: null,
      
                  //*共用的方法(可讀)
      
                  P__isChange: function(){
                      return this.__state === bomberConfig.layer.state.CHANGE;
                  },
                  P__isNormal: function () {
                      return this.__state === bomberConfig.layer.state.NORMAL;
                  },
                  P__iterator: function (handler) {
                      var args = Array.prototype.slice.call(arguments, 1),
                          nextElement = null;
      
                      while (this.hasNext()) {
                          nextElement = this.next();
                          nextElement[handler].apply(nextElement, args);  //要指向nextElement
                      }
                      this.resetCursor();
                  },
                  P__render: function () {
                      if (this.P__isChange()) {
                          this.clear();
                          this.draw();
                          this.setStateNormal();
                      }
                  }
              },
              Public: {
                  remove: function (sprite) {
                      this.base(function (e, obj) {
                          if (e.x === obj.x && e.y === obj.y) {
                              return true;
                          }
                          return false;
                      }, sprite);
                  },
                  addSprites: function(elements){
                      this.appendChilds(elements);
                  },
                  //設(shè)置狀態(tài)為NORMAL
                  setStateNormal: function () {
                      this.__state = bomberConfig.layer.state.NORMAL;
                  },
                  //設(shè)置狀態(tài)為CHANGE
                  setStateChange: function () {
                      this.__state = bomberConfig.layer.state.CHANGE;
                  },
                  Virtual: {
                      init: function () {
                          this.__getContext();
                      },
                      clear: function (sprite) {
                          if (arguments.length === 0) {
                              this.P__iterator("clear", this.P__context);
                          }
                          else if (arguments.length === 1) {
                              sprite.clear(this.P__context);
                          }
                      }
                  }
              },
              Abstract: {
                  setCanvas: function () {
                  },
                  //判斷并更改狀態(tài)
                  change: function () {
                  },
                  //統(tǒng)一繪制
                  draw: function () { },
                  //游戲主線程調(diào)用的函數(shù)
                  run: function () { }
              }
          });
      
          window.Layer = Layer;
      }());
      View Code

      SpriteFactory

              createBomb: function (playerSprite) {
                  return new BombSprite(playerSprite, bitmapFactory.createBitmap({ img: window.imgLoader.get("bomb"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
              },
              createFire: function () {
                  return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("fire"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
              },
              createExplode: function () {
                  return new FireSprite(null, bitmapFactory.createBitmap({ img: window.imgLoader.get("explode"), width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }));
              }

      LayerFactory

              createBomb: function () {
                  return new BombLayer();
              },
              createFire: function () {
                  return new FireLayer();
              }

      加入1個(gè)敵人

      往EnemyLayer集合中再加入一個(gè)EnemySprite實(shí)例,SpriteData增加第2個(gè)敵人的數(shù)據(jù),SpriteFactory增加工廠方法createEnemy2。

      相關(guān)代碼

      Game

                 _createEnemyLayerElement: function () {
                      var element = [],
                          enemy = spriteFactory.createEnemy(),
                          enemy2 = spriteFactory.createEnemy2();
      
                      enemy.init();
                      enemy2.init();
      
                      element.push(enemy);
                      element.push(enemy2);
      
                      return element;
                  },

      SpriteData

      enemy2: {
          //初始坐標(biāo)
              x: bomberConfig.WIDTH * 10,
          //x: 0,
              y: bomberConfig.HEIGHT * 10,
          //定義sprite走路速度的絕對(duì)值
              walkSpeed: bomberConfig.enemy.speed.NORMAL,
      
          //速度
              speedX: 1,
              speedY: 1,
      
              minX: 0,
              maxX: bomberConfig.canvas.WIDTH - bomberConfig.player.IMGWIDTH,
              minY: 0,
              maxY: bomberConfig.canvas.HEIGHT - bomberConfig.player.IMGHEIGHT,
      
              defaultAnimId: "stand_left",
      
              anims: {
                  "stand_right": new Animation(getFrames("enemy", "stand_right")),
                  "stand_left": new Animation(getFrames("enemy", "stand_left")),
                  "stand_down": new Animation(getFrames("enemy", "stand_down")),
                  "stand_up": new Animation(getFrames("enemy", "stand_up")),
                  "walk_up": new Animation(getFrames("enemy", "walk_up")),
                  "walk_down": new Animation(getFrames("enemy", "walk_down")),
                  "walk_right": new Animation(getFrames("enemy", "walk_right")),
                  "walk_left": new Animation(getFrames("enemy", "walk_left"))
              }
      }

      SpriteFactory

              createEnemy2: function () {
                  return new EnemySprite(getSpriteData("enemy2"), bitmapFactory.createBitmap({ img: window.imgLoader.get("enemy"), width: bomberConfig.player.IMGWIDTH, height: bomberConfig.player.IMGHEIGHT }));
              },

      炸死所有敵人后,提示游戲勝利

      GameState增加WIN枚舉值。在BombLayer中判斷是否將敵人都炸死了,如果都炸死了則設(shè)置GameState為WIN。在Game中判斷GameState,調(diào)用相應(yīng)的方法。

      領(lǐng)域模型

      相關(guān)代碼

      BombLayer

                 ___collideFireWithEnemy: function (bomb) {
                          var i = 0,
                              len = 0,
                              enemySprites = this.enemyLayer.getChilds();
      
                          for (i = 0, len = enemySprites.length ; i < len; i++) {
                              if (bomb.collideFireWithCharacter(enemySprites[i])) {
                                  this.___removeEnemy(enemySprites[i]);
                              }
                          }
      
                          //如果敵人都被炸死了,則游戲勝利!
                          if (this.enemyLayer.getChilds().length === 0) {
                              window.gameState = window.bomberConfig.game.state.WIN;
                          }
                  },

      Game

      _judgeGameState: function () {
          switch (window.gameState) {
              case window.bomberConfig.game.state.NORMAL:
                  break;
              case window.bomberConfig.game.state.OVER:
                  this.gameOver();
                  break;
              case window.bomberConfig.game.state.WIN:
                  this.gameWin();
                  break;
              default:
                  throw new Error("未知的游戲狀態(tài)");
          }
          return;
      }
      ...
      run: function () {
          this._judgeGameState();
      
          this.layerManager.run();
          this.layerManager.change();
      },

      本文最終領(lǐng)域模型

      查看大圖

      高層劃分

      炸彈層和炸彈精靈、火焰層和火焰精靈應(yīng)該放到哪個(gè)包?

      炸彈層和玩家層、炸彈精靈和玩家精靈緊密關(guān)聯(lián),火焰層和火焰精靈與炸彈層和炸彈精靈緊密關(guān)聯(lián),因此將炸彈層和炸彈精靈、火焰層和火焰精靈移到人物包中。

      新增包

      • 全局包
        GameState
      • 觀察者模式包
        Subject
      • 炸彈實(shí)現(xiàn)包
        BombSprite、FireSprite、BombLayer、FireLayer

      層、包

      對(duì)應(yīng)領(lǐng)域模型

      • 輔助操作層
        • 控件包
          PreLoadImg
        • 配置包
          Config
      • 用戶交互層
        • 入口包
          Main
      • 業(yè)務(wù)邏輯層
        • 輔助邏輯
          • 工廠包
            BitmapFactory、LayerFactory、SpriteFactory
          • 事件管理包
            KeyState、KeyEventManager
          • 抽象包
            Layer、Sprite、Hash、Collection
          • 全局包
            GameState
        • 游戲主邏輯
          • 主邏輯包
            Game
        • 層管理
          • 層管理包
            LayerManager
        • 實(shí)現(xiàn)
          • 人物實(shí)現(xiàn)包
            PlayerLayer、MoveSprite、PlayerSprite、EnemySprite、CharacterLayer、PlayerLayer、EnemyLayer、Context、PlayerState、WalkState、StandState、WalkState_X、WalkState_Y、StandLeftState、StandRightState、StandUpState、StandDownState、WalkLeftState、WalkRightState、WalkUpState、WalkDownState
          • 炸彈實(shí)現(xiàn)包
            BombSprite、FireSprite、BombLayer、FireLayer
          • 地圖實(shí)現(xiàn)包
            MapLayer、MapElementSprite
          • 算法包
            FindPath
          • 動(dòng)畫(huà)包
            Animation、GetSpriteData、SpriteData、GetFrames、FrameData
          • 觀察者模式包
            Subject
      • 數(shù)據(jù)操作層
        • 地圖數(shù)據(jù)操作包
          MapDataOperate、TerrainDataOperate
        • 路徑數(shù)據(jù)操作包
          GetPath
        • 圖片數(shù)據(jù)操作包
          Bitmap
      • 數(shù)據(jù)層
        • 地圖包
          MapData、TerrainData
        • 圖片路徑包
          ImgPathData

      本文參考資料

      深入理解JavaScript系列(32):設(shè)計(jì)模式之觀察者模式

      《設(shè)計(jì)模式之禪》

      歡迎瀏覽上一篇博文:炸彈人游戲開(kāi)發(fā)系列(7):加入敵人,使用A*算法尋路

      歡迎瀏覽下一篇博文:炸彈人游戲開(kāi)發(fā)系列(9):總結(jié) 

      posted @ 2013-10-21 11:51  楊元超  閱讀(4036)  評(píng)論(8)    收藏  舉報(bào)
      主站蜘蛛池模板: 国产精品第一二三区久久| 亚洲夂夂婷婷色拍WW47| 国产精品va无码一区二区| 人妻系列无码专区免费| 少妇高潮水多太爽了动态图| 亚洲中文字幕在线观看| 一个人看的www免费高清视频| 国产中年熟女高潮大集合| 亚洲精品国产aⅴ成拍色拍| 亚洲国产一区二区三区四| 国产卡一卡二卡三免费入口| 亚洲毛片多多影院| 久久这里都是精品二| 起碰免费公开97在线视频| 潮喷无码正在播放| 亚洲黄色片一区二区三区| 南皮县| 中文字幕一区二区三区麻豆| 国产精品午夜福利合集| 亚洲精品成人区在线观看| 四虎成人在线观看免费| 国产不卡精品视频男人的天堂 | 青草99在线免费观看| 色综合天天综合天天更新| 亚洲综合天堂一区二区三区| 尤物yw193无码点击进入| 中文字幕国产精品自拍| 欧美大屁股xxxx高跟欧美黑人| 国产成人片无码视频在线观看 | 色老99久久九九爱精品| 亚洲高潮喷水无码AV电影| 日本深夜福利在线观看| 国产午夜福利av在线麻豆| a级免费视频| 九九热爱视频精品视频| 涟源市| 成人免费A级毛片无码片2022 | 在线涩涩免费观看国产精品| 爱色精品视频一区二区| 18岁日韩内射颜射午夜久久成人| 成人免费无码大片a毛片|