前言

上文中我们实现了炸弹人与墙的碰撞检测,以及设置移动步长来解决发现的问题。本文会加入1个AI敌人,敌人使用A*算法追踪炸弹人。

本文目的

加入敌人,追踪炸弹人

本文主要内容

回顾上文更新后的领域模型

查看大图

开发策略

首先实现“加入敌人”功能。通过参考“炸弹人游戏开发系列(4):炸弹人显示与移动“中的实现,可以初步分析出需要加入敌人图片、敌人帧数据和精灵数据、敌人精灵类EnemySprite、敌人层EnemyLayer和敌人层管理类EnemyLayerManager。

然后实现“追踪炸弹人”功能。需要新建一个算法类FindPath,负责使用A*算法计算并返回路径数据。

敌人精灵类与算法类的交互关系:

并行开发

可以并行开发“加入敌人”和“追踪炸弹人”。

先定义一个FindPath类的的接口,指定findPath方法输入参数和返回参数的格式。

实现“加入敌人”功能时,可以按照接口指定的格式使用假的路径数据来测试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 + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向左站立
stand_left: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向上站立
stand_up: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向下站立
stand_down: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向上走
walk_up: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + width, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向下走
walk_down: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + width, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向右走
walk_right: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + width, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y + * height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
},
//向左走
walk_left: {
img: window.imgLoader.get("enemy"),
frames: [
{ x: offset.x, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + width, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: },
{ x: offset.x + * width, y: offset.y + height, width: sw, height: sh, imgWidth: imgWidth, imgHeight: imgHeight, duration: }
]
}
}
} return function (animName) {
return frames()[animName];
};
}()); window.getEnemyFrames = getEnemyFrames;
}());

然后加入敌人精灵类数据

(function () {
var getSpriteData = (function () {
var data = function(){
return {
...
//敌人精灵类
enemy: {
//初始坐标
x: bomberConfig.WIDTH * ,
//x: 0,
y: bomberConfig.HEIGHT * ,
//定义sprite走路速度的绝对值
walkSpeed: bomberConfig.enemy.speed.NORMAL, //速度
speedX: ,
speedY: , //注意坐标起始点为图片左上点! minX: ,
maxX: bomberConfig.canvas.WIDTH - bomberConfig.player.IMGWIDTH,
//maxX: 500,
minY: ,
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;
}());

加入EnemySprite类

增加敌人精灵类。

创建假的A*算法类FindPath类

创建返回假数据的FindPath类,用于测试EnemySprite类。

相关代码

FindPath

(function () {
//构造假数据
var findPath = {
aCompute: function (terrainData, begin, target) {
return {
path: [{ x: , y: }, { x: , y: }, { x: , y: }, { x: , y: }, { x: , y: }],
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: ,
y: , //精灵的速度
speedX: ,
speedY: , //精灵的坐标区间
minX: ,
maxX: ,
minY: ,
maxY: ,
//精灵包含的所有 Animation 集合. Object类型, 数据存放方式为" id : animation ".
anims: null,
//默认的Animation的Id , string类型
defaultAnimId: null, //当前的Animation.
currentAnim: null, //精灵的方向系数:
//往下走dirY为正数,往上走dirY为负数;
//往右走dirX为正数,往左走dirX为负数。
dirX: ,
dirY: , //定义sprite走路速度的绝对值
walkSpeed: , //一次移动步长中的需要移动的次数
stepX: ,
stepY: , //一次移动步长中已经移动的次数
moveIndex_x: ,
moveIndex_y: , //是否正在移动标志
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(, , 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"));
}

新增EnemyLayer

增加敌人画布,该画布于地图画布之上,与玩家画布的zIndex相同。同时增加对应的敌人层类EnemyLayer,它的集合元素为EnemySprite类的实例。

EnemyLayer

(function () {
var EnemyLayer = YYC.Class(Layer, {
Init: function (deltaTime) {
this.___deltaTime = deltaTime;
},
Private: {
___deltaTime: , ___iterator: function (handler) {
var args = Array.prototype.slice.call(arguments, ),
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": //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);
}

新增EnemyLayerManager

增加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类

领域模型

相关代码

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 = ; /********************函数主体部分*************************/ arr_map = setMap(mapData); map_w = mapData.length;
beginx = begin.x;
beginy = map_w - - begin.y;
endx = end.x;
endy = map_w - - end.y;
var startNodeNum = tile_num(beginx, beginy);
var targetNodeNum = tile_num(endx, endy); if (arr_map[targetNodeNum] && arr_map[targetNodeNum][] == ) {
showError("目的地无法到达!");
time = getTime(startTime);
return { path: [], time: time };
}
if (arr_map[startNodeNum][] == ) {
showError("起始点不可用!");
time = getTime(startTime);
return { path: [], time: time };
} if (arr_map[targetNodeNum] && arr_map[targetNodeNum][] * arr_map[startNodeNum][] == ) {
arr_map[startNodeNum] = [arr_map[startNodeNum][], startNodeNum, arr_map[startNodeNum][], arr_map[startNodeNum][], arr_map[startNodeNum][]];//起始点的父节点为自己
setH(targetNodeNum);
setOpenList(startNodeNum); //把开始节点加入到openlist中
//就要开始那个令人发指的循环了,==!!A*算法主体 while (open_list.length != ) {
var bestNodeNum = selectFmin(open_list);
stopn = ;
open_list.shift();
setCloseList(bestNodeNum); if (bestNodeNum == targetNodeNum) {
showPath(close_list, arr_path);
break;
}
var i = , j = ;
//当目标为孤岛时的判断
var tmp0 = new Array();
var k;
tmp0 = setSuccessorNode(targetNodeNum, map_w);
for (j; j < ; j++) {
if (j == ) {
k = ;
break;
}
if (tmp0[j][] == ) {
k = ;
break;
}
}
//当目标为孤岛时的判断语句结束
if (k == ) {
showError("目标成孤岛!");
time = getTime(startTime);
return { path: [], time: time };
}
else {
tmp = setSuccessorNode(bestNodeNum, map_w);
for (i; i < ; i++) {
if ((tmp[i][] == ) || (findInCloseList(tmp[i][]))) continue; if (findInOpenList(tmp[i][]) == ) {
if (tmp[i][] >= (arr_map[bestNodeNum][] + cost(tmp[i], bestNodeNum))) {
setG(tmp[i][], bestNodeNum); //算g值,修改arr_map中[2]的值
arr_map[tmp[i][]] = tmp[i] = [arr_map[tmp[i][]][], bestNodeNum, arr_map[tmp[i][]][], arr_map[tmp[i][]][], arr_map[tmp[i][]][]]; //修改tmp和arr_map中父节点的值,并修改tmp中g值,是之和arr_map中对应节点的值统一
}
}
if (findInOpenList(tmp[i][]) == ) {
setG(tmp[i][], bestNodeNum); //算g值,修改arr_map中[2]的值
arr_map[tmp[i][]] = tmp[i] = [arr_map[tmp[i][]][], bestNodeNum, arr_map[tmp[i][]][], arr_map[tmp[i][]][], arr_map[tmp[i][]][]]; //修改tmp和arr_map中父节点的值,并修改tmp中g值,是之和arr_map中对应节点的值统一
setOpenList(tmp[i][]); //存进openlist中 }
}
} stopn++;
//if (stopn == map_w * map_w - 1) { //2013.5.27修改
if (stopn == map_w * map_w * ) {
showError("找不到路径!");
time = getTime(startTime);
return { path: [], time: time }; // break;
}
} if (open_list.length == && 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) / ;
}; 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 - ;
for (a; a >= ; a--) {
var xTmp = tile_x(a); //把ID 编号值转换为x坐标值,用来对应读入地图数据
var yTmp = map_w - - tile_y(a); //把ID 编号值转换为y坐标值,用来对应读入地图数据,对应数组标号和我自定义xoy坐标位置 //[cost,parent,g,h,id]
if (mapData[yTmp][xTmp] == pass)
arr_map0[a] = [, , , , a];
else
arr_map0[a] = [, , , , 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 = ;
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][] = h * parseInt();
}
} /*********************************************************************
*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) == ) || ((y0 - y1) == )) {
arr_map[nowNode] = [arr_map[nowNode][], arr_map[nowNode][], arr_map[nowNode][] + parseInt(), arr_map[nowNode][], arr_map[nowNode][]]; }
else { arr_map[nowNode] = [arr_map[nowNode][], arr_map[nowNode][], arr_map[nowNode][] + parseInt(), arr_map[nowNode][], arr_map[nowNode][]];
}
} /*********************************************************************
*function selectFmin(open_list)
*功能:在openlist中对f值进行排序(冒泡排序),并选择一个f值最小的节点返回
*参数:openlist
***********************************************************************/
function selectFmin(open_list) {
var i, j, min, temp;
for (i = ; i < open_list.length; i++) {
for (j = i + ; j < open_list.length; j++) {
if ((open_list[i][] + open_list[i][]) > (open_list[j][] + open_list[j][])) {
temp = open_list[i];
open_list[i] = open_list[j];
open_list[j] = temp;
}
}
}
var min = open_list[];
return min[];
} /***********************************************************************
*function setOpenList(NodeNum)
*功能:把节点加入open表中
*参数:待加入openlist的节点的编号
************************************************************************/
function setOpenList(NodeNum) {
var n = open_list.length;
open_list[n] = [arr_map[NodeNum][], arr_map[NodeNum][], arr_map[NodeNum][], arr_map[NodeNum][], arr_map[NodeNum][]];
} /***********************************************************************
*function setCloseList(NodeNum)
*功能:把节点加入close表中
*参数:待加入closelist的节点的编号
************************************************************************/
function setCloseList(NodeNum) {
var n = close_list.length;
close_list[n] = [arr_map[NodeNum][], arr_map[NodeNum][], arr_map[NodeNum][], arr_map[NodeNum][], arr_map[NodeNum][]];
} /***********************************************************************
*function findInOpenList(nowNodeNum)
*功能:查询当前节点是否在openlist中,找到返回1,找不到返回0
*参数:待查询的节点的编号
************************************************************************/
function findInOpenList(nowNodeNum) {
var i;
for (i = ; i < open_list.length; i++) { if (open_list[i][] == nowNodeNum)
return ;
}
return ;
} /***********************************************************************
*function findInCloseList(nowNodeNum)
*功能:查询当前节点是否在closelist中,找到返回1,找不到返回0
*参数:待查询的节点的编号
************************************************************************/
function findInCloseList(nowNodeNum) {
var i;
for (i = ; i < close_list.length; i++) {
if (close_list[i][] == nowNodeNum)
return ;
else return ;
}
} /***********************************************************************
*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) == ) || ((y0 - y1) == )) {
return ;
}
else
return ;
} /**********************************************************************
*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 - ;
if ((x0 - ) >= && (y0) >= && (x0 - ) <= m && (y0) <= m) tmp[] = [arr_map[tile_num(x0 - , y0)][], arr_map[tile_num(x0 - , y0)][], arr_map[tile_num(x0 - , y0)][], arr_map[tile_num(x0 - , y0)][], arr_map[tile_num(x0 - , y0)][]]; else {
tmp[] = [, , , , ];
}
if ((x0) >= && (y0 + ) >= && (x0) <= m && (y0 + ) <= m) tmp[] = [arr_map[tile_num(x0, y0 + )][], arr_map[tile_num(x0, y0 + )][], arr_map[tile_num(x0, y0 + )][], arr_map[tile_num(x0, y0 + )][], arr_map[tile_num(x0, y0 + )][]]; else {
tmp[] = [, , , , ];
}
if ((x0) >= && (y0 - ) >= && (x0) <= m && (y0 - ) <= m) tmp[] = [arr_map[tile_num(x0, y0 - )][], arr_map[tile_num(x0, y0 - )][], arr_map[tile_num(x0, y0 - )][], arr_map[tile_num(x0, y0 - )][], arr_map[tile_num(x0, y0 - )][]]; else {
tmp[] = [, , , , ];
}
if ((x0 + ) >= && (y0) >= && (x0 + ) <= m && (y0) <= m) tmp[] = [arr_map[tile_num(x0 + , y0)][], arr_map[tile_num(x0 + , y0)][], arr_map[tile_num(x0 + , y0)][], arr_map[tile_num(x0 + , y0)][], arr_map[tile_num(x0 + , y0)][]]; else {
tmp[] = [, , , , ];
}
if (bomberConfig.algorithm.DIRECTION == ) {
if ((x0 - ) >= && (y0 + ) >= && (x0 - ) <= m && (y0 + ) <= m) tmp[] = [arr_map[tile_num(x0 - , y0 + )][], arr_map[tile_num(x0 - , y0 + )][], arr_map[tile_num(x0 - , y0 + )][], arr_map[tile_num(x0 - , y0 + )][], arr_map[tile_num(x0 - , y0 + )][]]; else {
tmp[] = [, , , , ];
} if ((x0 - ) >= && (y0 - ) >= && (x0 - ) <= m && (y0 - ) <= m) tmp[] = [arr_map[tile_num(x0 - , y0 - )][], arr_map[tile_num(x0 - , y0 - )][], arr_map[tile_num(x0 - , y0 - )][], arr_map[tile_num(x0 - , y0 - )][], arr_map[tile_num(x0 - , y0 - )][]]; else {
tmp[] = [, , , , ];
} if ((x0 + ) >= && (y0 + ) >= && (x0 + ) <= m && (y0 + ) <= m) tmp[] = [arr_map[tile_num(x0 + , y0 + )][], arr_map[tile_num(x0 + , y0 + )][], arr_map[tile_num(x0 + , y0 + )][], arr_map[tile_num(x0 + , y0 + )][], arr_map[tile_num(x0 + , y0 + )][]]; else {
tmp[] = [, , , , ];
} if ((x0 + ) >= && (y0 - ) >= && (x0 + ) <= m && (y0 - ) <= m) tmp[] = [arr_map[tile_num(x0 + , y0 - )][], arr_map[tile_num(x0 + , y0 - )][], arr_map[tile_num(x0 + , y0 - )][], arr_map[tile_num(x0 + , y0 - )][], arr_map[tile_num(x0 + , y0 - )][]]; else {
tmp[] = [, , , , ];
} }
if (bomberConfig.algorithm.DIRECTION == ) {
tmp[] = [, , , , ];
tmp[] = [, , , , ];
tmp[] = [, , , , ];
tmp[] = [, , , , ];
} return tmp;
} /*******************************************************************
*function showPath(close_list)
*功能:把结果路径存入arr_path输出
*参数:close_list
********************************************************************/
function showPath(close_list, arr_path) {
var n = close_list.length;
var i = n - ;
var ii = ;
var nn = ;
var mm = ; var arr_path_tmp = new Array();
var target = null; /**********把close_list中有用的点存入arr_path_tmp中*************/ for (ii; ; ii++) {
arr_path_tmp[ii] = close_list[n - ][];
if (close_list[n - ][] == close_list[i][]) {
break;
}
for (i = n - ; i >= ; i--) {
if (close_list[i][] == close_list[n - ][]) {
n = i + ;
break;
}
}
} var w = arr_path_tmp.length - ;
var j = ;
for (var i = w; i >= ; i--) {
arr_path[j] = arr_path_tmp[i];
j++;
}
for (var k = ; k <= w; k++) {
target = {
x: tile_x(arr_path[k]),
y: map_w - - 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;
}());

重构

重构状态模式Context类

重构前:

(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;
}());

删除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;
}());

提出基类Sprite

为什么要提出

  • EnemySprite与PlayerSpite有很多相同的代码
  • 从概念上来说,玩家精灵类与敌人精灵类都属于精灵类的概念

因此,提出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: , //精灵的坐标
x: ,
y: , //精灵的速度
speedX: ,
speedY: , //精灵的坐标区间
minX: ,
maxX: ,
minY: ,
maxY: ,
//精灵包含的所有 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: ,
dirY: , //定义sprite走路速度的绝对值
walkSpeed: , //一次移动步长中的需要移动的次数
stepX: ,
stepY: , //一次移动步长中已经移动的次数
moveIndex_x: ,
moveIndex_y: , //是否正在移动标志
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(, , 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 == ) {
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: ,
dirY: , //定义sprite走路速度的绝对值
walkSpeed: , //一次移动步长中的需要移动的次数
stepX: ,
stepY: , //一次移动步长中已经移动的次数
moveIndex_x: ,
moveIndex_y: , //是否正在移动标志
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(, , 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类

  • PlayerLayer、EnemyLayer有相似的模式
  • 从语义上来看,PlayerLayer、EnemyLayer都是属于”人物“的语义

因此,提出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, ),
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: , ___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().moving
},
___spriteStand: function () {
if (this.getChildAt().stand) {
this.getChildAt().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":
});
},
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":
});
},
getPlayer: function (playerLayer) {
this.playerLayer = playerLayer;
this.P__iterator("setPlayerSprite", playerLayer.getChildAt());
},
collideWidthPlayer: function () {
try{
this.P__iterator("collideWidthPlayer", this.playerLayer.getChildAt());
return false;
}
catch(e){
return true;
}
}, render: function () {
this.__getPath(); this.base();
}
}
}); window.EnemyLayer = EnemyLayer;
}());

提出父类MoveSprite

  • PlayerSprite、EnemySprite有相似的模式
  • 从语义上来看,PlayerSprite、EnemySprite都是能够移动的精灵类

因此,提出父类MoveSprite

为什么不把PlayerSprite、EnemySprite的相似的模式直接提到Sprite中?

  • 抽象层次不同

因为我提取的语义是“移动的精灵类”,而Sprite的语义是“精灵类”,属于更抽象的概念

为什么不叫CharacterSprite?

  • 因为关注的语义不同。

在提取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: ,
y: , //精灵包含的所有 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 !== || this.y % bomberConfig.HEIGHT !==
}
},
Public: {
//精灵的速度
speedX: ,
speedY: , //精灵的坐标区间
minX: ,
maxX: ,
minY: ,
maxY: , //精灵的方向系数:
//往下走dirY为正数,往上走dirY为负数;
//往右走dirX为正数,往左走dirX为负数。
dirX: ,
dirY: , //定义sprite走路速度的绝对值
walkSpeed: , //一次移动步长中的需要移动的次数
stepX: ,
stepY: , //一次移动步长中已经移动的次数
moveIndex_x: ,
moveIndex_y: , //是否正在移动标志
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(, , 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 == ) {
this.path = this.___findPath();
}
}
}
}); window.EnemySprite = EnemySprite;
}());

将Bitmap注入到Sprite中

反思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
}
},

领域模型

删除data -> frames.js中的imgWidth、imgHeight

现在FrameData中的imgWidth、imgHeight是多余的了,应该将其删除。

增加MapElementSprite

增加地图元素精灵类,它拥有图片Bitmap的实例。其中,地图的一个单元格就是一个地图元素精灵类。

为什么增加?

1、可以在创建MapLayer元素时,元素由bitmap改为精灵类,这样x、y属性就可以从bitmap移到精灵类中了.
2、精灵类包含动画,方便后期增加动态地图。

它的父类为Sprite还是MoveSprite?

因为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(, , 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":
}; $("#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, , ); },
clear: function () {
this.___contextBuffer.clearRect(, , bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
this.P__context.clearRect(, , bomberConfig.canvas.WIDTH, bomberConfig.canvas.HEIGHT);
},
render: function () {
if (this.P__isChange()) {
this.clear();
this.draw();
this.P__setStateNormal();
}
}
}
}); window.MapLayer = MapLayer;
}());

重构Bitmap

删除Bitmap的x、y属性。

现在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: ,
height:
}
}); window.Bitmap = Bitmap;
}());

重构LayerManager

LayerManager本来的职责为“负责层的逻辑”,但是我认为LayerManager的职责应该为“负责层的统一操作”,它应该为一个键-值集合类,它的元素应该为Layer的实例。

因此对LayerManager进行重构:

  • 将change的判断移到具体的Layer中

由Layer类应该负责自己状态的维护。

  • 将createElement放到Game中

创建层内元素createElement这个职责应该放到调用LayerManager的客户端,即Game类中。在Game中还要负责创建LayerManager、创建Layer。

  • 增加Hash

增加一个Hash类,它实现键-值集合的通用操作,然后让LayerManager继承于Hash,使之成为集合类。

为什么使用Hash类,而不是使用Collection类(数组集合类)

功能上分析:

因为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 = ,
len = ; for (i = , 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 :
img = window.imgLoader.get("ground");
break;
case :
img = window.imgLoader.get("wall");
break;
default:
break
} return img;
}
},
Public: {
//创建并设置每个地图单元bitmap,加入到元素数组中并返回。
createElement: function () {
var i = ,
j = ,
x = ,
y = ,
row = bomberConfig.map.ROW,
col = bomberConfig.map.COL,
element = [],
mapData = mapDataOperate.getMapData(),
img = null; for (i = ; i < row; i++) {
//注意!
//y为纵向height,x为横向width
y = i * bomberConfig.HEIGHT; for (j = ; 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().moving
},
__spriteStand: function () {
if (this.layer.getChildAt().stand) {
this.layer.getChildAt().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, ),
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 = ,
j = ,
x = ,
y = ,
row = bomberConfig.map.ROW,
col = bomberConfig.map.COL,
element = [],
mapData = mapDataOperate.getMapData(),
img = null; for (i = ; i < row; i++) {
//注意!
//y为纵向height,x为横向width
y = i * bomberConfig.HEIGHT; for (j = ; 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 :
img = window.imgLoader.get("ground");
break;
case :
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: ,
x: ,
y: ,
mainLoop: null, init: function () {
this.sleep = Math.floor( / 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;
}());

本文最终领域模型

查看大图

高层划分

新增包

  • 算法包
    FindPath
  • 精灵抽象包
    Sprite
  • 哈希集合包
    Hash

删除包

  • 层管理实现包
    经过本文重构后,去掉了PlayerLayerManager等子类,只保留了LayerManager类。因此去掉层管理实现包。

层、包

重构

将集合包重命名为数组集合包

“层”这个层级的集合包的命名太广泛了,应该具体化为数组集合包,这样才不至于与哈希集合包混淆。

将哈希集合包和数组集合包合并为集合包

哈希集合包与数组集合包都属于集合,因此应该合并为集合包,然后将集合包放到辅助逻辑层。

提出抽象包

  分析

封闭性:

层抽象包、精灵抽象包位于同一个层面(抽象层面),会对同一种性质的变化共同封闭。

如精灵抽象类(精灵抽象包)发生变化,可能会引起层抽象类的变化。

重用性:

两者相互关联。

两者为抽象类,具有通用性。

可以一起被重用。

  结论

因此,将层抽象包、精灵抽象包合并成抽象包,并放到辅助逻辑层。

将集合包也合并到抽象包中

抽象包与集合包有依赖关系,但实际上只是抽象包中的层抽象类与集合包有依赖,精灵抽象类与集合包没有管理,因此违反了共用重用原则CRP。

考虑到集合包也具有的通用性,将其也合并到抽象包中:

提出人物包、地图包

层和精灵都包含人物(精灵中为移动)、地图的概念,人物层与移动精灵、地图层与地图元素精灵联系紧密。

  分析

封闭性:

层实现包与精灵实现包违反了共同封闭原则CCP。如地图发生变化时,只会引起地图层和地图元素精灵的变化,而不会引起人物层和移动精灵的变化。

重用性:

层实现包与精灵实现包中,人物层与地图层、移动精灵与地图元素精灵没有关联,因此违反了共同重用原则CRP。

因此,分离出人物包、地图包,并将层、精灵这两个层合并为实现层:

  状态类放到哪

状态类与属于人物包的玩家精灵类和敌人精灵类紧密关联,因此应该放到人物包中。

  MoveSprite、CharacterLayer应不应该放到抽象包中

MoveSprite、CharacterLayer是抽象类,但是它们与具体的人物实现密切相关(因为它们是从人物实现类PlayerSprite和EnemySprite、PlayerLayer和EnemyLayer提取共同模式而形成的父类)。因此,它们应该放到人物实现包中。

本文最终层、包

对应领域模型

  • 辅助操作层

    • 控件包
      PreLoadImg
    • 配置包
      Config
  • 用户交互层
    • 入口包
      Main
  • 业务逻辑层
    • 辅助逻辑

      • 工厂包
        BitmapFactory、LayerFactory、SpriteFactory
      • 事件管理包
        KeyState、KeyEventManager
      • 抽象包
        Layer、Sprite、Hash、Collection
    • 游戏主逻辑
      • 主逻辑包
        Game
    • 层管理
      • 层管理包
        LayerManager
    • 实现
      • 人物实现包
        PlayerLayer、MoveSprite、PlayerSprite、EnemySprite、CharacterLayer、PlayerLayer、EnemyLayer、Context、PlayerState、WalkState、StandState、WalkState_X、WalkState_Y、StandLeftState、StandRightState、StandUpState、StandDownState、WalkLeftState、WalkRightState、WalkUpState、WalkDownState
      • 地图实现包
        MapLayer、MapElementSprite
      • 算法包
        FindPath
      • 动画包
        Animation、GetSpriteData、SpriteData、GetFrames、FrameData
  • 数据操作层
    • 地图数据操作包
      MapDataOperate
    • 路径数据操作包
      GetPath
    • 图片数据操作包
      Bitmap
  • 数据层
    • 地图包
      MapData、TerrainData
    • 图片路径包
      ImgPathData

演示地址

演示地址

本文参考资料

A星算法

欢迎浏览上一篇博文:炸弹人游戏开发系列(6):实现碰撞检测,设置移动步长

欢迎浏览下一篇博文:炸弹人游戏开发系列(8):放炸弹

炸弹人游戏开发系列(7):加入敌人,使用A*算法寻路的更多相关文章

  1. HTML5游戏开发系列教程10(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-10/ 最后我们将继续使用canvas来进行HTML5游戏开发系列 ...

  2. HTML5游戏开发系列教程9(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-9/ 今天我们将继续使用canvas来进行HTML5游戏开发系列的 ...

  3. Cocos2dx游戏开发系列笔记13:一个横版拳击游戏Demo完结篇

    懒骨头(http://blog.csdn.net/iamlazybone QQ:124774397 ) 写下这些东西的同时 旁边放了两部电影 周星驰的<还魂夜> 甄子丹的<特殊身份& ...

  4. HTML5游戏开发系列教程8(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-8/ 这是我们最新一篇HTML5游戏开发系列文章.我们将继续使用c ...

  5. HTML5游戏开发系列教程7(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-7/ 今天我们将完成我们第一个完整的游戏--打砖块.这次教程中,将 ...

  6. HTML5游戏开发系列教程6(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-6/ 这是我们最新一篇HTML5游戏开发系列文章.我们将继续使用c ...

  7. HTML5游戏开发系列教程5(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-5/ 最终我决定准备下一篇游戏开发系列的文章,我们将继续使用can ...

  8. HTML5游戏开发系列教程4(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-4/ 这篇文章是我们继续使用canvas来进行HTML5游戏开发系 ...

  9. unity3D实际的原始视频游戏开发系列讲座10它《战斗:外来入侵》在第一季度游戏开发

    解说文件夹 <保卫战:异形入侵>游戏开发 第一讲   游戏演示和资源的介绍 第二讲  "异形"怪物的实现 第三讲  "异形"怪物生命值的体现 第四讲 ...

随机推荐

  1. Jenkins+Maven+Gitlab+Tomcat 自动化构建打包、部署

    一.环境需求 本帖针对的是Linux环境,Windows或其他系统也可借鉴.具体只讲述Jenkins配置以及整个流程的实现. 1.JDK(或JRE)及Java环境变量配置,我用的是JDK1.8.0_1 ...

  2. Spring Boot(九)Swagger2自动生成接口文档和Mock模拟数据

    一.简介 在当下这个前后端分离的技术趋势下,前端工程师过度依赖后端工程师的接口和数据,给开发带来了两大问题: 问题一.后端接口查看难:要怎么调用?参数怎么传递?有几个参数?参数都代表什么含义? 问题二 ...

  3. Perl线程池

    Thread::Pool模块提供了Perl解释器线程的线程池,手册:https://metacpan.org/pod/Thread::Pool.

  4. JavaScript小实例:拖拽应用(二)

    经常在网站别人的网站的注册页中看到一个拖拽验证的效果,就是它的验证码刚开始不出来,而是有一个拖拽的条,你必须将这个拖拽条拖到底,验证码才出来,说了感觉跟没说一样,你还是不理解,好吧,我给个图你看看: ...

  5. vb.net C# AtlAxGetControl 函数使用方法

    先省略一万字的坑坑坑 找遍了互联网既然没搜索到相关资料 一定是我搜索的方法有问题 下面简单几句代码 第一种方法 Public Declare Function AtlAxGetControl Lib ...

  6. C# 设置Excel条件格式(二)

    上一篇文章中介绍了关于设置Excel条件格式,包括基于单元格值.自定义公式等应用条件格式.应用数据条条件类型格式.删除条件格式等内容.在本篇文章中将继续介绍C# 设置条件格式的方法. 要点概述: 1. ...

  7. Maven(十二)Maven 依赖详解

    依赖的传递性 注意1:在Eclipise创建的Maven项目,若依赖eclipse空间中其他自己创建的 的项目时,此时并不会报错,但是当执行mvn compile命令时还是会显示缺失败.所以依赖的其他 ...

  8. splay详解(一)

    前言 Spaly是基于二叉查找树实现的, 什么是二叉查找树呢?就是一棵树呗:joy: ,但是这棵树满足性质—一个节点的左孩子一定比它小,右孩子一定比它大 比如说 这就是一棵最基本二叉查找树 对于每次插 ...

  9. 【20190405】JavaScript-整理一些常用正则式

    匹配中文字符: let reg=/([\u4E00-\u9FFF]+)/; //\u代表Unicode编码 匹配电话号码: let reg=/^1[34578]\d{9}$/; 给每三位数字添加一个逗 ...

  10. thymeleaf的配置

    1.在springboto项目中使用thymeleaf标签,必须先添加依赖,如下. <dependency> <groupId>org.springframework.boot ...