HTML5 Canvas实战
8.7 创建Model类
阅读(

路径和文本

图形及组合

处理图像和视频

画布变换

动画给画布带来生机

与画布交互:为图形和区域附加事件监听器

创建图表

通过游戏开发来拯救世界

创建英雄和敌人的精灵表

创建关卡图像和边界地图

创建英雄和敌人的Actor类

创建Level类

创建Health Bar类

创建Controller类

创建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类

如果本教程对您帮助很大,请随意打赏。您的支持,将鼓励我们提供更好的教程!

← 键盘方向键翻页 →
返回顶部 手机访问 关注微信 返回底部

扫码访问歪脖网

随时随地,想看就看

关注歪脖网微信

分享 web 知识、交流 web 经验