坦克大战项目实践(Java)

发布时间 2023-04-17 18:43:58作者: 时人不识凌云木

 

一、设计简介

该项目是设计一个坦克大战小游戏,该游戏算不上精美,但是有始有终。有明确的游戏开始、单人模式、双人模式、游戏暂停和游戏失败等事件。设计内容包含图片双缓存技术,解决游戏屏幕闪动问题;还包含了击毁坦克的爆炸特效与子弹消失;还包含了碰撞检测,包括墙体和边界的。

二、相关工作

由于该游戏设计是实现了图形化界面,因此要有游戏元素的gif图片。在网上找到的图片要么是格式不对,要么就是像素太高,直接覆盖了整个画布,无法展示准确游戏元素。况且,这也与设计中游戏元素尺寸不符。所以,对于坦克元素玩家一、坦克元素玩家二、敌方坦克元素、围墙元素和基地元素都是我自己通过系统画图软件设计。可以自己调整像素值,使其符合游戏元素尺寸设定。如图2.1到2.5所示。

selecttank player2U enemy1U walls base

图2.1 图2.2 图2.3 图2.4 图2.5

可见,这些游戏元素都是灰色打底,那是因为设计的游戏界面就是灰色背景,为了带来视觉上的和谐,所以将图片背景填充为灰色。再者,就是坦克移动要有四个方向,所以每个坦克游戏元素,也都有对应的四个方向图片这里以玩家一的坦克为例,如图2.6到2.9。

player1D player1L player1R player1U

图2.6 图2.7 图2.8 图2.9

还有就是爆炸特效用到的一连串图片,由小到大排列。这里就不在一一展示,放在下文实验结果和实验原理中展示。

三、设计原理

3.1图片双缓存技术原理。使用双 缓存的方法来解决屏幕闪动,大体的思路是重新创建一个空的图片,把所有组件,先绘制到空的图片上,再把绘制好图片,一次性绘制到主窗口中。

//定义双缓存图片

Image offScreemImage=null;

......

//创建和容器一样大小的Image图片

if(offScreemImage==null) {

offScreemImage=this.createImage(width,height);

}

//获得该图片的画笔

Graphics gImage=offScreemImage.getGraphics();

//设置画笔颜色,设置背景颜色

gImage.setColor(Color.gray);

//绘制实心矩形,填充整个画布(Image)图片

gImage.fillRect(0, 0, width, height);

//改变画笔颜色

gImage.setColor(Color.blue);

//改变文字大小和样式

gImage.setFont(new Font("仿宋",Font.BOLD,50));

......

//将缓存区绘制好的图形整个绘制到窗口容器的画布中

g.drawImage(offScreemImage, 0,0,null);

3.2键盘监视器原理。首先编写的GamePanel类是继承JFrame类的,这样才有创建窗口,监听键盘鼠标等事件的功能。而编写的KeyMonitor类,又是继承KeyAdapter类。这里出现了一个@Override的符号,含义是重写父类中的方法。

//键盘监视器

class KeyMonitor extends KeyAdapter{

//按下键盘

@Override

public void keyPressed(KeyEvent e) {//继承KeyAdapter类的keyPressed()方法

//返回键值

int key=e.getKeyCode();

switch (key) {

case KeyEvent.VK_1:

a=1;

y=150;

break;

case KeyEvent.VK_2:

a=2;

y=250;

break;

......

default:

playerOne.keyPressed(e);

playerTwo.keyPressed(e);

}

}

//松开键盘

@Override//重写父类@Override

public void keyReleased(KeyEvent e) {

playerOne.keyReleased(e);

playerTwo.keyReleased(e);

}

}

3.3坦克移动原理。这里键盘事件值得注意的是,为了让坦克移动可以按住移动键,而不是朝一个方向移动要快速按下该方向键。这里对坦克移动做了设计。还有就是坦克移动要改变其方向,图片元素也要跟着改变。坦克移动的设计代码如下。

//按下键盘,变为true,执行对应方法。

//按住时,因为程序快速执行true和false的变换,所以一直处于运动状态,松开时不在为ture所以不再移动

public void keyPressed(KeyEvent e) {

int key =e.getKeyCode();

switch (key) {

case KeyEvent.VK_LEFT:

left=true;

break;

......

default:

break;

}

}

 

//松开键盘,自动变为false。

public void keyReleased(KeyEvent e) {

int key =e.getKeyCode();

switch (key) {

case KeyEvent.VK_LEFT:

left=false;

break;

......

}

}

public void move(){//除了构造函数,其他函数不会自动调用

if(left) {

leftward();

}

......

}

3.4线程原理。对于游戏界面的重绘,是需要调用repaint()的,由repaint()调用update()来刷新界面,删去以前的界面,变为现在的界面。然后对paint()函数进行重写覆盖父类。其中paint()函数是系统线程自动调用的。

repaint();//重绘构建,但是会闪烁-->双缓冲

try {

Thread.sleep(25);//线程暂停25ns

}catch(Exception e) {

e.printStackTrace();

}

再有就是关于坦克发射子弹冷却时间的考量。这里就需要引入新的线程。

//新线程

class AttackCD extends Thread{

public void run() {

//将攻击功能设置为冷却状态

attackCoolDown=false;

//休眠1秒

try {

Thread.sleep(attackCoolDownTime);

}catch(Exception e) {

e.printStackTrace();

}

//将攻击功能解除冷却

attackCoolDown=true;

//线程终止

this.stop();

}

3.5碰撞检测原理。与围墙和边界的碰撞都采取先检测,在移动的规则。以防止坦克开进墙里的情况。所以传参数的时候,也是传下一个位置的坐标。如:

if(hitWall(x+speed, y) && moveToBorder(x+speed, y)) {

this.x+=speed;

3.6继承原理。我认为该坦克大战游戏设计,贯穿设计核心的就是继承关系。首先创建游戏元素类GameObject,作为游戏的父类。然后是创建了坦克类Tank,继承了GameObject类。然后是玩家一的坦克类,和玩家二的坦克类,都继承Tank类。子弹类Bulllet也是继承GameObject类。关于子类的构造函数,可以快速调用父类的构造函数。Super(x,y,z....);

3.7游戏元素列表原理。通过创建游戏元素列表,方便游戏元素的增删。特殊的,对于子弹类元素的删除,必须再创建一个remove()列表,否则删除子弹就会报错。我想,这应该和子弹的自动移动有关。

//游戏元素列表

ArrayList<Bullet> bulletList=new ArrayList<Bullet>();

ArrayList<Bot> botList=new ArrayList<Bot>();

ArrayList<Bullet> removeList=new ArrayList<Bullet>();

ArrayList<Tank> playerList=new ArrayList<Tank>();

ArrayList<Wall> wallList=new ArrayList<Wall>();

ArrayList<Base> baseList=new ArrayList<Base>();

ArrayList<Blast> blastList=new ArrayList<Blast>();

四、实现结果

4.1游戏主界面。

1

4.2游戏开始,以选择双人模式为例。

2

4.3按P,游戏暂停。

4

4.4击毁墙壁,并且击毁敌方坦克产生爆炸特效。

5

4.5玩家坦克被摧毁,或者我方基地被摧毁,游戏失败。

3

4.6敌方坦克被消灭,游戏胜利。

6

五、项目心得

通过本项目,我受益颇多。深切感受到了面向对象编程的魅力,丰富的游戏元素抽象为丰富的类,再衍生出子类,继承或者重写覆盖父类中的方法。还有键盘监听事件的巧妙运用,在设计过程中出现了很多严重的BUG,比如玩家坦克被击毁后还可以发射炮弹,这也在我为坦克添加布尔变量alive后得以解决。该设计虽然算不上功能丰富,但是也是可以称得上一个小休闲游戏了。程序也难免依然存在未发现的BUG,但是作为第一个Java小程序,做成这样有头有尾,编写了近千行代码,还是很有成就感的。

在代码编写过程中,我也遇到一些陌生的知识符号,比如说@Override,经在网上查阅后了解,是对父类的重写。还有就是GamePanel类中的paint()方法,我起初奇怪,没有调用它,为何会在屏幕中调用实现,经过我的一番推理,我觉得launch()方法中的repaint()应该和它相关,经过查阅资料,确实是系统自动调用。