坦克大战--这款游戏,相信大家小时候都玩过,想当年还在读小学的时候,我和我表哥整个暑假拿着一台小霸王游戏机接上电视,就开始玩坦克大战,通关根本停不下来,
简直打遍天下无敌手。现在我上了大学,学的是软件工程专业,学了java,现在我自己用java做了这款小游戏,和大家分享一下,也说一下我在做这个游戏过程中遇到的一些问题,
希望对大家起到一定的帮助。源代码在附件里,大家可以下载
首先简略的说下整个坦克大战的大致流程、要实现的功能和最后的效果图
主要有以下这些类:
效果图:
大致流程如下
1、首先得有一个面板
2、加入一辆坦克
3、让坦克移动
4、加入子弹
5、加入敌人的坦克
6、让敌人的坦克动起来,并且能发子弹
7、子弹能够消灭坦克
实现功能:能够四处移动
能够打击敌人
敌人能够移动
能够模拟爆炸
能够产生障碍
能够增长生命
1,如何给游戏加上坦克等图片?
下面使用到了ClassLoader类装载器获取系统资源
ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。
ClassLoader提供了两个方法用于从装载的类路径中取得资源:
public URL getResource(String name);
public InputStream getResourceAsStream(String name);
这里name是资源的类路径,它是相对与“/”根路径下的位置。getResource得到的是一个URL对象来定位资源,而getResourceAsStream取得该资源输入流的引用保证程序可以从正确的位置抽取数据。
然而,程序中调用的通常并不是ClassLoader的这两个方法,而是Class的getResource和getResourceAsStream方法,因为Class对象可以从你的类得到(如YourClass.class或YourClass.getClass()),
而ClassLoader则需要再调用一次YourClass.getClassLoader()方法,但根据JDK文档的说法,Class对象的这两个方法其实是“委托”(delegate)给装载它的ClassLoader来做的,
所以只需要使用Class对象的这两个方法就可以了。
从文件中装入图像,使用类装载器的public URL getResource(String name);方法打开文件
这个要用到类的反射的相关知识,大家还有不懂的可以去查API文档或者百度
具体实现代码(大家可以在Tank类中找到这段代码):
private static Toolkit tk = Toolkit.getDefaultToolkit(); private static Image[] tankImages = null; private static Map<String,Image> imgs = new HashMap<String,Image>(); //静态代码区 static{ tankImages=new Image[]{ tk.getImage(Tank.class.getClassLoader().getResource("image/tankL.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankLU.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankU.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankRU.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankR.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankRD.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankD.gif")), tk.getImage(Tank.class.getClassLoader().getResource("image/tankLD.gif")) }; imgs.put("L",tankImages[0]); imgs.put("LU",tankImages[1]); imgs.put("U",tankImages[2]); imgs.put("RU",tankImages[3]); imgs.put("R",tankImages[4]); imgs.put("RD",tankImages[5]); imgs.put("D",tankImages[6]); imgs.put("LD",tankImages[7]); }
加入爆炸、子弹的图片和加入坦克的图片一样,这里就不做详细说明了
2,打第一颗子弹打出时没有爆炸产生
当我们照上面的方法加上爆照的图片后,发现第一发子弹打到敌人坦克时没有爆炸产生,从第二发开始都有爆炸产生
打一颗子弹时爆炸图片还木有到内存中来,第二次画爆照图片时已经到内存中里。我们定义的图片数组是static的,在类加载的时候,首先执行的就是static,这个时候还没有执行draw()方法,那么肯定不是这个原因
原因:getResource()执行时只拿到了图片的虚框(很多软件显示比较大的图片时也会出现这种情况),当调用draw()方法,还没有拿到数据
解决方案:
在Explode类中加入boolean类型 init变量
private static boolean init = false;
在Explode类draw()方法里面第一行加入以下代码
if(!init){ for (int i = 0; i < imgs.length; i++) { g.drawImage(imgs[i], -100, -100, null); } init=true; }
3、解决炮弹不消亡的问题
步骤:
加入控制子弹生死的量bLive(Missle)
当子弹已经死去就不需要对其重画
当子弹飞出边界就死亡
当子弹死亡就从容器中去除
package com.zkx.tank; import java.awt.*; import java.util.HashMap; import java.util.List; import java.util.Map; public class Missile { public static final int XSPEED=10; public static final int YSPEED=10; public static final int WIDTH=10; public static final int HEIGHT=10; int x,y; Direction dir; private boolean good; private boolean live=true; private TankClient tc; private static Toolkit tk = Toolkit.getDefaultToolkit(); private static Image[] missileImages = null; private static Map<String,Image> imgs = new HashMap<String,Image>(); //静态代码区 static{ missileImages=new Image[]{ tk.getImage(Missile.class.getClassLoader().getResource("image/missileL.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileLU.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileU.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileRU.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileR.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileRD.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileD.gif")), tk.getImage(Missile.class.getClassLoader().getResource("image/missileLD.gif")) }; imgs.put("L",missileImages[0]); imgs.put("LU",missileImages[1]); imgs.put("U",missileImages[2]); imgs.put("RU",missileImages[3]); imgs.put("R",missileImages[4]); imgs.put("RD",missileImages[5]); imgs.put("D",missileImages[6]); imgs.put("LD",missileImages[7]); } public boolean isLive() { return live; } public Missile(int x, int y,Direction dir) { this.x = x; this.y = y; this.dir = dir; } public Missile(int x,int y,boolean good,Direction dir,TankClient tc){ this(x,y,dir); this.good=good; this.tc=tc; } public void draw(Graphics g){ if(!live){ tc.missiles.remove(this); return; } switch(dir){ case L: g.drawImage(imgs.get("L"), x, y, null); break; case LU: g.drawImage(imgs.get("LU"), x, y, null); break; case U: g.drawImage(imgs.get("U"), x, y, null); break; case RU: g.drawImage(imgs.get("RU"), x, y, null); break; case R: g.drawImage(imgs.get("R"), x, y, null); break; case RD: g.drawImage(imgs.get("RD"), x, y, null); break; case D: g.drawImage(imgs.get("D"), x, y, null); break; case LD: g.drawImage(imgs.get("LD"), x, y, null); break; case STOP: break; } } private void move() { switch(dir){ case L: x-=XSPEED; break; case LU: x-=XSPEED; y-=YSPEED; break; case U: y-=YSPEED; break; case RU: x+=XSPEED; y-=YSPEED; break; case R: x+=XSPEED; break; case RD: x+=XSPEED; y+=YSPEED; break; case D: y+=YSPEED; break; case LD: x-=XSPEED; y+=YSPEED; break; case STOP: break; } if(x<0 || y<0 || x>TankClient.GAME_WIDTH||y>TankClient.GAME_HEIGHT){ live=false; } } public Rectangle getRect(){ return new Rectangle(x,y,WIDTH,HEIGHT); } public boolean hitTank(Tank t){ if(this.live&&this.getRect().intersects(t.getRect())&&t.isLive() && this.good!=t.isGood()){ if(t.isGood()){ t.setLife(t.getLife()-20); if(t.getLife()<=0) t.setLive(false); }else{ t.setLive(false); } this.live=false; Explode e=new Explode(x,y,tc); tc.explodes.add(e); return true; } return false; } public boolean hitTanks(List<Tank> tanks){ for(int i=0;i<tanks.size();i++){ if(hitTank(tanks.get(i))){ return true; } } return false; } public boolean hitWall(Wall w){ if(this.live&&this.getRect().intersects(w.getRect())){ this.live=false; return true; } return false; } }
4、如何让坦克向8个方向行走?
步骤:
添加记录按键状态的布尔量
添加代表方向的量(使用枚举)
根据按键状态确定Tank方向
根据方向进行下一步的移动(move)
void move(){ this.oldX=x; this.oldY=y; switch(dir){ case L: x-=XSPEED; break; case LU: x-=XSPEED; y-=YSPEED; break; case U: y-=YSPEED; break; case RU: x+=XSPEED; y-=YSPEED; break; case R: x+=XSPEED; break; case RD: x+=XSPEED; y+=YSPEED; break; case D: y+=YSPEED; break; case LD: x-=XSPEED; y+=YSPEED; break; case STOP: break; }