经过一段时间的小奋斗,终于做出了一个还能玩的多线程小游戏啦!
1:游戏介绍:
游戏一共3关,每关的不同点是敌方坦克的强度不同,玩家坦克用键盘操控(按键说明已写在附件中),每隔一段时间还会出现道具,其余游戏说明也已经写在附件中。
(开始界面)
(游戏运行界面)
2:主要技术要点和学到的经验
(1):技术要点:
技术要点主要有两个:一是多线程的应用,二就是双缓冲技术。关于线程的使用,在这里,我并没有去考虑机器的承受能力(做的比较猥琐),每个坦克一个线程,每个子弹一个线程,每次出现的爆炸效果也是一个线程,同时还有一个局面的监控线程(主要是负责判断游戏是否结束,是否进入下一关,和道具出现),线程之间的关系是:坦克线程(坦克线程在运行过程中判断是否捡到道具,是否可移动)在运行过程中调用开火方法启动一个子弹线程,子弹线程在运行过程中判断是否应该爆炸(是否打中了东西),如果爆炸的话,这启动爆炸效果线程,同时结束自己的生命。下面是关键代码:
// 坦克的run()
public void run() {
while (true) {
setBody();// 设置坦克属性值
if (getProperty() == 0) {
if (level < 2) {
level++;
}// 最高只能生到2级
setBody();
count = 0;
TankClient.properties.remove(0);
} else if (getProperty() == 1) {// 减少玩家剩余生命次数
TankClient.lifeTime--;
TankClient.lab.setText("剩余生命次数:" + TankClient.lifeTime);
TankClient.properties.remove(0);
} else if (getProperty() == 2) {// 炸掉玩家一架坦克
if (TankClient.Client.size() > 0) {
Bang bang = new Bang(TankClient.Client.get(0).getX(),
TankClient.Client.get(0).getY());
TankClient.Visual.add(bang);
bang.start();
TankClient.Client.remove(0);
TankClient.properties.remove(0);
}
}
if (TankClient.tanks.size() == 0) {
break;
}
TankMove();
count++;
if (count == SHOOT) {
//每循环一次,计数变量加一,当等于开火次数时,开火。
Fire();
count = 0;
}
if (Ruined) {
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 子弹的run
public void run() {
while (true) {
if (indoor()) {
if (TankClient.bullets.size() == 0) {
break;
}
Fly();
if (shoot_bride()) {//如果击中砖头
Bang bang = new Bang(this.getX(), this.getY());//启动爆炸效果线程
TankClient.Visual.add(bang);
bang.start();
TankClient.bullets.remove(this);
break;
}
if (shoot_fe()) {//如果击中铁
Bang bang = new Bang(this.getX(), this.getY());//启动爆炸效果线程
TankClient.Visual.add(bang);
bang.start();
TankClient.bullets.remove(this);
break;
}
if (Succeed()) {// 如果击中目标
Bang bang = new Bang(this.getX(), this.getY());//启动爆炸效果线程
TankClient.Visual.add(bang);
bang.start();
TankClient.bullets.remove(this);
// 如果到达死亡水平
if (TankClient.Client.get(0).getLevel() == -1) {
TankClient.Client.remove(0);
}
break;
}
} else {
Bang bang = new Bang(this.getX(), this.getY());//启动爆炸效果线程
TankClient.Visual.add(bang);
bang.start();
TankClient.bullets.remove(this);
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//爆炸效果run(setNum是控制绘制第几张爆炸效果图片,以形成动态的效果)
public void run(){
int i = 0;
setNum(i++);
try {
Thread.sleep(TankClient.FRANK-30);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNum(i++);
try {
Thread.sleep(TankClient.FRANK-30);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNum(i++);
try {
Thread.sleep(TankClient.FRANK-30);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNum(i++);
try {
Thread.sleep(TankClient.FRANK-30);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNum(i++);
try {
Thread.sleep(TankClient.FRANK-30);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNum(i++);
try {
Thread.sleep(TankClient.FRANK-30);
} catch (InterruptedException e) {
e.printStackTrace();
}
TankClient.Visual.remove(this);
}
}
这个是主监控线程的代码:
package TankWarv10;
import java.util.Random;
/**
* 监控线程:监控局面是否结束
*
* @author dell
*
*/
public class Monitor extends Thread {
private int Count = 0;// 计数器,每数100次出现一个道具
private Random ran = new Random();// 随机算子,产生随机道具
public void run() {
while (true) {
Count++;
if (Count == 100) {// 每数100次出现一个道具
int x, y;// 随机坐标
x = ran.nextInt(430) + 50;
y = ran.nextInt(430) + 50;
property tmp = new property(x,y);
int kind = ran.nextInt(3);
tmp.setKind(kind);// 设置道具种类
tmp.start();// 启动道具线程
TankClient.properties.add(tmp);
Count = 0;
}
// 如果我方坦克被摧毁,则游戏结束,清空所有局面
if (TankClient.Client.size() == 0) {
if (TankClient.lifeTime <= 0) {
TankClient.bullets.clear();
TankClient.pb.clear();
TankClient.tanks.clear();
TankClient.Visual.clear();
TankClient.stop = true;
break;
} else {
// 加入我的坦克
TankClient.Client.add(new PlayerTank(250, 480));
TankClient.Client.get(0).start();
TankClient.lifeTime--;
TankClient.lab.setText("剩余生命次数:" + TankClient.lifeTime);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果敌方坦克被摧毁完毕,则游戏结束,清空所有局面
switch (TankClient.mission) {
case 1://如果是第一关
if (TankClient.tanks.size() == 0) {
TankClient.bullets.clear();
TankClient.pb.clear();
TankClient.Visual.clear();
TankClient.clear();
TankClient.mission++;
TankClient.mis.setText("当前关卡:"+TankClient.mission);
TankClient.initBackground();
}break;
case 2://如果是第二关
if (TankClient.Sec_tanks.size() == 0) {
TankClient.bullets.clear();
TankClient.pb.clear();
TankClient.Visual.clear();
TankClient.clear();
TankClient.mission++;
TankClient.mis.setText("当前关卡:"+TankClient.mission);
TankClient.initBackground();
}break;
case 3://如果是第三关
if (TankClient.Tri_tanks.size() == 0) {
TankClient.bullets.clear();
TankClient.pb.clear();
TankClient.Visual.clear();
TankClient.clear();
TankClient.Client.clear();
TankClient.properties.clear();
TankClient.stop = true;
}break;
}
}
}
}
关于双缓冲技术的使用:双缓冲技术我理解也不能算十分深刻,在参见人家的代码以后,还算是能够做到吧。
我自己的理解:双缓冲主要就是去重写update方法,在更新窗体时把下一个要显示的图片整体的画到窗体上去。
下面是引用stchou博客中对闪屏的解释,和对双缓冲的解释:
paint闪烁的原因:
每一个paint的过后,程序会自行的调用repaint的方法,但是repaint方法中的绘制有的分配一个与原来窗口一样的的内存空间,但里面是没有存储东西的,所以一次次的paint,repaint的交替就会产生闪烁
解决方法:双缓冲技术的工作原理:先在内存中分配一个和窗口一样大的空间(在内存中的空间我门是看不到的),然后利用getGraphics()方法去获得该空间并将它全部一次性的显示到屏幕上.这样显示出来就非常的流畅了.避免了闪烁效果.
双缓冲的原理:
1.建立一个Image对象DbBuffer,通过DbBuffer=createrImage(int width,int height)来在内存中开辟一个长为width 宽为heithr空间.次空间的大小可以和你动画窗口的大小保持一致,也可以利用getwidth()和getheight()来获得动画窗口的大小.
2.建立一个Graphics 对象GraImage通过GraImage=DbBuffer.getGraphics();去把要绘制的对象并存放到分配好的内存空间中.
3.利用paint(GraImage);将其全部绘制带内存之中,最后调用我门的paint(Graphics g)方法中的g.drawImage(DbBuffer,0,0,null)将DbBuffer全部一次性的绘制到我门的动画窗口,然后把我门内存中分配的空间窗口关闭调用dispose()方法.
下面是关键代码:
// 重写paint()
public void paint(Graphics g) {
super.paint(g);
if (start) {
// 我方tank
if (Client.size() != 0) {
Client.get(0).drawTank(g);
}
// 敌方坦克
for (int i = 0; i < tanks.size(); i++) {
tanks.get(i).drawTank(g);
}
for (int i = 0; i < Sec_tanks.size(); i++) {
Sec_tanks.get(i).drawTank(g);
}
for (int i = 0; i < Tri_tanks.size(); i++) {
Tri_tanks.get(i).drawTank(g);
}
// 画河流
for (int i = 0; i < rivers.size(); i++) {
rivers.get(i).drawBride(g);
}
// 敌方子弹
for (int i = 0; i < bullets.size(); i++) {
bullets.get(i).drawBullet(g);
}
// 我方子弹
for (int i = 0; i < pb.size(); i++) {
pb.get(i).drawBullet(g);
}
// 命中效果
for (int i = 0; i < Visual.size(); i++) {
Visual.get(i).drawBang(g);
}
// 画草
for (int i = 0; i < Grass.size(); i++) {
Grass.get(i).drawGrass(g);
}
// 画铁
for (int i = 0; i < Fes.size(); i++) {
Fes.get(i).drawFe(g);
}
// 画砖头
for (int i = 0; i < brides.size(); i++) {
brides.get(i).drawBride(g);
}
// 遍历道具队列
for (int i = 0; i < properties.size(); i++) {
properties.get(i).drawProperty(g);
}
if (stop) {
new GameConsole().drawImage(g);// 画出游戏结束图片
}
} else {
new GameConsole().drawStart(g);// 画出游戏开始图片
}
}
// 重写update() 实现双缓冲
public void update(Graphics g) {
// super.update(g);
if (buf_image == null) {
buf_image = this.createImage(WINDOW_WHITH, WINDOW_HEIGHT);
}
Graphics gImage = buf_image.getGraphics();
Color c = gImage.getColor();
gImage.setColor(Color.BLACK);
gImage.fillRect(0, 0, WINDOW_WHITH, WINDOW_HEIGHT);
gImage.setColor(c);
paint(gImage);
g.drawImage(buf_image, 0, 0, null);
}
// 重绘线程
class PaintThread extends Thread {
private int Count = 0;// 计数变量(有时候会出现爆炸效果不消失现象,在此暂时解决);
public void run() {
while (true) {
if (stop) {
repaint();
break;
}
Count++;
if (Count == 50) {
Visual.clear();
Count = 0;
}
repaint();
try {
Thread.sleep(FRANK);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
经过这次坦克大战游戏的编写我所获得的经验:
(1):每个软件的编写都是徐徐渐进的,先实现简单的功能,在逐步逐步加入新的功能,最后达到质变的阶段.
附上几张小图:(版本的进程和最初版本的效果)
(2):做软件的过程中难免会碰到瓶颈,这时候要主动查询资料,和请教别人。
(3):做软件并不是随随便便,三下五除二就搞定的,它需要我们长时间的琢磨,无论是我们的UI还是AI,打造一个精美的软件是很有挑战性的。
最后,在附件中附上我的这个小游戏和源代码(在jar包中解压即可)。 感谢大家~~~~