好家伙,本篇是带着遗憾写完的。
很遗憾,我找了很久,找到了bug但并没有成功修复bug
再上一篇中我们看到
子弹射中了敌机,但是敌机并没有消失,所以这篇我们要来完善这个功能
按照惯例我们来捋一下思路:
看看表面的物理逻辑:
子弹击中敌机=>敌机爆炸=>敌机消失=>子弹消失
敌机爆炸=>敌机消失=>子弹消失
这三步的实现都非常简单
无非就是:
敌机爆炸:改变图片,渲染,
敌机消失:在敌机数组中删除被子弹击中的敌机
子弹消失:在子弹数组中删除击中敌机的子弹
我们重点来讲一讲如何判定子弹击中敌机
我用到的方法是一个非常粗暴的枚举
我们用两个for循环,去遍历所有的敌机和子弹
for (let i = 0; i < enemies.length; i++) { //遍历所有的子弹 for (let j = 0; j < hero.bulletList.length; j++) { //一个检测是否碰撞的方法 dosomething(); //如果碰到了才做某些事情 if (dosomething()) { //一个删除子弹和敌机的方法 dosomething(); dosomething(); } } }
在第二个for循环中,我们检测一架敌机是否与所有的子弹中的任意一颗有接触
有接触我们就把这个敌机和子弹删掉
大概是这么个思路,碰撞的具体判定方法我们放到下面去说
开干
1.全局碰撞检测函数
function checkHit() { // for (let i = 0; i < enemies.length; i++) { //遍历所有的子弹 for (let j = 0; j < hero.bulletList.length; j++) { //用第i架敌机去和第j颗子弹碰撞,返回的是一个布尔类型 enemies[i].hit(hero.bulletList[j]); console.log(enemies[i].hit(hero.bulletList[j])); //如果碰到了才做某些事情 if (enemies[i].hit(hero.bulletList[j])) { //清除敌机和子弹 //导向一个(控制爆炸渲染)变量方法 enemies[i].collide(); // hero.bulletList[j].collide(); } } } }
方法调用写上
case RUNNING: sky.judge(); sky.paint(context); //加载主角 hero.paint(context); hero.shoot(); createComponent(); //子弹发射 judgeComponent(); paintComponent(); deleteComponent(); checkHit(); // context.drawImage(hero_frame.live[0], 0, 0); break;
2.检测函数hit
我们把这个检测方法hit写在敌机enemy类中,然后我们把子弹当做参数传过去,
这样我们就可以在这个方法中同时处理敌机和子弹了
//检测敌机是否有撞到其他物体(子弹/hero) //敌机e //子弹o hit(o) { console.log("hit方法被触发"); //其他物体的左边 let ol = o.x; //其他物体的右边 let or = o.x + o.width; //其他物体的上边 let ot = o.y //其他物体的下边 let ob = o.y + o.height; //子弹的左边 let el = this.x; //子弹的右边 let er = this.x + this.width; //子弹的上边 let et = this.y; //子弹的下边 let eb = this.y + this.height; //判断是否有碰到 if (ol > er || or < el || ot > eb || ob < et) { //没有碰到 return false; } else { //碰到了 return true; //转到 } }
这里来讲一下碰撞判定的逻辑
理论上来说,我们分别拿到敌机和子弹的上下左右边
随后进行一个判断
如果子弹的左边大于敌机的右边,
子弹的右边小于敌机的左边,
如此类推
如此类推
那么子弹和飞机就没碰撞,
否则两者碰撞碰,碰撞后返回true,使我们在chickHit方法中调用collide()方法
3.collide()方法
这个方法被写在敌机enemy类中
collide() { //中弹后,减少生命值 console.log("collide方法被触发"); this.life--; //没血之后,机毁人亡 if (this.life === 0) { //1.将live标识标记为死亡状态 //2.播放死亡东湖 //3.播放死亡动画完毕后才有真正的销毁这架飞机 this.life = false; console.log(this.life); } }
我们有一个方法在"监视"life这个属性
当这个属性变为false时,我们开始播放敌机爆炸的动画
4.move()方法
这个方法被写在enemy类中
由于我们的move方法是一直在被调用的,所以我们可以把"敌机爆炸动画"放在这里
(这里只是改变图片,渲染放到别的方法里面去了,你应该能懂我意思)
并在动画结束后销毁敌机
move() { const currentTime = new Date().getTime(); // if (currentTime - this.lastTime >= this.speed) { //如果飞机活着 //this.live由collide if (this.live) { this.img = this.frame.live[0]; this.y++; //时间修正 this.lastTime = currentTime; } else { //死的时候播放死亡动画 0 1 2 3 //活着 爆炸中 死亡 this.img = this.frame.death[this.deathIndex++]; //如果死亡动画播放完毕之后就要销毁这家敌机 if (this.deathIndex === this.frame.death.length) { this.destory = true; console.log(this.destory); } //修正上一次时间 this.lastTime = currentTime; } } }
在最后我们是destory属性转为true
5.全局敌机/子弹删除函数
我们到这一步才真正销毁敌机
//全局函数 来销毁所有的子弹/敌人组件 function deleteComponent() { for(let i =0;i<hero.bulletList.length;i++){ //删除多余子弹 if(hero.bulletList[i].y<0){ hero.bulletList.splice(i,1); } } console.log("deleteComponent方法被触发"); for (let i = 0; i < enemies.length; i++) { //如果敌机处于一种销毁状态 if (enemies[i].destory) { //此处才是真正的销毁函数 enemies.splice(i, 1); console.log(enemies); } } }
有人会问了:为什么不直接在move()方法后面,在动画结束之后,直接销毁敌机呢?
因为如果直接这样写的话,销毁敌机这一步确实变得方便了,但其实我们还要照顾到子弹的销毁,
所以我们把敌机的销毁放到这个全局方法中,是一个省事的选择
大概就是这样了,
但很可惜,有bug,且浏览器没报错,目前查到是判定那块出了问题
接着修bug吧,
效果图暂无