对于雷电这样的游戏,偶是从来都不碰的,我曾经以每10秒一个币的速度,在当地所有的地痞,流氓,富二代的面前,将雷电拖到第5关,通常我投完币后,就会放一个大招,然后用我自己的身躯,堵住敌人所有的子弹,接着重复上面的操作,我的反应极为灵敏,就算屏幕上只有一颗远离我的子弹,我也会主动碰上去。每当我堆币的时候,身后的围观群众都会羡慕的说“这家伙真是个2b”。所以我还是比较喜欢打那些,就算没人操作也能存活两分钟以上的游戏,比如三国志什么的。。
随着学习的深入,偶教程中的蒸馏水将会越来越少,货也会越来越干,请大家自备饮料解渴。
上一篇教程中,我们已经实现了自己飞机的发射,和敌人飞机的追踪效果,但是我们的子弹打不中敌机,始终让人感觉不爽,今天我们来给子弹加上碰撞检测,让敌机不再得瑟!
首先我们给敌机和子弹加上一个destroy()销毁函数,当他们超出屏幕范围的时候,就销毁他们释放资源,否则这些子弹会一直留在游戏中,随着时间的推移,游戏就会越来越卡,当然我们的飞机就不需要加上销毁函数了,开什么玩笑?我自己做的游戏还能被敌人击中?我的游戏我做主!
首先在Bullet类中加入这两个函数
//检测子弹是不是应该被销毁
private void checkDead(){
boolean outHeight= this.y>GameHandler.FRAME_HEIGHT||this.y<0;
//如果高度越界就销毁
if(outHeight)destroy();
}
//销毁自己,释放资源
public void destroy(){
//在集合类中移除自己,这样就没有任何对象有对此实例的引用了
//jvm的gc会帮我们自动回收实例,并且释放资源
GameHandler.bulletList.remove(this);
//让图像到窗口外面去
this.y =1000;
}
同理在Enemy类中也一样
//检测是不是应该被销毁
private void checkDead(){
boolean outHeight= this.y>GameHandler.FRAME_HEIGHT;
//如果高度越界就销毁
if(outHeight)destroy();
}
//销毁自己,释放资源
public void destroy(){
//在集合类中移除自己,这样就没有任何对象有对此实例的引用了
//jvm的gc会帮我们自动回收实例,并且释放资源
GameHandler.enemyList.remove(this);
//让图像到窗口外面去
this.y =1000;
}
然后我们分别在他们的update逻辑循环中加入checkDead()函数
public void update(){
//每帧追踪敌机
this.trackMyPanel();
this.checkDead();
this.fire();
}
好,下面我们开始搞碰撞检测,碰撞检测其实是游戏AI里面很重要的一块,后面我们会单独拿出一期,详细的讲,这里暂时只是先介绍一个最简单的碰撞检测,敌机和子弹的碰撞检测,我们可以抽象的看成是判断两个矩形有没有相交,因为之前我们画圆,也是用矩形的内切圆来画的,所以这里我们首先写一个判断两个矩形是否相交的函数,我们把这类需要计算的函数,单独放到一个Calculate类中,以放便各个类的调用,和以后的复用。
package planet;
public class Calculate {
/**
* 判断两个矩形是否相交
* 参数为两个矩形的左上角坐标和其长宽
* @return 返回true表示碰撞成功了
*/
public static boolean isRectCollision(int x1,int y1,int width1,int height1,
int x2,int y2,int width2,int height2){
return x2 + width2 > x1 && x2 < x1 + width1
&& y2 + height2 > y1 && y2 < y1 + height1;
}
}
上面这个就是判断矩形是否相交的函数了,原理很简单,大家可以自己画个小图理解一下,不理解也没关系,你可以直接拿来用!
下面我们需要在子弹类中加入与敌机的碰撞检测!由于需要用到,子弹的长和宽以及敌机的长和宽的数值,我们把他们提取出来写成这样的静态常量:
public static final int ENEMY_WIDTH =50,ENEMY_HEIGHT =50;
我们每帧循环检测一颗子弹与所有敌机的是否碰撞,如果碰撞了就把自己这颗子弹和被碰撞的敌机都给销毁,既然是每一帧都要检测的函数,当然照例还是要放进update()里面的,这里我就不写了。
//遍历检测与敌机的碰撞
private void checkEnemyCollision(){
//检测这颗子弹与每一辆敌机是否碰撞
for (int i =0;i<GameHandler.enemyList.size();i++){
boolean result =this.judgeEnemyCollision(GameHandler.enemyList.get(i));
//如果有碰撞则跳出循环
if(result)break;
}
}
//判断这颗子弹与某一敌机是否碰撞
private boolean judgeEnemyCollision(Enemy enemy){
if (Calculate.isRectCollision((int)this.x,(int)this.y, this.rdius, this.rdius,
(int)enemy.x, (int)enemy.y, enemy.ENEMY_WIDTH, enemy.ENEMY_HEIGHT)){
//如果碰撞的话就销毁自己和敌机
this.destroy();
enemy.destroy();
return true;
}
return false;
}
好,现在大家可以运行一下程序测试一下,我们已经可以一枪一个的很轻松的戏谑敌机了! 只是,这敌机,只能傻乎乎的往我们的枪口上撞,似乎有欺负人之嫌,恩,说的对,我们也要让敌机可以打子弹,这样吧,我们让敌机每隔一秒钟朝我们打一颗子弹。
首先我们要先在敌机类中加入一个1s的计时器
//这里设置一秒钟产生1颗子弹
private Timer fireTimer =new Timer(GameHandler.FPS);
然后将MyPlanet中的fire函数复制过去,照例将fire()函数加入update()中
public void fire(){
//产生一颗子弹,位置就在敌人飞机的正前方
if(this.fireTimer.act())
//这里的+20和+55用来调整子弹的初始位置,让它从飞机的正前方打出来
GameHandler.bulletList.add(new Bullet(this.x+20,this.y+55));
}
之后运行,你就可以看到一个很搞笑的现象,就是敌机被自己打出来的子弹打死了,而且子弹是往上面飞的,这是由于我们的子弹现在还没有办法区分敌我,现在还是按之前,我们飞机打出子弹来设定的,因此我们这里给Bullet类的构造函数增加一个新的三个值的构造函数,增加一个isEnemy属性,用来判断是不是敌人飞机打出来的子弹
//判断子弹是由谁打出来的,默认是主角打出来的
public boolean isEnemy =false;
//子弹的长和宽
public Bullet(double x,double y){
this.x =x;
this.y = y;
//将此类加入GameHandler的子弹集合
GameHandler.bulletList.add(this);
}
public Bullet(double x,double y,boolean isEnemy){
this(x, y);
//判断子弹是谁发出来的
this.isEnemy=isEnemy;
}
然后我们在上面的fire()函数中调用新造的构造函数
//这里的+20和+55用来调整子弹的初始位置,让它从飞机的正前方打出来
GameHandler.bulletList.add(new Bullet(this.x+20,this.y+55,true));
然后我们要在Bullet类中的碰撞检测函数中,判断当前子弹是不是敌机打出来的,如果是敌机打的就不进行碰撞检测了
//遍历检测与敌机的碰撞
private void checkEnemyCollision(){
//如果这颗子弹是敌机打出来的,那么不进行与敌机的碰撞检测
if(this.isEnemy)return;
//检测这颗子弹与每一辆敌机是否碰撞
for (int i =0;i<GameHandler.enemyList.size();i++){
boolean result =this.judgeEnemyCollision(GameHandler.enemyList.get(i));
//如果有碰撞则跳出循环
if(result)break;
}
}
好,这时运行,敌机已经可以向我们打子弹了!shit,敌人的子弹竟然和我的一样快,这怎么可以!给他们的子弹减速!
public void update(){
this.checkDead();
this.checkEnemyCollision();
if (!this.isEnemy)
//如果这颗子弹是我打出来的就让它,按正常速度往上飞
this.y-=speed;
else
//如果是敌机打的,就让它半速往下降
this.y+=speed/2;
}
恩,现在运行起来,感觉好多了,不但能戏谑敌机,还不怕别人说闲话了!有的码农朋友要问了,我们是不是也要同理,把敌人子弹和我方飞机的碰撞检测也做一下啊?不然像现在这样,不管敌人的子弹有多少始终都打不中我们。。。
对于提出这样疑问的码农朋友,我只能语重心长的说一句,让你二大爷放学回家的时候小心一点!。。。
ok今天的主要功能已经完成了,但是还是有一点小小的瑕疵,敌人一碰到子弹就挂了,打击感比较欠缺,而且直接消失也比较突兀,而且敌我都一样,子弹也一样,不好区分,下面我们一一解决这些问题,首先我们给敌机加上生命值,只有碰撞的子弹达到一定数量的时候我们才让敌机消失。
首先我们给Enemy类加入一个生命值属性 public int life=3;
然后我们在Enemy中加入一个 hited函数
public void hited(){
//如果没有血了,就开始爆炸动画
if(--this.life<0)this.destroy();
}
然后我们修改Bullet类中的碰撞检测函数,让他碰撞的时候,改为调用hited函数。
这样我们再运行一下,这时候我们就需要打三下,才能消灭一架敌机了,接着我们为敌机用画圆的方式,模拟一下被击中爆炸的效果
我们为Enemy加入几个状态属性,用来模拟一个从小到大的圆
//是否爆炸状态,是否爆炸结束状态
public boolean isExplode=false,isExplodeDead =false;
//爆炸半径,最大爆炸半径
private int explodeRadius=0,explodeMaxRadius=125;
在draw函数中我们这样写
public void draw(Graphics g){
g.setColor(Color.ORANGE);
//如果爆炸状态开启,就开始画爆炸的效果
if(this.isExplode){
//画一个圆圈表示爆炸效果
g.drawOval((int)x-40,(int)y-40, explodeRadius, explodeRadius);
//每一帧让圆的半径增大25
this.explodeRadius+=25;
if(this.explodeRadius>this.explodeMaxRadius)this.isExplodeDead=true;
return;
}
//画一个矩形表示敌机
g.fillRect((int)this.x, (int)this.y, ENEMY_WIDTH, ENEMY_HEIGHT);
}
相应修改hited函数和destroy()函数,让他们在爆炸效果播放完之后再销毁
//检测是不是应该被销毁
private void checkDead(){
boolean outHeight= this.y>GameHandler.FRAME_HEIGHT;
//如果高度越界就销毁
if(outHeight)destroy();
//如果爆炸效果结束了,那么也销毁
if(this.isExplodeDead)this.destroy();
}
public void hited(){
//如果没有血了,就开始爆炸动画
if(--this.life<0)this.isExplode=true;
}
ok最后我们修改一下双方敌机和子弹的颜色,修改颜色的语法是这样的 g.setColor(Color.ORANGE);
好了现在我们的游戏看起来已经像模像样了!
游戏源码:http://download.csdn.net/detail/azhangzhengtong/5223888