Java坦克大战


目录

项目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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值