坦克大战
实现功能
- 绘制我方与敌方坦克
- 键盘控制我方坦克移动与发射
- 敌方发射子弹
- 子弹打到坦克,坦克会消失并且爆炸
- 敌方坦克自由移动且自由发射
- 我方坦克发射多颗子弹,并且设置同时存在的子弹数量上限
- 防止敌方坦克重叠并且设置移动范围
- 记录成绩,并且按照记录的信息继续上一局游戏
本次项目使用了:面向对象 OOP IO编程 文件编程 算法 GUI 多线程 线程知识
一、编写各种类
Tank类
- 坦克类是我放坦克和敌方坦克的父类,包含了坦克的横坐标,纵坐标,方向与速度,还定义了坦克上下左右移动的方法
package Tank2.Tank;
import javax.swing.*;
public class Tank extends JFrame {
private int x;//坦克的横坐标
private int y;//坦克的纵坐标
private int diret;//坦克的方向
private int speed = 1;//控制速度
boolean islive = true;
public Tank(int x, int y ) {
this.x = x;
this.y = y;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
//上右下左移动方法
public void moveUp() {
if (y > 0){
y -= speed;
}
}
public void moveRight() {
if (x + 60 < 1000){
x += speed;
}
}
public void moveDown() {
if (y + 60 < 750){
y += speed;
}
}
public void moveLeft() {
if (x > 0){
x -= speed;
}
}
public int getDiret() {
return diret;
}
public void setDiret(int diret) {
this.diret = diret;
}
@Override
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
@Override
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
Shot类
- 子弹类,同样定义了横纵坐标还有方向与速度,但也加入了判断子弹是否还存活的布尔型,为后面子弹打中坦克会消亡做功能铺垫
- 同时定义了移动的线程,因为子弹要一直动,所以要用到线程
- 同时还加入了什么时候将子弹生命置为false的判断语句
package Tank2.Tank;
public class Shot implements Runnable{
int x; //子弹x坐标
int y; //子弹y坐标
int direct = 0; //子弹方向
int speed = 3; //子弹的速度
boolean isLive = true; //子弹是否还存活
//构造器
public Shot(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
//子弹要一直动,而且不能一下子就到边界,所以用了线程然后还加入了休眠代码
@Override
public void run() {//射击
while (true) {
//休眠 50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//根据方向来改变x,y坐标
switch (direct) {
case 0://上
y -= speed;
break;
case 1://右
x += speed;
break;
case 2://下
y += speed;
break;
case 3://左
x -= speed;
break;
}
//当子弹移动到面板的边界时,就应该销毁(把启动的子弹的线程销毁),
//判断子弹是否存活,当子弹打到坦克时会被置为flase,线程随之退出
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isLive) ) {
isLive = false;
break;
}
}
}
}
enemyTank类
- 敌方坦克的类,继承了Tank父类,因为要一直动所以同样加入了线程
- 本类多加了Shot集合,用来存放敌方坦克发射的子弹,也加入了判断是否死亡的布尔型
- 这里提供一个方法,可以将MyPanel 的成员 Vector enemyTanks = new Vector<>();
//设置到 enemyTank 的成员 enemyTanks - 提供了判断敌方坦克重叠的方法
- 提供了坦克发射子弹的方法可以设置子弹上限,还有坦克自己随机移动的方法
package Tank2.Tank;
import java.util.Vector;
public class enemyTank extends Tank implements Runnable{
//类里创建Shot集合
Vector<Shot> shots = new Vector<>();
//判断敌方坦克是否存在
boolean islive = true;
//增加成员,enemyTank 可以得到敌人坦克的Vector
Vector<enemyTank> enemyTanks = new Vector<>();
public enemyTank(int x, int y) {
super(x, y);
}
//这里提供一个方法,可以将MyPanel 的成员 Vector<enemyTank> enemyTanks = new Vector<>();
//设置到 enemyTank 的成员 enemyTanks
public void setEnemyTanks(Vector<enemyTank> enemyTanks) {
this.enemyTanks = enemyTanks;
}
//编写方法,判断当前的这个敌人坦克,是否和 enemyTanks 中的其他坦克发生的重叠或者碰撞
public boolean isTouchEnemyTank(){
//判断当前敌人坦克(this) 方向
switch (this.getDiret()) {
case 0: //上
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY()]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 右上角的坐标 [this.getX() + 40, this.getY()]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY()]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 右上角的坐标 [this.getX() + 40, this.getY()]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
case 1: //右
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 右上角的坐标 [this.getX() + 60, this.getY()]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 60, this.getY() + 40]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 40
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 右上角的坐标 [this.getX() + 60, this.getY()]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 60, this.getY() + 40]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 60
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
case 2: //下
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 60]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 40, this.getY() + 60]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 40
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 60]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 40, this.getY() + 60]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 60
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
case 3: //左
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY() ]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 40]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY() ]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 40]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
}
return false;
}
@Override
public void run() {
while (true){
//这里我们判断如果shots size() < 15, 创建一颗子弹,放入到
//shots集合,并启动
if (islive && shots.size() < 15) {//一部坦克可以同时存在15颗子弹
Shot s = null;
//判断坦克的方向,创建对应的子弹
switch (getDiret()) {
case 0:
s = new Shot(getX() + 20, getY(), 0);
break;
case 1:
s = new Shot(getX() + 60, getY() + 20, 1);
break;
case 2: //向下
s = new Shot(getX() + 20, getY() + 60, 2);
break;
case 3://向左
s = new Shot(getX(), getY() + 20, 3);
break;
}
shots.add(s);
//启动
new Thread(s).start();
}
//根据坦克的方向来继续移动
//移动时加入判断是否重叠的方法
switch (getDiret()){
case 0://上
//保持一个方向走40步,不然太频繁变动方向了
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveUp();
//休眠代码是为了不让坦克一下子就走完40步,就会看不到中间走动的过程所以加上休眠
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://右
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveRight();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://下
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveDown();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://左
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveLeft();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//然后随机的改变坦克方向 0-3
setDiret((int) (Math.random() * 4));
//写并发程序,一定要考虑清楚,该线程什么时候结束
if (!islive) {
break; //退出线程.
}
}
}
}
MyHero类
package Tank2.Tank;
import java.util.Vector;
public class MyHero extends Tank {
Shot shot = null;
//发射多颗子弹的情况
//创建一个shots集合来保存多个shot对象
Vector<Shot> shots02 = new Vector<>();
public MyHero(int x, int y) {
super(x, y);
}
//射击
public void shotEnemyTank() {
//控制我方子弹最多同时存在4个,大于四个就不创建新的shot对象
if (shots02.size() > 4){
return;
}
//创建 Shot 对象, 根据当前Hero对象的位置和方向来创建Shot
int a = getDiret();
switch (a) {//得到Hero对象方向
case 0: //向上
shot = new Shot(getX() + 20, getY(), 0);
break;
case 1: //向右
shot = new Shot(getX() + 60, getY() + 20, 1);
break;
case 2: //向下
shot = new Shot(getX() + 20, getY() + 60, 2);
break;
case 3: //向左
shot = new Shot(getX(), getY() + 20, 3);
break;
}
//把新建的shot放入集合
shots02.add(shot);
//启动我们的Shot线程
new Thread(shot).start();
}
}
Bomb类
- 爆炸类,定义横纵坐标,还有生命周期用于实现渐变效果,还有是否存活
- 实现生命值减少的方法
package Tank2.Tank;
public class Bomb {
int x;//爆炸坐标
int y;//爆炸坐标
int life = 6;//生命周期
boolean islive = true;//是否存活
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
public void lifedown(){//配合出现图片的爆炸效果,不然切换的太快没有效果
if (life > 0){
life--;
}else{
islive = false;
}
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getLife() {
return life;
}
public void setLife(int life) {
this.life = life;
}
public boolean isIslive() {
return islive;
}
public void setIslive(boolean islive) {
this.islive = islive;
}
}
Node类
- 一个Node 对象,表示一个敌人坦克的信息
package Tank2.Tank;
// 一个Node 对象,表示一个敌人坦克的信息
public class Node {
private int x;
private int y;
private int direct;
public Node(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
this.direct = direct;
}
}
Recorder类
- 该类用于记录相关信息和文件交互
- 定义一个Node 的Vector ,用于保存敌人的信息node
- 建坦克Vector集合,用来接收mypanel中的enemytank集合,用来后面的遍历敌方坦克用
- 定义变量,记录我方击毁敌人坦克数
- 当游戏退出时,我们将allEnemyTankNum和保存敌人坦克的坐标和方向保存到 PATH
- 一个方法,用于读取PATH, 恢复相关信息 该方法,在继续上局的时候调用即可
package Tank2.Tank;
import java.io.*;
import java.util.Vector;
//该类用于记录相关信息和文件交互
public class Recorder {
//定义变量,记录我方击毁敌人坦克数
private static int allEnemyTankNum = 0;
//定义输入输出流
private static FileWriter fw = null;
private static BufferedWriter bw = null;
private static BufferedReader br = null;
//定义静态变量地址
private static final String PATH = "D:\\JavaProjects\\b站草稿\\坦克大战\\src\\MyRecord.txt";
//创建坦克Vector集合,用来接收mypanel中的enemytank集合,用来后面的遍历敌方坦克用
private static Vector<enemyTank> enemyTanks = null;
//定义一个Node 的Vector ,用于保存敌人的信息node
private static Vector<Node> nodes = new Vector<>();
public static String getPATH() {
return PATH;
}
//增加一个方法,用于读取PATH, 恢复相关信息
//该方法,在继续上局的时候调用即可
public static Vector<Node> getNodesAndEnemyTankRec(){
try {
br = new BufferedReader(new FileReader(PATH));
//从文件中取出击毁敌方坦克数量
allEnemyTankNum = Integer.parseInt(br.readLine());
String line = "";//255 40 0
//将读出的数据存入line字符串
while ((line = br.readLine())!=null){//循环取出,只要不为空就取出
String[] xyd = line.split(" ");//以空格作为分隔符形成新的对象
//0号是x坐标,1号是y坐标,2号是方向,并且要转成int类型
//将取出的数据当作node对象存入nodes集合里
Node node = new Node(Integer.parseInt(xyd[0]),
Integer.parseInt(xyd[1]),Integer.parseInt(xyd[2]));
nodes.add(node);
}
//固定格式
} catch (Exception e) {
e.printStackTrace();
}finally {
if (br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//返回nodes对象
return nodes;
}
public void setenemy(Vector<enemyTank> enemyTanks){
Recorder.enemyTanks = enemyTanks;
}
//增加一个方法,当游戏退出时,我们将allEnemyTankNum 保存到 PATH
//对keepRecord 进行升级, 保存敌人坦克的坐标和方向
public static void keepRecord(){
try {
bw = new BufferedWriter(new FileWriter(PATH));
//写入文件时要转成String类型不然会乱码
bw.write(Integer.toString(allEnemyTankNum));
bw.newLine();
//遍历enemytanks集合,取出每个坦克,然后吧坐标和方向写入文件
for (int i = 0; i < enemyTanks.size(); i++) {
enemyTank en = enemyTanks.get(i);
//用空格隔开方便取出时用空格作为分隔符形成新代码
String s = en.getX() + " " + en.getY() + " " + en.getDiret();
bw.write(s);
bw.newLine();
}
bw.flush();
} catch (IOException e) {
e.printStackTrace();
//固定写法
}finally {
if (bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static int getAllEnemyTankNum() {
return allEnemyTankNum;
}
public static void setAllEnemyTankNum(int allEnemyTankNum) {
Recorder.allEnemyTankNum = allEnemyTankNum;
}
public static void addenemytankNum(){
Recorder.allEnemyTankNum++;
}
}
AePlayWave类
- 此类用来播放音乐
package Tank2.Tank;
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
public class AePlayWave extends Thread {
private String filename;
public AePlayWave(String wavfile) { //构造器 , 指定文件
filename = wavfile;
}
public void run() {
File soundFile = new File(filename);
AudioInputStream audioInputStream = null;
try {
audioInputStream = AudioSystem.getAudioInputStream(soundFile);
} catch (Exception e1) {
e1.printStackTrace();
return;
}
AudioFormat format = audioInputStream.getFormat();
SourceDataLine auline = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
try {
auline = (SourceDataLine) AudioSystem.getLine(info);
auline.open(format);
} catch (Exception e) {
e.printStackTrace();
return;
}
auline.start();
int nBytesRead = 0;
//这是缓冲
byte[] abData = new byte[512];
try {
while (nBytesRead != -1) {
nBytesRead = audioInputStream.read(abData, 0, abData.length);
if (nBytesRead >= 0)
auline.write(abData, 0, nBytesRead);
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
auline.drain();
auline.close();
}
}
}
全项目最重点MyPanel类
- 坦克大战的绘图区域,所有物件包括坦克,子弹都在这里绘制,消亡啥的都在这里操作
package Tank2.Tank;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.Vector;
/**
*
* @version 1.0
* 坦克大战的绘图区域
*/
public class MyPanel extends JPanel implements KeyListener,Runnable {
//初始hero类
MyHero hero = null;
//定义敌方坦克
//创建Vector集合,线程安全,ArrayList线程不安全所以不用,定义类型为enemTank的集合
Vector<enemyTank> enemyTanks = new Vector<>();
//定义一个存放Node 对象的Vector, 用于恢复敌人坦克的坐标和方向
Vector<Node> nodes = new Vector<>();
//定义敌方坦克的数量
int enemyTanksize = 9;
//定义存放爆炸特效的集合
Vector<Bomb> bombs = new Vector<>();
//定义三张图片
Image image1 = null;
Image image2 = null;
Image image3 = null;
//定义Recorder类
Recorder recorder = new Recorder();
//mypanel的构造方法
public MyPanel(String key) {
//先判断记录的文件是否存在
//如果存在,就正常执行,如果文件不存在,提示,只能开启新游戏,key = "1"
File file = new File(Recorder.getPATH());
if (file.exists()) {
nodes = Recorder.getNodesAndEnemyTankRec();
} else {
System.out.println("文件不存在,只能开启新的游戏");
key = "1";
}
//将从文件得到的数据放入nodes集合里
nodes = recorder.getNodesAndEnemyTankRec();
//初始化我的坦克的位置
hero = new MyHero(500, 600);
hero.setSpeed(4);//设置我防坦克的移动速度,后期改难度可以改速度
//将mypanel对象的enemytanks设置给Recorder的enemytanks
recorder.setenemy(enemyTanks);
//选择是重新开始还是继续游戏
switch (key){
case "1":
//初始化敌人坦克
for (int i = 0; i < enemyTanksize; i++) {//初始多少部
//定义一个enemTank对象 并且定义位置,坦克之间间隔100像素
enemyTank en = new enemyTank(70 * (i + 1), 0);
en.setDiret(2);//定义方向
//启动线程,敌方坦克走动加射击
new Thread(en).start();
//初始化敌人坦克子弹,根据敌方坦克的方向来定义子弹方向和位置
Shot shot = new Shot(en.getX() + 20, en.getY() + 60, en.getDiret());
en.shots.add(shot);//将子弹添加到enemTank类里面的shots集合里
new Thread(shot).start();//启动线程
enemyTanks.add(en);
//将enemyTanks 设置给 enemyTank !!!,传入enemytank类里面的enemytanks集合
en.setEnemyTanks(enemyTanks);
}
break;
case "2":
//初始化击败数量
// recorder.setAllEnemyTankNum(0);
//初始化敌人坦克
for (int i = 0; i < nodes.size(); i++) {//初始多少部就就是nodes里面存有多少部
//拿出单个node对象
Node node = nodes.get(i);
//定义一个enemTank对象 并且定义位置,坦克之间间隔100像素
enemyTank en = new enemyTank(node.getX(),node.getY());
en.setDiret(node.getDirect());//定义方向
new Thread(en).start();
//初始化敌人坦克子弹,根据敌方坦克的方向来定义子弹方向和位置
Shot shot = new Shot(en.getX() + 20, en.getY() + 60, en.getDiret());
en.shots.add(shot);//将子弹添加到enemTank类里面的shots集合里
new Thread(shot).start();//启动线程
enemyTanks.add(en);
//将enemyTanks 设置给 enemyTank !!!,将坦克
en.setEnemyTanks(enemyTanks);
}
break;
}
//初始化图片对象,路径问题尤其重要
image1 = Toolkit.getDefaultToolkit().getImage("out/production/Java画图/bomb_1.gif");
image2 = Toolkit.getDefaultToolkit().getImage("out/production/Java画图/bomb_2.gif");
image3 = Toolkit.getDefaultToolkit().getImage("out/production/Java画图/bomb_3.gif");
//音乐
new AePlayWave("D:\\JavaProjects\\b站草稿\\坦克大战\\src\\111.wav").start();
}
public void showinof(Graphics g){
//画出玩家总成绩
g.setColor(Color.BLACK);
Font font = new Font("宋体",Font.BOLD,25);
g.setFont(font);
g.drawString("您击毁的坦克数量",1020,30);
drawTank(1020,60,g,0,1);
g.setColor(Color.BLACK);
//在recorder里拿出击败敌方坦克的数量然后显示
g.drawString(Integer.toString(Recorder.getAllEnemyTankNum()),1080,100);
}
//一整个作图的敌方,背景填充和敌我方坦克和子弹的显示
@Override
public void paint(Graphics g) {
//作图部分
super.paint(g);
//画一个正方形填满界面
g.fillRect(0, 0, 1000, 750);
showinof(g);
//运行DrawTank画出我方坦克方法
//假如我方坦克还活着,就按坐标画出坦克
if (hero.islive){
drawTank(hero.getX(), hero.getY(), g, hero.getDiret(), 0);
}
//画出我方坦克子弹,因为会发射多颗子弹所以用Vector存储子弹
for (int i = 0; i < hero.shots02.size(); i++) {
//从集合里面逐一拿出shot对象
Shot s1 = hero.shots02.get(i);
//判断子弹是否还存在并且是否为空
if (s1 != null && s1.isLive) {
//画出子弹
g.setColor(Color.GREEN);
//子弹坐标已经在Myhero中定义过了
g.fillRect(s1.x, s1.y, 3, 3);
}else {
//移除子弹
hero.shots02.remove(s1);
}
}
//画出爆炸效果,从bombs集合中拿出bomb对象,每个bomb对象都会有爆炸特效
for (int i = 0; i < bombs.size(); i++) {
Bomb bomb = bombs.get(i);
//按照生命周期来画出不同的图片来形成爆炸的渐变
if (bomb.life > 3){
g.drawImage(image1,bomb.x,bomb.y,60,60,this);
bomb.lifedown();
}else if(bomb.life > 1) {
g.drawImage(image2,bomb.x,bomb.y,60,60,this);
bomb.lifedown();
}else {
g.drawImage(image3,bomb.x,bomb.y,60,60,this);
bomb.lifedown();
}
//子弹不存活时把他从集合中去掉
if (!bomb.islive){
bombs.remove(bomb);
}
}
//画多少个敌方坦克
for (int i = 0; i < enemyTanks.size(); i++) {
//定义一个enemyTank类来接收enemyTanks集合里面的一个个元素
enemyTank en = enemyTanks.get(i);
if (en.islive) {//当敌人坦克存活时才画出,假如敌人被子弹打中islive会变成false,就不会画出来了,就等于坦克消失了
//运行Draw坦克方法
drawTank(en.getX(), en.getY(), g, en.getDiret(), 1);
//同时画出子弹
for (int j = 0; j < en.shots.size(); j++) {
//从shots集合提取一个元素传入shot里
Shot shot = en.shots.get(j);
if (shot.isLive) {
//画出子弹
g.setColor(Color.red);
g.fillRect(shot.x, shot.y, 3, 3);
} else {
//移除子弹
en.shots.remove(shot);
}
}
}
}
}
/**
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型
*/
public void drawTank(int x, int y, Graphics g, int direct, int type) {
//根据不同类型坦克,设置不同颜色
//定义坦克类型,区分颜色
switch (type) {
//我方坦克
case 0:
g.setColor(Color.CYAN);
break;
//敌方坦克
case 1:
g.setColor(Color.red);
break;
}
//根据坦克方向,来绘制坦克
switch (direct) {
case 0://上
g.fill3DRect(x, y, 10, 60, false);//左轮子
g.fill3DRect(x + 30, y, 10, 60, false);//画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false);//画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20);//画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y);//画出炮筒
break;
case 1: //表示向右
g.fill3DRect(x, y, 60, 10, false);//画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false);//画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false);//画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20);//画出圆形盖子
g.drawLine(x + 30, y + 20, x + 60, y + 20);//画出炮筒
break;
case 2: //表示向下
g.fill3DRect(x, y, 10, 60, false);//画出坦克左边轮子
g.fill3DRect(x + 30, y, 10, 60, false);//画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false);//画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20);//画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y + 60);//画出炮筒
break;
case 3: //表示向左
g.fill3DRect(x, y, 60, 10, false);//画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false);//画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false);//画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20);//画出圆形盖子
g.drawLine(x + 30, y + 20, x, y + 20);//画出炮筒
break;
default:
System.out.println("暂时没有处理");
}
}
@Override
public void keyTyped(KeyEvent e) {
}
//判断是否打中敌方坦克
//发射多个子弹,先遍历整个子弹集合在一个个比较有没有打中
public void hitenemytank(){
//循环取出我防坦克中的子弹集合
for (int j = 0; j < hero.shots02.size(); j++) {
Shot s2 = hero.shots02.get(j);
//一开始没按下j时子弹不存在,会出现空指针异常,所以要加判断shot不为空
if (s2!=null && s2.isLive){
//遍历每个敌方坦克,查看是否击中
for (int i = 0; i < enemyTanks.size(); i++) {
enemyTank enemyTank1 = enemyTanks.get(i);
//运行判断是否打中坦克方法
hitTank(s2,enemyTank1);
}
}
}
}
//敌人是否击中我们
public void hithero(){
//循环遍历敌方坦克,在遍历一个敌方坦克发射的所有子弹,两层循环
for (int i = 0; i < enemyTanks.size(); i++) {
enemyTank e = enemyTanks.get(i);
for (int j = 0; j < e.shots.size(); j++) {
Shot sh = e.shots.get(j);
//判断 shot 是否击中我的坦克
if (hero.islive && sh.isLive) {
hitTank(sh, hero);
}
}
}
}
//什么时候判断 子弹是否击中坦克 ? run方法,要一直判断是否击中
public void hitTank(Shot s, Tank enemyTank1) {
//得到单个坦克的方向,分上下,左右两种情况编写程序
int m = enemyTank1.getDiret();
switch (m) {
case 0:
case 2:
//判断坐标是否有重合
if (s.x > enemyTank1.getX() && s.x < enemyTank1.getX() + 40
&& s.y > enemyTank1.getY() && s.y < enemyTank1.getY() + 60) {
//击中后子弹和坦克一起消失,生命都为false,然后把坦克从集合中移走
s.isLive = false;
enemyTank1.islive = false;
enemyTanks.remove(enemyTank1);
//加判断,如果这个坦克是敌方坦克,击毁敌方坦克的总数就加一
if (enemyTank1 instanceof enemyTank){
Recorder.addenemytankNum();
}
//创建bomb对象,并添加到bombs集合
Bomb bomb = new Bomb(enemyTank1.getX(),enemyTank1.getY());
bombs.add(bomb);
}
break;
case 1: //坦克向右
case 3: //坦克向左
if (s.x > enemyTank1.getX() && s.x < enemyTank1.getX() + 60
&& s.y > enemyTank1.getY() && s.y < enemyTank1.getY() + 40) {
//击中后子弹和坦克一起消失
s.isLive = false;
enemyTank1.islive = false;
enemyTanks.remove(enemyTank1);
if (enemyTank1 instanceof enemyTank){
Recorder.addenemytankNum();
}
Bomb bomb = new Bomb(enemyTank1.getX(),enemyTank1.getY());
bombs.add(bomb);
}
}
}
//按下键盘会怎么样
@Override
public void keyPressed (KeyEvent e){
if (e.getKeyCode() == KeyEvent.VK_W) {//上
hero.setDiret(0);
hero.moveUp();
} else if (e.getKeyCode() == KeyEvent.VK_S) {//下
hero.setDiret(2);
hero.moveDown();
} else if (e.getKeyCode() == KeyEvent.VK_A) {//左
hero.setDiret(3);
hero.moveLeft();
} else if (e.getKeyCode() == KeyEvent.VK_D) {//右
hero.setDiret(1);
hero.moveRight();
}
if (e.getKeyCode() == KeyEvent.VK_J) {
//在Myhero类里定义了shotEnemyTank()方法,里面已经包含了方向速度,直接调用不会显得冗余
if (hero.islive){
hero.shotEnemyTank();
}
}
//让面板重绘
this.repaint();
}
@Override
public void keyReleased (KeyEvent e){
}
//线程,mypanel可以一直判断是否击中坦克
@Override
public void run () {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
hithero();
hitenemytank();
this.repaint();
}
}
}
TankGame主方法
package Tank2.Tank;
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Scanner;
public class TankGame02 extends JFrame {
Scanner scanner = new Scanner(System.in);
//定义MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame02 hspTankGame02 = new TankGame02();
}
public TankGame02() {
System.out.println("请输入选择 1: 新游戏 2: 继续上局");
String key = scanner.next();
mp = new MyPanel(key);
Thread thread = new Thread(mp);
//调用Mypanel线程,让他一直绘图
thread.start();
this.add(mp);//把面板(就是游戏的绘图区域)
this.setSize(1300, 750);
this.addKeyListener(mp);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
//监听窗口程序,点退出的时候会运行Recorder的存盘方法
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Recorder.keepRecord();
System.exit(0);
}
});
}
}
二、功能板块分析
1.绘制我方与敌方坦克
坦克每个方向都不一样,所有要画四种坦克的样子,后边改变方向的时候调用
分敌方和我方坦克,两个类型,两种颜色
/**
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型
*/
public void drawTank(int x, int y, Graphics g, int direct, int type) {
//根据不同类型坦克,设置不同颜色
//定义坦克类型,区分颜色
switch (type) {
//我方坦克
case 0:
g.setColor(Color.CYAN);
break;
//敌方坦克
case 1:
g.setColor(Color.red);
break;
}
//根据坦克方向,来绘制坦克
switch (direct) {
case 0://上
g.fill3DRect(x, y, 10, 60, false);//左轮子
g.fill3DRect(x + 30, y, 10, 60, false);//画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false);//画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20);//画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y);//画出炮筒
break;
case 1: //表示向右
g.fill3DRect(x, y, 60, 10, false);//画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false);//画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false);//画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20);//画出圆形盖子
g.drawLine(x + 30, y + 20, x + 60, y + 20);//画出炮筒
break;
case 2: //表示向下
g.fill3DRect(x, y, 10, 60, false);//画出坦克左边轮子
g.fill3DRect(x + 30, y, 10, 60, false);//画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false);//画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20);//画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y + 60);//画出炮筒
break;
case 3: //表示向左
g.fill3DRect(x, y, 60, 10, false);//画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false);//画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false);//画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20);//画出圆形盖子
g.drawLine(x + 30, y + 20, x, y + 20);//画出炮筒
break;
default:
System.out.println("暂时没有处理");
}
}
2.键盘控制我方坦克移动与发射
在Myhero类里面已经定义了移动和射击的方法,这里直接调用就行
public void keyPressed (KeyEvent e){
if (e.getKeyCode() == KeyEvent.VK_W) {//上
hero.setDiret(0);
hero.moveUp();
} else if (e.getKeyCode() == KeyEvent.VK_S) {//下
hero.setDiret(2);
hero.moveDown();
} else if (e.getKeyCode() == KeyEvent.VK_A) {//左
hero.setDiret(3);
hero.moveLeft();
} else if (e.getKeyCode() == KeyEvent.VK_D) {//右
hero.setDiret(1);
hero.moveRight();
}
if (e.getKeyCode() == KeyEvent.VK_J) {
//在Myhero类里定义了shotEnemyTank()方法,里面已经包含了方向速度,直接调用不会显得冗余
if (hero.islive){
hero.shotEnemyTank();
}
}
//让面板重绘
this.repaint();
}
3.敌方发射子弹
在enemytank类中定义发射的方法
//在enemytank类中定义发射的方法
public void run() {
while (true){
//这里我们判断如果shots size() < 15, 创建一颗子弹,放入到
//shots集合,并启动
if (islive && shots.size() < 15) {//一部坦克可以同时存在15颗子弹
Shot s = null;
//判断坦克的方向,创建对应的子弹
switch (getDiret()) {
case 0:
s = new Shot(getX() + 20, getY(), 0);
break;
case 1:
s = new Shot(getX() + 60, getY() + 20, 1);
break;
case 2: //向下
s = new Shot(getX() + 20, getY() + 60, 2);
break;
case 3://向左
s = new Shot(getX(), getY() + 20, 3);
break;
}
shots.add(s);
//启动
new Thread(s).start();
}
然后再myplano定义敌方坦克的时候画出子弹
//画多少个敌方坦克
for (int i = 0; i < enemyTanks.size(); i++) {
//定义一个enemyTank类来接收enemyTanks集合里面的一个个元素
enemyTank en = enemyTanks.get(i);
if (en.islive) {//当敌人坦克存活时才画出,假如敌人被子弹打中islive会变成false,就不会画出来了,就等于坦克消失了
//运行Draw坦克方法
drawTank(en.getX(), en.getY(), g, en.getDiret(), 1);
//同时画出子弹
for (int j = 0; j < en.shots.size(); j++) {
//从shots集合提取一个元素传入shot里
Shot shot = en.shots.get(j);
if (shot.isLive) {
//画出子弹
g.setColor(Color.red);
g.fillRect(shot.x, shot.y, 3, 3);
} else {
//移除子弹
en.shots.remove(shot);
}
}
}
}
4. 子弹打到坦克,坦克会消失并且爆炸
//什么时候判断 子弹是否击中坦克 ? run方法,要一直判断是否击中
public void hitTank(Shot s, Tank enemyTank1) {
//得到单个坦克的方向,分上下,左右两种情况编写程序
int m = enemyTank1.getDiret();
switch (m) {
case 0:
case 2:
//判断坐标是否有重合
if (s.x > enemyTank1.getX() && s.x < enemyTank1.getX() + 40
&& s.y > enemyTank1.getY() && s.y < enemyTank1.getY() + 60) {
//击中后子弹和坦克一起消失,生命都为false,然后把坦克从集合中移走
s.isLive = false;
enemyTank1.islive = false;
enemyTanks.remove(enemyTank1);
//加判断,如果这个坦克是敌方坦克,击毁敌方坦克的总数就加一
if (enemyTank1 instanceof enemyTank){
Recorder.addenemytankNum();
}
//创建bomb对象,并添加到bombs集合
Bomb bomb = new Bomb(enemyTank1.getX(),enemyTank1.getY());
bombs.add(bomb);
}
break;
case 1: //坦克向右
case 3: //坦克向左
if (s.x > enemyTank1.getX() && s.x < enemyTank1.getX() + 60
&& s.y > enemyTank1.getY() && s.y < enemyTank1.getY() + 40) {
//击中后子弹和坦克一起消失
s.isLive = false;
enemyTank1.islive = false;
enemyTanks.remove(enemyTank1);
if (enemyTank1 instanceof enemyTank){
Recorder.addenemytankNum();
}
Bomb bomb = new Bomb(enemyTank1.getX(),enemyTank1.getY());
bombs.add(bomb);
}
}
}
同时要启动线程
//线程,mypanel可以一直判断是否击中坦克
@Override
public void run () {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
hithero();
hitenemytank();
this.repaint();
}
}
敌方打到我方
//敌人是否击中我们
public void hithero(){
//循环遍历敌方坦克,在遍历一个敌方坦克发射的所有子弹,两层循环
for (int i = 0; i < enemyTanks.size(); i++) {
enemyTank e = enemyTanks.get(i);
for (int j = 0; j < e.shots.size(); j++) {
Shot sh = e.shots.get(j);
//判断 shot 是否击中我的坦克
if (hero.islive && sh.isLive) {
hitTank(sh, hero);
}
}
}
}
我方打到敌方
//判断是否打中敌方坦克
//发射多个子弹,先遍历整个子弹集合在一个个比较有没有打中
public void hitenemytank(){
//循环取出我防坦克中的子弹集合
for (int j = 0; j < hero.shots02.size(); j++) {
Shot s2 = hero.shots02.get(j);
//一开始没按下j时子弹不存在,会出现空指针异常,所以要加判断shot不为空
if (s2!=null && s2.isLive){
//遍历每个敌方坦克,查看是否击中
for (int i = 0; i < enemyTanks.size(); i++) {
enemyTank enemyTank1 = enemyTanks.get(i);
//运行判断是否打中坦克方法
hitTank(s2,enemyTank1);
}
}
}
5. 敌方坦克自由移动
用random随机数来随机改变坦克方向
public void run() {
while (true){
//根据坦克的方向来继续移动
//移动时加入判断是否重叠的方法
switch (getDiret()){
case 0://上
//保持一个方向走40步,不然太频繁变动方向了
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveUp();
//休眠代码是为了不让坦克一下子就走完40步,就会看不到中间走动的过程所以加上休眠
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://右
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveRight();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://下
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveDown();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://左
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveLeft();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//然后随机的改变坦克方向 0-3
setDiret((int) (Math.random() * 4));
//写并发程序,一定要考虑清楚,该线程什么时候结束
if (!islive) {
break; //退出线程.
}
}
}
6. 我方坦克发射多颗子弹,并且设置同时存在的子弹数量上限
Shot shot = null;
//发射多颗子弹的情况
//创建一个shots集合来保存多个shot对象
Vector<Shot> shots02 = new Vector<>();
public MyHero(int x, int y) {
super(x, y);
}
//射击
public void shotEnemyTank() {
//控制我方子弹最多同时存在4个,大于四个就不创建新的shot对象
if (shots02.size() > 4){
return;
}
//创建 Shot 对象, 根据当前Hero对象的位置和方向来创建Shot
int a = getDiret();
switch (a) {//得到Hero对象方向
case 0: //向上
shot = new Shot(getX() + 20, getY(), 0);
break;
case 1: //向右
shot = new Shot(getX() + 60, getY() + 20, 1);
break;
case 2: //向下
shot = new Shot(getX() + 20, getY() + 60, 2);
break;
case 3: //向左
shot = new Shot(getX(), getY() + 20, 3);
break;
}
//把新建的shot放入集合
shots02.add(shot);
//启动我们的Shot线程
new Thread(shot).start();
}
7. 防止敌方坦克重叠并且设置移动范围
总共有四大种八小种情况,将重叠情况列出来,然后得到是否重叠的方法,然后将方法加入到坦克随机移动的方法里面去
//编写方法,判断当前的这个敌人坦克,是否和 enemyTanks 中的其他坦克发生的重叠或者碰撞
public boolean isTouchEnemyTank(){
//判断当前敌人坦克(this) 方向
switch (this.getDiret()) {
case 0: //上
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY()]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 右上角的坐标 [this.getX() + 40, this.getY()]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY()]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 右上角的坐标 [this.getX() + 40, this.getY()]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
case 1: //右
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 右上角的坐标 [this.getX() + 60, this.getY()]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 60, this.getY() + 40]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 40
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 右上角的坐标 [this.getX() + 60, this.getY()]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 60, this.getY() + 40]
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 60
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
case 2: //下
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 60]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 40, this.getY() + 60]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 40
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 60]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 右下角的坐标 [this.getX() + 40, this.getY() + 60]
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 60
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
case 3: //左
//让当前敌人坦克和其它所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//从vector 中取出一个敌人坦克
enemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下
//1. 如果敌人坦克是上/下 x的范围 [enemyTank.getX(), enemyTank.getX() + 40]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 60]
if (enemyTank.getDiret() == 0 || enemyTank.getDiret() == 2) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY() ]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;
}
//3. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 40]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 60) {
return true;
}
}
//如果敌人坦克是 右/左
//1. 如果敌人坦克是右/左 x的范围 [enemyTank.getX(), enemyTank.getX() + 60]
// y的范围 [enemyTank.getY(), enemyTank.getY() + 40]
if (enemyTank.getDiret() == 1 || enemyTank.getDiret() == 3) {
//2. 当前坦克 左上角的坐标 [this.getX(), this.getY() ]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;
}
//3. 当前坦克 左下角的坐标 [this.getX(), this.getY() + 40]
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 40) {
return true;
}
}
}
}
break;
}
return false;
}
移动时加入判断是否重叠的方法
//根据坦克的方向来继续移动
//移动时加入判断是否重叠的方法
switch (getDiret()){
case 0://上
//保持一个方向走40步,不然太频繁变动方向了
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveUp();
//休眠代码是为了不让坦克一下子就走完40步,就会看不到中间走动的过程所以加上休眠
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://右
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveRight();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://下
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveDown();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://左
for (int i = 0; i < 40; i++) {
if (!isTouchEnemyTank())
moveLeft();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//然后随机的改变坦克方向 0-3
setDiret((int) (Math.random() * 4));
//写并发程序,一定要考虑清楚,该线程什么时候结束
if (!islive) {
break; //退出线程.
}
}
}
8. 记录成绩,并且按照记录的信息继续上一局游戏
创建输入输出流,记录击毁敌方数量和坦克位置,选择继续游戏时再把文件信息拿出来然后按信息创建敌方坦克。
//定义变量,记录我方击毁敌人坦克数
private static int allEnemyTankNum = 0;
//定义输入输出流
private static FileWriter fw = null;
private static BufferedWriter bw = null;
private static BufferedReader br = null;
//定义静态变量地址
private static final String PATH = "D:\\JavaProjects\\b站草稿\\坦克大战\\src\\MyRecord.txt";
//创建坦克Vector集合,用来接收mypanel中的enemytank集合,用来后面的遍历敌方坦克用
private static Vector<enemyTank> enemyTanks = null;
//定义一个Node 的Vector ,用于保存敌人的信息node
private static Vector<Node> nodes = new Vector<>();
public static String getPATH() {
return PATH;
}
//增加一个方法,用于读取PATH, 恢复相关信息
//该方法,在继续上局的时候调用即可
public static Vector<Node> getNodesAndEnemyTankRec(){
try {
br = new BufferedReader(new FileReader(PATH));
//从文件中取出击毁敌方坦克数量
allEnemyTankNum = Integer.parseInt(br.readLine());
String line = "";//255 40 0
//将读出的数据存入line字符串
while ((line = br.readLine())!=null){//循环取出,只要不为空就取出
String[] xyd = line.split(" ");//以空格作为分隔符形成新的对象
//0号是x坐标,1号是y坐标,2号是方向,并且要转成int类型
//将取出的数据当作node对象存入nodes集合里
Node node = new Node(Integer.parseInt(xyd[0]),
Integer.parseInt(xyd[1]),Integer.parseInt(xyd[2]));
nodes.add(node);
}
//固定格式
} catch (Exception e) {
e.printStackTrace();
}finally {
if (br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//返回nodes对象
return nodes;
}
public void setenemy(Vector<enemyTank> enemyTanks){
Recorder.enemyTanks = enemyTanks;
}
//增加一个方法,当游戏退出时,我们将allEnemyTankNum 保存到 PATH
//对keepRecord 进行升级, 保存敌人坦克的坐标和方向
public static void keepRecord(){
try {
bw = new BufferedWriter(new FileWriter(PATH));
//写入文件时要转成String类型不然会乱码
bw.write(Integer.toString(allEnemyTankNum));
bw.newLine();
//遍历enemytanks集合,取出每个坦克,然后吧坐标和方向写入文件
for (int i = 0; i < enemyTanks.size(); i++) {
enemyTank en = enemyTanks.get(i);
//用空格隔开方便取出时用空格作为分隔符形成新代码
String s = en.getX() + " " + en.getY() + " " + en.getDiret();
bw.write(s);
bw.newLine();
}
bw.flush();
} catch (IOException e) {
e.printStackTrace();
//固定写法
}finally {
if (bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
监听窗口程序,点退出的时候会运行Recorder的存盘方法
//监听窗口程序,点退出的时候会运行Recorder的存盘方法
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Recorder.keepRecord();
System.exit(0);
}
});
给玩家进行选择,新游戏还是继续游戏,若没有上一次 游戏的记录,就强行新游戏
//先判断记录的文件是否存在
//如果存在,就正常执行,如果文件不存在,提示,只能开启新游戏,key = "1"
File file = new File(Recorder.getPATH());
if (file.exists()) {
nodes = Recorder.getNodesAndEnemyTankRec();
} else {
System.out.println("文件不存在,只能开启新的游戏");
key = "1";
}
//将从文件得到的数据放入nodes集合里
nodes = recorder.getNodesAndEnemyTankRec();
//初始化我的坦克的位置
hero = new MyHero(500, 600);
hero.setSpeed(4);//设置我防坦克的移动速度,后期改难度可以改速度
//将mypanel对象的enemytanks设置给Recorder的enemytanks
recorder.setenemy(enemyTanks);
//选择是重新开始还是继续游戏
switch (key){
case "1":
//初始化敌人坦克
for (int i = 0; i < enemyTanksize; i++) {//初始多少部
//定义一个enemTank对象 并且定义位置,坦克之间间隔100像素
enemyTank en = new enemyTank(70 * (i + 1), 0);
en.setDiret(2);//定义方向
//启动线程,敌方坦克走动加射击
new Thread(en).start();
//初始化敌人坦克子弹,根据敌方坦克的方向来定义子弹方向和位置
Shot shot = new Shot(en.getX() + 20, en.getY() + 60, en.getDiret());
en.shots.add(shot);//将子弹添加到enemTank类里面的shots集合里
new Thread(shot).start();//启动线程
enemyTanks.add(en);
//将enemyTanks 设置给 enemyTank !!!,传入enemytank类里面的enemytanks集合
en.setEnemyTanks(enemyTanks);
}
break;
case "2":
//初始化击败数量
// recorder.setAllEnemyTankNum(0);
//初始化敌人坦克
for (int i = 0; i < nodes.size(); i++) {//初始多少部就就是nodes里面存有多少部
//拿出单个node对象
Node node = nodes.get(i);
//定义一个enemTank对象 并且定义位置,坦克之间间隔100像素
enemyTank en = new enemyTank(node.getX(),node.getY());
en.setDiret(node.getDirect());//定义方向
new Thread(en).start();
//初始化敌人坦克子弹,根据敌方坦克的方向来定义子弹方向和位置
Shot shot = new Shot(en.getX() + 20, en.getY() + 60, en.getDiret());
en.shots.add(shot);//将子弹添加到enemTank类里面的shots集合里
new Thread(shot).start();//启动线程
enemyTanks.add(en);
//将enemyTanks 设置给 enemyTank !!!,将坦克
en.setEnemyTanks(enemyTanks);
}
break;
}
三、总结
-
面向对象的思想尤其重要,要学会封装方法,方法该放入哪个类,而且要学会把存入外部文件当作一个对象去处理比如这里的node类,如果当成字符串处理就非常繁杂。
-
要注意文件的路径千万不要搞错
-
要考虑什么时候写并发程序,什么时候要用多线程,写并发程序,一定要考虑清楚,该线程什么时候结束,同时再必要的地方加入休眠代码,不然程序执行太快看不见效果。
-
要考虑线程安全问题,所有这里用Vector集合不用ArrayList
-
本项目经常会在类中定义集合,然后在实现类里面,把创建的集合传入类中的集合,这样就可以在类中写很多方法,也同样也是面向对象的思想。
比如:
- 提供一个方法,可以将MyPanel 的成员 Vector enemyTanks = new Vector<>();设置到 enemyTank 的成员 enemyTanks
- Myhero类中的Vector shots02 = new Vector<>();
- Recorder类中的创建坦克Vector集合,用来接收mypanel中的enemytank集合,用来后面的遍历敌方坦克用
private static Vector enemyTanks = null;
//定义一个Node 的Vector ,用于保存敌人的信息node
private static Vector nodes = new Vector<>();
四、游戏后期拓展
- 加入障碍,敌方坦克炮弹打不穿,我方坦克可以
- 加入河流,子弹可以穿过,但坦克不行