8.7 创建Model类
本节,我们将创建Model类,它负责初始化和更新英雄、坏人、关卡、生命值条。这些对象可以被看做是游戏的“数据”。
操作步骤
按照以下步骤,创建Canvas Hero游戏的模型:
1. 定义Model类的构造函数:
/* 游戏模型
*
* 模型负责初始化和 更新英雄、坏人、关卡、生命值条
*/
function Model(controller){
this.controller = controller;
this.healthBar = null;
this.hero = null;
this.level = null;
this.badGuys = []; // array of bad guys
this.heroCanvasPos = {};
}
2. 定义removeDefeatedBadGuys()方法,该方法遍历坏人数组,并删除不再存活的坏人:
Model.prototype.removeDefeatedBadGuys = function() {
for (var n = 0; n < this.badGuys.length; n++) {
var badGuy = this.badGuys[n];
if (!badGuy.alive && badGuy.opacity == 0) {
this.badGuys.splice(n, 1);
}
}
};
3. 定义updateBadGuys ()方法:
Model.prototype.updateBadGuys = function() {
var that = this;
for (var n = 0; n < this.badGuys.length; n++) {
var badGuy = this.badGuys[n];
if(badGuy.alive
&& this.hero.alive
&& !badGuy.attacking
&& badGuy.canAttack
&& this.nearby(this.hero, badGuy)
&& ((badGuy.x - this.hero.x > 0 && !badGuy.isFacingRight()) ||
(this.hero.x - badGuy.x > 0 && badGuy.isFacingRight()))) {
badGuy.attack();
setTimeout(function(){
that.hero.damage();
}, 200);
}
this.updateActor(badGuy);
}
};
4. 定义updateStage()方法,该方法在每个动画帧更新所有的游戏对象:
Model.prototype.updateStage = function() {
var controller = this.controller;
var canvas = controller.view.canvas;
if (controller.state == controller.states.PLAYING) {
this.removeDefeatedBadGuys();
// 如果英雄死亡,则把游戏状态设置为GAMEOVER
if (!this.hero.alive && controller.state == controller. states.PLAYING) {
controller.state = controller.states.GAMEOVER;
}
// 如果所有坏人全部被击败,则游戏状态转换为WON
if (this.badGuys.length == 0) {
controller.state = controller.states.WON;
}
//移动周围的坏人
this.moveBadGuys();
//更新关卡位置
this.updateLevel();
//更新坏人,并看看他们是否可以攻击英雄
this.updateBadGuys();
//更新英雄的相关信息
var oldHeroX = this.hero.x;
this.updateActor(this.hero);
this.updateHeroCanvasPos(oldHeroX);
//更新生命值条的相关信息
this.healthBar.setHealth(this.hero.health);
// 如果英雄掉进坑里,则把生命值设置为0
if (this.hero.y > canvas.height - this.hero.spriteSize * 2 / 3) {
this.hero.health = 0;
}
//更新平均FPS
var anim = controller.anim;
if (anim.getFrame() % 20 == 0) {
this.controller.avgFps = Math.round(anim.getFps() * 10) / 10;
}
}
};
5. 定义initHealthBar()方法,该方法初始化生命值条:
Model.prototype.initHealthBar = function(){
this.healthBar = new HealthBar({
controller: this.controller,
maxHealth: this.hero.maxHealth,
x: 10,
y: 10,
maxWidth: 150,
height: 20
});
};
6. 定义initLevel()方法,该方法初始化关卡:
Model.prototype.initLevel = function(){
this.level = new Level({
controller: this.controller,
x: 0,
y: 0,
leftBounds: 100,
rightBounds: 500
});
};
7. 定义initHero()方法,该方法初始化英雄:
Model.prototype.initHero = function(){
// 初始化英雄
var heroMotions = {
STANDING: {
index: 0,
numSprites: 5,
loop: true
},
AIRBORNE: {
index: 1,
numSprites: 5,
loop: false
},
RUNNING: {
index: 2,
numSprites: 6,
loop: true
},
ATTACKING: {
index: 3,
numSprites: 5,
loop: false
}
};
this.hero = new Actor({
controller: this.controller,
normalSpriteSheet: this.controller.images.heroSprites,
hitSpriteSheet: this.controller.images.heroHitSprites,
x: 30,
y: 381,
playerSpeed: 300,
motions: heroMotions,
startMotion: heroMotions.STANDING,
facingRight: true,
moving: false,
spriteInterval: 90,
maxHealth: 3,
attackRange: 100,
minAttackInterval: 200
});
this.heroCanvasPos = {
x: this.hero.x,
y: this.hero.y
};
};
8. 定义initBadGuys()方法,该方法初始化坏人的数组:
Model.prototype.initBadGuys = function(){
// 请注意:AIRBORNE和RUNNING使用同一个精灵动画
var badGuyMotions = {
RUNNING: {
index: 0,
numSprites: 6,
loop: true
},
AIRBORNE: {
index: 0,
numSprites: 4,
loop: false
},
ATTACKING: {
index: 1,
numSprites: 4,
loop: false
}
};
var badGuyStartConfig = [{
x: 600,
facingRight: true
}, {
x: 1460,
facingRight: true
}, {
x: 2602,
facingRight: true
}, {
x: 3000,
facingRight: true
}, {
x: 6402,
facingRight: true
}, {
x: 6602,
facingRight: true
}];
for (var n = 0; n < badGuyStartConfig.length; n++) {
this.badGuys.push(new Actor({
controller: this.controller,
normalSpriteSheet: this.controller.images.badGuySprites,
hitSpriteSheet: this.controller.images.badGuyHitSprites,
x: badGuyStartConfig[n].x,
y: 381,
playerSpeed: 100,
motions: badGuyMotions,
startMotion: badGuyMotions.RUNNING,
facingRight: badGuyStartConfig[n].facingRight,
moving: true,
spriteInterval: 160,
maxHealth: 3,
attackRange: 100,
minAttackInterval: 2000
}));
}
};
9. 定义moveBadGuys()方法,该方法是游戏的简单人工智能引擎:
Model.prototype.moveBadGuys = function(){
var level = this.level;
for (var n = 0; n < this.badGuys.length; n++) {
var badGuy = this.badGuys[n];
if (badGuy.alive) {
if (badGuy.isFacingRight()) {
badGuy.x += 5;
if (!level.getZoneInfo(badGuy.getCenter()).inBounds) {
badGuy.facingRight = false;
}
badGuy.x -= 5;
}
else {
badGuy.x -= 5;
if (!level.getZoneInfo(badGuy.getCenter()).inBounds) {
badGuy.facingRight = true;
}
badGuy.x += 5;
}
}
}
};
10. 定义updateLevel()方法:
Model.prototype.updateLevel = function(){
var hero = this.hero;
var level = this.level;
level.x = -hero.x + this.heroCanvasPos.x;
};
11. 定义updateHeroCanvasPos()方法,该方法更新英雄相对于画布的位置:
Model.prototype.updateHeroCanvasPos = function(oldHeroX){
this.heroCanvasPos.y = this.hero.y;
var heroDiffX = this.hero.x - oldHeroX;
var newHeroCanvasPosX = this.heroCanvasPos.x + heroDiffX;
// 如果正在向右移动,且没有超过右边界
if (heroDiffX > 0 && newHeroCanvasPosX < this.level.rightBounds) {
this.heroCanvasPos.x += heroDiffX;
}
// 如果正在向左移动,且没有超过左边界
if (heroDiffX < 0 && newHeroCanvasPosX > this.level.leftBounds) {
this.heroCanvasPos.x += heroDiffX;
}
if (this.hero.x < this.level.leftBounds) {
this.heroCanvasPos.x = this.hero.x;
}
};
12. 定义updateActor()方法:
Model.prototype.updateActor = function(actor) {
if (actor.alive) {
if (actor.health <= 0 || actor.y + actor.SPRITE_SIZE > this.controller.view.canvas.height) {
actor.alive = false;
}
else {
this.updateActorVY(actor);
this.updateActorY(actor);
this.updateActorX(actor);
actor.updateSpriteMotion();
actor.updateSpriteSeqNum();
}
}
else {
if (actor.opacity > 0) {
actor.fade();
}
}
};
13. 定义updateActorVY()方法,该方法使用向下的重力和向上的升力,来更新角色的垂直速度:
Model.prototype.updateActorVY = function(actor) {
var anim = this.controller.anim;
var level = this.level;
//应用重力(+y)
var gravity = this.controller.model.level.GRAVITY;
var speedIncrementEachFrame = gravity * anim.getTimeInterval() / 1000; // pixels / second
actor.vy += speedIncrementEachFrame;
//应用升力(-y)
if (level.getZoneInfo(actor.getCenter()).levitating) {
actor.vy = (65 - actor.y) / 200;
}
};
14. 定义updateActorY()方法,该方法根据角色的垂直速度来更新其y轴的位置:
Model.prototype.updateActorY = function(actor) {
var anim = this.controller.anim;
var level = this.level;
var oldY = actor.y;
actor.y += actor.vy * anim.getTimeInterval();
if (level.getZoneInfo(actor.getCenter()).inBounds) {
actor.airborne = true;
}
else {
actor.y = oldY;
// handle case where player has fallen to the ground
// if vy is less than zero, this means the player has just
// hit the ceiling, in which case we can simply leave
// this.y as oldY to prevent the player from going
// past the ceiling
if (actor.vy > 0) {
while (level.getZoneInfo(actor.getCenter()).inBounds) {
actor.y++;
}
actor.y--;
actor.vy = 0;
actor.airborne = false;
}
}
};
15. 定义updateActorX()方法,该方法更新角色的x轴位置:
Model.prototype.updateActorX = function(actor) {
var anim = this.controller.anim;
var level = this.level;
var oldX = actor.x;
var changeX = actor.playerSpeed * (anim.getTimeInterval() / 1000);
if (actor.moving) {
actor.facingRight ? actor.x += changeX : actor.x -= changeX;
}
if (!level.getZoneInfo(actor.getCenter()).inBounds) {
actor.x = oldX;
while (level.getZoneInfo(actor.getCenter()).inBounds) {
actor.facingRight ? actor.x++ : actor.x--;
}
// 重新定位到边界内最近的位置
actor.facingRight ? actor.x-- : actor.x++;
}
};
16. 定义nearby()方法,该方法确定两个角色是否彼此临近:
Model.prototype.nearby = function(actor1, actor2){
return (Math.abs(actor1.x - actor2.x) < actor1.attackRange) && Math.abs(actor1.y - actor2.y) < 30;
};
工作原理
在MVC架构中,模型被认为是架构的“肉”,因为它代表数据层。因为Canvas Hero是一个游戏,我们的数据由英雄、坏人、关卡、生命值条等对象组成,其中的每个对象都包含一些属性,这些属性必须在每个动画帧被更新和访问。
Canvas Hero的模型有三个关键职责:
- 初始化游戏中的对象
- 更新游戏中的对象
- 处理坏人的人工智能
完全可以说,我们的模型中最有趣的方法是moveBadGuys()方法,该方法可以被认为是我们游戏引擎的“人工智能”。我把“人工智能”放在引号中,老实说,是因为Canvas Hero中的坏人都是笨蛋。moveBadGuys()方法遍历所有的坏人对象,并使用Level对象的getZoneInfo()方法,确定他们是否离墙很近,如果他们将要撞墙了,再改变它们的方向。
了解更多
如果你想创建一个更有挑战性的游戏,你可能考虑增强moveBadGuys()方法,给坏人增加跳跃或使用悬浮舱的能力。
更多参考
- 第5章 创建Animation类