Java坦克大战游戏

写于 2022-05-29

目 录 Java坦克大战游戏 1 01 基本窗口的绘制 2 02 为窗口背景上色 3 03 为窗口添加启动文字 3 04 为窗口添加键盘事件 3 05 键盘控制选择游戏模式 4 06 双缓存解决闪动问题 6 07 游戏父类的编写 10 08 添加坦克类 11 09 初始化玩家 11 10 用键盘控制坦克移动 13 11 初始化子弹类 16 12 发射并绘制子弹 18 13 子弹间隔时间 20 14 批量增加敌方坦克 21 15 敌方坦克随机移动 25 16 敌方坦克发射子弹 26 17 坦克和子弹碰撞检测 27 18 增加游戏围墙 31 19 子弹与围墙碰撞检测 33 20 坦克与墙碰撞检测 34 21 坦克与边界碰撞检测 35 22 判断子弹是否出界 36 23 绘制游戏基地及子弹碰撞 36 24 添加游戏规则 38 25 增加爆炸特效 40 26 修改游戏开始界面文字 41 27 添加游戏玩家2 41 28 添加玩家坦克生命元素 43 最终代码 45 GamePanel 45 GameObject 49 Direction 49 Tank 50 Wall 52 Bullet 52 Bot 54 EnemyBullet 56 PlayerOne 56 PlayerTwo 57 Base 59 Blast 59 设计基本素材 60

01 基本窗口的绘制

import javax.swing.*;

public class GamePanel extends JFrame {
//窗口尺寸
int width =800;
int height = 610;

//窗口的启动方法
public void launch(){
//标题
setTitle("坦克大战游戏");
//窗口初始大小
setSize(width,height);
//使屏幕居中
setLocationRelativeTo(null);
//添加关闭事件
setDefaultCloseOperation(3);
//用户不能调整窗口大小
setResizable(false);
//使窗口可见
setVisible(true);
}

public static void main(String[] args) {
GamePanel gamePanel = new GamePanel();
gamePanel.launch();
}

}

02 为窗口背景上色

加一个绘画paint方法

@Override
//paint方法
public void paint(Graphics g){
//设置画笔颜色
g.setColor(Color.gray);
//绘制实心矩阵
g.fillRect(0,0,width,height);
}

03 为窗口添加启动文字

在paint方法添加以下内容

//设置画笔颜色
g.setColor(Color.blue);
g.setFont(new Font("宋体",Font.BOLD,50));
//添加文字
g.drawString("选择游戏模式",220,100);
g.drawString("单人模式",220,200);
g.drawString("双人模式",220,300);

04 为窗口添加键盘事件

添加键盘方法

//键盘监视器
class KeyMonitor extends KeyAdapter{
//按下按键
public void keyPressed(KeyEvent e){
//返回键值
System.out.println(e.getKeyChar());
}
}

在窗口launch里添加键盘监视器

05 键盘控制选择游戏模式

首先在添加2个变量

//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("image/tank1Right.png");
//指针初始纵坐标
int y = 150;

然后在paint方法中绘制指针

//绘制指针
g.drawImage(select,160,y,null);

然后在launch方法更新图片绘制

//重绘
while (true){
repaint();
try {
Thread.sleep(25);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

接着添加游戏模式

在变量中添加state表示游戏模式

//游戏模式 0-游戏未开始 1-单人模式 2-双人模式
int state = 0;

在paint方法中判定游戏模式,给出不同内容

@Override
//paint方法
public void paint(Graphics g){
//设置画笔颜色
g.setColor(Color.gray);
//绘制实心矩阵
g.fillRect(0,0,width,height);
//设置画笔颜色
g.setColor(Color.blue);
g.setFont(new Font("宋体",Font.BOLD,50));
if (state == 0){
//添加文字
g.drawString("选择游戏模式",220,100);
g.drawString("单人模式",220,200);
g.drawString("双人模式",220,300);
//绘制指针
g.drawImage(select,160,y,null);
}else if(state ==1

然后添加键盘事件。根据不同键盘选择模式,但是不能直接通过1赋值,需添加变量int a = 1;

//键盘监视器
class KeyMonitor extends KeyAdapter{
//按下按键
public void keyPressed(KeyEvent e){
//返回键值
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_1:
a = 1;
y = 150;
break;
case KeyEvent.VK_2:
a = 2;
y = 250;
break;
case KeyEvent.VK_ENTER:
state = a;
break;
}
}
}

06 双缓存解决闪动问题

在刚刚在游戏界面中,我们看到游戏界面会有闪动现象。

先定义属性:双缓存图片

//定义双闪存图片
Image offScreenImage = null;

然后在paint方法中创建和窗口一样大小的图片

//创建和窗口一样大小的图片
if(offScreenImage==null){
offScreenImage = this.createImage(width,height);
}

其次,获得这个图片的画笔

//获得该图片的画笔
Graphics gImage = offScreenImage.getGraphics();

接着,将元素都绘制到这个新的图片上。将原有g改为gImage

最后,将这个图片一次性绘制到窗口中

//将这个图片一次性绘制到窗口中 g.drawImage(offScreenImage,0,0,null);

最终效果如图所示,不再有闪动情况

这次完成后,主界面整体代码如下所示:

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

//创建游戏界面
public class GamePanel extends JFrame {
//定义双闪存图片
Image offScreenImage = null;
//窗口尺寸
int width =800;
int height = 610;
//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("image/tankR.png");
int y=160;
//游戏模式:0-游戏未开始 1-单人模式 2-双人模式
int state =0;
int a = 1;



//窗口的启动方法
public void launch(){
//标题
setTitle("坦克大战");
//窗口初始大小
setSize(width,height);
//屏幕居中
setLocationRelativeTo(null);
//添加关闭事件
setDefaultCloseOperation(3);
//用户不能调整大小
setResizable(false);
//使窗口可见
setVisible(true);
//添加键盘监视
this.addKeyListener(new KeyMonitor());

//重绘
while (true){
repaint();
try{
Thread.sleep(25);
}catch (Exception e){
e.printStackTrace();
}
}

}

//paint方法
public void paint(Graphics g){
//创建和窗口一样大小的图片
if(offScreenImage==null){
offScreenImage = this.createImage(width,height);
}
//获得该图片的画笔
Graphics gImage = offScreenImage.getGraphics();

//设置画笔颜色
gImage.setColor(Color.gray);
//绘制实心矩阵
gImage.fillRect(0,0,width,height);
//改变画笔颜色
gImage.setColor(Color.blue);
//改变文字大小
gImage.setFont(new Font("宋体",Font.BOLD,50));
//state=0,游戏未开始
if(state==0){
//添加文字
gImage.drawString("选择游戏模式",220,100);
gImage.drawString("单人模式",220,200);
gImage.drawString("双人模式",220,300);
gImage.drawString("按1、2选择模式,按回车开始游戏",0,400);
//绘制指针
gImage.drawImage(select,80,y,null);
} else if (state ==1

07 游戏父类的编写

后面需要将private GamePanel gamePanel修饰改为public

08 添加坦克类

方向修改符要改为public

09 初始化玩家

先创建PlayOne类

接着绘制玩家一,在GamePanel更改代码

需要把坦克四个方向的图片添加进来

在GamePanel类中定义PlayOne变量

然后,在paint方法中将玩家一绘制出来

10 用键盘控制坦克移动

用键盘来控制坦克移动

移动需要改变的变量:坐标和方向

在Tank类中加入以下控制坦克移动和方向的方法

添加键盘事件。在PlayerOne中增加

思路:定义4个boolean的方向变量,按下键盘时为true,调用对应的方法;松开键盘时,变量变为false,不再调用方法。这2个方法放到PlayerOne中

然后在GamePanel中调用这2个方法

11 初始化子弹类

新建一个子弹类,实现基本功能

import java.awt.*;

public class Bullet extends GameObject{
int width = 10;
int height = 10;
int speed = 5;
Direction direction;
public Bullet(String img, int x, int y, GamePanel gamePanel,Direction direction) {
super(img, x, y, gamePanel);
this.direction = direction;
}
public void upWard(){
y -= speed;
}
public void downWard(){
y += speed;
}
public void leftWard(){
x -= speed;
}
public void rightWard(){
x += speed;
}
public void go(){
switch (direction){
case UP:
upWard();
break;
case DOWN:
downWard();
break;
case LEFT:
leftWard();
break;
case RIGHT:
rightWard();
break;
}
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
go();
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,width,height);
}
}

12 发射并绘制子弹

1)初始化子弹

需要在坦克内添加一个发射方法;在这个过程中,需要编写一个方法来获取坦克头部的坐标。

获取子弹位置,需要通过获得坦克头部位置坐标再通过数学计算得到

然后,增加坦克攻击后发出子弹

public void attack(){
Point p = this.getHeadPoint();
Bullet bullet = new Bullet("image/bulletU.png",p.x,p.y,this.gamePanel,this.direction);
}

2)绘制子弹

坦克不只发射一颗子弹,因此需要将每颗子弹绘制出来

在GamePanel类中添加子弹列表

然后,再把新增加的子弹添加到列表中

最后,在paint方法中循环子弹列表,绘制每个子弹中的元素

3)添加键盘事件

在PlayerOne的中添加空格键事件,按下发射子弹

然后运行代码,可发射子弹

13 子弹间隔时间

新建一条线程,去计算射击的冷却时间

在Tank类中新增2个变量

//攻击冷却状态
private boolean attackCoolDown = true;
//攻击间隔时间,1000毫秒
private int attckCoolTime = 1000;

然后新增线程。新建一个内部类

class AttackCD extends Thread{
public void run(){
//将攻击功能设置为冷却状态
attackCoolDown = false;
try {
Thread.sleep(attckCoolTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//将攻击功能解除冷却状态
attackCoolDown = true;
//线程终止
this.interrupt();
}
}

注意,thread.stop()方法已经被弃用,换成interrupt

然后在attack方法里启动这个线程,修改attack方法代码

public void attack(){
if (attackCoolDown){
Point p = this.getHeadPoint();
Bullet bullet = new Bullet("image/bulletU.png",p.x,p.y,this.gamePanel,this.direction);
this.gamePanel.bulletList.add(bullet);
//线程开始
new AttackCD().start();
}
}

可启动代码测试子弹发射效果,有了间隔时间

14 批量增加敌方坦克

  1. 新增一辆敌方坦克

首先,添加Bot类,与PlayerOne相似

import java.awt.*;
public class Bot extends Tank{

public Bot(String img, int x, int y, GamePanel gamePanel, String upImage, String downImage, String leftImage, String rightImage) {
super(img, x, y, gamePanel, upImage, downImage, leftImage, rightImage);
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,width,height);
}
}

在GamePanel中增加敌方坦克变量

Bot bot = new Bot("image/badTankU.png",500,110,this,"image/badTankU.png",
"image/badTankU.png","image/badTankU.png","image/badTankU.png");

然后在paint方法中绘制

启动代码测试,单人游戏界面中多了一辆敌方坦克

2)批量增加敌方坦克

生成坦克列表

然后在launch()方法的while循环里面批量增加

为了控制添加敌方坦克的速度,给定变量count,每重画一次,count+1;每重画100次,追加1辆坦克;

在用Random随机位置生成敌方坦克,不多于10辆坦克,并用for方法绘制出所有坦克

涉及GamePanel多处改写,新代码如下:

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Random;

//创建游戏界面
public class GamePanel extends JFrame {
//定义双闪存图片
Image offScreenImage = null;
//窗口尺寸
int width =800;
int height = 610;
//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("image/tankR.png");
int y=160;
//游戏模式:0-游戏未开始 1-单人模式 2-双人模式
int state =0;
int a = 1;
//重绘次数
int count=0;
int enemyCount = 0;

//游戏元素列表
ArrayList<Bullet> bulletList = new ArrayList<Bullet>();
ArrayList<Bot> botList = new ArrayList<Bot>();
PlayerOne playerOne = new PlayerOne("image/goodTankU.png",125,510,this,
"image/tankU.png","image/goodTankU.png","image/tankR.png",
"image/tankL.png");

Bot bot = new Bot("image/badTankU.png",500,110,this,"image/badTankU.png",
"image/badTankU.png","image/badTankU.png","image/badTankU.png");

//窗口的启动方法
public void launch(){
//标题
setTitle("坦克大战");
//窗口初始大小
setSize(width,height);
//屏幕居中
setLocationRelativeTo(null);
//添加关闭事件
setDefaultCloseOperation(3);
//用户不能调整大小
setResizable(false);
//使窗口可见
setVisible(true);
//添加键盘监视
this.addKeyListener(new KeyMonitor());

//重绘
while (true){
//追加敌方坦克,每重画100次追加1个坦克; 坦克达到10辆不再生成
if (count % 100 ==1 && enemyCount<10){
//敌方坦克位置随机生成
Random random = new Random();
int rnum = random.nextInt(800);
botList.add(new Bot("image/badTankU.png",rnum,110,this,"image/badTankU.png",
"image/badTankU.png","image/badTankU.png","image/badTankU.png"));
enemyCount++;
}
repaint();
try{
Thread.sleep(25);
}catch (Exception e){
e.printStackTrace();
}
}
}

//paint方法
public void paint(Graphics g){
//创建和窗口一样大小的图片
if(offScreenImage==null){
offScreenImage = this.createImage(width,height);
}
//获得该图片的画笔
Graphics gImage = offScreenImage.getGraphics();

//设置画笔颜色
gImage.setColor(Color.gray);
//绘制实心矩阵
gImage.fillRect(0,0,width,height);
//改变画笔颜色
gImage.setColor(Color.blue);
//改变文字大小
gImage.setFont(new Font("宋体",Font.BOLD,50));
//state=0,游戏未开始
if(state==0){
//添加文字
gImage.drawString("选择游戏模式",220,100);
gImage.drawString("单人模式",220,200);
gImage.drawString("双人模式",220,300);
gImage.drawString("按1、2选择模式,按回车开始游戏",0,400);
//绘制指针
gImage.drawImage(select,80,y,null);
} else if (state ==1

15 敌方坦克随机移动

1)随机获取一个方向

在Bot类中生成方法

public Direction getRandomDirection(){
Random random = new Random();
int rnum = random.nextInt(4);
switch (rnum){
case 0:
return Direction.LEFT;
case 1:
return Direction.RIGHT;
case 2:
return Direction.UP;
case 3:
return Direction.DOWN;
default:
return null;
}
}

2)定时改变方向

先在Bot类中声明变量moveTime,移动大于20次则随机换一次方向

int moveTime = 0;

3)整合

在Bot类中写一个方法go

public void go(){
if(moveTime >= 20){
direction = getRandomDirection();
moveTime = 0;
}else {
moveTime++;
}
switch (direction){
case LEFT:
leftWard();
break;
case RIGHT:
rightWard();
break;
case UP:
uptWard();
break;
case DOWN:
downtWard();
break;
}
}

然后把go方法添加到paintSelf中

16 敌方坦克发射子弹

首先建立一个EnemyBullet类,并建立构造方法

import java.awt.*;

public class EnemyBullet extends Bullet{
public EnemyBullet(String img, int x, int y, GamePanel gamePanel, Direction direction) {
super(img, x, y, gamePanel, direction);
}
}

然后在敌方坦克类里新建方法,attack,和坦克类方法一样

public void attack(){
Point p = getHeadPoint();
Random random = new Random();
int rnum = random.nextInt(400);
if(rnum<4){
this.gamePanel.bulletList.add(new EnemyBullet("image/bullet2.png",p.x,p.y,gamePanel,direction));
}
}

然后在go方法加入attack方法

运行后敌方坦克会发射子弹。子弹发射速度可以通过attack的参数控制

17 坦克和子弹碰撞检测

1)了解碰撞检测

检测两个物体是否碰撞,调用intersects方法判断两个矩阵是否相交

检测完成后,要考虑后续。如碰撞后是否继续存在,运动状态是否发生改变

消除物体的方法,从paint遍历中去掉

2)我方子弹与敌方坦克

首先新建删除子弹列表

然后在原子弹列表中删除碰撞后的子弹列表

然后在Bullet类中增加hitBot方法

public void hitBot(){
ArrayList<Bot> bots = this.gamePanel.botList;
for (Bot bot:bots){
if(this.getRec().intersects(bot.getRec())){
this.gamePanel.botList.remove(bot);
this.gamePanel.removeList.add(this);
break;
}
}
}

最后,重新绘制

3)敌方子弹与我方坦克

首先,添加玩家列表

然后修改paint部分

然后在EnemyBullet增加hitPlayer的方法,并重新绘制自身

修改后的EnemyBullet类的代码如下

import java.awt.*;
import java.util.ArrayList;

public class EnemyBullet extends Bullet{
public EnemyBullet(String img, int x, int y, GamePanel gamePanel, Direction direction) {
super(img, x, y, gamePanel, direction);
}
public void hitPlayer(){
ArrayList<Tank> players = this.gamePanel.playerList;
for (Tank tank:players){
if(this.getRec().intersects(tank.getRec())){
this.gamePanel.playerList.remove(tank);
this.gamePanel.removeList.add(this);
break;
}
}
}
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
this.go();
hitPlayer();
}
}

运行代码测试,正常实现功能

18 增加游戏围墙

新增围墙类

import java.awt.*;

public class Wall extends GameObject{
//尺寸
int length = 50;

public Wall(String img, int x, int y, GamePanel gamePanel) {
super(img, x, y, gamePanel);
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,gamePanel);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,length,length);
}
}

然后在游戏界面中添加围墙

然后在GamePanel类的paint方法绘制围城元素

for (Wall wall:wallList){
wall.paintSelf(gImage);
}

最后在围墙列表中添加围墙

运行代码测试,加了围墙

19 子弹与围墙碰撞检测

在Bullet里增加围城碰撞检测功能

public void hitWall(){
ArrayList<Wall> walls = this.gamePanel.wallList;
for (Wall wall:walls){
if(this.getRec().intersects(wall.getRec())){
//如果子弹碰撞到围墙,则删去子弹和围墙
this.gamePanel.wallList.remove(wall);
this.gamePanel.removeList.add(this);
//停止循环
break;
}
}
}

然后将碰撞检测功能添加到go方法里

运行代码

20 坦克与墙碰撞检测

坦克不能进入墙体,遇到墙后不能移动

因此先检测再移动

首先在Tank类中增加一个碰撞类

//坦克与围墙碰撞检测
public boolean hitWall(int x,int y){
//围墙列表
ArrayList<Wall> walls = this.gamePanel.wallList;
//下一步矩形
Rectangle next = new Rectangle(x,y,width,height);
for (Wall wall:walls){
if(next.intersects(wall.getRec())){
return true;
}
}
return false;
}

然后给坦克移动添加条件。在4个方向的方法增加判断语句,看下一步的坐标坦克和围墙是否撞上,若撞上则不能移动

21 坦克与边界碰撞检测

与围墙碰撞检测类似

只需要判断坦克边缘坐标是否出界

public boolean moveToBorder(int x,int y){
if(x<0

然后同理在4个方向中添加判断条件

22 判断子弹是否出界

出界的子弹要删除,否则积累太多后,内存消耗巨大

在Bullet方法增加检测方法

//检测子弹是否出界,如果出界则删除该子弹
public void moveToBorder(int x,int y){
if(x<0

23 绘制游戏基地及子弹碰撞

首先创建基地类

import java.awt.*;

public class Base extends GameObject{
int length = 50;
public Base(String img, int x, int y, GamePanel gamePanel) {
super(img, x, y, gamePanel);
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,length,length);
}
}

然后在GamePanel中添加变量:基地列表

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

在paint方法中绘制出基地

for (Base base:baseList){
base.paintSelf(gImage);
}

添加基地

Base base = new Base("image/base.png",365,560,this);

在launch方法添加

baseList.add(base);

碰撞检测,和子弹击中围墙一样,在Bullet类中增加hitBase方法

public void hitBase(){
ArrayList<Base> bases = this.gamePanel.baseList;
for (Base base:bases){
if(this.getRec().intersects(base.getRec())){
this.gamePanel.baseList.remove(base);
this.gamePanel.removeList.add(this);
break;
}
}
}

然后在go方法加入此方法

24 添加游戏规则

1)游戏胜利

敌人数量为0时游戏胜利

设置state==5时游戏胜利,进行判定

2)游戏失败

自身死亡或基地灭亡

在launch方法中while(true)部分添加以下内容,改变游戏模式

//游戏失败判定
if((playerList.size()==0)&&(state==1

在paint部分,游戏失败时绘制相应文字

else if (state == 4){
gImage.drawString("游戏失败",220,200);
}

//TODO ,基地爆炸检测

3)游戏暂停

在GamePanel键盘监视器中,添加键盘事件,按P键时切换状态

case KeyEvent.VK_P:
//当按下P时,游戏暂停
if(state != 3){
//当游戏不处于暂停状态时,先记录state的当前值,再给state赋值为3
a = state;
state = 3;
}else{
state = a;
if(a==0){
a =1;
}
}

在paint部分,游戏暂停时绘制相应文字

else if (state ==3 ){
gImage.drawString("游戏暂停",220,200);
}

敌方坦克的随机生成需要在游戏进行时才能生成,暂停时不能生成,因此添加限定条件

25 增加爆炸特效

首先添加Blast类

import java.awt.*;

public class Blast extends GameObject{
Image blastImg = Toolkit.getDefaultToolkit().getImage("image/explode.png");
int expoldeCount = 0;
static {
for (int i = 0; i < 8; i++) {

}
}

public Blast(String img, int x, int y, GamePanel gamePanel) {
super(img, x, y, gamePanel);
}

@Override
public void paintSelf(Graphics g) {
if(expoldeCount<8){
g.drawImage(blastImg,x,y,null);
expoldeCount++;
}

}

@Override
public Rectangle getRec() {
return null;
}
}

其次在GamePanel中添加游戏元素,爆炸列表

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

然后,在GamePanel的paint方法中绘制爆炸特效

for (Blast bs:blastList){
bs.paintSelf(gImage);
}

然后,添加爆炸位置。找到子弹与坦克的碰撞检测,在

//爆炸特效消失

26 修改游戏开始界面文字

添加记录敌方剩余坦克数量

更改原先文字信息

27 添加游戏玩家2

首先生成PlayerTwo类,和PlayerOne是一致的,只有按键需要修改

import java.awt.*;
import java.awt.event.KeyEvent;

public class PlayerTwo extends Tank{
boolean up,down,left,right;
public PlayerTwo(String img, int x, int y, GamePanel gamePanel, String upImage, String downImage, String rightImage, String leftImage) {
super(img, x, y, gamePanel, upImage, downImage, rightImage, leftImage);
}

public void keyPressed(KeyEvent e){
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_LEFT:
left = true;
leftWard();
break;
case KeyEvent.VK_RIGHT:
right = true;
rightWard();
break;
case KeyEvent.VK_UP:
up = true;
uptWard();
break;
case KeyEvent.VK_DOWN:
down = true;
downtWard();
break;
default:
break;
}
}

public void keyReleased(KeyEvent e){
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_LEFT:
left = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
case KeyEvent.VK_K:
attack();
default:
break;
}
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,width,height);
}
}

然后在游戏界面GamePanel类中增加变量:玩家2

PlayerTwo playerTwo = new PlayerTwo(tank2Str,625,510,this,
tank1Str,tank1Str,tank1Str,
tank1Str);

在键盘监视器中增加双人模式

添加键盘事件

28 添加玩家坦克生命元素

在Tank类中增加alive变量

//玩家生命
public boolean alive = false;

再给attack方法增加判定条件

在游戏界面中将坦克alive状态改为true

在坦克被击中时改为false

最终代码

GamePanel

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Random;

//创建游戏界面
public class GamePanel extends JFrame {
//定义双闪存图片
Image offScreenImage = null;
//窗口尺寸
int width =800;
int height = 610;
//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("image/tank1Right.png");
int y=160;
//游戏模式:0-游戏未开始 1-单人模式 2-双人模式 3-游戏暂停 4-游戏失败 5-游戏胜利
int state =0;
int a = 1;
//重绘次数
int count=0;
int enemyCount = 0;

//游戏元素列表
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>();

PlayerOne playerOne = new PlayerOne("image/tank1Up.png",125,510,this,
"image/tank1Up.png","image/tank1Down.png","image/tank1Right.png",
"image/tank1Left.png");
PlayerTwo playerTwo = new PlayerTwo("image/tank2Up.png",625,510,this,
"image/tank2Up.png","image/tank2Down.png","image/tank2Right.png",
"image/tank2Left.png");

Bot bot = new Bot("image/badTankU.png",500,110,this,"image/badTankU.png",
"image/badTankD.png","image/badTankR.png","image/badTankL.png");
Base base = new Base("image/base.png",365,560,this);

//窗口的启动方法
public void launch(){
//标题
setTitle("坦克大战");
//窗口初始大小
setSize(width,height);
//屏幕居中
setLocationRelativeTo(null);
//添加关闭事件
setDefaultCloseOperation(3);
//用户不能调整大小
setResizable(false);
//使窗口可见
setVisible(true);
//添加键盘监视
this.addKeyListener(new KeyMonitor());

//添加围墙
for (int i = 0; i < 14; i++) {
wallList.add(new Wall("image/wall.png",50+i*50,170,this));
}
wallList.add(new Wall("image/wall.png",305,560,this));
wallList.add(new Wall("image/wall.png",305,500,this));
wallList.add(new Wall("image/wall.png",365,500,this));
wallList.add(new Wall("image/wall.png",425,500,this));
wallList.add(new Wall("image/wall.png",425,560,this));
baseList.add(base);

//重绘
while (true){
//游戏胜利判定
if(botList.size()==0 && enemyCount==10){
state = 5;
}
//游戏失败判定
if((playerList.size()==0)&&(state==1

GameObject

import java.awt.*;

public abstract class GameObject {
//图片
public Image img;
//坐标
public int x;
public int y;
//界面
public GamePanel gamePanel;

public GameObject(String img, int x, int y, GamePanel gamePanel) {
this.img = Toolkit.getDefaultToolkit().getImage(img);
this.x = x;
this.y = y;
this.gamePanel = gamePanel;
}
//绘制自身
public abstract void paintSelf(Graphics g);

//检测碰撞,获取矩形
public abstract Rectangle getRec();

}

Direction


public enum Direction{
UP,DOWN,RIGHT,LEFT;
}

Tank

import java.awt.*;
import java.util.ArrayList;
import java.util.DoubleSummaryStatistics;

public abstract class Tank extends GameObject{
//尺寸
public int width = 40;
public int height = 50;
//速度
private int speed = 3;
//坦克初始方向
Direction direction = Direction.UP;
//玩家生命
public boolean alive = false;

//四个方向图片
private String upImage;
private String downImage;
private String rightImage;
private String leftImage;
//攻击冷却状态
private boolean attackCoolDown = true;
//攻击间隔时间,1000毫秒
private int attckCoolTime = 1000;

public Tank(String img, int x, int y, GamePanel gamePanel,
String upImage, String downImage, String rightImage, String leftImage) {
super(img, x, y, gamePanel);
this.upImage = upImage;
this.downImage = downImage;
this.rightImage = rightImage;
this.leftImage = leftImage;
}

public void leftWard(){
direction = Direction.LEFT;
setImg(leftImage);
if (!hitWall(x-speed,y)&&(!moveToBorder(x-speed,y)))
this.x -= speed;
}
public void rightWard(){
direction = Direction.RIGHT;
setImg(rightImage);
if(!hitWall(x+speed,y)&&(!moveToBorder(x+speed,y)))
this.x += speed;
}
public void uptWard(){
direction = Direction.UP;
setImg(upImage);
if(!hitWall(x,y-speed)&&(!moveToBorder(x,y-speed)))
this.y -= speed;
}
public void downtWard(){
direction = Direction.DOWN;
setImg(downImage);
if (!hitWall(x,y+speed)&&(!moveToBorder(x,y+speed)))
this.y += speed;
}

public void attack(){
if (attackCoolDown && alive){
Point p = this.getHeadPoint();
Bullet bullet = new Bullet("image/bullet1.png",p.x,p.y,this.gamePanel,this.direction);
this.gamePanel.bulletList.add(bullet);
//线程开始
new AttackCD().start();
}
}

//新线程
class AttackCD extends Thread{
public void run(){
//将攻击功能设置为冷却状态
attackCoolDown = false;
try {
Thread.sleep(attckCoolTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//将攻击功能解除冷却状态
attackCoolDown = true;
//线程终止
this.interrupt();
}
}
public Point getHeadPoint(){
switch (direction){
case LEFT:
return new Point(x,y+height/2);
case RIGHT:
return new Point(x+width,y+height/2);
case UP:
return new Point(x+width/2,y);
case DOWN:
return new Point(x+width/2,y+height);
default:
return null;
}
}

//坦克与围墙碰撞检测
public boolean hitWall(int x,int y){
//围墙列表
ArrayList<Wall> walls = this.gamePanel.wallList;
//下一步矩形
Rectangle next = new Rectangle(x,y,width,height);
for (Wall wall:walls){
if(next.intersects(wall.getRec())){
return true;
}
}
return false;
}

public boolean moveToBorder(int x,int y){
if(x<0

Wall

import java.awt.*;

public class Wall extends GameObject{
//尺寸
int length = 50;

public Wall(String img, int x, int y, GamePanel gamePanel) {
super(img, x, y, gamePanel);
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,gamePanel);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,length,length);
}
}

Bullet

import java.awt.*;
import java.util.ArrayList;

public class Bullet extends GameObject{
//尺寸
int width = 10;
int height = 10;
//速度
int speed = 7;
//方向
Direction direction;

public Bullet(String img, int x, int y, GamePanel gamePanel,Direction direction) {
super(img, x, y, gamePanel);
this.direction = direction;
}
public void leftWard(){
x -= speed;
}
public void rightWard(){
x += speed;
}
public void upWard(){
y -= speed;
}
public void downWard(){
y += speed;
}

public void go(){
switch (direction){
case LEFT:
leftWard();
break;
case RIGHT:
rightWard();
break;
case UP:
upWard();
break;
case DOWN:
downWard();
break;
}
this.hitWall();
this.moveToBorder(x,y);
this.hitBase();
}

public void hitBot(){
ArrayList<Bot> bots = this.gamePanel.botList;
for (Bot bot:bots){
if(this.getRec().intersects(bot.getRec())){
this.gamePanel.blastList.add(new Blast("image/explode.png", bot.x-34,bot.y-14,this.gamePanel));
this.gamePanel.botList.remove(bot);
this.gamePanel.removeList.add(this);
break;
}
}
}
public void hitBase(){
ArrayList<Base> bases = this.gamePanel.baseList;
for (Base base:bases){
if(this.getRec().intersects(base.getRec())){
this.gamePanel.baseList.remove(base);
this.gamePanel.removeList.add(this);
break;
}
}
}

public void hitWall(){
ArrayList<Wall> walls = this.gamePanel.wallList;
for (Wall wall:walls){
if(this.getRec().intersects(wall.getRec())){
//如果子弹碰撞到围墙,则删去子弹和围墙
this.gamePanel.wallList.remove(wall);
this.gamePanel.removeList.add(this);
//停止循环
break;
}
}
}
//检测子弹是否出界,如果出界则删除该子弹
public void moveToBorder(int x,int y){
if(x<0

Bot

import java.awt.*;
import java.util.Random;

public class Bot extends Tank{
int moveTime = 0;
//Direction direction;
public Bot(String img, int x, int y, GamePanel gamePanel, String upImage, String downImage, String rightImage, String leftImage) {
super(img, x, y, gamePanel, upImage, downImage, rightImage, leftImage);
}
public Direction getRandomDirection(){
Random random = new Random();
int rnum = random.nextInt(4);
switch (rnum){
case 0:
return Direction.LEFT;
case 1:
return Direction.RIGHT;
case 2:
return Direction.UP;
case 3:
return Direction.DOWN;
default:
return null;
}
}
public void go(){
attack();
if(moveTime >= 20){
direction = getRandomDirection();
moveTime = 0;
}else {
moveTime++;
}
switch (direction){
case LEFT:
leftWard();
break;
case RIGHT:
rightWard();
break;
case UP:
uptWard();
break;
case DOWN:
downtWard();
break;
}
}
public void attack(){
Point p = getHeadPoint();
Random random = new Random();
int rnum = random.nextInt(400);
if(rnum<4){
this.gamePanel.bulletList.add(new EnemyBullet("image/bullet2.png",p.x,p.y,gamePanel,direction));
}
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
go();
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,width,height);
}
}

EnemyBullet

import java.awt.*;
import java.util.ArrayList;

public class EnemyBullet extends Bullet{
public EnemyBullet(String img, int x, int y, GamePanel gamePanel, Direction direction) {
super(img, x, y, gamePanel, direction);
}
public void hitPlayer(){
ArrayList<Tank> players = this.gamePanel.playerList;
for (Tank tank:players){
if(this.getRec().intersects(tank.getRec())){
this.gamePanel.playerList.remove(tank);
this.gamePanel.removeList.add(this);
tank.alive = false;
break;
}
}
}
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
this.go();
hitPlayer();
}
}

PlayerOne

import java.awt.*;
import java.awt.event.KeyEvent;

public class PlayerOne extends Tank{
boolean up,down,left,right;
public PlayerOne(String img, int x, int y, GamePanel gamePanel, String upImage, String downImage, String rightImage, String leftImage) {
super(img, x, y, gamePanel, upImage, downImage, rightImage, leftImage);
}

public void keyPressed(KeyEvent e){
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_A:
left = true;
leftWard();
break;
case KeyEvent.VK_D:
right = true;
rightWard();
break;
case KeyEvent.VK_W:
up = true;
uptWard();
break;
case KeyEvent.VK_S:
down = true;
downtWard();
break;
default:
break;
}
}

public void keyReleased(KeyEvent e){
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_A:
left = false;
break;
case KeyEvent.VK_D:
right = false;
break;
case KeyEvent.VK_W:
up = false;
break;
case KeyEvent.VK_S:
down = false;
break;
case KeyEvent.VK_SPACE:
attack();
default:
break;
}
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,width,height);
}
}

PlayerTwo

import java.awt.*;
import java.awt.event.KeyEvent;

public class PlayerTwo extends Tank{
boolean up,down,left,right;
public PlayerTwo(String img, int x, int y, GamePanel gamePanel, String upImage, String downImage, String rightImage, String leftImage) {
super(img, x, y, gamePanel, upImage, downImage, rightImage, leftImage);
}

public void keyPressed(KeyEvent e){
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_LEFT:
left = true;
leftWard();
break;
case KeyEvent.VK_RIGHT:
right = true;
rightWard();
break;
case KeyEvent.VK_UP:
up = true;
uptWard();
break;
case KeyEvent.VK_DOWN:
down = true;
downtWard();
break;
default:
break;
}
}

public void keyReleased(KeyEvent e){
int key = e.getKeyCode();
switch (key){
case KeyEvent.VK_LEFT:
left = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
case KeyEvent.VK_K:
attack();
default:
break;
}
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,width,height);
}
}

Base

import java.awt.*;
import java.util.ArrayList;

public class Base extends GameObject{
int length = 50;
public Base(String img, int x, int y, GamePanel gamePanel) {
super(img, x, y, gamePanel);
}

@Override
public void paintSelf(Graphics g) {
g.drawImage(img,x,y,null);
}

@Override
public Rectangle getRec() {
return new Rectangle(x,y,length,length);
}

public void hitBullet(){
ArrayList<Bullet> bullets = this.gamePanel.bulletList;
for (Bullet bullet:bullets){
if(this.getRec().intersects(bullet.getRec())){
//如果子弹碰撞到基地,则删去子弹和基地
this.gamePanel.baseList.remove(this);
this.gamePanel.removeList.add(bullet);
//停止循环
break;
}
}
}
}

Blast

import java.awt.*;

public class Blast extends GameObject{
Image blastImg = Toolkit.getDefaultToolkit().getImage("image/explode.png");
int expoldeCount = 0;
static {
for (int i = 0; i < 8; i++) {

}
}

public Blast(String img, int x, int y, GamePanel gamePanel) {
super(img, x, y, gamePanel);
}

@Override
public void paintSelf(Graphics g) {
if(expoldeCount<8){
g.drawImage(blastImg,x,y,null);
expoldeCount++;
}



}

@Override
public Rectangle getRec() {
return null;
}
}

设计基本素材

子弹: