好家伙,本篇介绍敌机
好了,按照惯例我们来理一下思路:
我们有一个敌机类,第一步当然是实例一个敌机对象,
然后我们把这个敌机放入我们的敌机群(敌机数组)
然后是熟悉的移动和绘制
那我们回顾一下子弹的生成逻辑
变量: 子弹 bullet 弹夹(用来装子弹的东西)bulletList[]
方法:装填子弹 绘制子弹 移动子弹
子弹发射的物理逻辑是很简单的:
生产第一个子弹,推入弹夹中,绘制弹夹(即绘制弹夹中的所有子弹),
生产第二个子弹,同样推入弹夹,移动第一颗子弹(应该说是改变第一颗子弹的y坐标),绘制弹夹中的所有子弹
。。。。。。
生产第n个子弹,推入弹夹中,改变第n-1颗子弹的Y坐标,绘制弹夹中的所有子弹
有没有感觉到两者逻辑的相似之处
(像啊,太像了)
子弹和敌机的处理,本质上是用的是同一套逻辑
那么,开始干活:
1.配置项
这里我们会用到两种类型的配置项E1和E2
(因为我们有两种类型的敌人,大敌机和小敌机,其中e1为小敌机(血少),e2为大敌机(血厚))
先设置一个数组存放图片资源
//e1用于存放小敌机的图片素材 const e1 = { live: [], death: [], } e1.live[0] = new Image(); e1.live[0].src = "img/enemy1.jpg" e1.death[0] = new Image(); e1.death[0].src = "img/enemy1_boom1.jpg" e1.death[1] = new Image(); e1.death[1].src = "img/enemy1_boom2.jpg" e1.death[2] = new Image(); e1.death[2].src = "img/enemy1_boom3.jpg" //e2用于存放小敌机的图片素材 const e2 = { live: [], death: [], } e2.live[0] = new Image(); e2.live[0].src = "img/enemy2.jpg" e2.death[0] = new Image(); e2.death[0].src = "img/enemy2_boom1.jpg"
(图片素材来自网络)
2.敌机配置项
//小敌机 const E1 = { type: 1, width: 57, height: 51, life: 1, //少点血,一下打死 score: 1, frame: e1, minSpeed: 20, maxSpeed: 10, } //大敌机 const E2 = { type: 2, width: 69, height: 95, life: 2, frame: e2, minSpeed: 50, maxSpeed: 20, }
minSpeed: 50, maxSpeed: 20, 值得说明一下,这两个玩意是为了弄敌机的随机速度(更刺激一点,但实际上好像没什么感觉) 关于如何弄到一个”随机速度“,接着往下看
3.敌机类
class Enemy { constructor(config) { //敌机类型 this.type = config.type; //敌机宽,高 this.width = config.width; this.height = config.height; //敌机的初始化位置 this.x = Math.floor(Math.random() * (480 - config.width)); //这里我们让飞机从头部开始渲染,所以Y轴坐标自然是飞机高度的负值 this.y = -config.height; //敌机生命 this.life = config.life; //敌机分数 this.score = config.score; //敌机图片库 this.frame = config.frame; //此刻展示的图片 this.img = null; //活着的证明 this.live = true; // this.minSpeed = config.minSoeed; // this.maxSpeed = config.speed; //随机去生成一个速度 this.speed = Math.floor(Math.random() * (config.minSpeed - config.maxSpeed + 1)) + config.maxSpeed; //最后渲染的时间 this.lastTime = new Date().getTime(); } //移动敌机 move() { const currentTime = new Date().getTime(); // if (currentTime - this.lastTime >= this.speed) { // console.log("此处为this.frame"+this.frame.live[0]); this.img = this.frame.live[0]; this.y++; //时间修正 this.lastTime = currentTime; } } //渲染敌机方法 paint(context) { // console.log("此处为this.img"+this.img); if(this.img !=null){ context.drawImage(this.img, this.x, this.y); } } }
3.1.随机速度
先浅浅的说明一下
随机数方法 Math.random
这玩意会在[0,1)也就是在0到1之间取一个值
然后问题来了,这是一个半开半闭区间,也就是说它会取到0但是不会取到1
this.speed = Math.floor(Math.random() * (config.minSpeed - config.maxSpeed + 1)) + config.maxSpeed;
在这里我们要取的是一个10到20之间的速度由于我们向下取整
Math.floor(Math.random() * (config.minSpeed - config.maxSpeed )) + config.maxSpeed;
必然只能取得10-19之间的数
于是我们在(config.minSpeed - config.maxSpeed )中加一
变成(Math.random() * (config.minSpeed - config.maxSpeed +1))
(聪明的你一定能很快想明白,而愚蠢的我想了很久才想明白)
3.2.敌机的移动方法
move() { const currentTime = new Date().getTime(); // if (currentTime - this.lastTime >= this.speed) { // console.log("此处为this.frame"+this.frame.live[0]); this.img = this.frame.live[0]; this.y++; //时间修正 this.lastTime = currentTime; } }
移动同样的用时间判定的方式去控制速率
现在和过去的时间差大于速度,更新地址
3.3.渲染方法
paint(context) { // console.log("此处为this.img"+this.img); if(this.img !=null){ context.drawImage(this.img, this.x, this.y); } }
嗯,非常好理解了,多加的一个if是为了防止出现空img导致报错
4.全局函数(生产敌机)
//以下三项均为全局变量 const enemies = []; //敌机产生的速率 const ENEMY_CREATE_INTERVAL = 2000; let ENEMY_LASTTIME = new Date().getTime(); //全局函数 用于生产敌机 function createComponent() { const currentTime = new Date().getTime(); const forenemyTime = new Date().getTime(); //一手经典判断 if (currentTime - ENEMY_LASTTIME >= ENEMY_CREATE_INTERVAL) { //当时间满足 实例化一架敌机 放入敌机数组中 // 小飞机 70% 中飞机30% //用随机数去弄概率 //[0,99] //Math.random()=>[0,1)*100 //EnemyTypeRandom产生的随机数用于判断产生不同的飞机 let EnemyTypeRandom = Math.floor(Math.random() * 100); if (EnemyTypeRandom > 70) { enemies.push(new Enemy(E1)); } else if (EnemyTypeRandom < 30) { enemies.push(new Enemy(E2)); } console.log(enemies); //更新时间 ENEMY_LASTTIME = currentTime; } }
这里同样的,我们用随机数去控制出现大/小敌机的概率
(E1,E2分别是大小敌机的配置项)
let EnemyTypeRandom = Math.floor(Math.random() * 100); if (EnemyTypeRandom > 70) { //产小敌机 enemies.push(new Enemy(E1)); } else if (EnemyTypeRandom < 30) { //产大敌机 enemies.push(new Enemy(E2)); }
你细品,这个控制得还是非常巧妙的
5.全局函数渲染
到这里就非常简单了
这里也揭开了前面的谜底
因为敌机生成和子弹生成的逻辑太过相似
所以我们把他们放到同一个全局函数是一个非常明智的选择
//全局函数 来移动所有的子弹/敌人组件 function judgeComponent() { console.log("judge被触发"); for (let i = 0; i < hero.bulletList.length; i++) { hero.bulletList[i].move(); } for(let i=1;i<enemies.length;i++){ enemies[i].move(); } } //全局函数 来绘制所有的子弹/敌人组件 function paintComponent() { for (let i = 0; i < hero.bulletList.length; i++) { hero.bulletList[i].paint(context); } for(let i=1;i<enemies.length;i++){ enemies[i].paint(context); } }
6.方法调用
case RUNNING: sky.judge(); sky.paint(context); //加载主角 hero.paint(context); hero.shoot(); createComponent(); //子弹发射 judgeComponent(); paintComponent(); deleteComponent(); // context.drawImage(hero_frame.live[0], 0, 0); break;
ok,来看看效果吧:
确实是非常地nice啊