前言css
上文中咱們實現了炸彈人與牆的碰撞檢測,以及設置移動步長來解決發現的問題。本文會加入1個AI敵人,敵人使用A*算法追蹤炸彈人。html
加入敵人,追蹤炸彈人算法
查看大圖canvas
開發策略數組
首先實現「加入敵人」功能。經過參考「炸彈人遊戲開發系列(4):炸彈人顯示與移動「中的實現,能夠初步分析出須要加入敵人圖片、敵人幀數據和精靈數據、敵人精靈類EnemySprite、敵人層EnemyLayer和敵人層管理類EnemyLayerManager。app
而後實現「追蹤炸彈人」功能。須要新建一個算法類FindPath,負責使用A*算法計算並返回路徑數據。ide
敵人精靈類與算法類的交互關係:函數
能夠並行開發「加入敵人」和「追蹤炸彈人」。oop
先定義一個FindPath類的的接口,指定findPath方法輸入參數和返回參數的格式。post
實現「加入敵人」功能時,能夠按照接口指定的格式使用假的路徑數據來測試EnemySprite類;實現「追蹤炸彈人」功能時,按照接口指定格式使用假的座標數據來測試FindPath類。
在EnemySprit和FindPath都實現後,再集成在一塊兒測試。由於二者接口一致,所以集成時不會有什麼困難。
加入敵人
如今地圖大小爲4*4,過小了。
加入一個敵人後:
所以,將地圖擴大爲20*20。
要實現這個功能,只須要修改MapData和TerrainData便可。
MapData
//地圖數據 (function () { var ground = bomberConfig.map.type.GROUND, wall = bomberConfig.map.type.WALL; var mapData = [ [ wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, wall, wall, wall, wall, wall, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, wall, wall, ground, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, wall, wall, wall, ground, wall, ground, wall, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ], [ wall, ground, ground, ground, wall, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground, ground ] ]; window.mapData = mapData; }());
TerrainData
(function () { var pass = bomberConfig.map.terrain.pass, stop = bomberConfig.map.terrain.stop; var terrainData = [ [ stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, stop, stop, stop, stop, stop, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, stop, stop, pass, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, stop, stop, stop, pass, stop, pass, stop, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ], [ stop, pass, pass, pass, stop, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass, pass ] ]; window.terrainData = terrainData; }());
首先加入敵人的精靈圖片
而後加入敵人幀動畫數據類
(function () { var getEnemyFrames = (function () { //一個動做在圖片中的寬度 var width = bomberConfig.enemy.WIDTH, //一個動做在圖片中的高度 height = bomberConfig.enemy.HEIGHT, //一個動做的偏移量 offset = { x: bomberConfig.enemy.offset.X, y: bomberConfig.enemy.offset.Y }, //一個動做橫向截取的長度 sw = bomberConfig.enemy.SW, //一個動做縱向截取的長度 sh = bomberConfig.enemy.SH, //一個動做圖片在canvas中的寬度 imgWidth = bomberConfig.enemy.IMGWIDTH, //一個動做圖片在canvas中的高度 imgHeight = bomberConfig.enemy.IMGHEIGHT; //幀數據 var frames = function () { return { //向右站立 stand_right: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y + 2 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向左站立 stand_left: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向上站立 stand_up: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y + 3 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向下站立 stand_down: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向上走 walk_up: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y + 3 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + width, y: offset.y + 3 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 2 * width, y: offset.y + 3 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 3 * width, y: offset.y + 3 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向下走 walk_down: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + width, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 2 * width, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 3 * width, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向右走 walk_right: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y + 2 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + width, y: offset.y + 2 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 2 * width, y: offset.y + 2 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 3 * width, y: offset.y + 2 * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] }, //向左走 walk_left: { img: window.imgLoader.get("enemy"), frames: [ { x: offset.x, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + width, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 2 * width, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 }, { x: offset.x + 3 * width, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: 100 } ] } } } return function (animName) { return frames()[animName]; }; }()); window.getEnemyFrames = getEnemyFrames; }());
而後加入敵人精靈類數據
(function () { var getSpriteData = (function () { var data = function(){ return { ... //敵人精靈類 enemy: { //初始座標 x: bomberConfig.WIDTH * 10, //x: 0, y: bomberConfig.HEIGHT * 3, //定義sprite走路速度的絕對值 walkSpeed: bomberConfig.enemy.speed.NORMAL, //速度 speedX: 1, speedY: 1, //注意座標起始點爲圖片左上點! minX: 0, maxX: bomberConfig.canvas.WIDTH - bomberConfig.player.IMGWIDTH, //maxX: 500, minY: 0, maxY: bomberConfig.canvas.HEIGHT - bomberConfig.player.IMGHEIGHT, defaultAnimId: "stand_left", anims: { "stand_right": new Animation(getEnemyFrames("stand_right")), "stand_left": new Animation(getEnemyFrames("stand_left")), "stand_down": new Animation(getEnemyFrames("stand_down")), "stand_up": new Animation(getEnemyFrames("stand_up")), "walk_up": new Animation(getEnemyFrames("walk_up")), "walk_down": new Animation(getEnemyFrames("walk_down")), "walk_right": new Animation(getEnemyFrames("walk_right")), "walk_left": new Animation(getEnemyFrames("walk_left")) } } } }; return function (spriteName) { return data()[spriteName]; }; }()); window.getSpriteData = getSpriteData; }());
增長敵人精靈類。
建立返回假數據的FindPath類,用於測試EnemySprite類。
FindPath
(function () { //構造假數據 var findPath = { aCompute: function (terrainData, begin, target) { return { path: [{ x: 8, y: 0 }, { x: 7, y: 0 }, { x: 6, y: 0 }, { x: 5, y: 0 }, { x: 4, y: 0 }], time: 0.1 }; } }; window.findPath = findPath; }());
EnemySprite
(function () { var EnemySprite = YYC.Class({ Init: function (data) { //初始座標 this.x = data.x; this.y = data.y; this.speedX = data.speedX; this.speedY = data.speedY; //x/y座標的最大值和最小值, 可用來限定移動範圍. this.minX = data.minX; this.maxX = data.maxX; this.minY = data.minY; this.maxY = data.maxY; this.defaultAnimId = data.defaultAnimId; this.anims = data.anims; this.walkSpeed = data.walkSpeed; this.speedX = data.walkSpeed; this.speedY = data.walkSpeed; this._context = new Context(this); }, Private: { //狀態模式上下文類 __context: null, //更新幀動畫 _updateFrame: function (deltaTime) { if (this.currentAnim) { this.currentAnim.update(deltaTime); } }, _computeCoordinate: function () { this.x = this.x + this.speedX * this.dirX; this.y = this.y + this.speedY * this.dirY; //由於移動次數是向上取整,可能會形成移動次數偏多(如stepX爲2.5,取整則stepX爲3), //座標可能會偏大(大於bomberConfig.WIDTH / bomberConfig.HEIGHT的整數倍), //所以此處須要向下取整。 //x、y爲bomberConfig.WIDTH/bomberConfig.HEIGHT的整數倍(向下取整) if (this.completeOneMove) { this.x -= this.x % bomberConfig.WIDTH; this.y -= this.y % bomberConfig.HEIGHT; } }, __getCurrentState: function () { var currentState = null; switch (this.defaultAnimId) { case "stand_right": currentState = new StandRightState(); break; case "stand_left": currentState = new StandLeftState; break; case "stand_down": currentState = new StandDownState; break; case "stand_up": currentState = new StandUpState; break; case "walk_down": currentState = new WalkDownState; break; case "walk_up": currentState = new WalkUpState; break; case "walk_right": currentState = new WalkRightState; break; case "walk_left": currentState = new WalkLeftState; break; default: throw new Error("未知的狀態"); break; } ; return currentState; }, //計算移動次數 __computeStep: function () { this.stepX = Math.ceil(bomberConfig.WIDTH / this.speedX); this.stepY = Math.ceil(bomberConfig.HEIGHT / this.speedY); } }, Public: { //精靈的座標 x: 0, y: 0, //精靈的速度 speedX: 0, speedY: 0, //精靈的座標區間 minX: 0, maxX: 9999, minY: 0, maxY: 9999, //精靈包含的全部 Animation 集合. Object類型, 數據存放方式爲" id : animation ". anims: null, //默認的Animation的Id , string類型 defaultAnimId: null, //當前的Animation. currentAnim: null, //精靈的方向係數: //往下走dirY爲正數,往上走dirY爲負數; //往右走dirX爲正數,往左走dirX爲負數。 dirX: 0, dirY: 0, //定義sprite走路速度的絕對值 walkSpeed: 0, //一次移動步長中的須要移動的次數 stepX: 0, stepY: 0, //一次移動步長中已經移動的次數 moveIndex_x: 0, moveIndex_y: 0, //是否正在移動標誌 moving: false, //站立標誌 //用於解決調用WalkState.stand後,PlayerLayer.render中P__isChange返回false的問題 //(不調用draw,從而仍會顯示精靈類walk的幀(而不會刷新爲更新狀態後的精靈類stand的幀))。 stand: false, //尋找的路徑 path: null, //設置當前Animation, 參數爲Animation的id, String類型 setAnim: function (animId) { this.currentAnim = this.anims[animId]; }, init: function () { this.__context.setPlayerState(this.__getCurrentState()); this.__computeStep(); this.path = window.findPath.aCompute().path; //設置當前Animation this.setAnim(this.defaultAnimId); }, // 更新精靈當前狀態 update: function (deltaTime) { this._updateFrame(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, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT); } }, move: function () { this.__context.move(); }, setDir: function () { if (this.moving) { return; } //返回並移除要移動到的座標 var target = this.path.shift(); //當前座標 var now = { x: self.x / bomberConfig.WIDTH, y: self.y / bomberConfig.HEIGHT }; //判斷要移動的方向,調用相應的方法 if (target.x > now.x) { this.__context.walkRight(); } else if (target.x < now.x) { this.__context.walkLeft(); } else if (target.y > now.y) { this.__context.walkDown(); } else if (target.y < now.y) { this.__context.walkUp(); } else { this.__context.stand(); } } } }); window.EnemySprite = EnemySprite; }());
SpriteFactory新增工廠方法createEnemy,用於建立EnemySprite實例。
SpriteFactory
createEnemy: function () { return new EnemySprite(getSpriteData("enemy")); }
增長敵人畫布,該畫布於地圖畫布之上,與玩家畫布的zIndex相同。同時增長對應的敵人層類EnemyLayer,它的集合元素爲EnemySprite類的實例。
EnemyLayer
(function () { var EnemyLayer = YYC.Class(Layer, { Init: function (deltaTime) { this.___deltaTime = deltaTime; }, Private: { ___deltaTime: 0, ___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(); }, ___update: function (deltaTime) { this.___iterator("update", deltaTime); }, __setDir: function () { this.___iterator("setDir"); }, ___move: function (deltaTime) { this.___iterator("move", deltaTime); } }, Public: { setCanvas: function () { this.P__canvas = document.getElementById("enemyLayerCanvas"); $("#enemyLayerCanvas").css({ "position": "absolute", "top": bomberConfig.canvas.TOP, "left": bomberConfig.canvas.LEFT, "border": "1px solid black", "z-index": 1 //z-index與playerLayer相同 }); }, draw: function (context) { this.___iterator("draw", context); }, clear: function (context) { this.___iterator("clear", context); }, render: function () { this.__setDir(); this.___move(this.___deltaTime); //判斷__state是否爲change狀態,若是是則調用canvas繪製精靈。 if (this.P__isChange()) { this.clear(this.P__context); this.___update(this.___deltaTime); this.draw(this.P__context); this.P__setStateNormal(); } } } }); window.EnemyLayer = EnemyLayer; }());
LayerFactory新增工廠方法createEnemy,用於建立EnemyLayer實例
LayerFactory
createEnemy: function (deltaTime) { return new EnemyLayer(deltaTime); }
增長EnemyLayer的管理類EnemyLayerManager
var EnemyLayerManager = YYC.Class(LayerManager, { Init: function (layer) { this.base(layer); }, Public: { createElement: function () { var element = [], enemy = spriteFactory.createEnemy(); enemy.init(); element.push(enemy); return element; }, change: function () { this.layer.change(); } } });
實現尋路算法
遊戲中的敵人採用A*算法尋路。參考資料:A星算法
遊戲開始時,敵人以它的當前位置爲起始點,炸彈人的位置爲終點尋路。若是敵人到達終點後,沒有碰撞到炸彈人,則再一次以它的當前位置爲起始點,炸彈人的位置爲終點尋路。
FindPath
(function () { var map_w, beginx, beginy, endx, endy; var arr_path_out = new Array(); var pass = bomberConfig.map.terrain.pass, stop = bomberConfig.map.terrain.stop; var arr_map = new Array(); var open_list = new Array(); //建立OpenList var close_list = new Array(); //建立CloseList var tmp = new Array(); //存放當前節點的八個方向的節點 var arr_map_tmp = window.mapData; //存儲從遊戲中讀入的地圖數據 var map_w = arr_map_tmp.length; function aCompute(mapData, begin, end) { //計算運行時間 var startTime, endTime; var d = new Date(); var time; startTime = d.getTime(); var arr_path = new Array(); var stopn = 0; /********************函數主體部分*************************/ arr_map = setMap(mapData); map_w = mapData.length; beginx = begin.x; beginy = map_w - 1 - begin.y; endx = end.x; endy = map_w - 1 - end.y; var startNodeNum = tile_num(beginx, beginy); var targetNodeNum = tile_num(endx, endy); if (arr_map[targetNodeNum] && arr_map[targetNodeNum][0] == 0) { showError("目的地沒法到達!"); time = getTime(startTime); return { path: [], time: time }; } if (arr_map[startNodeNum][0] == 0) { showError("起始點不可用!"); time = getTime(startTime); return { path: [], time: time }; } if (arr_map[targetNodeNum] && arr_map[targetNodeNum][0] * arr_map[startNodeNum][0] == 1) { arr_map[startNodeNum] = [arr_map[startNodeNum][0], startNodeNum, arr_map[startNodeNum][2], arr_map[startNodeNum][3], arr_map[startNodeNum][4]];//起始點的父節點爲本身 setH(targetNodeNum); setOpenList(startNodeNum); //把開始節點加入到openlist中 //就要開始那個使人髮指的循環了,==!!A*算法主體 while (open_list.length != 0) { var bestNodeNum = selectFmin(open_list); stopn = 0; open_list.shift(); setCloseList(bestNodeNum); if (bestNodeNum == targetNodeNum) { showPath(close_list, arr_path); break; } var i = 0, j = 0; //當目標爲孤島時的判斷 var tmp0 = new Array(); var k; tmp0 = setSuccessorNode(targetNodeNum, map_w); for (j; j < 9; j++) { if (j == 8) { k = 0; break; } if (tmp0[j][0] == 1) { k = 1; break; } } //當目標爲孤島時的判斷語句結束 if (k == 0) { showError("目標成孤島!"); time = getTime(startTime); return { path: [], time: time }; } else { tmp = setSuccessorNode(bestNodeNum, map_w); for (i; i < 8; i++) { if ((tmp[i][0] == 0) || (findInCloseList(tmp[i][4]))) continue; if (findInOpenList(tmp[i][4]) == 1) { if (tmp[i][2] >= (arr_map[bestNodeNum][2] + cost(tmp[i], bestNodeNum))) { setG(tmp[i][4], bestNodeNum); //算g值,修改arr_map中[2]的值 arr_map[tmp[i][4]] = tmp[i] = [arr_map[tmp[i][4]][0], bestNodeNum, arr_map[tmp[i][4]][2], arr_map[tmp[i][4]][3], arr_map[tmp[i][4]][4]]; //修改tmp和arr_map中父節點的值,並修改tmp中g值,是之和arr_map中對應節點的值統一 } } if (findInOpenList(tmp[i][4]) == 0) { setG(tmp[i][4], bestNodeNum); //算g值,修改arr_map中[2]的值 arr_map[tmp[i][4]] = tmp[i] = [arr_map[tmp[i][4]][0], bestNodeNum, arr_map[tmp[i][4]][2], arr_map[tmp[i][4]][3], arr_map[tmp[i][4]][4]]; //修改tmp和arr_map中父節點的值,並修改tmp中g值,是之和arr_map中對應節點的值統一 setOpenList(tmp[i][4]); //存進openlist中 } } } stopn++; //if (stopn == map_w * map_w - 1) { //2013.5.27修改 if (stopn == map_w * map_w * 1000) { showError("找不到路徑!"); time = getTime(startTime); return { path: [], time: time }; // break; } } if (open_list.length == 0 && bestNodeNum != targetNodeNum) { showError("沒有找到路徑!!"); //對於那種找不到路徑的點的處理 time = getTime(startTime); return { path: [], time: time }; } } time = getTime(startTime); return { path: arr_path_out, time: time }; } function getTime(startTime) { /***顯示運行時間********/ var endTime = new Date().getTime(); return (endTime - startTime) / 1000; }; function showError(error) { console.log(error); }; /********************************************************************** *function setMap(n) *功能:把外部的地圖數據抽象成該算法中可操做數組的形式來輸入算法 *參數:n爲地圖的寬度,生成方陣地圖 ************************************************************************/ function setMap(mapData) { map_w = mapData.length; var m = map_w * map_w; var arr_map0 = new Array(); //該函數對地圖數據轉換的操做數組 var a = m - 1; for (a; a >= 0; a--) { var xTmp = tile_x(a); //把ID 編號值轉換爲x座標值,用來對應讀入地圖數據 var yTmp = map_w - 1 - tile_y(a); //把ID 編號值轉換爲y座標值,用來對應讀入地圖數據,對應數組標號和我自定義xoy座標位置 //[cost,parent,g,h,id] if (mapData[yTmp][xTmp] == pass) arr_map0[a] = [1, 0, 0, 0, a]; else arr_map0[a] = [0, 0, 0, 0, a]; } return arr_map0; } /********************************************************************* *如下三個函數是地圖上的編號與數組索引轉換 *function tile_num(x,y) *功能:將 x,y 座標轉換爲地圖上塊的編號 *function tile_x(n) *功能:由塊編號得出 x 座標 *function tile_y(n) *功能:由塊編號得出 y 座標 ******************************************************************/ function tile_num(x, y) { return ((y) * map_w + (x)); } function tile_x(n) { return (parseInt((n) % map_w)); } function tile_y(n) { return (parseInt((n) / map_w)); } /********************************************************************* *function setH(targetNode) *功能:初始化全部點H的值 *參數:targetNode目標節點 **********************************************************************/ function setH(targetNode) { var x0 = tile_x(targetNode); var y0 = tile_y(targetNode); var i = 0; for (i; i < arr_map.length; i++) { var x1 = tile_x(i); var y1 = tile_y(i); /*****歐幾里德距離********************************/ // var h = (Math.sqrt((parseInt(x0) - parseInt(x1)) * (parseInt(x0) - parseInt(x1))) + Math.sqrt((parseInt(y0) - parseInt(y1)) * (parseInt(y0) - parseInt(y1)))); /*****對角線距離********************************/ var h = Math.max(Math.abs(parseInt(x0) - parseInt(x1)), Math.abs(parseInt(y0) - parseInt(y1))); /*****曼哈頓距離********************************/ // var h=Math.abs(parseInt(x0) - parseInt(x1))+Math.abs(parseInt(y0) - parseInt(y1)); arr_map[i][3] = h * parseInt(10); } } /********************************************************************* *function setG(nowNode,bestNode) *功能:計算現節點G的值 *參數:nowNode現節點,bestNode其父節點 **********************************************************************/ function setG(nowNode, bestNode) { var x0 = tile_x(bestNode); var y0 = tile_y(bestNode); var x1 = tile_x(nowNode); var y1 = tile_y(nowNode); if (((x0 - x1) == 0) || ((y0 - y1) == 0)) { arr_map[nowNode] = [arr_map[nowNode][0], arr_map[nowNode][1], arr_map[nowNode][2] + parseInt(10), arr_map[nowNode][3], arr_map[nowNode][4]]; } else { arr_map[nowNode] = [arr_map[nowNode][0], arr_map[nowNode][1], arr_map[nowNode][2] + parseInt(14), arr_map[nowNode][3], arr_map[nowNode][4]]; } } /********************************************************************* *function selectFmin(open_list) *功能:在openlist中對f值進行排序(冒泡排序),並選擇一個f值最小的節點返回 *參數:openlist ***********************************************************************/ function selectFmin(open_list) { var i, j, min, temp; for (i = 0; i < open_list.length; i++) { for (j = i + 1; j < open_list.length; j++) { if ((open_list[i][2] + open_list[i][3]) > (open_list[j][2] + open_list[j][3])) { temp = open_list[i]; open_list[i] = open_list[j]; open_list[j] = temp; } } } var min = open_list[0]; return min[4]; } /*********************************************************************** *function setOpenList(NodeNum) *功能:把節點加入open表中 *參數:待加入openlist的節點的編號 ************************************************************************/ function setOpenList(NodeNum) { var n = open_list.length; open_list[n] = [arr_map[NodeNum][0], arr_map[NodeNum][1], arr_map[NodeNum][2], arr_map[NodeNum][3], arr_map[NodeNum][4]]; } /*********************************************************************** *function setCloseList(NodeNum) *功能:把節點加入close表中 *參數:待加入closelist的節點的編號 ************************************************************************/ function setCloseList(NodeNum) { var n = close_list.length; close_list[n] = [arr_map[NodeNum][0], arr_map[NodeNum][1], arr_map[NodeNum][2], arr_map[NodeNum][3], arr_map[NodeNum][4]]; } /*********************************************************************** *function findInOpenList(nowNodeNum) *功能:查詢當前節點是否在openlist中,找到返回1,找不到返回0 *參數:待查詢的節點的編號 ************************************************************************/ function findInOpenList(nowNodeNum) { var i; for (i = 0; i < open_list.length; i++) { if (open_list[i][4] == nowNodeNum) return 1; } return 0; } /*********************************************************************** *function findInCloseList(nowNodeNum) *功能:查詢當前節點是否在closelist中,找到返回1,找不到返回0 *參數:待查詢的節點的編號 ************************************************************************/ function findInCloseList(nowNodeNum) { var i; for (i = 0; i < close_list.length; i++) { if (close_list[i][4] == nowNodeNum) return 1; else return 0; } } /*********************************************************************** *function cost(SuccessorNodeNum,bestNodeNum) *功能:現節點到達周圍節點的代價 *參數:SuccessorNodeNum周圍節點編號,bestNodeNum現節點 ************************************************************************/ function cost(SuccessorNodeNum, bestNodeNum) { var x0 = tile_x(bestNodeNum); var y0 = tile_y(bestNodeNum); var x1 = tile_x(SuccessorNodeNum); var y1 = tile_y(SuccessorNodeNum); if (((x0 - x1) == 0) || ((y0 - y1) == 0)) { return 10; } else return 14; } /********************************************************************** *function setSuccessorNode(bestNodeNum,map_w) *功能:把現節點的周圍8個節點放入預先準備好的臨時arr中以備檢察 *參數:現節點的編號 035 1 6 247 周圍八個點的排序 ***********************************************************************/ function setSuccessorNode(bestNodeNum, n) { var x0 = tile_x(bestNodeNum); var y0 = tile_y(bestNodeNum); var m = n - 1; if ((x0 - 1) >= 0 && (y0) >= 0 && (x0 - 1) <= m && (y0) <= m) tmp[1] = [arr_map[tile_num(x0 - 1, y0)][0], arr_map[tile_num(x0 - 1, y0)][1], arr_map[tile_num(x0 - 1, y0)][2], arr_map[tile_num(x0 - 1, y0)][3], arr_map[tile_num(x0 - 1, y0)][4]]; else { tmp[1] = [0, 0, 0, 0, 0]; } if ((x0) >= 0 && (y0 + 1) >= 0 && (x0) <= m && (y0 + 1) <= m) tmp[3] = [arr_map[tile_num(x0, y0 + 1)][0], arr_map[tile_num(x0, y0 + 1)][1], arr_map[tile_num(x0, y0 + 1)][2], arr_map[tile_num(x0, y0 + 1)][3], arr_map[tile_num(x0, y0 + 1)][4]]; else { tmp[3] = [0, 0, 0, 0, 0]; } if ((x0) >= 0 && (y0 - 1) >= 0 && (x0) <= m && (y0 - 1) <= m) tmp[4] = [arr_map[tile_num(x0, y0 - 1)][0], arr_map[tile_num(x0, y0 - 1)][1], arr_map[tile_num(x0, y0 - 1)][2], arr_map[tile_num(x0, y0 - 1)][3], arr_map[tile_num(x0, y0 - 1)][4]]; else { tmp[4] = [0, 0, 0, 0, 0]; } if ((x0 + 1) >= 0 && (y0) >= 0 && (x0 + 1) <= m && (y0) <= m) tmp[6] = [arr_map[tile_num(x0 + 1, y0)][0], arr_map[tile_num(x0 + 1, y0)][1], arr_map[tile_num(x0 + 1, y0)][2], arr_map[tile_num(x0 + 1, y0)][3], arr_map[tile_num(x0 + 1, y0)][4]]; else { tmp[6] = [0, 0, 0, 0, 0]; } if (bomberConfig.algorithm.DIRECTION == 8) { if ((x0 - 1) >= 0 && (y0 + 1) >= 0 && (x0 - 1) <= m && (y0 + 1) <= m) tmp[0] = [arr_map[tile_num(x0 - 1, y0 + 1)][0], arr_map[tile_num(x0 - 1, y0 + 1)][1], arr_map[tile_num(x0 - 1, y0 + 1)][2], arr_map[tile_num(x0 - 1, y0 + 1)][3], arr_map[tile_num(x0 - 1, y0 + 1)][4]]; else { tmp[0] = [0, 0, 0, 0, 0]; } if ((x0 - 1) >= 0 && (y0 - 1) >= 0 && (x0 - 1) <= m && (y0 - 1) <= m) tmp[2] = [arr_map[tile_num(x0 - 1, y0 - 1)][0], arr_map[tile_num(x0 - 1, y0 - 1)][1], arr_map[tile_num(x0 - 1, y0 - 1)][2], arr_map[tile_num(x0 - 1, y0 - 1)][3], arr_map[tile_num(x0 - 1, y0 - 1)][4]]; else { tmp[2] = [0, 0, 0, 0, 0]; } if ((x0 + 1) >= 0 && (y0 + 1) >= 0 && (x0 + 1) <= m && (y0 + 1) <= m) tmp[5] = [arr_map[tile_num(x0 + 1, y0 + 1)][0], arr_map[tile_num(x0 + 1, y0 + 1)][1], arr_map[tile_num(x0 + 1, y0 + 1)][2], arr_map[tile_num(x0 + 1, y0 + 1)][3], arr_map[tile_num(x0 + 1, y0 + 1)][4]]; else { tmp[5] = [0, 0, 0, 0, 0]; } if ((x0 + 1) >= 0 && (y0 - 1) >= 0 && (x0 + 1) <= m && (y0 - 1) <= m) tmp[7] = [arr_map[tile_num(x0 + 1, y0 - 1)][0], arr_map[tile_num(x0 + 1, y0 - 1)][1], arr_map[tile_num(x0 + 1, y0 - 1)][2], arr_map[tile_num(x0 + 1, y0 - 1)][3], arr_map[tile_num(x0 + 1, y0 - 1)][4]]; else { tmp[7] = [0, 0, 0, 0, 0]; } } if (bomberConfig.algorithm.DIRECTION == 4) { tmp[0] = [0, 0, 0, 0, 0]; tmp[2] = [0, 0, 0, 0, 0]; tmp[5] = [0, 0, 0, 0, 0]; tmp[7] = [0, 0, 0, 0, 0]; } return tmp; } /******************************************************************* *function showPath(close_list) *功能:把結果路徑存入arr_path輸出 *參數:close_list ********************************************************************/ function showPath(close_list, arr_path) { var n = close_list.length; var i = n - 1; var ii = 0; var nn = 0; var mm = 0; var arr_path_tmp = new Array(); var target = null; /**********把close_list中有用的點存入arr_path_tmp中*************/ for (ii; ; ii++) { arr_path_tmp[ii] = close_list[n - 1][4]; if (close_list[n - 1][1] == close_list[i][4]) { break; } for (i = n - 1; i >= 0; i--) { if (close_list[i][4] == close_list[n - 1][1]) { n = i + 1; break; } } } var w = arr_path_tmp.length - 1; var j = 0; for (var i = w; i >= 0; i--) { arr_path[j] = arr_path_tmp[i]; j++; } for (var k = 0; k <= w; k++) { target = { x: tile_x(arr_path[k]), y: map_w - 1 - tile_y(arr_path[k]) }; arr_path_out.push(target); } arr_path_out.shift(); } function _reset() { arr_path_out = new Array(); arr_map = new Array(); arr_map_tmp = window.mapData; map_w = arr_map_tmp.length; open_list = new Array(); //建立OpenList close_list = new Array(); //建立CloseList tmp = new Array(); //存放當前節點的八個方向的節點 }; var findPath = { aCompute: function (terrainData, begin, end) { _reset(); return aCompute(terrainData, begin, end); } }; window.findPath = findPath; }());
重構
重構前:
(function () { var Context = YYC.Class({ Init: function (sprite) { this.sprite = sprite; }, ... Static: { walkLeftState: new WalkLeftState(), walkRightState: new WalkRightState(), walkUpState: new WalkUpState(), walkDownState: new WalkDownState(), standLeftState: new StandLeftState(), standRightState: new StandRightState(), standUpState: new StandUpState(), standDownState: new StandDownState() } }); window.Context = Context; }());
緣由:
由於EnemySprite和PlayerSprite都要使用Context的實例。若是爲靜態實例的話,EnemySprite中的Context類實例與PlayerSprite中的Context類實例會共享靜態實例(具體狀態類實例)!會形成互相干擾!
重構後:
(function () { var Context = YYC.Class({ Init: function (sprite) { this.sprite = sprite; this.walkLeftState = new WalkLeftState(); this.walkRightState = new WalkRightState(); this.walkUpState = new WalkUpState(); this.walkDownState = new WalkDownState(); this.standLeftState = new StandLeftState(); this.standRightState = new StandRightState(); this.standUpState = new StandUpState(); this.standDownState = new StandDownState(); }, ... Static: { } }); window.Context = Context; }());
所以,提出EnemySprite、PlayerSprite基類Sprite。
Sprite 增長getCollideRect得到碰撞面積。EnemySprite增長collideWidthOther。
Sprite
(function () { var Sprite = YYC.AClass({ Init: function (data) { this.x = data.x; this.y = data.y; this.speedX = data.speedX; this.speedY = data.speedY; this.minX = data.minX; this.maxX = data.maxX; this.minY = data.minY; this.maxY = data.maxY; this.defaultAnimId = data.defaultAnimId; this.anims = data.anims; }, Private: { //更新幀動畫 _updateFrame: function (deltaTime) { if (this.currentAnim) { this.currentAnim.update(deltaTime); } } }, Public: { //在一個移動步長中已經移動的次數 moveIndex: 0, //精靈的座標 x: 0, y: 0, //精靈的速度 speedX: 0, speedY: 0, //精靈的座標區間 minX: 0, maxX: 9999, minY: 0, maxY: 9999, //精靈包含的全部 Animation 集合. Object類型, 數據存放方式爲" id : animation ". anims: null, //默認的Animation的Id , string類型 defaultAnimId: null, //當前的Animation. currentAnim: null, //設置當前Animation, 參數爲Animation的id, String類型 setAnim: function (animId) { this.currentAnim = this.anims[animId]; }, //重置當前幀 resetCurrentFrame: function (index) { this.currentAnim && this.currentAnim.setCurrentFrame(index); }, //取得精靈的碰撞區域, getCollideRect: function () { if (this.currentAnim) { var f = this.currentAnim.getCurrentFrame(); return { x1: this.x, y1: this.y, x2: this.x + f.imgWidth, y2: this.y + f.imgHeight } } }, Virtual: { //初始化方法 init: function () { this.setAnim(this.defaultAnimId); }, // 更新精靈當前狀態. update: function (deltaTime) { this._updateFrame(deltaTime); } } }, Abstract: { draw: function (context) { }, clear: function (context) { }, move: function () { }, setDir: function () { } } }); window.Sprite = Sprite; }());
EnemySprite
(function () { var EnemySprite = YYC.Class(Sprite, { Init: function (data) { this.base(data); this.walkSpeed = data.walkSpeed; this.speedX = data.walkSpeed; this.speedY = data.walkSpeed; this.__context = new Context(this); }, Private: { //狀態模式上下文類 __context: null, __getCurrentState: function () { var currentState = null; switch (this.defaultAnimId) { case "stand_right": currentState = new StandRightState(); break; case "stand_left": currentState = new StandLeftState; break; case "stand_down": currentState = new StandDownState; break; case "stand_up": currentState = new StandUpState; break; case "walk_down": currentState = new WalkDownState; break; case "walk_up": currentState = new WalkUpState; break; case "walk_right": currentState = new WalkRightState; break; case "walk_left": currentState = new WalkLeftState; break; default: throw new Error("未知的狀態"); break; } return currentState; }, //計算移動次數 __computeStep: function () { this.stepX = Math.ceil(bomberConfig.WIDTH / this.speedX); this.stepY = Math.ceil(bomberConfig.HEIGHT / this.speedY); } }, Public: { //精靈的方向係數: //往下走dirY爲正數,往上走dirY爲負數; //往右走dirX爲正數,往左走dirX爲負數。 dirX: 0, dirY: 0, //定義sprite走路速度的絕對值 walkSpeed: 0, //一次移動步長中的須要移動的次數 stepX: 0, stepY: 0, //一次移動步長中已經移動的次數 moveIndex_x: 0, moveIndex_y: 0, //是否正在移動標誌 moving: false, //站立標誌 //用於解決調用WalkState.stand後,PlayerLayer.render中P__isChange返回false的問題 //(不調用draw,從而仍會顯示精靈類walk的幀(而不會刷新爲更新狀態後的精靈類stand的幀))。 stand: false, //尋找的路徑 path: [], playerSprite: null, init: function () { this.__context.setPlayerState(this.__getCurrentState()); this.__computeStep(); this.base(); }, 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, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT); } }, //判斷是否和另一個精靈碰撞 collideWidthOther: function (sprite2) { var rect1 = this.getCollideRect(); var rect2 = sprite2.getCollideRect(); //若是碰撞,則拋出異常 if (rect1 && rect2 && !(rect1.x1 >= rect2.x2 || rect1.y1 >= rect2.y2 || rect1.x2 <= rect2.x1 || rect1.y2 <= rect2.y1)) { throw new Error(); } }, setPlayerSprite: function (sprite) { this.playerSprite = sprite; }, __computePath: function () { //playerSprite的座標要向下取整 var x = (this.playerSprite.x - this.playerSprite.x % window.bomberConfig.WIDTH) / window.bomberConfig.WIDTH; var y = (this.playerSprite.y - this.playerSprite.y % window.bomberConfig.HEIGHT) / window.bomberConfig.HEIGHT; return window.findPath.aCompute(window.terrainData, { x: this.x / window.bomberConfig.WIDTH, y: this.y / window.bomberConfig.HEIGHT }, { x: x, y: y }).path }, move: function () { this.__context.move(); }, setDir: function () { var target, now; if (this.moving) { return; } //特殊狀況,如尋找不到路徑 if (this.path === false) { return; } if (this.path.length == 0) { this.path = this.__computePath(); } //返回並移除要移動到的座標 target = this.path.shift(); //當前座標 now = { x: self.x / bomberConfig.WIDTH, y: self.y / bomberConfig.HEIGHT }; //判斷要移動的方向,調用相應的方法 if (target.x > now.x) { this.__context.walkRight(); } else if (target.x < now.x) { this.__context.walkLeft(); } else if (target.y > now.y) { this.__context.walkDown(); } else if (target.y < now.y) { this.__context.walkUp(); } else { this.__context.stand(); } } } }); window.EnemySprite = EnemySprite; }());
PlayerSprite
(function () { var PlayerSprite = YYC.Class(Sprite, { Init: function (data) { this.base(data); this.walkSpeed = data.walkSpeed; this.speedX = data.walkSpeed; this.speedY = data.walkSpeed; this.__context = new Context(this); }, Private: { //狀態模式上下文類 __context: null, __getCurrentState: function () { var currentState = null; switch (this.defaultAnimId) { case "stand_right": currentState = new StandRightState(); break; case "stand_left": currentState = new StandLeftState; break; case "stand_down": currentState = new StandDownState; break; case "stand_up": currentState = new StandUpState; break; case "walk_down": currentState = new WalkDownState; break; case "walk_up": currentState = new WalkUpState; break; case "walk_right": currentState = new WalkRightState; break; case "walk_left": currentState = new WalkLeftState; break; default: throw new Error("未知的狀態"); break; } return currentState; }, //計算移動次數 __computeStep: function () { this.stepX = Math.ceil(bomberConfig.WIDTH / this.speedX); this.stepY = Math.ceil(bomberConfig.HEIGHT / this.speedY); }, __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.__context.walkLeft(); } else if (window.keyState[keyCodeMap.D] === true) { this.__context.walkRight(); } else if (window.keyState[keyCodeMap.W] === true) { this.__context.walkUp(); } else if (window.keyState[keyCodeMap.S] === true) { this.__context.walkDown(); } } }, Public: { //精靈的方向係數: //往下走dirY爲正數,往上走dirY爲負數; //往右走dirX爲正數,往左走dirX爲負數。 dirX: 0, dirY: 0, //定義sprite走路速度的絕對值 walkSpeed: 0, //一次移動步長中的須要移動的次數 stepX: 0, stepY: 0, //一次移動步長中已經移動的次數 moveIndex_x: 0, moveIndex_y: 0, //是否正在移動標誌 moving: false, //站立標誌 //用於解決調用WalkState.stand後,PlayerLayer.render中P__isChange返回false的問題 //(不調用draw,從而仍會顯示精靈類walk的幀(而不會刷新爲更新狀態後的精靈類stand的幀))。 stand: false, init: function () { this.__context.setPlayerState(this.__getCurrentState()); this.__computeStep(); this.base(); }, 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, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT); } }, move: function () { this.__context.move(); }, setDir: function () { if (this.moving) { return; } if (this.__allKeyUp()) { this.__context.stand(); } else { this.__judgeAndSetDir(); } } } }); window.PlayerSprite = PlayerSprite; }());
所以,提出CharacterLayer父類
Layer
(function () { var Layer = YYC.AClass(Collection, { Init: function () { }, Private: { __state: bomberConfig.layer.state.CHANGE, //默認爲change __getContext: function () { this.P__context = this.P__canvas.getContext("2d"); } }, Protected: { //*共用的變量(可讀、寫) 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(); } }, Public: { addElements: function(elements){ this.appendChilds(elements); }, Virtual: { init: function () { this.__getContext(); }, change: function () { this.__state = bomberConfig.layer.state.CHANGE; } } }, Abstract: { setCanvas: function () { }, clear: function () { }, //統一繪製 draw: function () { }, //遊戲主線程調用的函數 render: function () { } } }); window.Layer = Layer; }());
CharacterLayer
(function () { var CharacterLayer = YYC.AClass(Layer, { Init: function (deltaTime) { this.___deltaTime = deltaTime; }, Private: { ___deltaTime: 0, ___update: function (deltaTime) { this.P__iterator("update", deltaTime); }, ___setDir: function () { this.P__iterator("setDir"); }, ___move: function () { this.P__iterator("move"); } }, Public: { draw: function () { this.P__iterator("draw", this.P__context); }, clear: function () { this.P__iterator("clear", this.P__context); }, render: function () { this.___setDir(); this.___move(); if (this.P__isChange()) { this.clear(); this.___update(this.___deltaTime); this.draw(); this.P__setStateNormal(); } } } }); window.CharacterLayer = CharacterLayer; }());
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: { 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": 1 }); }, change: function () { if (this.___keyDown() || this.___spriteMoving() || this.___spriteStand()) { this.base(); } } } }); window.PlayerLayer = PlayerLayer; }());
EnemyLayer
(function () { var EnemyLayer = YYC.Class(CharacterLayer, { Init: function (deltaTime) { this.base(deltaTime); }, Private: { __getPath: function () { this.P__iterator("getPath"); } }, Public: { playerLayer: null, init: function () { this.base(); }, setCanvas: function () { this.P__canvas = document.getElementById("enemyLayerCanvas"); $("#enemyLayerCanvas").css({ "position": "absolute", "top": bomberConfig.canvas.TOP, "left": bomberConfig.canvas.LEFT, "border": "1px solid black", "z-index": 1 }); }, getPlayer: function (playerLayer) { this.playerLayer = playerLayer; this.P__iterator("setPlayerSprite", playerLayer.getChildAt(0)); }, collideWidthPlayer: function () { try{ this.P__iterator("collideWidthPlayer", this.playerLayer.getChildAt(0)); return false; } catch(e){ return true; } }, render: function () { this.__getPath(); this.base(); } } }); window.EnemyLayer = EnemyLayer; }());
所以,提出父類MoveSprite
由於我提取的語義是「移動的精靈類」,而Sprite的語義是「精靈類」,屬於更抽象的概念
在提取CharacterLayer類時,關注的是PlayerLayer、EnemyLayer中「人物」語義;而在提取MoveSprite類時,關注的是PlayerSprite、EnemySprite中」移動「語義。所以,凡是屬於」人物「這個語義的Layer類,均可以考慮繼承於CharacterLayer;而凡有」移動「這個特色的Sprite類,均可以考慮繼承於MoveSprite。
Sprite
(function () { var Sprite = YYC.AClass({ Init: function (data, bitmap) { this.bitmap = bitmap; this.x = data.x; this.y = data.y; this.defaultAnimId = data.defaultAnimId; this.anims = data.anims; }, Private: { _updateFrame: function (deltaTime) { if (this.currentAnim) { this.currentAnim.update(deltaTime); } } }, Public: { bitmap: null, //精靈的座標 x: 0, y: 0, //精靈包含的全部 Animation 集合. Object類型, 數據存放方式爲" id : animation ". anims: null, //默認的Animation的Id , string類型 defaultAnimId: null, //當前的Animation. currentAnim: null, setAnim: function (animId) { this.currentAnim = this.anims[animId]; }, resetCurrentFrame: function (index) { this.currentAnim && this.currentAnim.setCurrentFrame(index); }, getCollideRect: function () { return { x1: this.x, y1: this.y, x2: this.x + this.bitmap.width, y2: this.y + this.bitmap.height } }, Virtual: { init: function () { //設置當前Animation this.setAnim(this.defaultAnimId); }, // 更新精靈當前狀態. update: function (deltaTime) { this._updateFrame(deltaTime); } } }, Abstract: { draw: function (context) { }, clear: function (context) { } } }); window.Sprite = Sprite; }());
MoveSprite
(function () { var MoveSprite = YYC.AClass(Sprite, { Init: function (data, bitmap) { this.base(data, bitmap); this.minX = data.minX; this.maxX = data.maxX; this.minY = data.minY; this.maxY = data.maxY; this.walkSpeed = data.walkSpeed; this.speedX = data.walkSpeed; this.speedY = data.walkSpeed; }, Protected: { //狀態模式上下文類 P__context: null }, Private: { __getCurrentState: function () { var currentState = null; switch (this.defaultAnimId) { case "stand_right": currentState = new StandRightState(); break; case "stand_left": currentState = new StandLeftState; break; case "stand_down": currentState = new StandDownState; break; case "stand_up": currentState = new StandUpState; break; case "walk_down": currentState = new WalkDownState; break; case "walk_up": currentState = new WalkUpState; break; case "walk_right": currentState = new WalkRightState; break; case "walk_left": currentState = new WalkLeftState; break; default: throw new Error("未知的狀態"); break; }; return currentState; }, //計算移動次數 __computeStep: function () { this.stepX = Math.ceil(bomberConfig.WIDTH / this.speedX); this.stepY = Math.ceil(bomberConfig.HEIGHT / this.speedY); }, __isMoving: function(){ return this.x % bomberConfig.WIDTH !== 0 || this.y % bomberConfig.HEIGHT !== 0 } }, Public: { //精靈的速度 speedX: 0, speedY: 0, //精靈的座標區間 minX: 0, maxX: 9999, minY: 0, maxY: 9999, //精靈的方向係數: //往下走dirY爲正數,往上走dirY爲負數; //往右走dirX爲正數,往左走dirX爲負數。 dirX: 0, dirY: 0, //定義sprite走路速度的絕對值 walkSpeed: 0, //一次移動步長中的須要移動的次數 stepX: 0, stepY: 0, //一次移動步長中已經移動的次數 moveIndex_x: 0, moveIndex_y: 0, //是否正在移動標誌 moving: false, //站立標誌 //用於解決調用WalkState.stand後,PlayerLayer.render中P__isChange返回false的問題 //(不調用draw,從而仍會顯示精靈類walk的幀(而不會刷新爲更新狀態後的精靈類stand的幀))。 stand: false, init: function () { this.P__context.setPlayerState(this.__getCurrentState()); this.__computeStep(); this.base(); }, draw: function (context) { var frame = null; if (this.currentAnim) { frame = this.currentAnim.getCurrentFrame(); context.drawImage(this.bitmap.img, frame.x, frame.y, frame.width, frame.height, this.x, this.y, this.bitmap.width, this.bitmap.height); } }, clear: function (context) { var frame = null; if (this.currentAnim) { frame = this.currentAnim.getCurrentFrame(); //直接清空畫布區域 context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT); } }, //得到當前座標對應的方格座標 getCurrentCellPosition: function () { if (this.__isMoving()) { throw new Error("精靈正在移動且未完成一個移動步長"); } return { x: this.x / bomberConfig.WIDTH, y: this.y / bomberConfig.HEIGHT } } }, Abstract: { move: function () { }, setDir: function () { } } }); window.MoveSprite = MoveSprite; }());
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(); } } }, Public: { move: function () { this.P__context.move(); }, setDir: function () { if (this.moving) { return; } if (this.__allKeyUp()) { this.P__context.stand(); } else { this.__judgeAndSetDir(); } } } }); window.PlayerSprite = PlayerSprite; }());
EnemySprite
(function () { var EnemySprite = YYC.Class(MoveSprite, { Init: function (data, bitmap) { this.base(data, bitmap); this.P__context = new Context(this); }, Private: { ___findPath: function () { return window.findPath.aCompute(window.terrainData, this.___computeCurrentCoordinate(), this.___computePlayerCoordinate()).path }, ___computeCurrentCoordinate: function () { if (this.x % window.bomberConfig.WIDTH || this.y % window.bomberConfig.HEIGHT) { throw new Error("當前座標應該爲方格尺寸的整數倍!"); } return { x: this.x / window.bomberConfig.WIDTH, y: this.y / window.bomberConfig.HEIGHT }; }, ___computePlayerCoordinate: function () { return { x: Math.floor(this.playerSprite.x / window.bomberConfig.WIDTH), y: Math.floor(this.playerSprite.y / window.bomberConfig.HEIGHT) }; }, ___getAndRemoveTarget: function () { return this.path.shift(); }, ___judgeAndSetDir: function (target) { //當前座標 var current = this.___computeCurrentCoordinate(); //判斷要移動的方向,調用相應的方法 if (target.x > current.x) { this.P__context.walkRight(); } else if (target.x < current.x) { this.P__context.walkLeft(); } else if (target.y > current.y) { this.P__context.walkDown(); } else if (target.y < current.y) { this.P__context.walkUp(); } else { this.P__context.stand(); } } }, Public: { //尋找的路徑 path: [], playerSprite: null, collideWidthPlayer : function(sprite2){ var rect1=this.getCollideRect(); var rect2=sprite2.getCollideRect(); //若是碰撞,則拋出異常 if (rect1 && rect2 && !(rect1.x1 >= rect2.x2 || rect1.y1 >= rect2.y2 || rect1.x2 <= rect2.x1 || rect1.y2 <= rect2.y1)) { throw new Error(); } }, setPlayerSprite: function (sprite) { this.playerSprite = sprite; }, move: function () { this.P__context.move(); }, setDir: function () { //若是正在移動或者找不到路徑,則返回 if (this.moving || this.path === false) { return; } this.___judgeAndSetDir(this.___getAndRemoveTarget()); }, getPath: function () { if (this.moving) { return; } if (this.path.length == 0) { this.path = this.___findPath(); } } } }); window.EnemySprite = EnemySprite; }());
反思Sprite類,發如今getCollideRect方法中,使用了圖片的寬度和高度:
getCollideRect: function () { if (this.currentAnim) { var f = this.currentAnim.getCurrentFrame(); return { x1: this.x, y1: this.y, x2: this.x + f.imgWidth, y2: this.y + f.imgHeight } } },
此處圖片的寬度和高度是從FrameData中讀取的:
var getFrames = (function () { ... imgWidth = bomberConfig.player.IMGWIDTH, imgHeight = bomberConfig.player.IMGHEIGHT; ...
圖片的寬度和高度屬於圖片信息,應該都放到Bitmap類中!
在建立精靈實例時,將圖片的寬度和高度包裝到Bitmap中,並注入到精靈類中:
SpriteFactory
(function () { var spriteFactory = { createPlayer: function () { return new PlayerSprite(getSpriteData("player"), bitmapFactory.createBitmap({ img: window.imgLoader.get("player"), width: bomberConfig.player.IMGWIDTH, height: bomberConfig.player.IMGHEIGHT })); }, createEnemy: function () { return new EnemySprite(getSpriteData("enemy"), bitmapFactory.createBitmap({ img: window.imgLoader.get("enemy"), width: bomberConfig.player.IMGWIDTH, height: bomberConfig.player.IMGHEIGHT })); } } window.spriteFactory = spriteFactory; }());
而後在getCollideRect方法中改成讀取Bitmap實例引用的寬度和高度:
Sprite
Init: function (data, bitmap) { this.bitmap = bitmap; ... }, ... bitmap: null, ... getCollideRect: function () { return { x1: this.x, y1: this.y, x2: this.x + this.bitmap.width, y2: this.y + this.bitmap.height } },
如今FrameData中的imgWidth、imgHeight是多餘的了,應該將其刪除。
增長地圖元素精靈類,它擁有圖片Bitmap的實例。其中,地圖的一個單元格就是一個地圖元素精靈類。
一、能夠在建立MapLayer元素時,元素由bitmap改成精靈類,這樣x、y屬性就能夠從bitmap移到精靈類中了.
二、精靈類包含動畫,方便後期增長動態地圖。
由於MapElementSprite不屬於「移動」語義,且它與MoveSprite沒有相同的模式,因此它應該繼承於Sprite。
MapElementSprite
(function () { var MapElementSprite = YYC.Class(Sprite, { Init: function (data, bitmap) { this.base(data, bitmap); }, Protected: { }, Private: { }, 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.MapElementSprite = MapElementSprite; }());
MapLayer
(function () { var MapLayer = YYC.Class(Layer, { Init: function () { }, Private: { ___canvasBuffer: null, ___contextBuffer: null, ___getCanvasBuffer: function () { //緩衝的canvas也要在html中建立並設置width、height! this.___canvasBuffer = document.getElementById("mapLayerCanvas_buffer"); }, ___getContextBuffer: function () { this.___contextBuffer = this.___canvasBuffer.getContext("2d"); }, ___drawBuffer: function () { this.P__iterator("draw", this.___contextBuffer); } }, Public: { setCanvas: function () { this.P__canvas = document.getElementById("mapLayerCanvas"); var css = { "position": "absolute", "top": bomberConfig.canvas.TOP, "left": bomberConfig.canvas.LEFT, "border": "1px solid blue", "z-index": 0 }; $("#mapLayerCanvas").css(css); //緩衝canvas的css也要設置! $("#mapLayerCanvas_buffer").css(css); }, init: function(){ //*雙緩衝 //得到緩衝canvas this.___getCanvasBuffer(); //得到緩衝context this.___getContextBuffer(); this.base(); }, draw: function () { this.___drawBuffer(); this.P__context.drawImage(this.___canvasBuffer, 0, 0); }, clear: function () { this.___contextBuffer.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT); this.P__context.clearRect(0, 0, bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT); }, render: function () { if (this.P__isChange()) { this.clear(); this.draw(); this.P__setStateNormal(); } } } }); window.MapLayer = MapLayer; }());
如今Bitmap的x、y屬性用於保存地圖圖片的座標。如今座標保存在地圖精靈類中了,故Bitmap中多餘的x、y屬性。
Bitmap
(function () { var Bitmap = YYC.Class({ Init: function (data) { this.img = data.img; this.width = data.width; this.height = data.height; }, Private: { }, Public: { img: null, width: 0, height: 0 } }); window.Bitmap = Bitmap; }());
LayerManager原本的職責爲「負責層的邏輯」,可是我認爲LayerManager的職責應該爲「負責層的統一操做」,它應該爲一個鍵-值集合類,它的元素應該爲Layer的實例。
所以對LayerManager進行重構:
由Layer類應該負責本身狀態的維護。
建立層內元素createElement這個職責應該放到調用LayerManager的客戶端,即Game類中。在Game中還要負責建立LayerManager、建立Layer。
增長一個Hash類,它實現鍵-值集合的通用操做,而後讓LayerManager繼承於Hash,使之成爲集合類。
功能上分析:
由於LayerManager集合的元素爲Layer實例,而每個層的實例都是惟一的,即如PlayerLayer實例只有一個,不會有二個PlayerLayer實例。所以,使用Hash結構,能夠經過key得到LayerManager集合中的每一個元素。
Hash的優點:
Hash結構不須要知道LayerManager集合裝入Layer實例的順序,經過key值就能夠惟一得到元素;而Collection結構(數組結構)須要知道裝入順序。
LayerManager、PlayerLayerManager、EnemyLayerManager、MapLayerManager重構前:
(function () { //* 父類 var LayerManager = YYC.AClass({ Init: function (layer) { this.layer = layer; }, Private: { }, Public: { layer: null, addElement: function (element) { var i = 0, len = 0; for (i = 0, len = element.length; i < len; i++) { this.layer.appendChild(element[i]); } }, render: function () { this.layer.render(); }, Virtual: { initLayer: function () { this.layer.setCanvas(); this.layer.init(); this.layer.change(); } } }, Abstract: { createElement: function () { }, change: function () { } } }); //*子類 var MapLayerManager = YYC.Class(LayerManager, { Init: function (layer) { this.base(layer); }, Private: { __getMapImg: function (i, j, mapData) { var img = null; switch (mapData[i][j]) { case 1: img = window.imgLoader.get("ground"); break; case 2: img = window.imgLoader.get("wall"); break; default: break } return img; } }, Public: { //建立並設置每一個地圖單元bitmap,加入到元素數組中並返回。 createElement: function () { var i = 0, j = 0, x = 0, y = 0, row = bomberConfig.map.ROW, col = bomberConfig.map.COL, element = [], mapData = mapDataOperate.getMapData(), img = null; for (i = 0; i < row; i++) { //注意! //y爲縱向height,x爲橫向width y = i * bomberConfig.HEIGHT; for (j = 0; j < col; j++) { x = j * bomberConfig.WIDTH; img = this.__getMapImg(i, j, mapData); element.push(spriteFactory.createMapElement({ x: x, y: y }, bitmapFactory.createBitmap({ img: img, width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }))); } } return element; }, change: function () { } } }); var PlayerLayerManager = YYC.Class(LayerManager, { Init: function (layer) { this.base(layer); }, 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.layer.getChildAt(0).moving }, __spriteStand: function () { if (this.layer.getChildAt(0).stand) { this.layer.getChildAt(0).stand = false; return true; } else { return false; } } }, Public: { createElement: function () { var element = [], player = spriteFactory.createPlayer(); player.init(); element.push(player); return element; }, change: function () { if (this.__keyDown() || this.__spriteMoving() || this.__spriteStand()) { this.layer.change(); } } } }); var EnemyLayerManager = YYC.Class(LayerManager, { Init: function (layer) { this.base(layer); }, Private: { }, Public: { initLayer: function (playerLayerManager) { this.layer.setCanvas(); this.layer.init(); this.layer.getPlayer(playerLayerManager.layer); this.layer.change(); }, createElement: function () { var element = [], enemy = spriteFactory.createEnemy(); enemy.init(); element.push(enemy); return element; }, collideWidthPlayer: function () { return this.layer.collideWidthPlayer(); }, change: function () { this.layer.change(); } } }); window.LayerManager = LayerManager; //用於測試 window.MapLayerManager = MapLayerManager; window.PlayerLayerManager = PlayerLayerManager; window.EnemyLayerManager = EnemyLayerManager; }());
LayerManager重構後:
(function () { var LayerManager = YYC.Class(Hash, { Private: { __iterator: function (handler) { var args = Array.prototype.slice.call(arguments, 1), i = null, layers = this.getChilds(); for (i in layers) { if (layers.hasOwnProperty(i)) { layers[i][handler].apply(layers[i], args); } } } }, Public: { addLayer: function (name, layer) { this.add(name, layer); return this; }, getLayer: function (name) { return this.getValue(name); }, initLayer: function () { this.__iterator("setCanvas"); this.__iterator("init"); }, render: function () { this.__iterator("render"); }, change: function () { this.__iterator("change"); } } }); window.LayerManager = LayerManager; }());
Hash
(function () { var Hash = YYC.AClass({ Private: { //容器 _childs: {} }, Public: { getChilds: function () { return YYC.Tool.extend.extend({}, this._childs); }, getValue: function (key) { return this._childs[key]; }, add: function (key, value) { this._childs[key] = value; return this; } } }); window.Hash = Hash; }());
Game
(function () { var Game = YYC.Class({ Init: function () { }, Private: { _pattern: null, _ground: null, _layerManager: null, _createLayerManager: function () { this._layerManager = new LayerManager(); this._layerManager.addLayer("mapLayer", layerFactory.createMap()); this._layerManager.addLayer("playerLayer", layerFactory.createPlayer(this.sleep)); this._layerManager.addLayer("enemyLayer", layerFactory.createEnemy(this.sleep)); }, _addElements: function () { var mapLayerElements = this._createMapLayerElement(), playerLayerElements = this._createPlayerLayerElement(), enemyLayerElements = this._createEnemyLayerElement(); this._layerManager.addElements("mapLayer", mapLayerElements); this._layerManager.addElements("playerLayer", playerLayerElements); this._layerManager.addElements("enemyLayer", enemyLayerElements); }, //建立並設置每一個地圖方格精靈,加入到元素數組中並返回。 _createMapLayerElement: function () { var i = 0, j = 0, x = 0, y = 0, row = bomberConfig.map.ROW, col = bomberConfig.map.COL, element = [], mapData = mapDataOperate.getMapData(), img = null; for (i = 0; i < row; i++) { //注意! //y爲縱向height,x爲橫向width y = i * bomberConfig.HEIGHT; for (j = 0; j < col; j++) { x = j * bomberConfig.WIDTH; img = this._getMapImg(i, j, mapData); element.push(spriteFactory.createMapElement({ x: x, y: y }, bitmapFactory.createBitmap({ img: img, width: bomberConfig.WIDTH, height: bomberConfig.HEIGHT }))); } } return element; }, _getMapImg: function (i, j, mapData) { var img = null; switch (mapData[i][j]) { case 1: img = window.imgLoader.get("ground"); break; case 2: img = window.imgLoader.get("wall"); break; default: break } return img; }, _createPlayerLayerElement: function () { var element = [], player = spriteFactory.createPlayer(); player.init(); element.push(player); return element; }, _createEnemyLayerElement: function () { var element = [], enemy = spriteFactory.createEnemy(); enemy.init(); element.push(enemy); return element; }, _initLayer: function () { this._layerManager.initLayer(); this._layerManager.getLayer("enemyLayer").getPlayer(this._layerManager.getLayer("playerLayer")); }, _initEvent: function () { //監聽整個document的keydown,keyup事件 keyEventManager.addKeyDown(); keyEventManager.addKeyUp(); } }, Public: { context: null, sleep: 0, x: 0, y: 0, mainLoop: null, init: function () { this.sleep = Math.floor(1000 / bomberConfig.FPS); this._createLayerManager(); this._addElements(); this._initLayer(); this._initEvent(); }, start: function () { var self = this; this.mainLoop = window.setInterval(function () { self.run(); }, this.sleep); }, run: function () { if (this._layerManager.getLayer("enemyLayer").collideWidthPlayer()) { clearInterval(this.mainLoop); alert("Game Over!"); return; } this._layerManager.render(); this._layerManager.change(); } } }); window.Game = Game; }());
本文最終領域模型
高層劃分
「層」這個層級的集合包的命名太普遍了,應該具體化爲數組集合包,這樣纔不至於與哈希集合包混淆。
哈希集合包與數組集合包都屬於集合,所以應該合併爲集合包,而後將集合包放到輔助邏輯層。
封閉性:
層抽象包、精靈抽象包位於同一個層面(抽象層面),會對同一種性質的變化共同封閉。
如精靈抽象類(精靈抽象包)發生變化,可能會引發層抽象類的變化。
重用性:
二者相互關聯。
二者爲抽象類,具備通用性。
能夠一塊兒被重用。
所以,將層抽象包、精靈抽象包合併成抽象包,並放到輔助邏輯層。
抽象包與集合包有依賴關係,但實際上只是抽象包中的層抽象類與集合包有依賴,精靈抽象類與集合包沒有管理,所以違反了共用重用原則CRP。
考慮到集合包也具備的通用性,將其也合併到抽象包中:
層和精靈都包含人物(精靈中爲移動)、地圖的概念,人物層與移動精靈、地圖層與地圖元素精靈聯繫緊密。
封閉性:
層實現包與精靈實現包違反了共同封閉原則CCP。如地圖發生變化時,只會引發地圖層和地圖元素精靈的變化,而不會引發人物層和移動精靈的變化。
重用性:
層實現包與精靈實現包中,人物層與地圖層、移動精靈與地圖元素精靈沒有關聯,所以違反了共同重用原則CRP。
所以,分離出人物包、地圖包,並將層、精靈這兩個層合併爲實現層:
狀態類與屬於人物包的玩家精靈類和敵人精靈類緊密關聯,所以應該放到人物包中。
MoveSprite、CharacterLayer是抽象類,可是它們與具體的人物實現密切相關(由於它們是從人物實現類PlayerSprite和EnemySprite、PlayerLayer和EnemyLayer提取共同模式而造成的父類)。所以,它們應該放到人物實現包中。
演示地址
本文參考資料