一、简单介绍
写在前面:
这是我通过学习网上的教程写出的代码,并不是简单的复制粘贴,发出此代码的目的主要是为了让大家学习编程的思路,希望可以通过此项目让你学习到新的内容。
实现的内容:
1、单人游戏
2、双人游戏
3、选择关卡
4、游戏帮助
5、游戏关于
6、退出游戏
主要内容
游戏截图
GameMain类
package c02.n02.app;
import c02.n02.game.GameFrame;
public class GameMain {
public static void main(String[] args) {
//窗口初始化
new GameFrame();
}
}
Bullet类
package c02.n02.game;
import c02.n02.tank.Tank;
import c02.n02.util.Constant;
import java.awt.*;
/**
* 子弹类
*/
public class Bullet {
public static final int DEFAULT_SPEED = 20; //子弹默认速度
public static final int RADIUS = 4; //炮弹的半径
private int x, y; //子弹坐标
private int speed = DEFAULT_SPEED; //子弹速度
private int dir; //方向
private int atk; //攻击力
private Color color; //颜色
private boolean visible = true; //子弹是否可见
/**
* 子弹类构造函数
*
* @param x X坐标
* @param y Y坐标
* @param dir 方向
* @param atk 攻击力
* @param color 颜色
*/
public Bullet(int x, int y, int dir, int atk, Color color) {
this.x = x;
this.y = y;
this.dir = dir;
this.atk = atk;
this.color = color;
}
/**
* 无参构造函数
* 给对象池使用的,参数全部默认,因为炮弹是哪个坦克发射的都不知道
*/
public Bullet() {
}
/*====================START=============GETTER/SETTER========================================*/
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 getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDir() {
return dir;
}
public void setDir(int dir) {
this.dir = dir;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
/*====================END=============GETTER/SETTER========================================*/
/**
* 炮弹自身的绘制方法
*
* @param g
*/
public void draw(Graphics g) {
//如果子弹不可见就不画了
if (!visible) return;
logic();
g.setColor(color); //设置炮弹颜色
//画实心炮弹
g.fillOval(x - RADIUS, y - RADIUS, RADIUS * 2, RADIUS * 2);
}
/**
* 炮弹的逻辑
* 逻辑就是移动
*/
private void logic() {
move();
}
/**
* 炮弹的移动方法
*/
private void move() {
switch (dir) {
case Tank.DIR_UP:
y -= speed;
if (y <= 0) {
//如果子弹飞出屏幕外就为不可见状态
visible = false;
}
break;
case Tank.DIR_DOWN:
y += speed;
if (y > Constant.FRAME_HIGH) {
visible = false;
}
break;
case Tank.DIR_LEFT:
x -= speed;
if (x < 0) {
visible = false;
}
break;
case Tank.DIR_RIGHT:
x += speed;
if (x > Constant.FRAME_WIDTH) {
visible = false;
}
break;
}
}
}
Explode类
package c02.n02.game;
import c02.n02.util.MyUtil;
import java.awt.*;
/**
* 爆炸效果类
*/
public class Explode {
//设置爆炸效果一共有几帧
public static final int EXPLODE_FRAME_COUNT = 6;
//导入爆炸图片
private static Image[] img;
//在静态代码块中进行初始化
static {
img = new Image[EXPLODE_FRAME_COUNT];
for (int i = 0; i < img.length; i++) {
img[i] = MyUtil.createImage("image/boom/b" + i + ".png");
}
}
//爆炸效果的属性
private int x;
private int y;
//当前播放的是第几帧的下标
private int index;
//是否可见
private boolean visible = true;
//爆炸图片宽度和高度的一半
private static int explodeWidth = 60 / 2;
private static int explodeHight = 60 / 2;
//============START==========GETTER/SETTER===========================
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 getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
//============END==========GETTER/SETTER===========================
/**
* 爆炸类的无参构造函数
* 给对象池使用的,参数全部默认,因为爆炸效果是哪个坦克的都不知道
*/
public Explode() {
//设置为从第0帧开始
index = 0;
}
/**
* 爆炸类的构造方法
*
* @param x X位置
* @param y Y位置
*/
public Explode(int x, int y) {
this.x = x;
this.y = y;
//设置为从第0帧开始
index = 0;
}
/**
* 炮弹类的绘制方法
*
* @param g
*/
public void draw(Graphics g) {
//如果帧下标超出最大的话就重新设置为0
//为了防止调用时没有手动设置为从第0帧开始播放导致下标越界
if (index > EXPLODE_FRAME_COUNT - 1) {
index = 0;
}
//只有可见的时候才绘制
if (visible) {
g.drawImage(img[index], x - explodeWidth, y - explodeHight, null);
index++;
}
//播放完最后一帧变成不可见状态
if (index >= EXPLODE_FRAME_COUNT) {
visible = false;
}
}
}
GameFrame类
package c02.n02.game;
import c02.n02.map.GameMap;
import c02.n02.tank.EnemyTank;
import c02.n02.tank.MyTank;
import c02.n02.tank.Tank;
import c02.n02.util.Constant;
import c02.n02.util.EnemyTanksPool;
import c02.n02.util.MusicUtil;
import c02.n02.util.MyUtil;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import static c02.n02.util.Constant.*;
/*
*游戏主窗口类
*所有游戏显示的内容都在此窗口中实现
*注意:为了方便维护所有的常量都在常量类中
*继承了Frame类用于绘制
*
*/
public class GameFrame extends Frame implements Runnable {
// 定义一张和屏幕一样大的图片用于双缓冲
private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HIGH, BufferedImage.TYPE_4BYTE_ABGR);
// 菜单界面背景
private Image menuImg = null;
//游戏地图背景
private Image mapImg = null;
//选择关卡背景
private Image levelImg = null;
//帮助界面背景
private Image helpImg = null;
//关于界面背景
private Image aboutImg = null;
// 游戏结束的图片,第一次使用的时候加载,而不是类加载的时候加载,这样效率更高
private Image overImg = null;
// 游戏通关的图片,第一次使用的时候加载,而不是类加载的时候加载,这样效率更高
private Image winImg = null;
// 游戏状态
private static int gameState;
// 菜单指向
public static int menuIndex;
// 关卡指向
public static int levelIndex;
// 标题栏的高度
public static int titleBarH;
// 定义坦克对象
private static Tank myTank;
// 定义第二个坦克对象
private static Tank myTank2;
// 定义一个变量用来判断是否是双人模式
private static boolean isTwoMode = true;
// 是否开启坦克之间的碰撞
private static boolean tankCollide = false;
// 是否开启队友伤害
private static boolean killMyTank = false;
// 用来记录本关卡产生了多少个敌人
private static int bornEnemyCount;
// 用来记录本关消灭了多少敌人
public static int killEnemyCount;
// 定义敌人的坦克容器
private static List<Tank> enemies = new ArrayList<>();
// 定义并创建地图对象
private static GameMap gameMap = new GameMap();
// 获取总关卡数量
private static int levlCount = GameInfo.getLevelCount();
public static int getGameState() {
return gameState;
}
public static void setGameState(int gameState) {
GameFrame.gameState = gameState;
}
/*
* 对窗口进行初始化
*/
public GameFrame() {
//初始化属性
initFrame();
// 初始化窗口按键监听
initEventListener();
// 启用用于刷新窗口的进程
new Thread(this).start();
// 播放背景音乐
MusicUtil.playStart();
}
/**
* 对游戏内容进行初始化
*/
public void initGAME() {
gameState = STATE_MENU;
}
/**
* 初始化函数 对属性进行初始化
*/
private void initFrame() {
// 设置标题
setTitle(GAME_TITLE);
// 设置窗口大小
setSize(FRAME_WIDTH, FRAME_HIGH);
setLocation(FRAME_X, FRAME_Y);
// 设置窗口大小不可变
setResizable(false);
// 设置窗口可见
setVisible(true);
// 求标题栏的高度
titleBarH = getInsets().top;
}
/**
* 该方法负责所有的绘制的内容,也就是说需要在屏幕中显示的内容都需要在该方法中调用 updata方法是继承的,自动调用,不用自己调用
*/
public void update(Graphics g1) {
// 得到图片的画笔,用于双缓冲
Graphics g = bufImg.getGraphics();
g.setFont(FONT);
switch (gameState) {
case STATE_MENU:
drawMenu(g);
break;
case STATE_LEVEL:
drawLevel(g);
break;
case STATE_HELP:
drawHelp(g);
break;
case STATE_ABOUT:
drawAbout(g);
break;
case STATE_RUN:
drawRun(g);
break;
case STATE_OVER:
drawOver(g);
break;
case STATE_WIN:
drawWin(g);
break;
}
// 使用系统画笔将图片绘制到frame上,用于双缓冲
g1.drawImage(bufImg, 0, 0, null);
}
/* =================START==========绘制方法==================================== */
/**
* 绘制菜单的方法
*
* @param g
*/
private void drawMenu(Graphics g) {
// 设置画笔颜色
g.setColor(Color.MAGENTA);
// 画个矩形当做背景
// g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH);
// 只有menuImg为空的时候才加载
if (menuImg == null) {
menuImg = MyUtil.createImage("image/Menu/background.png");
}
// 使用图片作为背景
g.drawImage(menuImg, 0, 0, null);
// 定位文字位置
int x = 560;
int y = 350;
int dis = 60; // 行间距
// g.setColor(Color.PINK);
// 根据选择设置菜单颜色
for (int i = 0; i < MENUS.length; i++) {
if (i == menuIndex) {
g.setColor(Color.cyan);
} else {
g.setColor(Color.BLUE);
}
g.drawString(MENUS[i], x, y + dis * i);
}
}
/**
* 绘制关卡界面方法
*
* @param g
*/
private void drawLevel(Graphics g) {
// 只有levelImg为空的时候才加载
if (levelImg == null) {
levelImg = MyUtil.createImage("image/Menu/level.png");
}
// 使用图片作为背景
g.drawImage(levelImg, 0, 0, null);
g.setColor(Color.BLACK);
// 定位文字位置
int x = 500;
int y = 280;
int dis = 60; // 行间距
// g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); //绘制纯色背景
g.setFont(FONT);
for (int i = 0; i < levlCount; i++) {
if (i == levelIndex) {
g.setColor(Color.RED);
} else {
g.setColor(Color.BLUE);
}
g.drawString(i + 1 + "", x + dis * i, y);
}
}
/**
* 游戏运行时的绘制方法
*
* @param g
*/
private void drawRun(Graphics g) {
//绘制地图背景
g.setColor(Color.BLACK);
// g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); // 单色背景
if (mapImg == null) {
mapImg = MyUtil.createImage("image/Map/background.png");
}
g.drawImage(mapImg, 0, 0, null); //绘制地图背景
// TODO 显示内测版本
g.setColor(Color.CYAN);
g.setFont(SMALL_FONT);
g.drawString("版本 v2020-12-24 最终答辩版", 10, FRAME_HIGH - 10);
// 绘制地图,先绘制没有遮挡的块
gameMap.drawBK(g);
// 绘制敌人的坦克
drawEnemies(g);
//如果己方两个坦克都死亡的话就切换到游戏结束状态
if (myTank.isDie() && myTank2.isDie()) {
GameFrame.setGameState(Constant.STATE_OVER);
}
// 绘制自己的坦克
//只有坦克没有死的时候才绘制
if (!myTank.myTankisDie()) {
myTank.draw(g);
} else { //坦克死亡后就绘制到屏幕外
myTank.draw(g);
myTank.setX(1500);
myTank.setY(1500);
}
if (!myTank2.myTankisDie()) {
myTank2.draw(g);
} else {
myTank2.draw(g);
myTank2.setX(1500);
myTank2.setY(1500);
}
// 绘制地图的遮挡层
gameMap.drawCover(g);
// 子弹和坦克的碰撞方法
bulletCollideTank();
// 爆炸效果
drawExplodes(g);
// 坦克和子弹和所有地图块的碰撞
bulletAndTanksCollideMapTile();
//坦克之间的碰撞
if (tankCollide) {
tankCollideTank();
}
}
/**
* 绘制敌人坦克的方法,并且移除已经死亡的坦克
*
* @param g
*/
private void drawEnemies(Graphics g) {
for (int i = 0; i < enemies.size(); i++) {
Tank enemy = enemies.get(i);
// 如果坦克已经死亡就从容器中移除
if (enemy.isDie()) {
enemies.remove(i);
i--;
continue;
}
enemy.draw(g);
}
}
/**
* 绘制关于界面的方法
*
* @param g
*/
private void drawAbout(Graphics g) {
//绘制关于界面
g.setColor(Color.BLACK);
// g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); // 单色背景
if (aboutImg == null) {
aboutImg = MyUtil.createImage("image/Menu/about.png");
}
g.drawImage(aboutImg, 0, 0, null); //绘制地图背景
}
/**
* 绘制帮助界面的方法
*
* @param g
*/
private void drawHelp(Graphics g) {
//绘制帮助界面
g.setColor(Color.BLACK);
// g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); // 单色背景
if (helpImg == null) {
helpImg = MyUtil.createImage("image/Menu/help.png");
}
g.drawImage(helpImg, 0, 0, null); //绘制地图背景
}
// 绘制游戏结束的方法
private void drawOver(Graphics g) {
// 只有overImg为空的时候才加载
if (overImg == null) {
overImg = MyUtil.createImage("image/gameover.png");
}
// 求图片宽和高,让图片居中显示
int imgW = overImg.getWidth(null);
int imgH = overImg.getHeight(null);
g.drawImage(overImg, (FRAME_WIDTH - imgW) / 2, (FRAME_HIGH - imgH) / 2, null);
// 添加按键提示信息
g.setColor(Color.RED);
g.drawString(OVER_STR[0], 120, FRAME_HIGH - 100);
g.setColor(Color.BLUE);
g.drawString(OVER_STR[1], FRAME_WIDTH - 500, FRAME_HIGH - 100);
}
// 绘制游戏胜利的方法
private void drawWin(Graphics g) {
// 只有winImg为空的时候才加载
if (winImg == null) {
winImg = MyUtil.createImage("image/gamewin.png");
}
// 求图片宽和高,让图片居中显示
int imgW = winImg.getWidth(null);
int imgH = winImg.getHeight(null);
g.drawImage(winImg, (FRAME_WIDTH - imgW) / 2, (FRAME_HIGH - imgH) / 2, null);
// 添加按键提示信息
g.setColor(Color.RED);
g.drawString(OVER_STR[0], 120, FRAME_HIGH - 100);
g.setColor(Color.BLUE);
g.drawString(OVER_STR[1], FRAME_WIDTH - 500, FRAME_HIGH - 100);
}
/* =================END==========绘制方法==================================== */
/* =================START==========按键响应==================================== */
/**
* 菜单状态对按键的相应
*
* @param keyCode
*/
private void keyPressEventMenu(int keyCode) {
//每按一次就播放一次音乐
// MusicUtil.playMenuCase(); //效果不佳,取消此选项
switch (keyCode) {
// 按了上键和W键
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
// 每按一次menuIndex就减1,小于0时变成最大
menuIndex--;
if (menuIndex < 0) {
menuIndex = MENUS.length - 1;
}
break;
// 按了下键和S键
case KeyEvent.VK_DOWN:
case KeyEvent.VK_S:
menuIndex++;
if (menuIndex > MENUS.length - 1) {
menuIndex = 0;
}
break;
// 如果按下了回车键,就说明开始新游戏
case KeyEvent.VK_ENTER:
switch (menuIndex) {
case 0: // 开始游戏
isTwoMode = false; //设置为单人模式
newGame(1);
break;
case 1: //双人游戏
isTwoMode = true; //设置为双人模式
newGame(1);
break;
case 2: // 选择关卡
setGameState(STATE_LEVEL);
break;
case 3: // 帮助
setGameState(STATE_HELP);
break;
case 4: // 关于
setGameState(STATE_ABOUT);
break;
case 5: // 退出游戏
System.exit(0);
break;
}
break;
}
}
/**
* 菜单状态对按键的相应
*
* @param keyCode
*/
private void keyPressEventlevel(int keyCode) {
switch (keyCode) {
case KeyEvent.VK_LEFT:
// 每按一次menuIndex就减1,小于0时变成最大
levelIndex--;
if (levelIndex < 0) {
levelIndex = levlCount - 1;
}
break;
case KeyEvent.VK_RIGHT:
levelIndex++;
if (levelIndex > levlCount - 1) {
levelIndex = 0;
}
break;
// 如果按下了回车键,就说明开始新游戏
case KeyEvent.VK_ENTER:
newGame(levelIndex + 1);
break;
}
}
/**
* 游戏运行中的按键处理方法-移动
*
* @param keyCode
*/
private void keyPressEventRun(int keyCode) {
switch (keyCode) {
case KeyEvent.VK_UP:
myTank2.setDir(Tank.DIR_UP);
myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
break;
case KeyEvent.VK_W:
myTank.setDir(Tank.DIR_UP);
myTank.setState(Tank.STATE_MOVE); // 坦克移动
break;
case KeyEvent.VK_DOWN:
myTank2.setDir(Tank.DIR_DOWN);
myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
break;
case KeyEvent.VK_S:
myTank.setDir(Tank.DIR_DOWN);
myTank.setState(Tank.STATE_MOVE); // 坦克移动
break;
case KeyEvent.VK_LEFT:
myTank2.setDir(Tank.DIR_LEFT);
myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
break;
case KeyEvent.VK_A:
myTank.setDir(Tank.DIR_LEFT);
myTank.setState(Tank.STATE_MOVE); // 坦克移动
break;
case KeyEvent.VK_RIGHT:
myTank2.setDir(Tank.DIR_RIGHT);
myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
break;
case KeyEvent.VK_D:
myTank.setDir(Tank.DIR_RIGHT);
myTank.setState(Tank.STATE_MOVE); // 坦克移动
break;
case KeyEvent.VK_SPACE:
myTank.fire();
break;
case KeyEvent.VK_NUMPAD0:
myTank2.fire();
break;
}
}
/**
* 游戏运行中的按键处理方法-松开按键坦克停止
*
* @param keyCode
*/
private void keyReleasedEventRun(int keyCode) {
switch (keyCode) {
case KeyEvent.VK_UP:
myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
break;
case KeyEvent.VK_W:
myTank.setState(Tank.STATE_STAND); // 坦克停止
break;
case KeyEvent.VK_DOWN:
myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
break;
case KeyEvent.VK_S:
myTank.setState(Tank.STATE_STAND); // 坦克停止
break;
case KeyEvent.VK_LEFT:
myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
break;
case KeyEvent.VK_A:
myTank.setState(Tank.STATE_STAND); // 坦克停止
break;
case KeyEvent.VK_RIGHT:
myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
break;
case KeyEvent.VK_D:
myTank.setState(Tank.STATE_STAND); // 坦克停止
break;
}
}
/**
* 游戏关于界面按键处理
*
* @param keyCode
*/
private void keyPressEventAbout(int keyCode) {
setGameState(STATE_MENU);
}
/**
* 游戏帮助按界面键处理
*
* @param keyCode
*/
private void keyPressEventHelp(int keyCode) {
setGameState(STATE_MENU);
}
/**
* 游戏结束的按键处理
*
* @param keyCode
*/
private void keyPressEventOver(int keyCode) {
// 如果按了ESC就退出游戏
if (keyCode == KeyEvent.VK_ESCAPE) {
System.exit(0);
} else if (keyCode == KeyEvent.VK_ENTER) {
// 如果按了ENTER就回到主菜单,并且开始播放背景音乐
setGameState(STATE_MENU);
MusicUtil.playStart();
// 还需要关闭很多游戏操作,重置某些属性
resetGame();
}
}
/**
* 游戏通关按键的处理
*
* @param keyCode
*/
private void keyPressEventWin(int keyCode) {
keyPressEventOver(keyCode); // 直接调用游戏结束的按键处理
}
/* =================END==========按键响应==================================== */
/**
* 开始游戏方法
*
* @param level 关卡信息
*/
private static void newGame(int level) {
// 先清空敌人坦克容器
enemies.clear();
if (gameMap == null) {
gameMap = new GameMap();
}
// 设置关卡信息
gameMap.initMap(level);
// 播放背景音乐
// MusicUtil.playStart();
MusicUtil.stopStart();
// 先把产生的敌人数量设置为0
bornEnemyCount = 0;
// 初始化消灭的敌人
killEnemyCount = 0;
// 设置当前模式
gameState = STATE_RUN;
// 创建坦克对象,x y就是坦克出生的坐标,最后的属性是设置坦克默认朝向
myTank = new MyTank(500, 670, Tank.DIR_UP); //创建一号坦克对象
myTank2 = new MyTank(780, 670, Tank.DIR_UP); //创建二号坦克对象
myTank2.setTwo(true); //设置为二号坦克
// 判断是否为单人模式,如果为单人模式那么二号坦克对象直接设置为0血
if (!isTwoMode) {
myTank2.setHp(0);
}
// 使用一个单独的线程用于创建敌人的坦克
new Thread() {
@Override
public void run() {
while (true) {
// 如果没有超过本关最大坦克数量和屏幕最大坦克数量才新建坦克
if (LevelInof.getInstance().getEnemyCount() > bornEnemyCount && enemies.size() < ENEMY_MAX_COUNT) {
Tank enemy = EnemyTank.createEnemy();
enemies.add(enemy);
bornEnemyCount++; // 每创建一个敌人就加1
}
// 产生坦克生成间隔时间
try {
Thread.sleep(ENEMY_BORN_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果游戏不在RUN状态就不再创建敌人
if (gameState != STATE_RUN) {
break;
}
}
}
}.start();
}
/**
* 初始化窗口按键监听
*/
private void initEventListener() {
// 注册监听事件
addWindowListener(new WindowAdapter() {
// 点击关闭按钮时自动调用此方法
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
// 添加按键监听事件
addKeyListener(new KeyAdapter() {
// 按下按键
@Override
public void keyPressed(KeyEvent e) {
// 获取按下的键
int keyCode = e.getKeyCode();
// 把获取到的键值传递给当前的状态
switch (gameState) {
case STATE_MENU:
keyPressEventMenu(keyCode);
break;
case STATE_LEVEL:
keyPressEventlevel(keyCode);
break;
case STATE_HELP:
keyPressEventHelp(keyCode);
break;
case STATE_ABOUT:
keyPressEventAbout(keyCode);
break;
case STATE_RUN:
keyPressEventRun(keyCode);
break;
case STATE_OVER:
keyPressEventOver(keyCode);
break;
case STATE_WIN:
keyPressEventWin(keyCode);
break;
}
}
// 按键松开时,游戏中的处理方法
@Override
public void keyReleased(KeyEvent e) {
// 获取按下的键
int keyCode = e.getKeyCode();
// 把获取到的键值传递给当前的状态
if (gameState == STATE_RUN) {
keyReleasedEventRun(keyCode);
}
}
});
}
/**
* 单独设置刷新率的线程
*/
@Override
public void run() {
while (true) {
// 在此调用repaint
repaint();
try {
Thread.sleep(REPAINT_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 子弹碰撞方法
*/
private void bulletCollideTank() {
// 我的子弹和敌人的坦克碰撞
for (int i = 0; i < enemies.size(); i++) {
Tank enemy = enemies.get(i);
enemy.collideBullets(myTank.getBullets());
enemy.collideBullets(myTank2.getBullets());
//队友伤害
if (killMyTank) {
myTank2.collideBullets(myTank.getBullets());
myTank.collideBullets(myTank2.getBullets());
}
}
// 敌人的子弹和我的坦克碰撞
for (int i = 0; i < enemies.size(); i++) {
Tank enemy = enemies.get(i);
myTank.collideBullets(enemy.getBullets());
myTank2.collideBullets(enemy.getBullets());
}
}
/**
* 坦克和坦克的碰撞方法
*/
private void tankCollideTank() {
//敌方坦克和我方坦克的碰撞
for (Tank enemy : enemies) {
boolean collideTank = enemy.isCollideTank(myTank);
boolean collideTank2 = enemy.isCollideTank(myTank2);
if (collideTank || collideTank2) {
enemy.back();
}
}
//我方坦克和坦克的碰撞
boolean collideTanks = myTank.isCollideTanks(enemies); //一号坦克和敌方坦克的碰撞检测
boolean collideTanks2 = myTank2.isCollideTanks(enemies); //二号坦克和敌方坦克的碰撞检测
boolean collideTank = myTank.isCollideTank(myTank2); //一号坦克和二号坦克的碰撞检测
boolean collideTank2 = myTank2.isCollideTank(myTank); //二号坦克和一号坦克的碰撞检测
//如果发生了碰撞坦克就后退
if (collideTank || collideTanks) {
myTank.back();
}
if (collideTank2 || collideTanks2) {
myTank2.back();
}
}
/**
* 子弹和地图块的碰撞方法 坦克和地图块的碰撞方法
*/
private void bulletAndTanksCollideMapTile() {
// 自己坦克子弹和所有地图块的碰
myTank.bulletsCollideMapTiles(gameMap.getTiles());
myTank2.bulletsCollideMapTiles(gameMap.getTiles());
// 敌人坦克子弹和所有地图块的碰撞
for (Tank enemy : enemies) {
enemy.bulletsCollideMapTiles(gameMap.getTiles());
}
// 我的坦克和地图块的碰撞
boolean collideTile = myTank.isCollideTile(gameMap.getTiles());
boolean collideTile2 = myTank2.isCollideTile(gameMap.getTiles());
//如果发生了碰撞坦克就后退
if (collideTile) {
myTank.back();
}
if (collideTile2) {
myTank2.back();
}
// 敌人的坦克和地图块的碰撞
for (Tank enemy : enemies) {
boolean collideTile1 = enemy.isCollideTile(gameMap.getTiles());
if (collideTile1) {
enemy.back();
}
}
// 清理所有的被销毁的地图块
gameMap.clearDestoryTile();
}
// 绘制所有坦克上的爆炸效果
private void drawExplodes(Graphics g) {
// 绘制所有敌人坦克上的爆炸效果
for (Tank enemy : enemies) {
enemy.drawExplodes(g);
}
// 绘制自己坦克上的爆炸效果
myTank.drawExplodes(g);
myTank2.drawExplodes(g);
}
/**
* 重置游戏状态方法
*/
private void resetGame() {
menuIndex = 0; // 重置菜单指向
myTank.bulletsReturn(); // 还回所有子弹,并清空子弹容器
myTank = null; // 销毁自己的坦克
myTank2.bulletsReturn(); // 还回所有子弹,并清空子弹容器
myTank2 = null; // 销毁自己的坦克
// 还回敌人所有子弹,并且清空子弹容器,归还坦克
for (Tank enemy : enemies) {
enemy.bulletsReturn();
EnemyTanksPool.theReturn(enemy);
}
enemies.clear(); // 清空敌人坦克容器
gameMap.clearMap(); // 还回所有地图块
gameMap = null; // 清空地图资源
}
/**
* 判断游戏是否是最后一关
*
* @return true表示为最后一关
*/
public static boolean isLastLevel() {
// 获取当前关卡信息
int currLevel = LevelInof.getInstance().getLevel();
// int levlCount = GameInfo.getLevelCount();
// 如果当前关卡是最后一关就返回true
return currLevel == levlCount;
}
/**
* 判断是否过关
*
* @return true为过关
*/
public static boolean isCrossLevel() {
// 如果杀敌数量等于关卡规定数量就说明过关了
return killEnemyCount == LevelInof.getInstance().getEnemyCount();
}
/**
* 进入下一关的方法
*/
public static void nextLevel() {
// 当前关卡加一
newGame(LevelInof.getInstance().getLevel() + 1);
}
}
GameInfo类
package c02.n02.game;
import java.io.FileInputStream;
import java.util.Properties;
/**
* 游戏相关的信息类
*/
public class GameInfo {
//关卡数量
private static int levelCount;
static {
Properties prop = new Properties();
try {
//加载配置文件
prop.load(new FileInputStream("level/gameinfo"));
//获取关卡数量
levelCount = Integer.parseInt(prop.getProperty("levelCount"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取关卡总数量
*
* @return 返回关卡数量
*/
public static int getLevelCount() {
return levelCount;
}
}
LevelInfo类
package c02.n02.game;
import c02.n02.util.MyUtil;
/**
* 用来管理当前关卡的信息的类
*/
public class LevelInof {
//关卡编号
private int level;
//敌人的数量
private int enemyCount;
//通关的要求的时长,-1不限时
private int crossTime = -1;
//敌人类型信息
private int[] enemyType;
//游戏难度
private int levelType;
//===========START=================GETTER/SETTER===============
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getEnemyCount() {
return enemyCount;
}
public void setEnemyCount(int enemyCount) {
this.enemyCount = enemyCount;
}
public int getCrossTime() {
return crossTime;
}
public void setCrossTime(int crossTime) {
this.crossTime = crossTime;
}
public int[] getEnemyType() {
return enemyType;
}
public void setEnemyType(int[] enemyType) {
this.enemyType = enemyType;
}
public int getLevelType() {
if (levelType <= 0) {
levelType = 1;
}
return levelType;
}
public void setLevelType(int leevelType) {
this.levelType = leevelType;
}
//===========END=================GETTER/SETTER===============
//构造方法私有化
private LevelInof() {
}
//定义静态的本类类型的变量,用来指向唯一的实例
private static LevelInof instance;
/**
* 懒汉模式的单例
* 第一次使用该实例的时候创建唯一的实例
*
* @return
*/
public static LevelInof getInstance() {
if (instance == null) {
//创建了唯一的实例
instance = new LevelInof();
}
return instance;
}
/**
* 获得一个随机的敌人类型
*
* @return 返回敌人类型
*/
public int getRandomEnemyType() {
int index = MyUtil.getRandomNumber(0, enemyType.length);
return enemyType[index];
}
}
GameMap类
package c02.n02.map;
import c02.n02.game.GameFrame;
import c02.n02.game.LevelInof;
import c02.n02.tank.Tank;
import c02.n02.util.Constant;
import c02.n02.util.MapTilePool;
import java.awt.*;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* 游戏地图类
* 游戏中显示的地图都来自此类
*/
public class GameMap {
//设置地图坐标,地图离边框1.5个坦克距离
public static final int MAP_X = Tank.RADIUS * 3;
public static final int MAP_Y = Tank.RADIUS * 3 + GameFrame.titleBarH;
//设置地图宽高
public static final int MAP_WIDTH = Constant.FRAME_WIDTH - Tank.RADIUS * 6;
public static final int MAP_HEIGHT = Constant.FRAME_HIGH - Tank.RADIUS * 8 - GameFrame.titleBarH;
//地图距离边框为0
// public static final int MAP_X = 0;
// public static final int MAP_Y = 30;
// public static final int MAP_WIDTH = Constant.FRAME_WIDTH;
// public static final int MAP_HEIGHT = Constant.FRAME_HIGH;
private int width; //地图宽
private int height; //地图高
//地图元素块的容器
private List<MapTile> tiles = new ArrayList<>();
//基地对象
private TankHouse house;
//===========START=================GETTER/SETTER===============
public List<MapTile> getTiles() {
return tiles;
}
public void setTiles(List<MapTile> tiles) {
this.tiles = tiles;
}
//===========END=================GETTER/SETTER===============
/**
* 游戏地图类的无参构造函数
*/
public GameMap() {
}
/**
* 初始化地图元素块
*
* @param level 第几关
*/
public void initMap(int level) {
//初始化地图,先把里面原有的块清空掉重新加载
tiles.clear();
//从关卡文件里读取关卡信息
try {
loadLevel(level);
} catch (Exception e) {
e.printStackTrace();
}
//初始化基地
house = new TankHouse();
addHouse();
}
/**
* 加载关卡信息
*
* @param level 第几关
*/
private void loadLevel(int level) throws Exception {
//获得关卡信息类的唯一实例对象
LevelInof levelInof = LevelInof.getInstance();
//将所有的地图信息都加载进来
Properties prop = new Properties();
prop.load(new FileInputStream("level/lv_" + level));
//从文件中读取指定的信息
//敌人数量
int enemyCount = Integer.parseInt(prop.getProperty("enemyCount"));
//敌人类型,根据逗号分隔
String[] enemyType = prop.getProperty("enemyType").split(",");
int[] type = new int[enemyType.length];
for (int i = 0; i < type.length; i++) {
type[i] = Integer.parseInt(enemyType[i]);
}
//调用的方法
String methodName = prop.getProperty("method");
//方法调用的次数
int invokeCount = Integer.parseInt(prop.getProperty("invokeCount"));
//把参数读取到数组中
String[] params = new String[invokeCount];
for (int i = 1; i <= invokeCount; i++) {
params[i - 1] = prop.getProperty("param" + i);
}
//获取关卡难度信息
int levelType = Integer.parseInt(prop.getProperty("levelType"));
//设置关卡信息
levelInof.setEnemyCount(enemyCount); //设置敌人数量
levelInof.setLevel(level); //设置关卡编号
levelInof.setEnemyType(type); //设置敌人类型
levelInof.setLevelType(levelType); //设置关卡难度
//根据方法的名字和参数调用对应的方法
invokeMethod(methodName, params);
}
/**
* 根据方法的名字和参数调用对应的方法
*
* @param name 调用的方法名
* @param params 参数
*/
private void invokeMethod(String name, String[] params) {
for (String param : params) {
//获取每一行的参数,解析
String[] split = param.split(",");
//使用一个数组来保存解析后的内容
int[] arr = new int[split.length];
for (int i = 0; i < split.length; i++) {
arr[i] = Integer.parseInt(split[i]);
}
//块之间的间隔是地图块的倍数
final int DIS = MapTile.tileW;
//根据配置信息生成块
switch (name) {
case "addRow":
addRow(MAP_X + arr[0] * DIS, MAP_Y + arr[1] * DIS, MAP_X + MAP_WIDTH - arr[2] * DIS, arr[3], arr[4] * DIS);
break;
case "addCol":
addCol(MAP_X + arr[0] * DIS, MAP_Y + arr[1] * DIS, MAP_Y + MAP_HEIGHT - arr[2] * DIS, arr[3], DIS);
break;
case "addRect":
addRect(MAP_X + arr[0] * DIS, MAP_Y + arr[1] * DIS, MAP_X + MAP_WIDTH - arr[2] * DIS, MAP_Y + MAP_HEIGHT - arr[2] * DIS, arr[4], DIS);
break;
case "addCustom":
addCustom(params);
return;
}
}
}
/**
* 将基地的块添加到地图容器中
*/
private void addHouse() {
tiles.addAll(house.getTiles());
}
/**
* 判断某个点是否和tiles集合中的所有块发生了重叠
*
* @param tiles tiles集合
* @param x 点x
* @param y 点y
* @return 有重叠返回true,否则返回false
*/
private boolean isCollide(List<MapTile> tiles, int x, int y) {
for (MapTile tile : tiles) {
int tileX = tile.getX();
int tileY = tile.getY();
//如果点到块左上点的距离小于块的宽度就说明发生了重叠
if (Math.abs(tileX - x) < MapTile.tileW && Math.abs(tileY - y) < MapTile.tileW) {
return true;
}
}
return false;
}
/**
* 用来绘制没有遮挡效果的块
* 比如 砖块、铁块
*
* @param g
*/
public void drawBK(Graphics g) {
for (MapTile tile : tiles) {
if (tile.getType() != MapTile.TYPE_COVER) {
tile.draw(g);
}
}
}
/**
* 用来绘制有遮挡效果的块
* 比如 草块
*
* @param g
*/
public void drawCover(Graphics g) {
for (MapTile tile : tiles) {
if (tile.getType() == MapTile.TYPE_COVER) {
tile.draw(g);
}
}
}
/**
* 将所有不可见的地图块从容器中移除
*/
public void clearDestoryTile() {
for (int i = 0; i < tiles.size(); i++) {
MapTile tile = tiles.get(i);
if (!tile.isVisible()) {
tiles.remove(i);
}
}
}
/**
* 往地图容器中添加一行指定类型的地图块
*
* @param startX 起始x点坐标
* @param startY 起始y点坐标
* @param endX 结束点x坐标,因为是生成一行,所以不需要结束点Y坐标
* @param type 地图块类型
* @param DIS 地图块的间隔,如果是块的宽度就说明地图块是连续的
* 如果大于块的宽度就说明是不连续的
*/
public void addRow(int startX, int startY, int endX, int type, final int DIS) {
//计算一行有多少个地图块
int count = 0;
count = (endX - startX) / (MapTile.tileW + DIS);
for (int i = 0; i < count; i++) {
MapTile tile = MapTilePool.get();
tile.setType(type);
tile.setX(startX + i * (MapTile.tileW + DIS));
tile.setY(startY);
tile.setVisible(true);
tiles.add(tile);
}
}
/**
* 往地图容器中添加一列指定类型的地图块
*
* @param startX 起始点x坐标
* @param startY 起始点y坐标
* @param endY 结束点y坐标
* @param type 地图块类型
* @param DIS 地图块的间隔,如果是块的宽度就说明地图块是连续的
* 如果大于块的宽度就说明是不连续的
*/
public void addCol(int startX, int startY, int endY, int type, final int DIS) {
//计算一列有多少个地图块
int count = 0;
count = (endY - startY) / (MapTile.tileW + DIS);
for (int i = 0; i < count; i++) {
MapTile tile = MapTilePool.get();
tile.setType(type);
tile.setX(startX);
tile.setY(startY + i * (MapTile.tileW + DIS));
tile.setVisible(true);
tiles.add(tile);
}
}
/**
* 往地图容器中添加一个矩形地图块
*
* @param startX 起始点x
* @param startY 起始点y
* @param endX 结束点x
* @param endY 结束点y
* @param type 地图块类型
* @param DIS 地图块水平和垂直间隔
*/
public void addRect(int startX, int startY, int endX, int endY, int type, final int DIS) {
// //计算一行应该填充多少地图块
// int rows = (endX - startX) / (MapTile.tileW + DIS);
//计算一列应该填充多少地图块
int cols = (endY - startY) / (MapTile.tileW + DIS);
//一个矩形区域由若干行组成
for (int i = 0; i < cols; i++) {
addRow(startX, startY + i * (MapTile.tileW + DIS), endX, type, DIS);
}
}
/**
* 往地图容器中添加自定义的地图块,通过配置文件里的数组来设置地图
*
* @param params
*/
public void addCustom(String[] params) {
for (int i = 0; i < params.length; i++) {
String param = params[i];
//获取每一行的参数,解析
String[] split = param.split(",");
//使用一个数组来保存解析后的内容
int[] arr = new int[split.length];
for (int j = 0; j < split.length; j++) {
arr[j] = Integer.parseInt(split[j]);
}
//如果类型等于9那么就跳过本次循环
for (int j = 0; j < arr.length; j++) {
int a = arr[j];
if (a == 9) {
continue;
}
//砖块对象池中拿出一个砖块
MapTile tile = MapTilePool.get();
tile.setType(a);
tile.setX(MAP_X + j * 60);
tile.setY(MAP_Y + i * 60);
tile.setVisible(true);
tiles.add(tile);
}
}
}
/**
* 清空地图容器并且把所有地图块还回池中
*/
public void clearMap() {
for (MapTile tile : tiles) {
tile.setVisible(false);
MapTilePool.theReturn(tile);
}
tiles.clear();
}
}
MapTile类
package c02.n02.map;
import c02.n02.game.Bullet;
import c02.n02.util.MyUtil;
import java.awt.*;
import java.util.List;
/**
* 地图中的所有块的类
*/
public class MapTile {
//地图块的类型
public static final int TYPE_HOUSE = 0; //基地块
public static final int TYPE_NORMAL = 1; //普通块(砖块)
public static final int TYPE_HARD = 2; //铁块
public static final int TYPE_COVER = 3; //遮挡块(草丛)
//设置地图块类型,默认砖块
private int type = TYPE_NORMAL;
//设置图片宽,因为图片是正方形,所以只需要获取一个
public static int tileW = 60;
//设置半径
public static int radius = tileW / 2;
//创建图片数组
private static Image[] tileImg;
static {
tileImg = new Image[4];
tileImg[TYPE_NORMAL] = MyUtil.createImage("image/Map/tile.png");
tileImg[TYPE_HOUSE] = MyUtil.createImage("image/Map/house.png");
tileImg[TYPE_COVER] = MyUtil.createImage("image/Map/cover.png");
tileImg[TYPE_HARD] = MyUtil.createImage("image/Map/hard.png");
}
//砖块左上角坐标
private int x;
private int y;
//砖块是否可见
private boolean visible = true;
//===========START=================GETTER/SETTER===============
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 boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
//===========END=================GETTER/SETTER===============
/**
* 地图块类的无参构造函数
*/
public MapTile() {
}
/**
* 地图块类的构造函数
*
* @param x X坐标
* @param y Y坐标
*/
public MapTile(int x, int y) {
this.x = x;
this.y = y;
}
/**
* 绘制方法
* 根据不同的类型绘制不同的砖块
*
* @param g
*/
public void draw(Graphics g) {
//不可见就不绘制
if (!visible) {
return;
}
g.drawImage(tileImg[type], x, y, null);
}
/**
* 判断块是否碰到了子弹(所有子弹)
* 遮挡块不用判断
*
* @param bullets 所有子弹所在的列表
* @return 碰撞了返回true否则返回false
*/
public boolean isCollideBullet(List<Bullet> bullets) {
//如果地图块不可见或者块类型为遮挡块,直接返回false
if (!visible || type == TYPE_COVER) {
return false;
}
for (Bullet bullet : bullets) {
//获取子弹的坐标
int bulletX = bullet.getX();
int bulletY = bullet.getY();
//判断是否碰撞,因为传入的是方块的中心点,所以x y需要加半径
boolean collide = MyUtil.isCollide(x + radius, y + radius, radius, bulletX, bulletY);
if (collide) {
//销毁子弹
bullet.setVisible(false);
return true;
}
}
return false;
}
/**
* 判断当前的地图块是否是基地
*
* @return 如果为基地块就返回true
*/
public boolean isHouse() {
return type == TYPE_HOUSE;
}
}
TankHouse类
package c02.n02.map;
import c02.n02.util.Constant;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* 我方基地
* 大概就长这样
* ■■■
* ■☆■
*/
public class TankHouse {
//坐标
public static final int HOUSE_X = (Constant.FRAME_WIDTH - 3 * MapTile.tileW) / 2;
public static final int HOUSE_Y = Constant.FRAME_HIGH - 2 * MapTile.tileW;
//地图块容器
private List<MapTile> tiles = new ArrayList<>();
public TankHouse() {
tiles.add(new MapTile(HOUSE_X + MapTile.tileW, HOUSE_Y + MapTile.tileW)); //中间,基地
tiles.add(new MapTile(HOUSE_X, HOUSE_Y)); //左上
tiles.add(new MapTile(HOUSE_X, HOUSE_Y + MapTile.tileW)); //左下
tiles.add(new MapTile(HOUSE_X + MapTile.tileW, HOUSE_Y)); //上中
tiles.add(new MapTile(HOUSE_X + 2 * MapTile.tileW, HOUSE_Y)); //右上
tiles.add(new MapTile(HOUSE_X + 2 * MapTile.tileW, HOUSE_Y + MapTile.tileW)); //右下
//设置基地块的类型
tiles.get(0).setType(MapTile.TYPE_HOUSE);
}
/**
* 获取我方基地块
*
* @return 返回一个地图块数组
*/
public List<MapTile> getTiles() {
return tiles;
}
/**
* 设置我方基地块
*
* @param tiles 传入一个地图块数组
*/
public void setTiles(List<MapTile> tiles) {
this.tiles = tiles;
}
/**
* 我方基地绘制方法
*
* @param g
*/
public void draw(Graphics g) {
for (MapTile tile : tiles) {
tile.draw(g);
}
}
}
EnemyTank类
package c02.n02.tank;
import c02.n02.game.GameFrame;
import c02.n02.game.LevelInof;
import c02.n02.util.Constant;
import c02.n02.util.EnemyTanksPool;
import c02.n02.util.MyUtil;
import java.awt.*;
/**
* 敌人坦克类
* 继承了坦克类
*/
public class EnemyTank extends Tank {
public static final int TYPE_GREEN = 0; //绿坦克
public static final int TYPE_YELLOW = 1; //黄色坦克
//敌人坦克类型,默认绿坦克
private int type = TYPE_GREEN;
//定义坦克图片数组
private static Image[] greenImg; //绿色坦克图片数组
private static Image[] yellowImg; //黄色图片数组
//记录5秒开始的时间
private long aiTime;
//在静态代码块中进行初始化
static {
greenImg = new Image[4];
greenImg[0] = MyUtil.createImage("image/TankImg/enemyTank/green/u.png");
greenImg[1] = MyUtil.createImage("image/TankImg/enemyTank/green/d.png");
greenImg[2] = MyUtil.createImage("image/TankImg/enemyTank/green/l.png");
greenImg[3] = MyUtil.createImage("image/TankImg/enemyTank/green/r.png");
//todo 替换图片
yellowImg = new Image[4];
yellowImg[0] = MyUtil.createImage("image/TankImg/enemyTank/yellow/u.png");
yellowImg[1] = MyUtil.createImage("image/TankImg/enemyTank/yellow/d.png");
yellowImg[2] = MyUtil.createImage("image/TankImg/enemyTank/yellow/l.png");
yellowImg[3] = MyUtil.createImage("image/TankImg/enemyTank/yellow/r.png");
}
/**
* 获取敌人坦克类型
*
* @return 返回类型
*/
public int getType() {
return type;
}
/**
* 设置坦克类型
*
* @param type 传入类型
*/
public void setType(int type) {
this.type = type;
}
/**
* 敌人坦克类的无参构造方法
*/
public EnemyTank() {
//当敌人创建时获取系统时间
aiTime = System.currentTimeMillis();
//随机创建不同的坦克
// type = MyUtil.getRandomNumber(0, 2);
}
/**
* 敌人坦克类的构造方法
*
* @param x X坐标
* @param y Y坐标
* @param dir 方向
*/
public EnemyTank(int x, int y, int dir) {
super(x, y, dir);
//当敌人创建时获取系统时间
aiTime = System.currentTimeMillis();
//随机创建不同的坦克
// type = MyUtil.getRandomNumber(0, 2);
}
/**
* 重写坦克类的绘制方法
*
* @param g
*/
@Override
public void drawImgTank(Graphics g) {
ai(); //调用AI
//根据不同的类型绘制不同的坦克
if (type == TYPE_GREEN) {
g.drawImage(greenImg[getDir()], getX() - RADIUS, getY() - RADIUS, null);
} else if (type == TYPE_YELLOW) {
g.drawImage(yellowImg[getDir()], getX() - RADIUS, getY() - RADIUS, null);
}
}
/**
* 创建一个敌人坦克
*
* @return 返回一个敌人坦克对象
*/
public static Tank createEnemy() {
int x = MyUtil.getRandomNumber(0, 2); //生成0或1
int y = GameFrame.titleBarH + RADIUS; //定位在顶部
int dir = DIR_DOWN; //方向默认朝下
//只在左上角和右上角生成坦克
if (x == 0) {
x = RADIUS; //如果X=0就在左上角生成坦克
} else {
x = Constant.FRAME_WIDTH - RADIUS;
}
EnemyTank enemy = (EnemyTank) EnemyTanksPool.get(); //从坦克池里拿坦克,强制转换成敌人坦克类型
enemy.setX(x);
enemy.setY(y);
enemy.setDir(dir);
enemy.setEnemy(true); //设置为敌方坦克
enemy.setState(STATE_MOVE);
//为了避免拿到已经死亡的坦克就要重新设血量属性
//根据关卡难度设置血量
int hp = DEFAULT_HP * LevelInof.getInstance().getLevelType();
enemy.setHp(hp);
enemy.setMaxHp(hp);
//通过关卡信息中的敌人类型来设置当前出生的敌人类型
int enemyType = LevelInof.getInstance().getRandomEnemyType();
enemy.setType(enemyType);
return enemy;
}
/**
* 坦克AI
* 每隔一段时间让敌人坦克随机获得一个状态(站立,行走)
* 游戏的每一帧都随机(概率可调)判断敌人是否发射子弹
*/
private void ai() {
//如果当前的时间减去创建坦克的时间大于2秒就切换状态
if (System.currentTimeMillis() - aiTime > Constant.ENEMY_AI_INTERVAL) {
//改变方向
int dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1); //获取一个随机的方向
int tankDir = getDir(); //获取目前坦克的方向
//判断是否在边缘,如果在边缘并且方向还是往外,那么就立即重新随机获取一个方向
if (super.getX() <= 30 && dir == DIR_LEFT) {
dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
} else if (super.getX() >= Constant.FRAME_WIDTH - 70 && dir == DIR_RIGHT) {
dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
} else if (super.getY() <= 50 && dir == DIR_UP) {
dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
} else if (super.getX() >= Constant.FRAME_HIGH - 30 && dir == DIR_DOWN) {
dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
}
setDir(dir);
//改变状态
int i = MyUtil.getRandomNumber(0, 2);
if (i == 0) {
setState(STATE_STAND);
} else {
setState(STATE_MOVE);
}
//重新计时
aiTime = System.currentTimeMillis();
}
//生成一个0-1的随机数,如果小于概率的话就发射炮弹
if (Math.random() < Constant.ENEMY_FIRE_PERCENT) {
fire();
}
}
}
MyTank类
package c02.n02.tank;
import c02.n02.util.MyUtil;
import java.awt.*;
/**
* 自己坦克类
* 继承了坦克类
*/
public class MyTank extends Tank {
//坦克图片数组
private static Image[] tankImg;
//二号坦克图片数组
private static Image[] tankImg2;
//在静态代码块中进行初始化
static {
tankImg = new Image[4];
tankImg[0] = MyUtil.createImage("image/TankImg/myTank/u.png");
tankImg[1] = MyUtil.createImage("image/TankImg/myTank/d.png");
tankImg[2] = MyUtil.createImage("image/TankImg/myTank/l.png");
tankImg[3] = MyUtil.createImage("image/TankImg/myTank/r.png");
tankImg2 = new Image[4];
tankImg2[0] = MyUtil.createImage("image/TankImg/myTank/u2.png");
tankImg2[1] = MyUtil.createImage("image/TankImg/myTank/d2.png");
tankImg2[2] = MyUtil.createImage("image/TankImg/myTank/l2.png");
tankImg2[3] = MyUtil.createImage("image/TankImg/myTank/r2.png");
}
/**
* 自己坦克的构造方法
*
* @param x X坐标
* @param y Y坐标
* @param dir 方向
*/
public MyTank(int x, int y, int dir) {
super(x, y, dir);
}
/**
* 重写draw方法
*
* @param g
*/
@Override
public void drawImgTank(Graphics g) {
//如果为二号坦克就画二号坦克的图片
if (!isTwo()) {
g.drawImage(tankImg[getDir()], getX() - RADIUS, getY() - RADIUS, null);
} else {
g.drawImage(tankImg2[getDir()], getX() - RADIUS, getY() - RADIUS, null);
}
}
}
Tank类
package c02.n02.tank;
import c02.n02.game.Bullet;
import c02.n02.game.Explode;
import c02.n02.game.GameFrame;
import c02.n02.map.MapTile;
import c02.n02.util.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/*
* 坦克类
* */
public abstract class Tank {
//四个方向
public static final int DIR_UP = 0;
public static final int DIR_DOWN = 1;
public static final int DIR_LEFT = 2;
public static final int DIR_RIGHT = 3;
//坦克的半径
public static final int RADIUS = 30;
//默认速度,每帧的速度
public static final int DEFAULT_SPEED = 10;
//坦克的状态
public static final int STATE_STAND = 0; //静止
public static final int STATE_MOVE = 1; //移动
public static final int STATE_DIE = 2; //死亡
//坦克的初始生命值
public static final int DEFAULT_HP = 1000;
//坦克的最大血量
private int maxHp = DEFAULT_HP;
private int x, y; //坦克坐标
private int oldX = -1; //坦克旧坐标
private int oldY = -1;
private int hp = DEFAULT_HP; //当前血量
private String name; //坦克名字
private int atk; //攻击力
private boolean isTwo = false; //设置是否为第二号坦克
//攻击力范围
public static final int ATK_MAX = 200;
public static final int ATK_MIN = 150;
//坦克上一次开火的时间,用于设置开火间隔
private long fireTime;
//开火间隔
public static final int FIRE_INTERVAL = 200;
private int speed = DEFAULT_SPEED; //速度
private int dir; //方向
private int state = STATE_STAND; //坦克状态
private Color color; //坦克颜色
private boolean isEnemy = false; //是否是敌人
//创建血条对象
private BloodBar bar = new BloodBar();
//炮弹容器
private List<Bullet> bullets = new ArrayList();
//爆炸效果容器
private List<Explode> explodes = new ArrayList<>();
//===========START=================GETTER/SETTER===============
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 getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = hp;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDir() {
return dir;
}
public void setDir(int dir) {
this.dir = dir;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public List getBullets() {
return bullets;
}
public void setBullets(List bullets) {
this.bullets = bullets;
}
public boolean isEnemy() {
return isEnemy;
}
public void setEnemy(boolean enemy) {
isEnemy = enemy;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMaxHp() {
return maxHp;
}
public void setMaxHp(int maxHp) {
this.maxHp = maxHp;
}
public boolean isTwo() {
return isTwo;
}
public void setTwo(boolean isTwo) {
this.isTwo = isTwo;
}
//==============END==============GETTER/SETTER==================
//构造函数
public Tank(int x, int y, int dir) {
this.x = x;
this.y = y;
this.dir = dir;
color = MyUtil.getRandomColor();
name = MyUtil.getRandomName();
atk = MyUtil.getRandomNumber(ATK_MIN, ATK_MAX); //随机一个攻击力
}
//无参构造函数
public Tank() {
color = MyUtil.getRandomColor();
name = MyUtil.getRandomName();
atk = MyUtil.getRandomNumber(ATK_MIN, ATK_MAX); //随机一个攻击力
}
/*
* 全部的绘制方法
* 绘制坦克
* 绘制炮弹
* */
public void draw(Graphics g) {
logic();
drawImgTank(g); //绘制坦克
drawBullets(g); //绘制炮弹
drawName(g); //绘制名字
bar.draw(g); //绘制血条
}
private void drawName(Graphics g) {
g.setColor(color);
g.setFont(Constant.SMALL_FONT);
g.drawString(name, x - RADIUS, y - 50);
}
//绘制图片坦克
public abstract void drawImgTank(Graphics g);
// {
// //敌我坦克图片不同,所以需要判断
// if (isEnemy) {
// g.drawImage(enemyImg[dir], x - RADIUS, y - RADIUS, null);
// } else {
// g.drawImage(tankImg[dir], x - RADIUS, y - RADIUS, null);
// }
// }
/**
* 绘制坦克方法
* 已弃用!!!!
*
* @param g
*/
public void drawTank(Graphics g) {
g.setColor(color);
//绘制坦克的圆
g.fillOval(x - RADIUS, y - RADIUS, RADIUS * 2, RADIUS * 2);
//绘制炮管
int endX = x;
int endY = y;
switch (dir) {
case DIR_UP:
endY = y - RADIUS * 2;
break;
case DIR_DOWN:
endY = y + RADIUS * 2;
break;
case DIR_LEFT:
endX = x - RADIUS * 2;
;
break;
case DIR_RIGHT:
endX = x + RADIUS * 2;
break;
}
g.drawLine(x, y, endX, endY);
}
//坦克的逻辑处理
private void logic() {
switch (state) {
case STATE_STAND:
break;
case STATE_MOVE://处于move状态时就调用move方法
move();
break;
case STATE_DIE:
break;
}
}
//坦克移动的方法
private void move() {
//设置旧的坐标,用于坦克和墙碰撞
oldX = x;
oldY = y;
switch (dir) {
case DIR_UP:
y -= speed;
//0坐标是从左上角算的,所以要减去标题栏的高度
if (y < RADIUS + GameFrame.titleBarH) {
y = RADIUS + GameFrame.titleBarH;
}
break;
case DIR_DOWN:
y += speed;
if (y > Constant.FRAME_HIGH - RADIUS) {
y = Constant.FRAME_HIGH - RADIUS;
}
break;
case DIR_LEFT:
x -= speed;
if (x < RADIUS) {
x = RADIUS;
}
break;
case DIR_RIGHT:
x += speed;
if (x > Constant.FRAME_WIDTH - RADIUS) {
x = Constant.FRAME_WIDTH - RADIUS;
}
break;
}
}
/**
* 开火方法
* 包括控制开火速度
*/
public void fire() {
//坦克上一次开火和这一次开火的时间间隔指定时间后才能再次开火
if (System.currentTimeMillis() - fireTime > FIRE_INTERVAL) {
//计算炮弹出现坐标(炮口处坐标)
int bulletX = x;
int bulletY = y;
switch (dir) {
case DIR_UP:
bulletY -= RADIUS;
break;
case DIR_DOWN:
bulletY += RADIUS;
break;
case DIR_LEFT:
bulletX -= RADIUS;
break;
case DIR_RIGHT:
bulletX += RADIUS;
break;
}
//从池塘中拿子弹
Bullet bullet = BulletsPool.get();
//设置参数
bullet.setX(bulletX);
bullet.setY(bulletY);
bullet.setDir(dir);
bullet.setAtk(atk);
bullet.setColor(color);
bullet.setVisible(true);
//放到炮弹容器中
bullets.add(bullet);
//记录本次发射时间
fireTime = System.currentTimeMillis();
//播放开火音效
MusicUtil.playBoom();
}
}
//将坦克发射的炮弹绘制出来
private void drawBullets(Graphics g) {
for (Bullet bullet : bullets) {
bullet.draw(g);
}
//遍历全部的子弹,将不可见的子弹移除,并还原到对象池
for (int i = 0; i < bullets.size(); i++) {
Bullet bullet = bullets.get(i);
if (!bullet.isVisible()) {
//移除时会返回移除的对象,用remove接收
Bullet remove = bullets.remove(i); //移除
BulletsPool.theReturn(remove); //还原
i--; //ArrayList删除元素后,当前位置后面的元素下标会变成被当前位置,如果没有i--那么就会少判断一个元素
}
}
}
//坦克销毁时,还回所有子弹并清空子弹容器
public void bulletsReturn() {
for (Bullet bullet : bullets) {
BulletsPool.theReturn(bullet);
}
bullets.clear();
}
//坦克和敌人子弹的碰撞方法
public void collideBullets(List<Bullet> bullets) {
//遍历所有子弹,和当前的坦克进行碰撞检测
for (Bullet bullet : bullets) {
//获取子弹坐标
int bulletX = bullet.getX();
int bulletY = bullet.getY();
//如果子弹和坦克发生了碰撞
if (MyUtil.isCollide(x, y, RADIUS, bulletX, bulletY)) {
//子弹消失
bullet.setVisible(false);
//坦克受到伤害
hurt(bullet);
//添加爆炸效果
addExplode(bulletX, bulletY);
}
}
}
//添加爆炸效果的方法,传入子弹坐标
private void addExplode(int bulletX, int bulletY) {
//添加爆炸效果,坐标是子弹的坐标
Explode explode = ExplodePool.get();
explode.setX(bulletX);
explode.setY(bulletY);
explode.setIndex(0); //重新设置从第0帧开始播放
explode.setVisible(true);
explodes.add(explode);
}
/**
* 坦克减血方法
*
* @param bullet 传入子弹,用于获取子弹的攻击力
*/
private void hurt(Bullet bullet) {
final int atk = bullet.getAtk();
hp = hp - atk;
if (hp < 0) {
hp = 0;
die();
}
}
/**
* 坦克死亡方法
*/
private void die() {
//判断是否为敌方坦克
if (isEnemy) {
//消灭的敌人加一
GameFrame.killEnemyCount++;
//绘制一个在坦克中间的爆炸效果
addExplode(x, y);
//坦克被消灭之后就归还对象池
EnemyTanksPool.theReturn(this);
//判断本关是否结束
if (GameFrame.isCrossLevel()) {
//判断是为最后一关
if (GameFrame.isLastLevel()) {
//如果是最后一关就设置为胜利状态
GameFrame.setGameState(Constant.STATE_WIN);
} else {
//TODO 否则进入下一关
GameFrame.nextLevel();
}
}
} else {
// //玩家死亡若干毫秒后进入游戏结束画面
// delaySecondsToOver(0);
}
}
/**
* 判断当前坦克是否死亡
*
* @return
*/
public boolean isDie() {
//只有坦克血量小于或等于0并且坦克身上的爆炸效果绘制完毕才视为死亡,解决了坦克死亡后立即消失的问题
if (hp <= 0 && explodes.size() == 0) {
return true;
} else {
return false;
}
}
public boolean myTankisDie() {
if (hp <= 0) {
return true;
} else {
return false;
}
}
/**
* 绘制当前坦克上的爆炸效果,和砖块上的爆炸效果
*
* @param g
*/
public void drawExplodes(Graphics g) {
for (Explode explode : explodes) {
explode.draw(g);
}
//遍历全部的爆炸效果,将不可见的爆炸效果移除,并还原到对象池
for (int i = 0; i < explodes.size(); i++) {
Explode explode = explodes.get(i);
if (!explode.isVisible()) {
//移除时会返回移除的对象,用remove接收
Explode remove = explodes.remove(i); //移除
ExplodePool.theReturn(remove); //还原
i--;
}
}
}
/**
* 子弹和所有地图块的碰撞
*
* @param tiles 所有地图块所在的数组
*/
public void bulletsCollideMapTiles(List<MapTile> tiles) {
for (MapTile tile : tiles) {
if (tile.isCollideBullet(bullets)) {
//添加爆炸效果,在方块中心
addExplode(tile.getX() + MapTile.radius, tile.getY() + MapTile.radius);
//如果是铁块的话就直接从这结束
if (tile.getType() == MapTile.TYPE_HARD) {
continue;
}
//设置地图块销毁
tile.setVisible(false);
//归还对象池
MapTilePool.theReturn(tile);
//当基地被击毁之后,若干毫秒切换到游戏结束的画面
if (tile.isHouse()) {
delaySecondsToOver(500);
}
}
}
}
/**
* 延迟若干毫秒进入死亡状态
*
* @param millisSecond 延迟多少毫秒
*/
private void delaySecondsToOver(int millisSecond) {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(millisSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
GameFrame.setGameState(Constant.STATE_OVER);
}
}.start();
}
/**
* 地图块和当前坦克碰撞方法
* 遮挡块没有碰撞效果
* 从地图中提取8个点,如果坦克有任何一个点和这8个点发生了碰撞就说明坦克和点发生了碰撞
* 8个点的顺序是从左上角开始顺时针一圈
*
* @param tiles 所有地图块所在的集合
* @return 碰到返回true否则返回false
*/
public boolean isCollideTile(List<MapTile> tiles) {
for (MapTile tile : tiles) {
//如果地图块不可见或者地图块类型为遮挡块就直接结束
if (!tile.isVisible() || tile.getType() == MapTile.TYPE_COVER) {
continue;
}
//第一个点,左上
int tileX = tile.getX();
int tileY = tile.getY();
boolean collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第二个点,上中
tileX += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第三个点,右上
//因为上面已经加过半径了,所以这里只用再加一次就行
tileX += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第四个点,右中
tileY += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第五个点,右下
tileY += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第六个点,下中
tileX -= MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第七个点,左下
tileX -= MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第八个点,左中
tileY -= MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
}
//如果都没有碰到就返回false
return false;
}
/**
* 坦克和多个坦克发生碰撞的方法
*
* @param tanks 传入坦克数组
* @return 如果发生了碰撞返回true
*/
public boolean isCollideTanks(List<Tank> tanks) {
for (Tank tank : tanks) {
//第一个点,左上
int tileX = tank.getX();
int tileY = tank.getY();
boolean collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第二个点,上中
tileX += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第三个点,右上
//因为上面已经加过半径了,所以这里只用再加一次就行
tileX += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第四个点,右中
tileY += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第五个点,右下
tileY += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第六个点,下中
tileX -= Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第七个点,左下
tileX -= Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第八个点,左中
tileY -= Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
}
//如果都没有碰到就返回false
return false;
}
/**
* 坦克和单个坦克碰撞的方法
*
* @param tank 传入单个坦克
* @return 如果发生了碰撞返回true
*/
public boolean isCollideTank(Tank tank) {
//第一个点,左上
int tileX = tank.getX();
int tileY = tank.getY();
boolean collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第二个点,上中
tileX += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第三个点,右上
//因为上面已经加过半径了,所以这里只用再加一次就行
tileX += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第四个点,右中
tileY += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第五个点,右下
tileY += Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第六个点,下中
tileX -= Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第七个点,左下
tileX -= Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//第八个点,左中
tileY -= Tank.RADIUS;
collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则就判断下一个点
if (collide) {
return true;
}
//如果都没有碰到就返回false
return false;
}
/**
* 坦克回退方法
*/
public void back() {
//如果发生了碰撞那么坦克就回到上一步的坐标
x = oldX;
y = oldY;
}
/**
* 血条类
* 为了方便封装所以写成了内部类
*/
class BloodBar {
//血条宽度
public static final int BAR_LENGTH = 50;
//血条高度
public static final int BAR_HEIGHT = 5;
//血条离坦克的距离
public static final int BAR_DISTANCE = 15;
public void draw(Graphics g) {
//设置底色
g.setColor(Color.CYAN);
//绘制矩形,当做血条
//因为要显示在坦克上方,所以才要 y-RADIUS-20
g.fillRect(x - RADIUS, y - RADIUS - BAR_DISTANCE, BAR_LENGTH, BAR_HEIGHT);
//设置血量颜色
g.setColor(Color.RED);
//当前血量乘血条长度除以默认血量就是当前血条长度
g.fillRect(x - RADIUS, y - RADIUS - BAR_DISTANCE, hp * BAR_LENGTH / maxHp, BAR_HEIGHT);
//设置边框颜色并且绘制边框
g.setColor(Color.GREEN);
g.drawRect(x - RADIUS, y - RADIUS - BAR_DISTANCE, BAR_LENGTH, BAR_HEIGHT);
}
}
}
BulletsPool类
在这里插入代码片package c02.n02.util;
import c02.n02.game.Bullet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* 子弹对象池类
*/
public class BulletsPool {
//设置默认的池大小可以容纳200个子弹对象
public static final int DEFAULT_POOL_SIZE = 200;
//设置池塘最多能放多少子弹对象
public static final int POOL_MAX_SIZE = 300;
//用于保存所有子弹的容器
private static List<Bullet> pool = new ArrayList<>();
private static HashSet<Bullet> poo=new HashSet<>();
//在类加载的时候创建200个子弹对象
static {
for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
pool.add(new Bullet());
}
}
/**
* 从池塘中获取一个子弹对象
*
* @return 返回一个子弹对象
*/
public static Bullet get() {
Bullet bullet = null;
if (pool.size() == 0) {
//如果池塘被拿空了就新建一个子弹
bullet = new Bullet();
// System.out.println("池空");
} else {
//取出一个子弹,并且在池塘中删除
bullet = pool.remove(0);
// System.out.println("从池中取出子弹,池中还剩"+pool.size());
}
return bullet;
}
/**
* 归还子弹
*
* @param bullet 传入子弹对象
*/
public static void theReturn(Bullet bullet) {
if (pool.size() == POOL_MAX_SIZE) {
//池塘中的子弹个数到达了最大值那就不再归还
// System.out.println("池满");
return;
} else {
// System.out.println("归还子弹");
pool.add(bullet);
}
}
}
Constant类
package c02.n02.util;
import java.awt.*;
/**
* 游戏常量类,所有的常量都在此类中
*/
public class Constant {
/*=================START============游戏窗口相关============================*/
//标题
public static final String GAME_TITLE = "坦克大战";
//游戏窗口大小
public static final int FRAME_WIDTH = 1280;
public static final int FRAME_HIGH = 720;
//动态获取系统的宽和高
public static final int SCREEN_W = Toolkit.getDefaultToolkit().getScreenSize().width;
public static final int SCREEN_H = Toolkit.getDefaultToolkit().getScreenSize().height;
//左上角位置,把游戏窗口定位到屏幕中间
public static final int FRAME_X = (SCREEN_W - FRAME_WIDTH) / 2;
public static final int FRAME_Y = (SCREEN_H - FRAME_HIGH) / 2;
/*=================END============游戏窗口相关============================*/
/*=================START============游戏菜单相关============================*/
//七个状态
public static final int STATE_MENU = 0; //菜单
public static final int STATE_HELP = 1; //帮助
public static final int STATE_ABOUT = 2; //关于
public static final int STATE_RUN = 3; //开始游戏
public static final int STATE_OVER = 4; //死亡
public static final int STATE_WIN = 5; //胜利
public static final int STATE_LEVEL = 6; //选择关卡
public static final String[] MENUS = {
"单人游戏",
"双人游戏",
"选择关卡",
"游戏帮助",
"游戏关于",
"退出游戏"
};
public static final String[] OVER_STR = {
"ESC键退出游戏",
"ENTER键返回主菜单"
};
//字体
public static final Font FONT = new Font("迷你简菱心", Font.PLAIN, 36);
//小号状态
public static final Font SMALL_FONT = new Font("黑体", Font.PLAIN, 14);
/*=================END============游戏菜单相关============================*/
//刷新时间,毫秒
public static final int REPAINT_INTERVAL = 30;
//敌人坦克数量
public static final int ENEMY_MAX_COUNT = 10;
//敌人坦克产生间隔,毫秒
public static final int ENEMY_BORN_INTERVAL = 2000;
//坦克AI,敌人改变状态间隔
public static final int ENEMY_AI_INTERVAL = 2000;
//发射子弹概率
public static final double ENEMY_FIRE_PERCENT = 0.05;
}
EnemyTanksPool类
package c02.n02.util;
import c02.n02.tank.EnemyTank;
import c02.n02.tank.Tank;
import java.util.ArrayList;
import java.util.List;
/**
* 敌人坦克池
*/
public class EnemyTanksPool {
//设置默认的池大小可以容纳20个坦克对象
public static final int DEFAULT_POOL_SIZE = 20;
//设置池塘最多能放多少坦克对象
public static final int POOL_MAX_SIZE = 30;
//用于保存所有坦克的容器
private static List<Tank> pool = new ArrayList<>();
//在类加载的时候创建20个坦克对象
static {
for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
pool.add(new EnemyTank());
}
}
/**
* 从池塘中获取一个坦克对象
*
* @return 返回一个坦克对象
*/
public static Tank get() {
Tank tank = null;
if (pool.size() == 0) {
//如果池塘被拿空了就新建一个坦克
tank = new EnemyTank();
// System.out.println("池空");
} else {
//取出一个坦克,并且在池塘中删除
tank = pool.remove(0);
// System.out.println("从池中取出坦克,池中还剩"+pool.size());
}
return tank;
}
/**
* 归还坦克
*
* @param tank 传入坦克对象
*/
public static void theReturn(Tank tank) {
if (pool.size() == POOL_MAX_SIZE) {
//池塘中的坦克个数到达了最大值那就不再归还
// System.out.println("池满");
return;
} else {
// System.out.println("归还坦克");
pool.add(tank);
}
}
}
ExplodePool类
package c02.n02.util;
import c02.n02.game.Explode;
import java.util.ArrayList;
import java.util.List;
/**
* 爆炸效果对象池类
*/
public class ExplodePool {
//设置默认的池大小可以容纳20个爆炸对象
public static final int DEFAULT_POOL_SIZE = 20;
//设置池塘最多能放多少爆炸对象
public static final int POOL_MAX_SIZE = 30;
//用于保存所有爆炸效果的容器
private static List<Explode> pool = new ArrayList<>();
//在类加载的时候创建20个爆炸对象
static {
for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
pool.add(new Explode());
}
}
/**
* 从池塘中获取一个爆炸对象
*
* @return 返回一个爆炸对象
*/
public static Explode get() {
Explode explode = null;
if (pool.size() == 0) {
//如果池塘被拿空了就新建一个爆炸对象
explode = new Explode();
// System.out.println("池空");
} else {
//取出一个爆炸对象,并且在池塘中删除
explode = pool.remove(0);
// System.out.println("从池中取出爆炸效果,池中还剩"+pool.size());
}
return explode;
}
/**
* 归还爆炸效果
*
* @param explode 传入爆炸效果对象
*/
public static void theReturn(Explode explode) {
if (pool.size() == POOL_MAX_SIZE) {
//池塘中的爆炸对象到达了最大值那就不再归还
// System.out.println("池满");
return;
} else {
// System.out.println("归还爆炸对象");
pool.add(explode);
}
}
}
MapTilePool类
package c02.n02.util;
import c02.n02.map.MapTile;
import java.util.ArrayList;
import java.util.List;
/**
* 砖块对象池
*/
public class MapTilePool {
//设置默认的池大小可以容纳100个砖块对象
public static final int DEFAULT_POOL_SIZE = 100;
//设置池塘最多能放多少砖块对象
public static final int POOL_MAX_SIZE = 150;
//用于保存所有砖块的容器
private static List<MapTile> pool = new ArrayList<>();
//在类加载的时候创建100个砖块对象
static {
for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
pool.add(new MapTile());
}
}
/**
* 从池塘中获取一个砖块对象
*
* @return 返回一个砖块对象
*/
public static MapTile get() {
MapTile tile = null;
if (pool.size() == 0) {
//如果池塘被拿空了就新建一个砖块
tile = new MapTile();
// System.out.println("池空");
} else {
//取出一个砖块,并且在池塘中删除
tile = pool.remove(0);
// System.out.println("从池中取出砖块,池中还剩"+pool.size());
}
return tile;
}
/**
* 归还砖块
*
* @param tile 传入砖块对象
*/
public static void theReturn(MapTile tile) {
if (pool.size() == POOL_MAX_SIZE) {
//池塘中的砖块个数到达了最大值那就不再归还
// System.out.println("池满");
return;
} else {
// System.out.println("归还砖块,池中还剩"+pool.size());
pool.add(tile);
}
}
}
MusicUtil类
package c02.n02.util;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
import java.io.IOException;
/**
* 音乐类
*/
public class MusicUtil {
private static Clip start;
private static AudioClip boom;
private static AudioClip menuCase;
//静态,大文件请使用底下的play方法
static {
//适用于小文件
try {
boom = Applet.newAudioClip(new File("music/boom.wav").toURL()); //加载爆炸的音乐
menuCase = Applet.newAudioClip(new File("music/menuCase.wav").toURL()); //加载选择菜单时候的音乐
} catch (IOException e) {
//TODO
System.out.println("音频接口异常");
e.printStackTrace();
}
//适用于大文件
try {
start = AudioSystem.getClip();
start.open(AudioSystem.getAudioInputStream(new File("music/F-777 - The 7 Seas.wav")));
} catch (Exception exc) {
exc.printStackTrace(System.out);
}
}
/**
* 播放开始音乐
* 循环播放10次
*/
public static void playStart() {
start.loop(10);
}
/**
* 结束播放音乐
*/
public static void stopStart() {
start.stop();
}
/**
* 播放爆炸音乐
*/
public static void playBoom() {
boom.play();
}
/**
* 播放选择菜单音乐
*/
public static void playMenuCase() {
menuCase.play();
}
}
MyUtil类
package c02.n02.util;
import java.awt.*;
/*
* 工具类
* */
public class MyUtil {
private MyUtil() {
}
/**
* 得到指定区间内的随机数
*
* @param min 区间最小值,包含
* @param max 区间最大值
* @return 随机数
*/
public static final int getRandomNumber(int min, int max) {
return (int) (Math.random() * (max - min) + min);
}
/**
* 返回随机颜色
*
* @return 返回一个颜色
*/
public static final Color getRandomColor() {
int r = getRandomNumber(0, 256);
int g = getRandomNumber(0, 256);
int b = getRandomNumber(0, 256);
Color c = new Color(r, g, b);
return c;
}
/**
* 碰撞检测
* 判断一个点是否在某一个正方形中
*
* @param rectX 正方形中心点X
* @param rectY 正方形中心点Y
* @param radius 正方形的半径
* @param pointX 点X
* @param pointY 点Y
* @return 点在正方形中返回true,否则返回false
*/
public static final boolean isCollide(int rectX, int rectY, int radius, int pointX, int pointY) {
//正方形中心点和点的X轴Y轴的距离
//因为可能为负数,所以要取绝对值
int disX = Math.abs(rectX - pointX);
int disY = Math.abs(rectY - pointY);
//如果某个方向的距离小于正方形的半径就说明碰撞了
if (disX < radius && disY < radius) {
return true;
} else {
return false;
}
}
/**
* 根据图片的路径创建加载图片对象
*
* @param path 图片路径
* @return 返回对象
*/
public static final Image createImage(String path) {
return Toolkit.getDefaultToolkit().createImage(path);
}
//形容词
public static final String[] MODIFIY = {
"一样", "喜欢", "美丽", "一定", "原来", "美好", "开心", "可能",
"可爱", "明白", "所有", "后来", "重要", "经常", "自然", "真正",
"害怕", "空中", "红色", "干净"
};
//名词
public static final String[] NAMES = {
"豹虎", "蜂猴", "熊猴", "叶猴", "紫貂", "貂熊", "熊狸", "云豹",
"雪豹", "儒艮", "黑麂", "野马", "鼷鹿", "坡鹿", "豚鹿", "麋鹿",
"野牛", "藏羚", "河狸", "蝎子"
};
/**
* 返回一个随机的名字
*
* @return 返回一个名字
*/
public static final String getRandomName() {
String s = MODIFIY[getRandomNumber(0, MODIFIY.length)] + "的" + NAMES[getRandomNumber(0, NAMES.length)];
return s;
}
}