坦克大战--这款游戏,相信大家小时候都玩过,想当年还在读小学的时候,我和我表哥整个暑假拿着一台小霸王游戏机接上电视,就开始玩坦克大战,通关根本停不下来,
简直打遍天下无敌手。现在我上了大学,学的是软件工程专业,学了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;
}