日期:2014-05-20  浏览次数:20866 次

分享贪吃蛇的Java实现,带地砖地图,望对大家有用
   前段时间,花了3天时间(课余零碎时间)帮网上一个人做课程设计,做的是简单的贪吃蛇。

   不过由于很少做Java游戏,所以也借此机会对Java 2D游戏的开发做了个简单的回顾。
  
   1.地图:采用的是地砖地图,即用图元的方式生成地图。然后以一个二维数组定义整张游戏地图。游戏如果要做成多关卡,只要相应地增加对应二维数组即可。当然由于比较匆忙,地图二维数组我直接硬编码在代码里。你也可以把它提到配置文件中,然后运行时加载。
   2.主要算法全部在Snake类中,包含:图像生成、判断是否碰撞、蛇改变运动方向、判断食物是否被吃掉、蛇运动。
   3.游戏的一些细节作了优化,下面就这方面简单说说。
   
   > 在使用循环时,尽量避免多重嵌套循环,而且要把忙“事务”放到外层去。比如
   
// 蛇头与障碍物相撞
ArrayList<Node> barriers = SnakeMap.getBarriers();
for (int j = 0; j < barriers.size(); j++) {
int bx = barriers.get(j).x;
int by = barriers.get(j).y;
                        //
                }

   上面的barriers实例化,当然如果我放在循环体内部也是可以的,但是这样每次循环都要实例化一次,无疑会让性能大大下降。另外在循环体内部先获取bx和by,是因为下面多处用到。
   另外一些频繁被调用的变量如果可以避免重复实例化尽量避免,比如这个游戏中的蛇身体部分图片加载,我采用的是一个static的Image数组存储,在游戏第一次绘制的时候就加载(构造函数中)完,以后每一次绘制不需要再重复加载,具体可以看GameCanva类。
  
private static Image[] imgs = new Image[9]; // 保存9个蛇身图像
  int i = 0;
for (i = 0; i < imgs.length; i++) {
imgs[i] = new ImageIcon("src/res/ball/" + (i + 1) + ".png")
.getImage();
}
  


  > 游戏地图绘制的优化,开始采用的是每一次绘制过程都一个个图元地绘制,后来采用缓存图像来进行了优化,代码如下,具体见GameCanva类。
  

  private static BufferedImage mapImg = null;
  // 每次都要重绘地图,这里为优化可以考虑把地图存为BufferedImage
int i = 0;
int j = 0;

if (mapImg == null) { // 第一次绘制的时候没有地图缓存,所以直接绘制
// System.out.println("直接通过贴片绘制地图");
mapImg = new BufferedImage(this.getWidth(), this.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g3 = mapImg.createGraphics();
for (i = 0; i < 30; i++) {
for (j = 0; j < 30; j++) {
g2d.drawImage(tm.getTile(i, j), 20 * i, 20 * j, 20, 20,
this);
g3.drawImage(tm.getTile(i, j), 20 * i, 20 * j, 20, 20,
this);
}
}
g3.dispose();
}
else { //已经有地图缓存,则直接从缓存图像绘制地图就好了
// System.out.println("通过地图缓存绘制地图!");
g2d.drawImage(mapImg, 0, 0, 600, 600, this);
}
  


  > 尽量用移位运算代替求模运算,求模运算的性能是很差的。如下:
  

  //这里采用了一个算术优化,即用x&1代替x%2,因为位运算远比模运算快
if ((direction&1) != (newDer&1)) // 如果与原来方向相同或相反,则无法改变
direction = newDer;
  

  x%y等效于x与y的位掩码,位掩码即模数减去1。比如本例y等于2,模数为10(二进制),减去1等于1(十进制)。

  其他的一些优化技巧也略作总结如下:
  1.消除“死亡”代码:死亡代码即不能被执行到的代码,对执行效果没有任何影响;
  2.循环不变项提升:不变项就是不变的项目,如常量。要把不变项放到循环外部;
  3.公共子表达式消除:
  如 int a = 4*x*x + 2*x +3;
     int b = 4*x*x + 2*x +5;
  可以优化为:
     int temp = 4*x*x + 2*x;
     int a = temp + 3;
     int b = temp + 5;
  4.积极内联代码;
  5.尽量用移位运算代替其他运算,比如左移等于乘以2,右移表示除以2。还有上面的求模运算。
  6.对于不能替代为位运算的乘法运算和指数运算,可以这样做:乘法运算可能可以换成开销小很多的加法运算,比如
  
 for(int i=0;i<array.length;i++) {
                 array[i] = x*i;
              }
              //如果x为5,则结果为0,5,10,15...,则可以优化如下
              int value = 0;
              for(int i=0;i<array.length;i++) {
                 array[i] = value;
                 value += x;
              }
  

  而对于指数运算考虑能否转化成乘法运算。
  如
  

  for(int i=0;i<array.length;i++) {
     array[i] = (int) Math.pow(x, i);
  }
  //如果x为5,则结果为1, 5, 25, ..,则可以优化为如下
  int value = 1;
  for(int i=0;i<array.length;i++) {
     array[i] = value;
     value *= x;
  }