关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

vue3 + canvas 实现坦克大战(一)(下)

发布时间:2023-06-26 21:00:11

BattleCity 构造函数


BattleCity 构造函数定义坦克的各种配置信息,和方法函数


let TankConfig = function (cfg) {  this.explosion_count = cfg.explosion_count  this.width = cfg.type.dimension[0]  this.height = cfg.type.dimension[1]  this.missle_type = cfg.missle_type || MISSILE_TYPE.NORMAL  this.x = cfg.x || 0  this.y = cfg.y || 0  this.direction = cfg.direction || DIRECTION.UP  this.is_player = cfg.is_player || 0  this.moving = cfg.moving || 0  this.alive = cfg.alive || 1  this.border_x = cfg.border_x || 0  this.border_y = cfg.border_y || 0  this.speed = cfg.speed || TANK_SPEED  this.direction = cfg.direction || DIRECTION.UP  this.type = cfg.type || TANK_TYPE.PLAYER0 }

   


实现坦克的移动


用键盘的 W、S、A、D、来表示上下左右方向键,按下键盘则会触发对应坦克实例的 move 函数,用于计算移动后的位置坐标信息,注意:对边界条件的判断,不可使其超出战场边界。


CanvasSprite.prototype.move = function (d, obstacle_sprites) {  this.direction = d  switch (d) {  case DIRECTION.UP:  if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {  this.y -= this.speed  if (this.y <= 5) {  if (!this.out_of_border_die) {  this.y = 0  } else {  // this.alive = 0;  this.explode()  document.getElementById('steelhit').play()  }  }  }  break  case DIRECTION.DOWN:  if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {  this.y += this.speed  if (this.y + this.height >= this.border_y - 10) {  if (!this.out_of_border_die) {  this.y = this.border_y - this.height  } else {  // this.alive = 0;  this.explode()  document.getElementById('steelhit').play()  }  }  }  break  case DIRECTION.LEFT:  if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {  this.x -= this.speed  if (this.x <= 5) {  if (!this.out_of_border_die) {  this.x = 0  } else {  // this.alive = 0;  this.explode()  document.getElementById('steelhit').play()  }  }  }  break  case DIRECTION.RIGHT:  if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {  this.x += this.speed  if (this.x + this.width >= this.border_x - 10) {  if (!this.out_of_border_die) {  this.x = this.border_x - this.width  } else {  // this.alive = 0;  this.explode()  document.getElementById('steelhit').play()  }  }  }  break  }  }

   



坦克发射子弹的逻辑


首先需要定义子弹的配置信息以及构造函数;


let MissileConfig = function (cfg) {  this.x = cfg.x  this.y = cfg.y  this.type = cfg.type || MISSILE_TYPE.NORMAL  this.width = cfg.width || this.type.dimension[0]  this.height = cfg.height || this.type.dimension[1]  this.direction = cfg.direction || DIRECTION.UP  this.is_from_player = cfg.is_from_player  this.out_of_border_die = cfg.out_of_border_die || 1 // 判断边界类型  this.border_x = cfg.border_x || 0  this.border_y = cfg.border_y || 0  this.speed = cfg.speed || TANK_SPEED  this.alive = cfg.alive || 1 }  var Missile = function (MissileConfig) {  var x = MissileConfig.x  var y = MissileConfig.y  var width = MissileConfig.width  var height = MissileConfig.width  var direction = MissileConfig.direction  this.type = MissileConfig.type  this.is_from_player = MissileConfig.is_from_player || 0  var explosion_count = 0  CanvasSprite.apply(this, [  {  alive: 1,  out_of_border_die: 1,  border_y: HEIGHT,  border_x: WIDTH,  speed: MISSILE_SPEED,  direction: direction,  x: x,  y: y,  width: width,  height: height,  },  ])  this.isDestroied = function () {  return explosion_count > 0  }  this.explode = function () {  if (explosion_count++ === 5) {  this.alive = 0  }  }  this.getImg = function () {  if (explosion_count > 0) {  return {  width: TANK_EXPLOSION_FRAME[explosion_count].dimension[0],  height: TANK_EXPLOSION_FRAME[explosion_count].dimension[1],  offset_x: TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[0],  offset_y: TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[1],  }  } else {  return {  width: width,  height: height,  offset_x: this.type.image_coordinates[0],  offset_y: this.type.image_coordinates[1],  }  }  }  this.getHeadCoordinates = function () {  var h_x, h_y  switch (this.direction) {  case DIRECTION.UP:  h_x = this.x + this.width / 2 - this.type.dimension[0] / 2  h_y = this.y - this.type.dimension[1] / 2  break  case DIRECTION.DOWN:  h_x = this.x + this.width / 2 - this.type.dimension[0] / 2  h_y = this.y + this.height - this.type.dimension[1] / 2  break  case DIRECTION.LEFT:  h_x = this.x  h_y = this.y + this.width / 2 - this.type.dimension[0] / 2  break  case DIRECTION.RIGHT:  h_x = this.x + this.height  h_y = this.y + this.width / 2 - this.type.dimension[0] / 2  }  console.log({  x: h_x,  y: h_y,  })  return {  x: h_x,  y: h_y,  }  }  this._generateId = function () {  return uuidv4()  }  sprites[this._generateId()] = this  }

   


然后再定义一个 fire 开发函数,当开火后,会使用 window.requestAnimationFrame() 来达到循环的效果,每次重绘最新的位置信息


this.fire = function (boolean_type) {  if (!this.missle || !this.missle.alive) {  var coor = this.getCannonCoordinates()  this.missle = new Missile(  new MissileConfig({  x: coor.x,  y: coor.y,  direction: this.direction,  type: this.miss_type,  is_from_player: boolean_type,  })  )  if (boolean_type) {  document.getElementById('shoot').play()  }  }  }

   



总结


利用 requestAnimationFrame 来实现循环刷新画布,通过修改各元素位置坐标值,在下一次画布重绘时更新视图,这是阶段交互的基本逻辑;

到这里已经实现了坦克移动和发射子弹的效果,下篇将介绍最关键的子弹和物体碰撞的实现逻辑。


/template/Home/leiyu/PC/Static