详解Java实现坦克大战小游戏(JavaFX,完整源码+注释说明)

游戏效果图

类的组织(UML)

上图中的Recorder和EnemyTank的行动逻辑未实现,与实际代码有小小的出入

一些核心思路

接口与抽象类

接口Updatable和Redrawable

编写了接口Updatable和Redrawable,实现这两个接口的类是可更新且可重绘的,如GameUnit(游戏单元)

抽象类GameUnit

我设计在坦克大战这样的实时游戏中,一个游戏单元如坦克,子弹,墙等都应该是可随时间流逝而更新的,更新之后,便需要在面板上重绘;亦或者说更新是重绘的前提

游戏单元,既然可重绘,就应该可见,所以具有坐标属性(x和y)

tier(层级)是指游戏单元所含节点在面板(Pane)中的绘制层级,我想通过优先列表来实现不同类型的单元指定摆放次序,因为在BBTankPane中,是通过遍历游戏单元列表,进行重绘,来使各个单元可见的

survival是单元的存活状态,若一个单元已死亡,则会在它的update方法中通知主面板(BBTankPane)在其单元列表中进行删除,那么稍后,这个失去引用的对象便会被gc

类BBTankPane

BBTankPane类承载了很多功能,本类采用了单例的设计模式,这是为了方便各游戏单元获取它的对象

gameUnitList是游戏单元列表,采用了线程安全的Vector来实现,这个实现没有符合前文对层级的需求

unitListAddT和unitListDelT是后来添加的,因为在使用迭代器遍历游戏单元列表时很难添加或删除游戏单元,所以采用这两个列表暂存要添加和删除的游戏单元们

BBTankPane类实现了Runnable接口,在它的run方法中,先遍历更新各游戏单元(指逻辑更新),再更新单元列表(批量添加和删除单元),最后遍历重绘各游戏单元;这样可以确保在首先的逻辑更新中被判定死亡的游戏单元不会在紧随的重绘阶段被无意义地再次绘制

项目代码

import javafx.application.Platform;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;


/**
 * @Date 2023/2/27 13:00
 * @Created by 邦邦拒绝魔抗
 * @Description 绘制背景和各单元(坦克、子弹、爆炸等),处理事件
 */
public class BBTankPane extends Pane implements Runnable {
    private static BBTankPane instance = new BBTankPane();
    private List<GameUnit> gameUnitList;//游戏单元列表
    private List<GameUnit> unitListAddT;//单元添加暂存
    private List<GameUnit> unitListDelT;//单元删除暂存
    private HeroTank heroA;//玩家坦克A
    private HeroTank heroB;//玩家坦克B
    private boolean needRun = true;//是否仍需运行

    public List<GameUnit> getGameUnitList() {
        return gameUnitList;
    }

    public static BBTankPane getInstance() {
        return instance;
    }

    private BBTankPane() {
        //背景
        setBackground(new Background(new BackgroundFill(
                Color.BLACK, null, null)));//颜色、圆角、插图
        //元素
        gameUnitList = new Vector<>();
        unitListAddT = new Vector<>();
        unitListDelT = new Vector<>();

        launchDefaultMap();
        redrawUnits(this);
    }

    /**
     * 加载默认地图
     */
    private void launchDefaultMap() {
        heroA = new HeroTank(300, 300, 0, Color.YELLOW);
        heroB = new HeroTank(700, 300, 0, Color.GREEN.brighter().brighter());
        gameUnitList.add(heroA);
        gameUnitList.add(heroB);
        //添加敌人坦克
        List<EnemyTank> enemyTanks = new ArrayList<>();
        Color enemyTankColor = Color.AQUA;
        for (int i = 0; i < 9; i++) {
            enemyTanks.add(new EnemyTank(100 * i + 100, 100, 1, enemyTankColor));
            enemyTanks.add(new EnemyTank(100 * i + 100, 500, 0, enemyTankColor));
        }
        gameUnitList.addAll(enemyTanks);
        //添加墙
        List<Wall> walls = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            //不可摧毁墙
            walls.add(new Wall(100 * i + 100, 150, 0));
            walls.add(new Wall(100 * i + 100, 450, 0));
            if (i == 0 || i == 4 || i == 8) {
                walls.add(new Wall(100 * i + 100, 300, 0));
            }
            //可摧毁墙
            walls.add(new Wall(100 * i + 100 - 16, 150, 1));
            walls.add(new Wall(100 * i + 100 - 16, 450, 1));
            walls.add(new Wall(100 * i + 100 + 16, 150, 1));
            walls.add(new Wall(100 * i + 100 + 16, 450, 1));
            if (i == 0 || i == 4 || i == 8) {
                walls.add(new Wall(100 * i + 100, 300 - 16, 1));
                walls.add(new Wall(100 * i + 100, 300 + 16, 1));
            }
            if (i == 1 || i == 3 || i == 5 || i == 7) {
                walls.add(new Wall(100 * i + 100, 300 - 80, 1));
                walls.add(new Wall(100 * i + 100, 300 - 16, 1));
                walls.add(new Wall(100 * i + 100, 300, 1));
                walls.add(new Wall(100 * i + 100, 300 + 16, 1));
                walls.add(new Wall(100 * i + 100, 300 + 80, 1));
            }
            //可跨越墙
            walls.add(new Wall(100 * i + 100, 150 + 16, 2));
            walls.add(new Wall(100 * i + 100, 450 - 16, 2));
            if (i == 1 || i == 3 || i == 5 || i == 7) {
                walls.add(new Wall(100 * i + 100, 300 - 64, 2));
                walls.add(new Wall(100 * i + 100, 300 - 48, 2));
                walls.add(new Wall(100 * i + 100, 300 - 32, 2));
                walls.add(new Wall(100 * i + 100, 300 + 32, 2));
                walls.add(new Wall(100 * i + 100, 300 + 48, 2));
                walls.add(new Wall(100 * i + 100, 300 + 64, 2));
            }
        }
        gameUnitList.addAll(walls);
    }

    public void createKeyListener() {
        setOnKeyPressed(e -> {//按下
            KeyCode kc = e.getCode();
            switch (kc) {
                case W:
                    heroA.setNeedUp(true);//↑
                    break;
                case UP:
                    heroB.setNeedUp(true);//↑
                    break;
                case S:
                    heroA.setNeedDown(true);//↓
                    break;
                case DOWN:
                    heroB.setNeedDown(true);//↓
                    break;
                case A:
                    heroA.setNeedLeft(true);//←
                    break;
                case LEFT:
                    heroB.setNeedLeft(true);//←
                    break;
                case D:
                    heroA.setNeedRight(true);//→
                    break;
                case RIGHT:
                    heroB.setNeedRight(true);//→
                    break;
                case J:
                    heroA.setNeedShot(true);//biu~
                    break;
                case NUMPAD1:
                    heroB.setNeedShot(true);//biu~
                    break;
                default:
                    return;
            }
        });
        setOnKeyReleased(e -> {//释放
            KeyCode kc = e.getCode();
            switch (kc) {
                case W:
                    heroA.setNeedUp(false);//↑
                    break;
                case UP:
                    heroB.setNeedUp(false);//↑
                    break;
                case S:
                    heroA.setNeedDown(false);//↓
                    break;
                case DOWN:
                    heroB.setNeedDown(false);//↓
                    break;
                case A:
                    heroA.setNeedLeft(false);//←
                    break;
                case LEFT:
                    heroB.setNeedLeft(false);//←
                    break;
                case D:
                    heroA.setNeedRight(false);//→
                    break;
                case RIGHT:
                    heroB.setNeedRight(false);//→
                    break;
                case J:
                    heroA.setNeedShot(false);//biu~
                    break;
                case NUMPAD1:
                    heroB.setNeedShot(false);//biu~
                    break;
                default:
                    return;
            }
        });
    }

    @Override
    public void run() {
        while (needRun) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            Platform.runLater(() -> {
                //先更新单元
                updateUnits();
                //再更新列表
                updateUnitList();
                //再绘制
                redrawUnits(this);
            });
        }
    }

    public void addUnitLater(GameUnit unit) {
        unitListAddT.add(unit);
    }

    public void delUnitLater(GameUnit unit) {
        unitListDelT.add(unit);
    }

    public void setNeedRun(boolean needRun) {
        this.needRun = needRun;
    }

    /**
     * 更新各个单元
     */
    private void updateUnits() {
        for (GameUnit gameUnit : gameUnitList) {
            gameUnit.update();
        }
    }

    /**
     * 更新单元列表
     */
    private void updateUnitList() {
        if (!unitListAddT.isEmpty()) {//有单元要添加
            for (GameUnit u : unitListAddT) {
                gameUnitList.add(u);
                System.out.println("游戏中添加了一个单元,当前单元数量:" + gameUnitList.size());
            }
            unitListAddT.clear();
        }
        if (!unitListDelT.isEmpty()) {//有单元要删除
            for (GameUnit u : unitListDelT) {
                gameUnitList.remove(u);
                System.out.println("游戏中删除了一个单元,当前单元数量:" + gameUnitList.size());
            }
            unitListDelT.clear();
        }
    }

    /**
     * 重绘各个单元
     */
    private void redrawUnits(Pane parent) {
        //清空面板
        getChildren().clear();
        for (GameUnit gameUnit : gameUnitList) {
            gameUnit.redraw(parent);
        }
    }
}
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

/**
 * @Date 2023/2/27 17:24
 * @Created by 邦邦拒绝魔抗
 * @Description TODO
 */
public class Bomb extends GameUnit {
    private int tankR;
    private int bombR;
    private int animationLife = 6;//炸弹动画生命
    private Circle bombOuter;
    private Circle bombInner;

    public Bomb(double x, double y, int r, Color tankColor) {
        this.x = x;
        this.y = y;
        tankR = r;
        bombR = tankR;
        bombOuter = new Circle(x, y, bombR);
        bombOuter.setFill(Color.WHITE);
        bombInner = new Circle(x, y, bombR - 2);
        bombInner.setFill(tankColor.darker().darker().darker().darker());
    }

    @Override
    public void update() {
        super.update();
        if (!survival) {
            return;
        }
        switch (animationLife) {
            case 6:
            case 5:
                break;
            case 4:
            case 3:
                bombR = tankR - 2;
                break;
            case 2:
            case 1:
                bombR = tankR - 4;
                break;
        }
        animationLife--;
        if (animationLife == 0) {
            survival = false;
        }
    }

    @Override
    public void redraw(Pane parent) {
        bombOuter.setRadius(bombR);
        bombInner.setRadius(bombR - 2);
        parent.getChildren().addAll(bombOuter, bombInner);
    }
}
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;

/**
 * @Date 2023/2/27 17:25
 * @Created by 邦邦拒绝魔抗
 * @Description 子弹
 */
public class Bullet extends GameUnit {
    private int direction;
    private int speed = 10;
    private double endX;
    private double endY;
    private Line bullet;//子弹

    public Bullet(double x, double y, int direction) {
        this.x = x;
        this.y = y;
        this.direction = direction;
        updateEndXY();
        bullet = new Line(x, y, endX, endY);
        bullet.setStroke(Color.WHITE);
        bullet.setStrokeWidth(2);
    }

    @Override
    public void update() {
        super.update();
        if (!survival) {
            return;
        }
        fly();
        updateEndXY();
        updateSurvival();
    }

    private void updateEndXY() {
        endX = x;
        endY = y;
        switch (direction) {
            case 0:
                endY -= 2;
                break;
            case 1:
                endY += 2;
                break;
            case 2:
                endX -= 2;
                break;
            case 3:
                endX += 2;
                break;
            case 4:
                endX += Math.pow(2, 0.5);
                endY -= Math.pow(2, 0.5);
                break;
            case 5:
                endX -= Math.pow(2, 0.5);
                endY += Math.pow(2, 0.5);
                break;
            case 6:
                endX -= Math.pow(2, 0.5);
                endY -= Math.pow(2, 0.5);
                break;
            case 7:
                endX += Math.pow(2, 0.5);
                endY += Math.pow(2, 0.5);
                break;
        }
    }

    @Override
    public void redraw(Pane parent) {
        bullet.setStartX(x);
        bullet.setStartY(y);
        bullet.setEndX(endX);
        bullet.setEndY(endY);
        parent.getChildren().add(bullet);
    }

    /**
     * 更新子弹存活状态
     */
    private void updateSurvival() {
        if (x < 0 || x > 1000 || y < 0 || y > 600) {
            survival = false;
            return;
        }
        for (GameUnit u : BBTankPane.getInstance().getGameUnitList()) {
            if (u instanceof Tank) {
                Tank t = (Tank) u;
                if (inTank(t)) {
                    survival = false;
                    t.setSurvival(false);
                    return;
                }
            } else if (u instanceof Wall) {
                Wall w = (Wall) u;
                if (inWall(w)) {
                    switch (w.getWallType()) {
                        case 0://不可摧毁
                            survival = false;
                            break;
                        case 1://可摧毁
                            survival = false;
                            w.lifeLoss(5);
                            break;
                        case 2://可跨越
                            break;
                    }
                    return;
                }
            }
        }
    }

    private void fly() {
        switch (direction) {
            case 0://向上飞行
                y -= speed;
                break;
            case 1://向下飞行
                y += speed;
                break;
            case 2://向左飞行
                x -= speed;
                break;
            case 3://向右飞行
                x += speed;
                break;
            case 4://右上飞行
                x += speed * Math.pow(2, 0.5) / 2;
                y -= speed * Math.pow(2, 0.5) / 2;
                break;
            case 5://左下飞行
                x -= speed * Math.pow(2, 0.5) / 2;
                y += speed * Math.pow(2, 0.5) / 2;
                break;
            case 6://左上飞行
                x -= speed * Math.pow(2, 0.5) / 2;
                y -= speed * Math.pow(2, 0.5) / 2;
                break;
            case 7://右下飞行
                x += speed * Math.pow(2, 0.5) / 2;
                y += speed * Math.pow(2, 0.5) / 2;
                break;
        }
    }

    private boolean inTank(Tank tank) {
        double d1 = getDistance(tank.getX(), tank.getY(), x, y);//子弹尾端与坦克的距离
        double d2 = getDistance(tank.getX(), tank.getY(), endX, endY);//子弹顶端与坦克的距离
        if (d1 < tank.getR() || d2 < tank.getR()) {
            return true;
        }
        return false;
    }

    private double getDistance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    }

    private boolean inWall(Wall wall) {
        if (endX >= wall.getX() - 8 && endX <= wall.getX() + 8
                && endY >= wall.getY() - 8 && endY <= wall.getY() + 8) {
            return true;
        }
        return false;
    }
}
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

/**
 * @Date 2023/2/27 12:41
 * @Created by 邦邦拒绝魔抗
 * @Description TODO
 */
public class Demo extends Application {

    private BorderPane borderPane;
    private BBTankPane bbTankPane;

    public static void main(String[] args) {
        launch(args);//调用start
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        borderPane = new BorderPane();
        bbTankPane = BBTankPane.getInstance();
        borderPane.setCenter(bbTankPane);
        Scene scene = new Scene(borderPane, 1000, 600);

        primaryStage.setTitle("BBTank06");
        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.show();

        bbTankPane.requestFocus();//添加焦点
        bbTankPane.createKeyListener();//键盘监听
        new Thread(bbTankPane).start();//提供线程

        primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            @Override
            public void handle(WindowEvent event) {
                bbTankPane.setNeedRun(false);//关闭窗口后,各元素的更新重绘也终止
            }
        });
    }
}
import javafx.scene.paint.Color;

/**
 * @Date 2023/3/1 20:19
 * @Created by 邦邦拒绝魔抗
 * @Description 敌人坦克
 */
public class EnemyTank extends Tank {
    public EnemyTank(double x, double y, int direction, Color tankColor) {
        super(x, y, direction, tankColor);
        speed = 2;
    }
}
/**
 * @Date 2023/2/27 16:23
 * @Created by 邦邦拒绝魔抗
 * @Description 游戏单元,可更新,可重绘
 */
public abstract class GameUnit implements Redrawable {
    protected double x;//x坐标
    protected double y;//y坐标
    protected int tier = 0;//绘制层级
    protected boolean survival = true;//是否存活

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public boolean isSurvival() {
        return survival;
    }

    public void setSurvival(boolean survival) {
        this.survival = survival;
    }

    /**
     * 将单元的存活状态体现在列表中,实际的删除会在重绘前施行
     */
    @Override
    public void update() {
        if (!survival) {
            BBTankPane.getInstance().delUnitLater(this);
        }
    }
}
import javafx.scene.paint.Color;

/**
 * @Date 2023/2/27 17:44
 * @Created by 邦邦拒绝魔抗
 * @Description 玩家坦克
 */
public class HeroTank extends Tank {
    private boolean needUp = false;
    private boolean needDown = false;
    private boolean needLeft = false;
    private boolean needRight = false;
    private boolean needShot = false;

    public HeroTank(double x, double y, int direction, Color tankColor) {
        super(x, y, direction, tankColor);
        speed = 2;
    }

    public void setNeedUp(boolean needUp) {
        this.needUp = needUp;
    }

    public void setNeedDown(boolean needDown) {
        this.needDown = needDown;
    }

    public void setNeedLeft(boolean needLeft) {
        this.needLeft = needLeft;
    }

    public void setNeedRight(boolean needRight) {
        this.needRight = needRight;
    }

    public void setNeedShot(boolean needShot) {
        this.needShot = needShot;
    }

    @Override
    public void update() {
        super.update();
        if (!survival) {
            return;
        }
        if (needUp) {
            if (needLeft) {
                direction = 6;//左上
                move(direction);
            } else if (needRight) {
                direction = 4;//右上
                move(direction);
            } else {
                direction = 0;//向上
                move(direction);
            }
        } else if (needDown) {
            if (needLeft) {
                direction = 5;//左下
                move(direction);
            } else if (needRight) {
                direction = 7;//右下
                move(direction);
            } else {
                direction = 1;//向下
                move(direction);
            }
        } else if (needLeft) {
            direction = 2;//向左
            move(direction);
        } else if (needRight) {
            direction = 3;//向右
            move(direction);
        }
        if (needShot) {
            shot();
        }
    }
}
import javafx.scene.layout.Pane;

/**
 * @Date 2023/2/28 13:21
 * @Created by 邦邦拒绝魔抗
 * @Description 可重绘的
 */
public interface Redrawable extends Updatable {
    public void redraw(Pane parent);
}
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;

import java.io.Serializable;

/**
 * @Date 2023/2/27 17:15
 * @Created by 邦邦拒绝魔抗
 * @Description 坦克
 */
public abstract class Tank extends GameUnit implements Serializable {
    protected static int r = 8;//坦克半径
    protected Color tankColor;//坦克颜色
    protected int direction = 0;//坦克方向
    protected int speed = 1;//移动速度
    protected Circle chassis;//底盘
    protected Circle battery;//炮台
    protected Arc barrel;//炮管
    protected Circle cap;//炮顶

    public static int getR() {
        return r;
    }

    public Tank(double x, double y, int direction, Color tankColor) {
        this.x = x;
        this.y = y;
        this.direction = direction;
        this.tankColor = tankColor;
        chassis = new Circle(x, y, r);
        chassis.setFill(tankColor);
        battery = new Circle(x, y, r - 2);
        battery.setFill(tankColor.darker().darker());
        barrel = new Arc();
        barrel.setFill(tankColor.darker().darker());
        barrel.setType(ArcType.ROUND);
        barrel.setRadiusX(r);
        barrel.setRadiusY(r);
        barrel.setLength(40);
        cap = new Circle(x, y, r - 5);
        cap.setFill(tankColor);
    }

    /**
     * 坦克移动
     */
    public void move(int direction) {
        this.direction = direction;
        for (int i = 0; i < speed; i++) {
            if (!testOnly1()) {
                return;
            }
            moveOnly1();
        }
    }

    private boolean testOnly1() {
        double tempX = x;
        double tempY = y;
        switch (direction) {
            case 0://向上移动
                tempY--;
                break;
            case 1://向下移动
                tempY++;
                break;
            case 2://向左移动
                tempX--;
                break;
            case 3://向右移动
                tempX++;
                break;
            case 4://右上移动
                tempX += Math.pow(2, 0.5) / 2;
                tempY -= Math.pow(2, 0.5) / 2;
                break;
            case 5://左下移动
                tempX -= Math.pow(2, 0.5) / 2;
                tempY += Math.pow(2, 0.5) / 2;
                break;
            case 6://左上移动
                tempX -= Math.pow(2, 0.5) / 2;
                tempY -= Math.pow(2, 0.5) / 2;
                break;
            case 7://右下移动
                tempX += Math.pow(2, 0.5) / 2;
                tempY += Math.pow(2, 0.5) / 2;
                break;
        }
        for (GameUnit u : BBTankPane.getInstance().getGameUnitList()) {
            if (u instanceof Wall) {
                Wall w = (Wall) u;
                if (inWall(tempX, tempY, w)) {
                    return false;
                }
            } else if (u instanceof Tank && u != this) {
                Tank t = (Tank) u;
                if (inTank(t)) {
                    survival = false;
                    t.setSurvival(false);
                    return false;
                }
            }
        }
        return true;
    }

    private boolean inWall(double tankX, double tankY, Wall wall) {
        switch (wall.getWallType()) {
            case 0:
            case 1:
                if (tankX >= wall.getX() - 16 && tankX <= wall.getX() + 16
                        && tankY >= wall.getY() - 16 && tankY <= wall.getY() + 16) {
                    return true;
                }
            case 2:
                if (getDistance(tankX, tankY, wall.getX(), wall.getY()) <= 16) {
                    return true;
                }
        }
        return false;
    }

    private boolean inTank(Tank tank) {
        if (getDistance(tank.getX(), tank.getY(), x, y) < r + tank.getR()) {
            return true;
        }
        return false;
    }

    private double getDistance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    }

    /**
     * 坦克仅移动一步
     */
    private void moveOnly1() {
        switch (direction) {
            case 0://向上移动
                y--;
                break;
            case 1://向下移动
                y++;
                break;
            case 2://向左移动
                x--;
                break;
            case 3://向右移动
                x++;
                break;
            case 4://右上移动
                x += Math.pow(2, 0.5) / 2;
                y -= Math.pow(2, 0.5) / 2;
                break;
            case 5://左下移动
                x -= Math.pow(2, 0.5) / 2;
                y += Math.pow(2, 0.5) / 2;
                break;
            case 6://左上移动
                x -= Math.pow(2, 0.5) / 2;
                y -= Math.pow(2, 0.5) / 2;
                break;
            case 7://右下移动
                x += Math.pow(2, 0.5) / 2;
                y += Math.pow(2, 0.5) / 2;
                break;
        }
    }

    public void shot() {
        double bulletX = x;
        double bulletY = y;
        switch (direction) {
            case 0:
                bulletY -= 9;
                break;
            case 1:
                bulletY += 9;
                break;
            case 2:
                bulletX -= 9;
                break;
            case 3:
                bulletX += 9;
                break;
            case 4:
                bulletX += 9 * Math.pow(2, 0.5) / 2;
                bulletY -= 9 * Math.pow(2, 0.5) / 2;
                break;
            case 5:
                bulletX -= 9 * Math.pow(2, 0.5) / 2;
                bulletY += 9 * Math.pow(2, 0.5) / 2;
                break;
            case 6:
                bulletX -= 9 * Math.pow(2, 0.5) / 2;
                bulletY -= 9 * Math.pow(2, 0.5) / 2;
                break;
            case 7:
                bulletX += 9 * Math.pow(2, 0.5) / 2;
                bulletY += 9 * Math.pow(2, 0.5) / 2;
                break;
        }
        BBTankPane.getInstance().addUnitLater(new Bullet(bulletX, bulletY, direction));
    }

    /**
     * 若坦克已消亡,生成爆炸
     */
    @Override
    public void update() {
        super.update();
        if (!survival) {
            BBTankPane.getInstance().addUnitLater(new Bomb(x, y, r, tankColor));
            return;
        }
    }

    @Override
    public void redraw(Pane parent) {
        //同步坦克
        chassis.setCenterX(x);
        chassis.setCenterY(y);
        battery.setCenterX(x);
        battery.setCenterY(y);
        cap.setCenterX(x);
        cap.setCenterY(y);
        //修改炮管
        barrel.setCenterX(x);
        barrel.setCenterY(y);
        switch (direction) {
            case 0://炮管向上
                barrel.setStartAngle(70);
                break;
            case 1://炮管向下
                barrel.setStartAngle(70 + 180);
                break;
            case 2://炮管向左
                barrel.setStartAngle(70 + 90);
                break;
            case 3://炮管向右
                barrel.setStartAngle(70 + 270);
                break;
            case 4://炮管右上
                barrel.setStartAngle(25);
                break;
            case 5://炮管左下
                barrel.setStartAngle(25 + 180);
                break;
            case 6://炮管左上
                barrel.setStartAngle(25 + 90);
                break;
            case 7://炮管右下
                barrel.setStartAngle(25 + 270);
                break;
        }
        parent.getChildren().addAll(chassis, battery, barrel, cap);
    }
}
/**
 * @Date 2023/2/27 16:20
 * @Created by 邦邦拒绝魔抗
 * @Description 可更新的
 */
public interface Updatable {
    public void update();
}
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;

/**
 * @Date 2023/3/1 20:38
 * @Created by 邦邦拒绝魔抗
 * @Description 墙
 */
public class Wall extends GameUnit {
    private int wallLife;
    private int wallType;
    /* 0    不可摧毁
     * 1    可摧毁
     * 2    可跨越
     */
    private Rectangle wallOuter;
    private Rectangle wallInner;
    private Line wallInnerLine1;
    private Line wallInnerLine2;
    private Circle wallInnerCircle;
    private Circle wallInnerInnerCircle;

    public Wall(double x, double y, int wallType) {
        this.x = x;
        this.y = y;
        this.wallType = wallType;
        wallOuter = new Rectangle(x - 8, y - 8, 16, 16);
        wallInner = new Rectangle(x - 6, y - 6, 12, 12);
        wallInnerLine1 = new Line(x - 6, y - 6, x + 6, y + 6);
        wallInnerLine1.setStrokeWidth(2);
        wallInnerLine2 = new Line(x - 6, y + 6, x + 6, y - 6);
        wallInnerLine2.setStrokeWidth(2);
        wallInnerCircle = new Circle(x, y, 8);
        wallInnerInnerCircle = new Circle(x, y, 6);
        switch (wallType) {
            case 0://不可摧毁
                wallLife = 10;
                wallOuter.setFill(Color.AZURE);
                wallInner.setFill(Color.BLACK);
                wallInnerLine1.setStroke(Color.LIGHTCYAN);
                wallInnerLine2.setStroke(Color.LIGHTCYAN);
                wallInnerCircle.setVisible(false);
                wallInnerInnerCircle.setVisible(false);
                break;
            case 1://可摧毁
                wallLife = 10;
                wallOuter.setFill(Color.LIGHTCYAN);
                wallInner.setFill(Color.LIGHTCYAN.darker().darker());
                wallInnerLine1.setVisible(false);
                wallInnerLine2.setVisible(false);
                wallInnerCircle.setVisible(false);
                wallInnerInnerCircle.setVisible(false);
                break;
            case 2://可跨越
                wallLife = 10;
                wallInnerCircle.setFill(Color.AZURE);
                wallInnerInnerCircle.setFill(Color.BLACK);
                wallInnerLine1.setStartX(x - 6);
                wallInnerLine1.setEndX(x + 6);
                wallInnerLine1.setStartY(y);
                wallInnerLine1.setEndY(y);
                wallInnerLine2.setStartX(x);
                wallInnerLine2.setEndX(x);
                wallInnerLine2.setStartY(y - 6);
                wallInnerLine2.setEndY(y + 6);
                wallInnerLine1.setStroke(Color.AZURE);
                wallInnerLine2.setStroke(Color.AZURE);
                wallOuter.setVisible(false);
                wallInner.setVisible(false);
                break;
        }
    }

    public int getWallType() {
        return wallType;
    }

    /**
     * 墙的生命损失
     */
    public void lifeLoss(int life) {
        wallLife -= life;
    }

    @Override
    public void update() {
        super.update();
        if (wallLife <= 0) {
            survival = false;
        }
    }

    @Override
    public void redraw(Pane parent) {
        if (wallType == 1 && wallLife == 5) {
            wallOuter.setFill(Color.LIGHTCYAN.darker().darker());
            wallInner.setFill(Color.LIGHTCYAN.darker().darker().darker().darker());
            wallInnerLine1.setStrokeWidth(4);
            wallInnerLine2.setStrokeWidth(4);
            wallInnerLine1.setStroke(Color.LIGHTCYAN.darker().darker().darker().darker());
            wallInnerLine2.setStroke(Color.LIGHTCYAN.darker().darker().darker().darker());

            wallInnerLine1.setStartX(x - 6);
            wallInnerLine1.setEndX(x + 6);
            wallInnerLine1.setStartY(y);
            wallInnerLine1.setEndY(y);
            wallInnerLine2.setStartX(x);
            wallInnerLine2.setEndX(x);
            wallInnerLine2.setStartY(y - 6);
            wallInnerLine2.setEndY(y + 6);

            wallInnerLine1.setVisible(true);
            wallInnerLine2.setVisible(true);
        }
        parent.getChildren().addAll(wallOuter, wallInner,
                wallInnerCircle, wallInnerInnerCircle,
                wallInnerLine1, wallInnerLine2);
    }
}

缺陷与反思

可以改为使用工厂模式提供游戏单元对象