目录
项目git地址:https://gitee.com/java-course-design_1/java-programming-tank-game
项目git地址:https://gitee.com/java-course-design_1/java-programming-tank-game
项目简介:
这是一个功能相对全的Java版坦克大战,界面绘制是通过Java的图形化用户界面swing完成的,包括了菜单界面和游戏界面。其中菜单界面可以供玩家选择重新开始游戏、暂停、继续、等操作;游戏界面绘制了坦克、草地、墙壁、鹰碉堡等经典坦克场景,玩家在游戏界面操作自己的坦克开始对战。
项目技术:
本游戏使用的主要技术有Swing编程、面向对象编程、多线程编程。
功能需求分析
游戏实现的主要功能有:
1、我方坦克默认可以渡河,碰到墙壁不能走,鹰碉堡被击中游戏结束
2、坦克可以上下左右、以及左上左下右上右下八个方向移动,移动时添加音效
3、坦克可以发子弹(可以连发),发射时添加音效
4、击中对方坦克时,坦克消失,显示爆炸效果;子弹击中墙壁时,子弹消失
5、我方坦克吃到血块时,生命值加30(可以自己设定);我方被击中时每次血量减50
6、移动过程中检测碰撞,包括坦克与坦克,坦克与草地、河流、墙壁等
7、声音处理(开始音乐、背景音乐、移动音效、爆炸音效等)
8、菜单处理(重新开始、暂停/继续游戏、是否播放背景音乐、设置游戏难度、帮助等
功能架构图:
app包下的GameMain类是游戏的入口函数。
map包下是游戏地图的绘制
game包下的类是关于绘制可视化图像,使用Graphics类绘制游戏窗口。
tank包下的类是tank的实体类,tank类分为我方坦克MyTank类、敌方坦克EnemyTank类。两个坦克类都继承了坦克父类tank类。
utils包下有工具类、爆炸对象池、子弹对象池、游戏常量对象
编写思路及部分代码
游戏窗口初始化
1:对游戏的窗口进行初始化,设置
(主要代码截图)
package com.lwj.game;
import com.lwj.tank.EnemyTank;
import com.lwj.tank.MyTank;
import com.lwj.tank.Tank;
import com.lwj.util.MusicUtil;
import com.lwj.util.MyUtil;
import com.lwj.map.GameMap;
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 com.lwj.util.Constant.*;
/**
* 游戏的主窗口类。
* 所有的游戏展示的内容都要在该类中实现。
*/
public class GameFrame extends Frame implements Runnable{
//第一次使用的时候加载,而不是类加载的时候加载,
private Image overImg = null;
//1:定义一张和屏幕大小一致的图片
private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH,FRAME_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);
//游戏状态
private static int gameState;
//菜单指向
private static int menuIndex;
//标题栏的高度
public static int titleBarH;
//定义坦克对象
private static Tank myTank;
//敌人的坦克容器
private static List<Tank> enemies = new ArrayList<>();
//用来记录本关卡产生了多少个敌人
private static int bornEnemyCount;
public static int killEnemyCount;
//定义地图相关的内容
private static GameMap gameMap = new GameMap();;
/**
* 对窗口进行初始化
*/
public GameFrame() {
initFrame();
initEventListener();
//启动用于刷新窗口的线程
new Thread(this).start();
}
/**
* 进入下一关的方法
*/
private void nextLevel() {
startGame(LevelInof.getInstance().getLevel()+1);
}
//开始过关动画
public static int flashTime;
public static final int RECT_WIDTH = 40;
public static final int RECT_COUNT = FRAME_WIDTH/RECT_WIDTH+1;
public static boolean isOpen = false;
public static void startCrossLevel(){
gameState = STATE_CROSS;
flashTime = 0;
isOpen = false;
}
//绘制过关动画
public void drawCross(Graphics g){
gameMap.drawBk(g);
myTank.draw(g);
gameMap.drawCover(g);
g.setColor(Color.BLACK);
//关闭百叶窗的效果
if(!isOpen) {
for (int i = 0; i < RECT_COUNT; i++) {
g.fillRect(i * RECT_WIDTH, 0, flashTime, FRAME_HEIGHT);
}
//所有的叶片都关闭了
if (flashTime++ - RECT_WIDTH > 5) {
isOpen = true;
//初始化下一个地图
gameMap.initMap(LevelInof.getInstance().getLevel()+1);
}
}else{
//开百叶窗的效果
for (int i = 0; i < RECT_COUNT; i++) {
g.fillRect(i * RECT_WIDTH, 0, flashTime, FRAME_HEIGHT);
}
if(flashTime-- == 0){
startGame(LevelInof.getInstance().getLevel());
}
}
}
/**
* 对游戏进行初始化
*/
private void initGame() {
gameState = STATE_MENU;
}
/**
* 属性进行初始化
*/
private void initFrame() {
//设置标题
setTitle(GAME_TITLE);
//设置窗口大小
setSize(FRAME_WIDTH, FRAME_HEIGHT);
//设置窗口的左上角的坐标
setLocation(FRAME_X, FRAME_Y);
//设置窗口大小不可改变
setResizable(false);
//设置窗口可见
setVisible(true);
//求标题栏的高度
titleBarH = getInsets().top;
}
/**
标题,大小,坐标,窗口的事件的监听
2:游戏菜单的显示,以及游戏菜单的按键的控制
游戏被分成了若干个游戏的状态: gameState
游戏菜单
游戏帮助
游戏关于
游戏中
游戏结束
在不同的状态下,绘制不同的内容,按键有不同的处理的方式
3:调整整个游戏窗口的重绘的机制。
FPS:frame per second
要每秒固定的刷新我们的整个窗口。fps==33帧
每隔30ms 刷新一次(对整个窗口进行重绘)。repaint()
单独启动一个线程用于窗口的重绘。
运行截图:
坦克类的定义
4.坦克类的定义,和绘制
5.坦克的四方向行走,以及边界的控制
6:坦克发射子弹
7: 坦克的绘制使用图片
8: 解决屏幕闪烁的问题 双缓冲解决
9.敌人坦克的控制
10: 随机的在屏幕的左上角和右上角产生敌人的坦克
11: 坦克相关的类:父类:Tank 子类:MyTank、 EnemyTank
12: 敌人坦克AI:每隔五秒让敌人坦克随机获得一个状态(站立、行走)
敌人发射子弹的AI:游戏的每一帧都随机(0.05 概率)判断敌人是否发射子弹
13:添加爆炸类,爆炸效果的控制
14:使用对象池来管理爆炸对象
15: 给坦克添加血条和名字
16:敌人的坦克对象池管理
17:自己的坦克死亡的处理,切换游戏状态,游戏结束。
提供两个选项:回到主菜单,退出游戏、重置游戏数据、开始新游戏
(相关代码如下)
package com.lwj.tank;
import com.lwj.game.GameFrame;
import com.lwj.game.LevelInof;
import com.lwj.util.Constant;
import com.lwj.util.EnemyTanksPool;
import com.lwj.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("res/ul.png");
greenImg[1] = MyUtil.createImage("res/dl.png");
greenImg[2] = MyUtil.createImage("res/ll.png");
greenImg[3] = MyUtil.createImage("res/rl.png");
yellowImg = new Image[4];
yellowImg[0] = MyUtil.createImage("res/u.png");
yellowImg[1] = MyUtil.createImage("res/d.png");
yellowImg[2] = MyUtil.createImage("res/l.png");
yellowImg[3] = MyUtil.createImage("res/r.png");
}
private EnemyTank(int x, int y, int dir) {
super(x, y, dir);
//敌人一旦创建就计时
aiTime = System.currentTimeMillis();
type = MyUtil.getRandomNumber(0,2);
}
public EnemyTank(){
type = MyUtil.getRandomNumber(0,2);
aiTime = System.currentTimeMillis();
}
//用于创建一个敌人的坦克
public static Tank createEnemy(){
int x = MyUtil.getRandomNumber(0,2) == 0 ? RADIUS :
Constant.FRAME_WIDTH-RADIUS;
int y = GameFrame.titleBarH + RADIUS;
int dir = DIR_DOWN;
EnemyTank enemy = (EnemyTank)EnemyTanksPool.get();
enemy.setX(x);
enemy.setY(y);
enemy.setDir(dir);
enemy.setEnemy(true);
enemy.setState(STATE_MOVE);
//根据游戏的难度设置敌人的血量
int maxHp = Tank.DEFAULT_HP*LevelInof.getInstance().getLevelType();
enemy.setHp(maxHp);
enemy.setMaxHP(maxHp);
//通过关卡信息中的敌人类型,来设置当前出生的敌人的类型
int enemyType = LevelInof.getInstance().getRandomEnemyType();
enemy.setType(enemyType);
return enemy;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public void drawImgTank(Graphics g){
ai();
g.drawImage(type == TYPE_GREEN ? greenImg[getDir()] :
yellowImg[getDir()],getX()-RADIUS,getY()-RADIUS,null);
}
/**
* 敌人的AI
*/
private void ai(){
if(System.currentTimeMillis() - aiTime > Constant.ENEMY_AI_INTERVAL){
//间隔5秒随机一个状态
setDir(MyUtil.getRandomNumber(DIR_UP,DIR_RIGHT+1));
setState(MyUtil.getRandomNumber(0,2) == 0 ? STATE_STAND : STATE_MOVE);
aiTime = System.currentTimeMillis();
}
//比较小的概率去开火
if(Math.random() < Constant.ENEMY_FIRE_PERCENT){
fire();
}
}
}
package com.lwj.tank;
import com.lwj.util.MyUtil;
import java.awt.*;
/**
* 自己的坦克
*/
public class MyTank extends Tank{
//坦克的图片数组
private static Image[] tankImg;
//静态代码块中对它进行初始化
static{
tankImg = new Image[4];
for (int i = 0; i <tankImg.length ; i++) {
tankImg[i] = MyUtil.createImage("res/tank1_"+i+".png");
}
}
public MyTank(int x, int y, int dir) {
super(x, y, dir);
}
@Override
public void drawImgTank(Graphics g) {
g.drawImage(tankImg[getDir()],getX()-RADIUS,getY()-RADIUS,null);
}
}
package com.lwj.tank;
import com.lwj.game.Bullet;
import com.lwj.game.Explode;
import com.lwj.game.GameFrame;
import com.lwj.map.MapTile;
import com.lwj.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 = 20;
//默认速度 每帧 30ms
public static final int DEFAULT_SPEED = 4;
//坦克的状态
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 = 100;
private int maxHP = DEFAULT_HP;
private int x,y;
private int hp = DEFAULT_HP;
private String name;
private int atk;
public static final int ATK_MAX = 25;
public static final int ATK_MIN = 15;
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<>();
public Tank(int x, int y, int dir) {
this.x = x;
this.y = y;
this.dir = dir;
initTank();
}
public Tank(){
initTank();
}
private void initTank(){
color = MyUtil.getRandomColor();
name = MyUtil.getRandomName();
atk = MyUtil.getRandomNumber(ATK_MIN,ATK_MAX);
}
/**
* 绘制坦克
* @param g
*/
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 - 35);
}
/**
* 使用图片的方式去绘制坦克
* @param g
*/
public abstract void drawImgTank(Graphics g);
/**
* 使用系统的方式去绘制坦克
* @param g
*/
private void drawTank(Graphics g){
g.setColor(color);
//绘制坦克的圆
g.fillOval(x-RADIUS,y-RADIUS,RADIUS<<1,RADIUS<<1);
int endX = x;
int endY = y;
switch(dir){
case DIR_UP:
endY = y - RADIUS*2;
g.drawLine(x-1,y,endX-1,endY);
g.drawLine(x+1,y,endX+1,endY);
break;
case DIR_DOWN:
endY = y + RADIUS*2;
g.drawLine(x-1,y,endX-1,endY);
g.drawLine(x+1,y,endX+1,endY);
break;
case DIR_LEFT:
endX = x - 2 * RADIUS;
g.drawLine(x,y-1,endX,endY-1);
g.drawLine(x,y+1,endX,endY+1);
break;
case DIR_RIGHT:
endX = x + 2 * RADIUS;
g.drawLine(x,y-1,endX,endY-1);
g.drawLine(x,y+1,endX,endY+1);
break;
}
g.drawLine(x,y,endX,endY);
}
//坦克的逻辑处理
private void logic(){
switch(state){
case STATE_STAND:
break;
case STATE_MOVE:
move();
break;
case STATE_DIE:
break;
}
}
private int oldX = -1, oldY = -1;
//坦克移动的功能
private void move(){
oldX = x;
oldY = y;
switch (dir){
case DIR_UP:
y -= speed;
if(y < RADIUS + GameFrame.titleBarH){
y = RADIUS + GameFrame.titleBarH;
}
break;
case DIR_DOWN:
y += speed;
if(y > Constant.FRAME_HEIGHT-RADIUS){
y = Constant.FRAME_HEIGHT-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 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;
}
@Override
public String toString() {
return "Tank{" +
"x=" + x +
", y=" + y +
", hp=" + hp +
", atk=" + atk +
", speed=" + speed +
", dir=" + dir +
", state=" + state +
'}';
}
//上一次开火的时间
private long fireTime;
//子弹发射的最小的间隔
public static final int FIRE_INTERVAL = 200;
/**
* 坦克的功能,坦克开火的方法
* 创建了一个子弹对象,子弹对象的属性信息通过坦克的信息获得
* 然后将创建的子弹添加到坦克管理的容器中。
*/
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.playBomb();
}
}
/**
* 将当前坦克的发射的所有的子弹绘制出来
*/
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);
bullet.draw(g);
}
//是遍历所有的子弹,将不可见的子弹移除,并还原回对象池
for (int i = 0; i < bullets.size(); i++) {
Bullet bullet = bullets.get(i);
if(!bullet.isVisible()) {
Bullet remove = bullets.remove(i);
i--;
BulletsPool.theReturn(remove);
}
}
}
/**
* 坦克销毁的时候处理坦克的所有的子弹
*/
public void bulletsReturn(){
// for (Bullet bullet : bullets) {
// BulletsPool.theReturn(bullet);
// }
for (int i = 0; i < bullets.size(); i++) {
Bullet bullet = bullets.get(i);
BulletsPool.theReturn(bullet);
}
bullets.clear();
}
//坦克和敌人的子弹碰撞的方法。
public void collideBullets(List<Bullet> bullets){
for (int i = 0; i < bullets.size(); i++) {
Bullet bullet = bullets.get(i);
// }
// 遍历所有的子弹,依次和当前的坦克进行碰撞的检测
// for (Bullet bullet : bullets) {
int bulletX = bullet.getX();
int bulletY = bullet.getY();
//子弹和坦克碰上了。
if(MyUtil.isCollide(this.x,y,RADIUS,bulletX,bulletY)){
//子弹消失
bullet.setVisible(false);
//坦克受到伤害
hurt(bullet);
//添加爆炸效果
addExplode(x,y+RADIUS);
}
}
}
private void addExplode(int x,int y){
//添加爆炸效果,以当前被击中的坦克的坐标为参考
Explode explode = ExplodesPool.get();
explode.setX(x);
explode.setY(y);
explode.setVisible(true);
explode.setIndex(0);
explodes.add(explode);
}
//坦克收到伤害
private void hurt(Bullet bullet){
int atk = bullet.getAtk();
System.out.println("atk = "+atk);
hp -= atk;
if(hp < 0){
hp = 0;
die();
}
}
//坦克死亡需要处理的内容
private void die(){
//敌人
if(isEnemy){
GameFrame.killEnemyCount ++;
//敌人坦克被消灭了 归还对象池
EnemyTanksPool.theReturn(this);
//本关是否结束? TODO
if (GameFrame.isCrossLevel()){
//判断游戏是否通关了?
if(GameFrame.isLastLevel()){
//通关了
GameFrame.setGameState(Constant.STATE_WIN);
}else {
//TODO 进入下一关
GameFrame.startCrossLevel();
}
}
}else{
delaySecondsToOver(3000);
}
}
/**
* 判断当前的坦克是否死亡
* @return
*/
public boolean isDie(){
return hp <= 0;
}
/**
* 绘制当前坦克上的所有的爆炸的效果
* @param g
*/
public void drawExplodes(Graphics g){
// for (Explode explode : explodes) {
for (int i = 0; i < explodes.size(); i++) {
Explode explode = explodes.get(i);
explode.draw(g);
}
//将不可见的爆炸效果删除,还回对象池
for (int i = 0; i < explodes.size(); i++) {
Explode explode = explodes.get(i);
if(!explode.isVisible()){
Explode remove = explodes.remove(i);
ExplodesPool.theReturn(remove);
i--;
}
}
}
//内部类,来表示坦克的血条
class BloodBar{
public static final int BAR_LENGTH = 50;
public static final int BAR_HEIGHT = 3;
public void draw(Graphics g){
//填充底色
g.setColor(Color.YELLOW);
g.fillRect(x - RADIUS , y-RADIUS-BAR_HEIGHT*2,BAR_LENGTH,BAR_HEIGHT);
//红色的当前血量
g.setColor(Color.RED);
g.fillRect(x - RADIUS , y-RADIUS-BAR_HEIGHT*2,hp*BAR_LENGTH/maxHP,BAR_HEIGHT);
//蓝色的边框
g.setColor(Color.WHITE);
g.drawRect(x - RADIUS , y-RADIUS-BAR_HEIGHT*2,BAR_LENGTH,BAR_HEIGHT);
}
}
//坦克的子弹和地图所有的块的碰撞
public void bulletsCollideMapTiles(List<MapTile> tiles){
//foreach遍历容器中的元素,
//在遍历的过程中只能使用迭代器的删除方式删除元素
//都切换为使用基本的for循环
// for (MapTile tile : tiles) {
for (int i = 0; i < tiles.size(); i++) {
MapTile tile = tiles.get(i);
if(tile.isCollideBullet(bullets)){
//添加爆炸效果
addExplode(tile.getX()+MapTile.radius,tile.getY() +MapTile.tileW);
//地图水泥块没有击毁的处理
if(tile.getType() == MapTile.TYPE_HARD)
continue;
//设置地图块销毁
tile.setVisible(false);
//归还对象池
MapTilePool.theReturn(tile);
//当老巢被击毁之后,一秒钟切换到游戏结束的画面
if(tile.isHouse()){
delaySecondsToOver(3000);
}
}
}
// }
}
/**
* 延迟 若干毫秒 切换到 游戏结束
* @param millisSecond
*/
private void delaySecondsToOver(int millisSecond){
new Thread(){
public void run() {
try {
Thread.sleep(millisSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
GameFrame.setGameState(Constant.STATE_LOST);
}
}.start();
}
/**
* 一个地图块和当前的坦克碰撞的方法
* 从tile 中提取8个点 来判断 8个点是否有任何一个点和当前的坦克有了碰撞
* 点的顺序从左上角的点开始,顺时针遍历
*/
public boolean isCollideTile(List<MapTile> tiles){
final int len = 2;
for (int i = 0; i < tiles.size(); i++) {
MapTile tile = tiles.get(i);
// }
// for (MapTile tile : tiles) {
//如果块不可见,或者是遮挡块就不进行碰撞的检测
if(!tile.isVisible() || tile.getType() == MapTile.TYPE_COVER)
continue;
//点-1 左上角
int tileX = tile.getX();
int tileY = tile.getY();
boolean collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
//如果碰上了就直接返回,否则继续判断下一个点
if(collide){
return true;
}
//点-2 中上点
tileX += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
//点-3 右上角点
tileX += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
//点-4 右中点
tileY += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
//点-5 右下点
tileY += MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
//点-6 下中点
tileX -= MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
//点-7 左下点
tileX -= MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
//点-8 左中点
tileY -= MapTile.radius;
collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
if(collide){
return true;
}
}
return false;
}
/**
* 坦克回退的方法
*/
public void back(){
x = oldX;
y = oldY;
}
public int getMaxHP() {
return maxHP;
}
public void setMaxHP(int maxHP) {
this.maxHP = maxHP;
}
}
地图类
20: 地图 地图元素类
设计:上、左右,距离窗口的宽度为1.5倍的坦克宽度。
21:实现一张地图的绘制显示
22:优化了地图随机初始化。子弹和地图碰撞
23:坦克和地图的碰撞
24:敌人坦克和地图的碰撞
25:玩家的老巢,老巢被击毁 游戏结束。延迟若干秒
26:控制玩家的发射炮弹的速度(最小间隔,200毫秒)
27:添加其他种类的地图元素
不可击毁的地图元素
遮挡物
28:添加不同地图的种类
package com.lwj.map;
import com.lwj.game.GameFrame;
import com.lwj.game.LevelInof;
import com.lwj.tank.Tank;
import com.lwj.util.Constant;
import com.lwj.util.MapTilePool;
import com.lwj.util.MyUtil;
import java.awt.*;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
/**
* 游戏地图类
*/
public class GameMap {
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_HEIGHT-Tank.RADIUS*8-GameFrame.titleBarH;
//地图元素块的容器
private List<MapTile> tiles = new ArrayList<>();
//大本营
private TankHouse house;
public GameMap() {}
/**
* 初始化地图元素块,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();
levelInof.setLevel(level);
Properties prop = new Properties();
prop.load(new FileInputStream("level/lv_"+level));
//将所有的地图信息都加载进来
int enemyCount = Integer.parseInt(prop.getProperty("enemyCount"));
//设置敌人数量
levelInof.setEnemyCount(enemyCount);
//0,1 对敌人类型解析
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]);
}
//设置敌人类型
levelInof.setEnemyType(type);
//关卡难度
//如果没有设计游戏难度,那么就当默认的处理
String levelType = prop.getProperty("levelType");
levelInof.setLevelType(Integer.parseInt(levelType==null? "1" : levelType));
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);
}
//使用读取到的参数,调用对应的方法。
invokeMethod(methodName,params);
}
//根据方法的名字和参数调用对应的方法
private void invokeMethod(String name,String[] params){
for (String param : params) {
//获得每一行的方法的参数,解析。
String[] split = param.split(",");
//使用一个int 数组保存解析后的内容
int[] arr = new int[split.length];
int i;
for (i = 0; i < split.length-1; i++) {
arr[i] = Integer.parseInt(split[i]);
}
//块之间的间隔是地图块的倍数
final int DIS = MapTile.tileW ;
//解析最后一个double值
int dis = (int)(Double.parseDouble(split[i])*DIS);
switch(name){
case "addRow":
addRow(MAP_X+arr[0]*DIS,MAP_Y+arr[1]*DIS,
MAP_X+MAP_WIDTH-arr[2]*DIS,arr[3],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[3]*DIS,
arr[4],dis);
break;
}
}
}
//将老巢的所有的元素块添加到地图的容器中
private void addHouse(){
tiles.addAll(house.getTiles());
}
/**
* 某一个点确定的地图块。是否和 tiles 集合中的所有的块 有重叠的部分
* @param tiles
* @param x
* @param 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 List<MapTile> getTiles() {
return tiles;
}
/**
* 将所有的不可见的地图块从容器中移除
*/
public void clearDestroyTile(){
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坐标
* @param type 地图块的类型
* @param DIS 地图块之间的中心点的间隔 如果是块的宽度 意味着是连续的,
* 如果大于块的宽度就是不连续的
*/
public void addRow(int startX,int startY, int endX, int type, final int DIS){
int count = (endX - startX +DIS )/(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);
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 = (endY - startY +DIS)/(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));
tiles.add(tile);
}
}
//对指定的矩形区域添加元素块
public void addRect(int startX,int startY,int endX, int endY, int type, final int DIS){
int rows = (endY-startY+DIS)/(MapTile.tileW+DIS);
for (int i = 0; i <rows ; i++) {
addRow(startX,startY+i*(MapTile.tileW+DIS),endX,type,DIS);
}
}
}
运行结果如下
游戏常量类
/**
* 游戏常量类
*/
public class Constant {
/****************游戏窗口相关属性*********************/
public static final String GAME_TITLE = "坦克大战";
public static final int FRAME_WIDTH = 1000;
public static final int FRAME_HEIGHT = 900;
//获取系统屏幕的宽高
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 >> 1;
public static final int FRAME_Y = SCREEN_H - FRAME_HEIGHT >> 1;
/****************游戏菜单相关属性********************************/
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 String[] MENUS = {
"开始游戏",
"继续游戏",
"游戏帮助",
"游戏相关",
"退出游戏"
};
public static final Font GAME_FONT = new Font("宋体", Font.BOLD, 24);
//窗口刷新间隔
public static final int REPAINT_INTERVAL = 30;
//最大敌人数量
public static final int ENEMY_MAX_COUNT = 10;
//敌方坦克生产间隔
public static final int ENEMY_BORN_INTERVAL = 5000;
//敌方坦克状态切换的间隔
public static final int ENEMY_AI_INTERVAL = 1000;
//敌方坦克每帧发射子弹的概率
public static final double ENEMY_AI_PERCENT = 0.10;
}
Size().width;
public static final int SCREEN_H = Toolkit.getDefaultToolkit().getScreenSize().height;
//游戏窗口顶点坐标
public static final int FRAME_X = SCREEN_W - FRAME_WIDTH >> 1;
public static final int FRAME_Y = SCREEN_H - FRAME_HEIGHT >> 1;
/****************游戏菜单相关属性********************************/
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 String[] MENUS = {
"开始游戏",
"继续游戏",
"游戏帮助",
"游戏相关",
"退出游戏"
};
public static final Font GAME_FONT = new Font("宋体", Font.BOLD, 24);
//窗口刷新间隔
public static final int REPAINT_INTERVAL = 30;
//最大敌人数量
public static final int ENEMY_MAX_COUNT = 10;
//敌方坦克生产间隔
public static final int ENEMY_BORN_INTERVAL = 5000;
//敌方坦克状态切换的间隔
public static final int ENEMY_AI_INTERVAL = 1000;
//敌方坦克每帧发射子弹的概率
public static final double ENEMY_AI_PERCENT = 0.10;
}
优化 :添加背景音乐和音效
33 : 添加了游戏信息类,获得了所有的关卡的数量。游戏通关的判断
34:过关判断,通关的处理。
35:切换关卡的处理
操作演示视频
坦克大战 2022-06-16 20-40-03