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 来实现循环刷新画布,通过修改各元素位置坐标值,在下一次画布重绘时更新视图,这是阶段交互的基本逻辑;
到这里已经实现了坦克移动和发射子弹的效果,下篇将介绍最关键的子弹和物体碰撞的实现逻辑。