这个游戏我企图用我仅有的一点的java知识和面对对象的知识来编程,里面不合理的地方请不要介意,如果能留下您宝贵的意见,感激不尽。
游戏主要分成:
实体类:子弹(Bullet)类、坦克(Tank)类
线程类:子弹移动类(BulletMoveThread)、英雄坦克移动类(TankMoveThread)、敌方坦克自动射击移动类(EnemyTankMoveThread)
package game;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/*
* 游戏主体
* */
public class Game {
private JFrame jf = new JFrame("坦克大战"); //创建一个JFrame对象,存放整个窗体
JPanel topMenu = new JPanel(); // 头部菜单
private int enemyNum; // 敌机数量
private Tank[] enemy;// 敌机
private EnemyTankMoveThread[] enemyTank; // 敌机的移动线程
// 以下变量被我用static修饰,是为了方便我在isGameOver(判断游戏结束)方法中能使用到
// 因为静态方法得使用静态变量
// isGameOver在Tank类中的reduceLifeVaule(扣除生命值)方法中被调用
private static JPanel jp = new JPanel(); // 游戏主体
private static int heroLifeNum; // 我方生命
private static JLabel heroLifeNumLabel = new JLabel(); // 提示英雄方剩余生命的标签
private static JLabel enemyNumLabel = new JLabel(); // 提示敌机数量的标签
private static Tank hero; // 英雄机(我方)
private static TankMoveThread heroTank; // 英雄机的移动线程
public Game() {
// 游戏初始化
jf.setLayout(new BorderLayout()); //设置边框布局
jf.setBounds(300, 100, 600, 650); //设置窗口大小和位置
jf.setVisible(true); // 设置窗体可见,不写看不见
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 头部区域
gameHeader();
// 游戏区域
init();
}
void gameHeader() {
// 游戏头部
topMenu.removeAll();
JLabel enemyNumTips = new JLabel("敌机数量"); // 提示标签
JLabel heroLifeNumTips = new JLabel("我方生命"); // 提示标签
JButton resetButton = new JButton("新对局"); // 重置按钮
resetButton.addActionListener(new ResetGame());
topMenu.add(enemyNumTips);
topMenu.add(enemyNumLabel);
topMenu.add(heroLifeNumTips);
topMenu.add(heroLifeNumLabel);
topMenu.add(resetButton);
jf.add(topMenu, BorderLayout.NORTH);
}
void init() {
jp.removeAll(); // 清空游戏主体的内容
jp.setLayout(null);
// 初始化(顶部栏)数据
enemyNum = 10; // 敌人数量
heroLifeNum = 3; // 我方生命
enemyNumLabel.setText(String.valueOf(enemyNum));
heroLifeNumLabel.setText(String.valueOf(heroLifeNum));
// 添加敌机
enemy = new Tank[enemyNum]; // 敌机组
enemyTank = new EnemyTankMoveThread[enemyNum]; // 敌机线程组
for (int i=0;i<enemyNum;i++) {
enemy[i] = new Tank(10,10); // 新建敌机
enemy[i].setBelongTo("enemy"); // 标识为敌机
enemy[i].setBackground(Color.red); // 设置颜色
enemyTank[i] = new EnemyTankMoveThread(); // 新建对应线程
enemyTank[i].setTank(enemy[i]); // 将坦克传入到线程中
enemyTank[i].start(); // 开启线程
enemyTank[i].resumeThread(); // 唤醒线程
jp.add(enemy[i]); // 添加JPanel容器中
}
// 设置游戏主题内容
hero = new Tank(jf.getSize().width-80,jf.getSize().height-140);
hero.setBelongTo("hero"); // 表示为英雄机
jp.add(hero);
heroTank = new TankMoveThread();
heroTank.setTank(hero);
heroTank.start();
jp.setBackground(Color.black);//设置背景色
jp.addKeyListener(new keyCodeListener());
jp.enableInputMethods (false); // 切换输入法状态,默认窗口打开时输入法是中文的,导致(子弹)键盘按键不可用
jf.add(jp, BorderLayout.CENTER);
// (如果没有这句(下面的这句代码),swing默认jframe和jpanel和jlabel是没有焦点的,
// swing自动把焦点给了窗体中第一个可以获取焦点的组件,
// 因为我把键盘监听事件(上一句)添加到jp,所以jp要获取焦点)
jp.requestFocus(); // jp获取焦点,才能执行键盘的监听事件
}
public static void main(String[] args) {
new Game();
}
public static void isGameOver() {
int enemys = 0; // 敌机数量
int heros = 0; // 英雄机
for (int i = 0; i < jp.getComponentCount(); i++) {
Object obj = jp.getComponent(i);
if (obj instanceof Tank) {
// 先找出坦克
if (((Tank)obj).getBelongTo() == "enemy") {
// 敌机
enemys++;
}
if (((Tank)obj).getBelongTo() == "hero") {
// 英雄机
heros++;
}
}
}
if (enemys == 0) {
// 敌机数量为0,以消灭玩所有的敌机,赢了
// 停止英雄机的移动线程
Game.hero.setExist(false); // 因为坦克移动线程的终止条件是坦克不存在,所以这里之间设置了坦克不存在。
// 提示信息
JOptionPane.showMessageDialog(Game.jp, "成功消灭所有敌机,你赢得了战争", "游戏结束", JOptionPane.ERROR_MESSAGE);
}
if (Game.heroLifeNum == 0) {
// 停止敌机的移动线程
for (int i = 0; i < jp.getComponentCount(); i++) {
Object obj = jp.getComponent(i);
if (obj instanceof Tank) {
((Tank)obj).setExist(false);
}
}
// 提示信息
JOptionPane.showMessageDialog(Game.jp, "噢不,你输掉了战争,失去了一切", "游戏结束", JOptionPane.ERROR_MESSAGE);
}
if (heros == 0 && Game.heroLifeNum != 0) {
// 初始化英雄坦克
hero = new Tank(jp.getSize().width-70,jp.getSize().height-70);
hero.setBelongTo("hero"); // 表示为英雄机
// 初始化英雄坦克的移动线程
heroTank = new TankMoveThread();
heroTank.setTank(hero);
heroTank.start();
// 添加到JPanel中
jp.add(hero);
Game.heroLifeNum--;
}
// 设置页面上的对应的英雄生命数量
Game.heroLifeNumLabel.setText(String.valueOf(Game.heroLifeNum));
Game.enemyNumLabel.setText(String.valueOf(enemys));
}
public void reset() {
// 停止敌机和子弹的线程
for (int i = 0; i < jp.getComponentCount(); i++) {
Object obj = jp.getComponent(i);
if (obj instanceof Tank) {
((Tank)obj).setExist(false);
}
if (obj instanceof Bullet) {
((Bullet)obj).setExist(false);
}
}
// 重绘窗体
jf.repaint();
// 头部区域
gameHeader();
// 游戏区域
init();
}
class keyCodeListener implements KeyListener{
@Override
public void keyPressed(KeyEvent e) {
// 按键按下未松开
// 1号玩家坦克
if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_DOWN) {
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT :
heroTank.setDirection("left");
break;
case KeyEvent.VK_UP :
heroTank.setDirection("up");
break;
case KeyEvent.VK_DOWN :
heroTank.setDirection("down");
break;
case KeyEvent.VK_RIGHT :
heroTank.setDirection("right");
break;
}
// 唤醒1号玩家坦克移动线程
heroTank.resumeThread();
}
// 1号玩家射击
if (e.getKeyCode() == KeyEvent.VK_NUMPAD1) {
// 创建一个子弹线程,并传入一个子弹,字段类所需参数是坦克的方向
Bullet bullet = new Bullet("hero",hero.getDirection(),hero.getPositionX(),hero.getPositionY());
jp.add(bullet);
new BulletMoveThread(bullet).start();
}
}
@Override
public void keyReleased(KeyEvent e) {
// 按键松开时
// 1号玩家坦克
if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_DOWN) {
//停止1号玩家坦克移动线程
heroTank.pauseThread();
}
}
@Override
public void keyTyped(KeyEvent e) {
// 按键输入时
}
}
class ResetGame implements ActionListener{
// 重置游戏
public void actionPerformed(ActionEvent event){
reset();
}
}
}
package game;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
/*
坦克类
*/
public class Tank extends JLabel{
/*
坦克的属性:颜色,运动方向,移动速度,状态(生存、死亡),位置(x,y),阵营(敌机还是英雄)
* */
private Color tankColor = Color.yellow; // 坦克的颜色
private int movingSpeed = 10; // 移动速度
private int positionX = 10; // X坐标
private int positionY = 10; // Y坐标
private String belongTo; // 是英雄(hero)还是敌机(enemy)
private String direction = "up"; // 方向,子弹类会需要这个值
private int lifeValue = 3;// 生命值
private boolean exist = true; // 判断是否存在的标志
public Tank(int x, int y) {
this.setBackground(tankColor);
this.setOpaque(true); // 是否不透明,不设没颜色。。。。
//this.setPreferredSize(new Dimension(50, 50));
this.positionX = x;
this.positionY = y;
this.setBounds(positionX, positionY, 50, 50);
this.setText("↑");
this.setHorizontalAlignment(JLabel.CENTER);
this.setFont(new java.awt.Font("Dialog", 1, 30));
}
public void setTankColor(Color color) {
// 设置背景颜色
this.tankColor = color;
}
public void reduceLifeVaule() {
// 扣除生命值
this.lifeValue--;
if (this.lifeValue == 0) {
// 生命值扣完,对应坦克死亡,消除存在
this.exist = false;
// 从JPanel中删除
JPanel jp = (JPanel)this.getParent();
for (int i = 0; i < jp.getComponentCount(); i++) {
Object obj = jp.getComponent(i);
if (obj instanceof Tank && (Tank)obj == this) {
jp.remove((Tank)obj);
jp.repaint();
Game.isGameOver();
}
}
}
}
public void setName(String name) {
this.setText(name);
}
public void setMovingSpeed(int speed) {
// 设置移动速度
this.movingSpeed = speed;
}
public void setPositionX(int x) {
// 设置PositionX
this.positionX = x;
}
public void setPositionY(int y) {
// 设置PositionY
this.positionY = y;
}
public void setExist(boolean exist) {
this.exist = exist;
}
public void setBelongTo(String belongTo) {
// 设置阵营
this.belongTo = belongTo;
}
public String getBelongTo() {
// 返回
return this.belongTo;
}
public int getPositionX() {
// 返回X轴坐标
return this.positionX;
}
public int getPositionY() {
// 返回Y轴坐标
return this.positionY;
}
public boolean getExist() {
return this.exist;
}
public void setPosition (String direction) {
// direction 方向
if (this.exist == false) return;
switch(direction) {
case "up":
this.setText("↑");
this.direction = "up";
if (this.positionY > this.movingSpeed ) this.positionY -= this.movingSpeed;
break;
case "right":
this.setText("→");
this.direction = "right";
// 当父容器宽度大于 当前X轴坐标+当前坦克宽度+移动速度时,目的是为了让坦克不碰到窗体边界
if(this.getParent().getWidth() > this.positionX+this.getSize().width+this.movingSpeed*2) this.positionX += this.movingSpeed;
break;
case "down":
this.setText("↓");
this.direction = "down";
if(this.getParent().getHeight() > this.positionY+this.getSize().height+this.movingSpeed*2) this.positionY += this.movingSpeed;
break;
case "left":
this.setText("←");
this.direction = "left";
if (this.positionX > 10 ) this.positionX -= this.movingSpeed;
break;
}
this.setBounds(positionX, positionY, 50, 50);
}
public String getDirection() {
return this.direction;
}
public static void main(String[] args) {
}
}
package game;
import java.awt.Color;
import java.awt.Rectangle;
import javax.swing.JLabel;
import javax.swing.JPanel;
/*
子弹类
*/
public class Bullet extends JLabel{
/*
属性:速度, 方向, 大小,状态(消失、出现)
方法:
* */
private Color bulletColor = Color.yellow; // 子弹的颜色
private int movingSpeed = 5; // 移动速度
private int positionX = 0; // X坐标
private int positionY = 0; // Y坐标
private String direction; // 移动方向
private boolean exist = true; // // 判断是否存在的标志
private String belongTo; // 是英雄(hero)的子弹还是敌机(enemy)的子弹
public Bullet(String belongTo,String direction, int x, int y) {
// 传入三个参数,方向,坦克的XY轴坐标
this.setBackground(bulletColor);
this.setOpaque(true); // 是否不透明,不设没颜色。。。。
this.belongTo = belongTo; // 是英雄的子弹还是敌机的子弹
this.positionX = x+20; // 加上20是为了让子弹到坦克的中间
this.positionY = y+20;
this.setBounds(positionX, positionY, 10, 10);
this.direction = direction;
}
public void setExist(boolean exist) {
this.exist = exist;
}
public void setPosition () {
// direction 方向
// switch 内部的if判断是为了不让子弹超出边界
if (this.exist == false) return;
switch(direction) {
case "up":
if (this.positionY > this.movingSpeed ) this.positionY -= this.movingSpeed; else this.exist = false;
break;
case "right":
if(this.getParent().getParent().getWidth() > this.positionX+this.getSize().width+this.movingSpeed*2) this.positionX += this.movingSpeed; else this.exist = false;
break;
case "down":
if(this.getParent().getParent().getHeight() > this.positionY+this.getSize().height*5+this.movingSpeed*2) this.positionY += this.movingSpeed; else this.exist = false;
break;
case "left":
if (this.positionX > 10 ) this.positionX -= this.movingSpeed; else this.exist = false;
break;
}
this.setBounds(positionX, positionY, 10, 10);
isRam(); // 判断子弹是否碰撞其他单位,如果撞击会修改exist的值
if (!this.exist){/*如果子弹撞到边界或是撞击了其他单位*/clearBullet(); /*执行清除方法*/}
}
private void isRam() {
// 判断子弹是否碰撞其他单位
JPanel jp = (JPanel)this.getParent();
for (int i = 0; i < jp.getComponentCount(); i++) {
if (jp.getComponent(i) != null) {
Object obj = jp.getComponent(i);
if (obj instanceof Tank && ((Tank) obj).getBelongTo() != this.belongTo) {
// 找出所有的坦克并且不是跟子弹的发出阵营相同的坦克
Tank tank = (Tank)obj;
Rectangle currentBullet = new Rectangle(positionX,positionY,5,5); // x轴坐标,y轴坐标,宽和高,均为int类型
Rectangle currentTank = new Rectangle(tank.getPositionX(),tank.getPositionY(),50,50);
if (currentBullet.intersects(currentTank)) {
// 发生了碰撞,修改子弹的存在状态
this.exist = false;
// 被撞击的坦克,生命值减1
tank.reduceLifeVaule();
}
}
}
}
}
private void clearBullet() {
// 找出JPanel中的子弹,如果是当前子弹就将此子弹从JPnael中清除掉
JPanel jp = (JPanel)this.getParent();
for (int i = 0; i < jp.getComponentCount(); i++) {
if (jp.getComponent(i) != null ) {
Object obj = jp.getComponent(i);
if (obj instanceof Bullet) {
// 如果类型是子弹
if ((Bullet)obj == this) {
// 如果是当前子弹
//this.setVisible(false);
jp.remove((Bullet)obj); // 移除子弹
jp.repaint(); // 重绘JPanel
}
}
}
}
}
public boolean getExist() {
// 返回exist值
return this.exist;
}
public static void main(String[] args) {
}
}
package game;
/*
* https://www.cnblogs.com/bieyaoxiguan/p/11493590.html 此线程类参考借鉴了这个博客
*
// 1.创建自己的线程
MyThread myThread = new MyThread();
// 2.在合适的地方启动线程(你需要在什么地方启动它)
myThread.start();
// 3.启动后线程的 阻塞/暂停
myThread.pauseThread();
// 4.以及 阻塞/暂停 线程后的 唤醒/继续
myThread.resumeThread();
* */
public class BulletMoveThread extends Thread {
//-------------------------------------------------------------
//无意义
private final Object lock = new Object();
//标志线程阻塞情况
private boolean pause = false;
/**
* 设置线程是否阻塞
*/
public void pauseThread() {
this.pause = true;
}
/**
* 调用该方法实现恢复线程的运行
*/
public void resumeThread() {
this.pause = false;
synchronized (lock) {
//唤醒线程
lock.notify();
}
}
/**
* 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
*/
void onPause() {
synchronized (lock) {
try {
//线程 等待/阻塞
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//-------------------------------------------------以上内容不适合修改
private Bullet bullet; // 子弹
public BulletMoveThread(Bullet bullet) {
// 构造子弹线程时,传入子弹
this.bullet = bullet;
}
@Override
public void run() {
super.run();
//一直循环
while (bullet.getExist()) {
// 如果子弹已经达到边界,即返回值为false,此线程结束
try {
//程序每20毫秒执行一次 值可更改
Thread.sleep(20);
// 子弹移动
bullet.setPosition();
// 判断子弹是否碰到其他坦克
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
}
package game;
/*
* https://www.cnblogs.com/bieyaoxiguan/p/11493590.html
*
// 1.创建自己的线程
MyThread myThread = new MyThread();
// 2.在合适的地方启动线程(你需要在什么地方启动它)
myThread.start();
// 3.启动后线程的 阻塞/暂停
myThread.pauseThread();
// 4.以及 阻塞/暂停 线程后的 唤醒/继续
myThread.resumeThread();
* */
public class TankMoveThread extends Thread {
//-------------------------------------------------------------
//无意义
private final Object lock = new Object();
//标志线程阻塞情况
private boolean pause = false;
/**
* 设置线程是否阻塞
*/
public void pauseThread() {
this.pause = true;
}
/**
* 调用该方法实现恢复线程的运行
*/
public void resumeThread() {
this.pause = false;
synchronized (lock) {
//唤醒线程
lock.notify();
}
}
/**
* 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
*/
void onPause() {
synchronized (lock) {
try {
//线程 等待/阻塞
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//-------------------------------------------------以上内容不适合修改
private String direction = ""; // 坦克移动方向
private Tank tank; // 坦克
public void setDirection(String direction) {
// 设置移动方向
this.direction = direction;
}
public void setTank(Tank tank) {
// 设置该线程对应的坦克
this.tank = tank;
}
@Override
public void run() {
super.run();
int i = 0;
//一直循环
while (tank.getExist()) {
if (pause || direction == "") {
//线程 阻塞/等待
onPause();
}
try {
//程序每50毫秒执行一次 值可更改
Thread.sleep(50);
// 坦克移动
tank.setPosition(direction);
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
}
package game;
import java.util.Random;
public class EnemyTankMoveThread extends Thread{
//-------------------------------------------------------------
//无意义
private final Object lock = new Object();
//标志线程阻塞情况
private boolean pause = false;
/**
* 设置线程是否阻塞
*/
public void pauseThread() {
this.pause = true;
}
/**
* 调用该方法实现恢复线程的运行
*/
public void resumeThread() {
this.pause = false;
synchronized (lock) {
//唤醒线程
lock.notify();
}
}
/**
* 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
*/
void onPause() {
synchronized (lock) {
try {
//线程 等待/阻塞
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//-------------------------------------------------以上内容不适合修改
private Tank tank; // 坦克
private Random r = new Random(1); // 随机数
private int step = 0; // 坦克移动的步数,到了就换方向
private int rate = 0; // 射击频率
private String direction; // 移动方向
public void setTank(Tank tank) {
// 设置该线程对应的坦克
this.tank = tank;
}
@Override
public void run() {
super.run();
//一直循环
while (tank.getExist()) {
if (pause) {
//线程 阻塞/等待
onPause();
}
try {
// 敌机坦克射击(射击频率)
if (rate == 0) {
// 如果射击频率运行到了0,就射击
while(rate == 0) {/* 为0就循环,直到不为0*/rate = (int)(Math.random()*20);}
Bullet bullet = new Bullet("enemy",tank.getDirection(),tank.getPositionX(),tank.getPositionY());
if (tank.getParent()!= null ) {
// 在点击新对局(重置)按钮的时候,由于tank.getParent()即JPanel容器未存在
//就无法添加子弹到其中,所以加此判断
tank.getParent().add(bullet);
new BulletMoveThread(bullet).start();
}
}
// 决定何时改变坦克的移动方向
Thread.sleep(200);
if (step == 0) {
// 如果坦克步骤运行到了0,就转向
while(step == 0) {
// 为0就循环,直到不为0
step = (int)(Math.random()*20);
}
// 随机决定坦克移动的方向
int number=(int)(Math.random()*4);
switch(number) {
case 0:
direction = "up";
break;
case 1:
direction = "right";
break;
case 2:
direction = "down";
break;
case 3:
direction = "left";
break;
}
}
step--;
rate--;
tank.setPosition(direction); // 调用坦克的位置移动方法
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
}