一.游戏编写的背景
为加深对多线程的理解与运用,熟悉多线程的机制,编写了坦克大战这一个小游戏。其中实现了多个线程的控制及线程的监听功能。通过着以游戏的开发,确实加深了我对多线程的理解,也加深了对键盘监听机制、界面的熟悉,还有是双缓冲的学习与使用。
二.游戏截图
三.技术点
①.多线程:敌方坦克由五个线程控制,还有一个监听线程,还有一个控制屏幕刷新的线程,还有若干控制子弹的线程。有两个方法判断子弹是否与击中坦克,一个是用一个监听线程,在监听线程里得到装载有子弹属性的队列,装载坦克属性的队列,通过判断坦克种类是否与子弹种类一致(即自己一方的坦克发出标记为己方类型的子弹,敌方的坦克发出的子弹标记为敌方类型的子弹),在判断子弹与坦克是否有交集,就可以知道子弹是否击中坦克了。
有一个坦克的父类,父类中油坦克的属性,坦克开火、移动的方法,自己的坦克与敌方的坦克分别继承父类坦克,己方坦克用主线程,敌方坦克新建一个线程,子弹通过坦克的方法发射,所以子弹线程中要得到坦克发射的坐标,子弹线程中还需要判断子弹是否与地图上的元素相撞(坦克也如此)。地图通过一个二维数组保存,不同元素标记为不同的数值,通过数值的不同,就可以判断子弹,坦克是否可以前行了。
所以,总结一点就是,框架很重要,线程的控制是重点,框架要实现各个线程对各种因素的控制。
②.重绘与双缓冲技术
坦克大战还有重要的一个,就是实现界面上各个因素的重绘。随意每个因素都必须要写一个draw()的方法,再在panit()中调用。不过随着图片的增多,界面会出现闪烁的线程,要解决这种现象,就要用到双缓冲技术,即重写update()方法,在update()方法中创建一个图片对象,相当于以后重绘的各种图片都绘在这张图片上一样。
// 声明缓冲图像 Image offScreenImage = null; // 双缓冲机制的具体实现 // 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示 public void update(Graphics g) { if (offScreenImage == null) { // 截取窗体所在位置的图片 offScreenImage = this.createImage(GameWidth, GameHeight); } // 获得截取图片的画布 Graphics gImage = offScreenImage.getGraphics(); // 获取画布的底色并且使用这种颜色填充画布(默认的颜色为黑色) Color c = Color.BLACK; gImage.setColor(c); gImage.fillRect(0, 0, GameWidth, GameHeight); // 有清除上一步图像的功能,相当于gImage.clearRect(0, // 0, WIDTH, HEIGHT) // 将截下的图片上的画布传给重绘函数,重绘函数只需要在截图的画布上绘制即可,不必在从底层绘制 paint(gImage); // 将接下来的图片加载到窗体画布上去,才能考到每次画的效果 g.drawImage(offScreenImage, 0, 0, null); } // 重绘坦克、子弹、地图 public void paint(Graphics g) { // 在重绘函数中实现双缓冲机制 offScreenImage = this.createImage(WIDTH, HEIGHT); // 获得截取图片的画布 gImage = offScreenImage.getGraphics(); // 获取画布的底色并且使用这种颜色填充画布,如果没有填充效果的画,则会出现拖动的效果 gImage.setColor(gImage.getColor()); gImage.fillRect(0, 0, WIDTH, HEIGHT); // 有清楚上一步图像的功能,相当于gImage.clearRect(0, 0, WIDTH, HEIGHT) // 调用父类的重绘方法,传入的是截取图片上的画布,防止再从最底层来重绘 super.paint(gImage); gImage.setColor(Color.RED); gImage.drawString("敌方坦克数量:" + ThreadListener.count2, 10, 20); gImage.drawString("我方坦克数量:" + ThreadListener.count1, 10, 40); if (!Cannonball.isStop) { paintBallCollide(gImage); paintMap(g, Map.Map1Copy); paintTank(gImage); } if (Cannonball.isStop) { // 如果停止,再将Map1赋给Map1 for (int i = 0; i < Map.Map1.length; i++) { for (int j = 0; j < Map.Map1[i].length; j++) { Map.Map1Copy[i][j] = Map.Map1[i][j]; } } paintMap(gImage, Map.Map1Copy); } // System.out.println("<__>" + tankarray.size()); }
还有一种解决办法就是用一个线程,不断地刷新界面,消除闪烁。
// 利用一线程实现画面不停的刷新 class PaintThread implements Runnable { public void run() { while (true) { repaint(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }