先上效果图
在上个版本中,如果敌机出现多个之后就会出现卡顿闪屏的问题,大大降低了游戏体验感。这次采用一个非常好的方法来解决这个问题并继续完善各个功能。
队列思想
在上个版本中,我们飞机,子弹,敌方飞机,敌方子弹,一共开启了四个线程,而在每个线程中都有Thread.sleep()让线程休眠,这样就会出现卡顿现象。
在进阶版本中,我们这些对象都继承一个父类,然后建立一个关于父类的总队列,里面专门放所有出现的对象,然后我们在一个死循环中一直循环取出队列中的对象并调用所有的方法,这样就巧妙的解决了卡顿的问题。
缓存思想
当我们利用普通画笔的时候是实时进行画图,当我们利用一个画笔同时在画飞机子弹类和背景类的时候,就会出现延迟,即当我在用画笔画背景的时候,此时没有画笔在画飞机,这个时候飞机就会消失,因为时间间隔很短,所以也就是所谓的闪屏,会出现图片的闪动。
而利用缓存画笔则巧妙的解决了这个问题,这个相当于一次性让画笔画完一整张图,然后再一次性呈现出来,这样就解决了闪屏。
1:先建立缓存图片
2:让缓存图片获得一个画笔
3:将这个画笔传到所有需要用到画笔的方法中
4:最后利用界面的画笔来画出缓存图片
private ArrayList<Entity> list=new ArrayList();
//建立背景对象,加入总队列
Background bk=new Background();
list.add(bk);
//建立已方战机对象,加入总队列
Hero hero=new Hero(x1,y1);
list.add(hero);
//为界面加键盘监听器
Mouse mouse=new Mouse(x1,y1,list,hero);
jf.addKeyListener(mouse);
//建立检测碰撞对象,加入总队列
Remove remove=new Remove(list, hero);
list.add(remove);
while(true){
//建立缓存图片
BufferedImage Bimage=new BufferedImage(jf.getWidth(),jf.getHeight(),BufferedImage.TYPE_INT_RGB);
//让缓存图片获得画笔
Graphics Ggraphics=Bimage.getGraphics();
for(int i=0;i<list.size();i++){
Entity entity=list.get(i);
//将缓存画笔传进去,利用缓存画笔一次性画完一张图
entity.draw(Ggraphics);
entity.move();
entity.remove();
}
//再利用界面画笔将缓存图片画出来
g.drawImage(Bimage, 0,0, null);
//控制进度
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
多态
因为我们要把所有对象都要加到一个总队列中,而加入队列的条件则是需要对象所属的类别相同,所以当所有的类都继承一个父类之后,其所属的类别也就能够加入到父类的总队列中。在父类中将所有子类都要用到的方法全部都定义上,在子类中进行方法重写即可。
import java.awt.Graphics;
public interface Entity {
public int type();
public void move();
public void draw(Graphics g);
public void remove();
// public void PlayBackGroundMusic();
}
定时器
由于我们采用了循环队列的方式来代替多个线程,那么我们就不能再用创造线程的方法来专门做一个线程产生敌机,所以这里就使用了定时器。引用java.util中Timer和TimerTask,
Timer timer=new Timer();
timer.schedule(new TimerTask() {},时间,时间);
先创建定时器对象,调用schedule方法,TimerTask(){}则是方法体,里面写具体要实现的方法,后面两个一个是第一次开始的时间,一个是间隔时间。我们则在这个方法体中创建敌机对象和子弹对象,并且加入到队列中去即可。如果要写boss,则可将时间拉长。
检测碰撞
检测碰撞是写这个游戏中稍微有点难度的地方,不过Java中有一个检测碰撞的方法。
矩形1.intersects(矩形2)
如果两个矩形相互接触则会返回true,否则返回false。所以我们需要从队列中取出各个对象,然后根据对象的大小建立两个矩形,进行比对,
//括号中前两个值为矩形所在的起始位置,后两个值为矩形的长和宽
Rectangle rectenemybullet = new Rectangle(enemybullet.x, enemybullet.y, 50, 25);
Rectangle rectbullet = new Rectangle(bullet.x, bullet.y, 50, 25);
这里还有一个难点,因为我们是将所有的对象都放在一个队列中,所以取出来的时候还要进行类别判断
父类 instanceof 某个子类
判断从父类队列取出来的对象是否属于某个子类类别,如果是则返回true,否则返回false。
当检测到两个对象相互碰撞的时候,就将这两个对象移出队列,这里要注意一下i与j的关系,移出队列相对于队列中对象总数会有减少,所以i与j的大小也要随之减少,不然程序运行到一段时间会出现越界问题。这里刚开始也困扰到了博主。
for (int i = 0; i < list.size(); i++) {
Entity entity = list.get(i);
// 判断是否为英雄子弹
if (entity instanceof Herobullet) {
// 强制转型子类
Herobullet bullet = (Herobullet) entity;
for (int j = 0; j < list.size(); j++) {
Entity enti = list.get(j);
// 判断是否为敌机类
if (enti instanceof Enemy) {
Enemy enemy = (Enemy) enti;
// 判断碰撞
Rectangle rectenemy = new Rectangle(enemy.x, enemy.y, 80, 50);
Rectangle rectbullet = new Rectangle(bullet.x, bullet.y, 50, 25);
if (rectenemy.intersects(rectbullet)) {
//打开开关,出现爆炸图像
enemy.type = 10;
if (enemy.num == 2) {
list.remove(i);
if (i < j)
j--;
else
i--;
list.remove(j);
hero.score++;
}
}
}
// 找到敌方子弹类
if (enti instanceof Enemybullet) {
// 强制转型
Enemybullet enemybullet = (Enemybullet) enti;
// 判断碰撞
Rectangle rectenemybullet = new Rectangle(enemybullet.x, enemybullet.y, 50, 25);
Rectangle rectbullet = new Rectangle(bullet.x, bullet.y, 50, 25);
if (rectenemybullet.intersects(rectbullet)) {
list.remove(i);
if (i < j)
j--;
else
i--;
list.remove(j);
hero.score++;
}
}
if(enti instanceof Boss) {
//强制转型
Boss boss=(Boss)enti;
//判断碰撞
Rectangle rectBoss=new Rectangle(boss.x,boss.y,140,180);
Rectangle rectherobullet=new Rectangle(bullet.x,bullet.y,50,25);
if(rectBoss.intersects(rectherobullet)) {
boss.type=10;
if(boss.num==2) {
boss.blood--;
if(boss.blood<=0) {
list.remove(j);
if(j<i) {
i--;
}
else
j--;
}
if(boss.blood>0) {
list.remove(i);
i--;
}
hero.score+=5;
}
}
}
}
}
}