一、项目主类:
1.Ball(子弹);2.BallListener(监听线程——暂废弃);
3.Config(相关配置);4.DrawPanel(战场类);
5.GameListener(游戏监听)
6.Plane(飞机);7.ThreadControl(程序总控制);
8.WAR_Earth(主界面)
二、结构组成
本项目主要有两大线程组成,一个是子弹线程,另一个则是飞机线程,其中每个线程又分为敌我两个线程,每一种线程对象 对应不同的属性数据。 由于线程任务分配的不规律性,所以 项目在运行时,采用边运行,边监听的的模式,以达到准确的 对每个线程进行操作。
三、项目流程
1.相关配置,其中的配置主要有两种,一种是用来存放线程对象的队列,另一种就是上面提到的战场类的对象,这是考虑到整个流程操作,战场面板贯穿整个流程,所以把它归为配置中。
2.完成主界面,界面主要包括两部分,一是菜单条——主要是对游戏一些的说明,二是战场面板——这是作战的整个范围,另外就是负责对我方飞机属性的一些描述,整个界面采用边界布局。
3.战场类的的处理:由于无论是子弹线程,还是飞机线程,他们在运行时,各自的对象都是一直在不停地运动,所以需要不停地 刷新界面,最终都要经过paint函数,所以在子弹线程和飞机线程中并不负责绘制各自对象,他们只是负责处理数据,整个的绘制都是通过借用队列里的数据在paint函数里完成。
4.监听器,主要负责通过键盘获得触发时间信息,完成对游戏的操作,包括开始、暂停、继续、结束,我方飞机的移动和发子弹。另外在监听器中还包含两个计时器,一个是负责敌方飞机的定时产生,另一个则是负责对界面的定时刷新。
5.飞机线程和子弹线程:发射子弹是飞机的一个属性,所以在飞机线程里包含一个定时发子弹的计时器,以负责子弹的发出。飞机的属性包括生命值、被打爆后的经验值以及速度方向,子弹的属性主要包括伤害值和速度方向。另外每个线程都有控制自己单个线程结束与否的属性。
6.整个线程的总开关,主要实现对整个进程的开始,暂停,继续,结束,以及对数据的清空处理等操作.
四、主要问题:
1.Panel 对象 Warpanel 的大小设置,和画布获取?
分析:在整个项目的开始进行时,对对象的绘制是在各自的线程内部完成的,但由于静态对象在所有对象创建之前被加载,
在游戏监听器的构造器里获得通过Warpanel获得的画笔是空的,但有一点暂时任然不明白,就是我在类DrawPanel的构造器已经通过 setPreferredSize(new Dimension(width, height)); 给对象设定了大小,但从其他类里用getWidth()得到 的却是0,令人费解,后来我又在上面的语句后加上 setSize(new Dimension(width, height));这样问题就解决了,但我不知是什么原因,不知道是不是我把Warpanel放在接口里作为静态对象处理的结果。希望某位大侠帮忙解决一下
2.监听线程达不到一枪一个的效果,直接导致无法计分?
分析:由于线程任务分配的不规律性,所以即使子弹打中对方,但并没有立刻进行数据处理,而是等到分配到了再处理,所以果 断舍弃监听线程,采用边监听,边运行的模式,具体就是在每个线程内部每运行一次就判断一次,然后立刻处理
3.屏幕狂闪?
分析:由于每一个线程对象移动一次 ,就要对整个屏幕进行重绘,加上每次重绘时数据也比较多,而且是整个屏幕重绘,所以程序需要
不断地改变窗体中正在被绘制的图象,造成绘制的缓慢,加剧了闪烁,概括一下就是屏幕的更新方法有问题,所以必须采用双缓冲(原理见附录);
4.飞机的定时产生,会有重叠?
分析:这也算是第二个问题的延续了,由于在飞机产生时位置是随机产生的,所以并不能确保会不会有重叠,解决方法就是 加一句判断语句,但这个问题我可是找了好久,特别是两个敌方飞机完全重叠,完全以假乱真了
五、附录
1. 双缓冲技术的工作原理:先在内存中分配一个和我们动画窗口一样大的空间(在内存中的空间我们是看不到的),然后利用getGraphics()方法去获得双缓冲画笔,
接着利用双缓冲画笔给空间我们想画的东西,最后将它全部一次性的显示到我门的屏幕上.这样在我门的动画窗口上面是显示出来就非常的流畅了.避免了上面的闪烁效果。
2. 双缓冲的使用
它的执行过程是这样的:repaint() 到update()再到paint(),而我们的双缓冲代码就写在update()里。
1)定义一个Graphics对象gOff和一个Image对象offScreenImage。按屏幕大小建立一个缓冲对象给offScreenImage。然后取得offScreenImage的Graphics赋给gOff。此处可以把gOff理解为逻辑上的缓冲屏幕,而把offScreenImage理解为缓冲屏幕上的图象。
2) 在gOff(逻辑上的屏幕)上用paint(Graphics g)函数绘制图象。
3)将后台图象offScreenImage全部一次性的绘制到我们的动画窗口,然后把我们内存中分配的空间窗口关闭调用dispose()方法.
具体代码如下:
Image offScreenImage = null;
if (offScreenImage == null)
offScreenImage = createImage(BLOCK_SIZE * COLS, BLOCK_SIZE * ROWS);
Graphics gOff = offScreenImage.getGraphics();
// 4.调用paint(),将缓冲图象的画笔传入
paint(gOff);
// 5.再将此缓冲图像一次性绘到代表屏幕的Graphics对象,即该方法传入的“g”上
g.drawImage(offScreenImage, 0, 0, null);
3、重载update(Graphics g)实现双缓冲:
这是比较传统的做法。也是实际开发中比较常用的做法。
private Image iBuffer;
private Graphics gBuffer;
//重载paint(Graphics scr)函数:
public void paint(Graphics scr)
{
scr.setColor(Color.RED);
scr.fillOval(90,ypos,80,80);
}
//重载update(Graphics scr)函数:
public void update(Graphics scr)
{
if(iBuffer==null)
{
iBuffer=createImage(this.getSize().width,this.getSize().height);
gBuffer=iBuffer.getGraphics();
}
gBuffer.setColor(getBackground());
gBuffer.fillRect(0,0,this.getSize().width,this.getSize().height);
paint(gBuffer);
scr.drawImage(iBuffer,0,0,this);
}