从零开发Java坦克大战:架构设计与难点突破 (上)
引言
- 项目目标: 在本地IDE上基于Java Swing实现一个双人坦克大战的小游戏, 具备基本的坦克操作控制, 地图设计, 子弹发射及碰撞, 坦克碰撞, 游戏结束提示等功能
- 学习期望: 通过本游戏, 希望深刻认识Java Swing库及其相关API组件功能, 巩固面向对象的编程思维, 熟悉入门游戏开发的基本流程
- 预期效果: 坦克动荡小游戏,在线玩,4399小游戏
https://www.4399.com/flash/34789_3.htm
-
1. 坦克大战架构设计
-
1.1 游戏基本结构图
-
1.2 类的设计与职能分发
- a. 主线程 TankWar_UI : 底层JFrame界面开发, 启动初始化界面
- b. 移动物体类 MoveObject: 封装所有可移动物体的基础属性; 标准JavaBean模板实现本类的有参(坐标x,y)构造, get/set方法获取物体的坐标, 设置/获取物体的移动速度,方向以及长宽
- c. 坦克一 TankA: 继承MoveObject类以获得所有get/set方法; 通过有参构造设置坦克的基础属性(x,y,size,image); 编写绘制坦克方法, 根据传入的Direction参数绘制不同朝向的坦克图片
- d. 坦克二 TankB: 同上
- e. 地图 BattleMap: 公开设置基本属性(方块元素化:m行n列,砖块代号x); 二维数组创建map1对象; 编写绘制地图方法, 判断是否撞墙方法
- f. 子弹 Bullet: 私有化设置子弹基本属性, 有参构造绑定基本参数; 移动/绘制/获取子弹边界/判断子弹是否激活/判断子弹来源 等方法
- g. 美化 scorePanel: 绘制界面装饰图片以及提供人性化的战斗进度标识
- h. 核心控制系统 GamePanel: 继承JPanel并实现KeyListener接口获取的丰富API与方法; 设置所有核心的私有化参数, 通过无参构造创建(坦克,子界面, 定时器)对象,并启动游戏循环定时器; 核心方法:{ 生成坦克A/B, 监听并处理键盘输入, 更新游戏状态方法, 核心渲染方法, 控制坦克移动, 创建子弹, 展示结束提示框, 重置游戏 等 }
-
-
2. 主类: 核心初始化方法
-
2.1 完整代码展示
-
public class TankWar_UI {public void initUI() {JFrame jf = new JFrame();jf.setSize(1150, 950);jf.setTitle("---Tank War---");jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);jf.setResizable(false);jf.setLocation(500, 5);GamePanel gamePanel = new GamePanel();scorePanel scorePanel = new scorePanel();scorePanel.setBackground(Color.WHITE);gamePanel.setBackground(Color.WHITE);jf.add(scorePanel, BorderLayout.SOUTH);jf.add(gamePanel, BorderLayout.CENTER);jf.setVisible(true);gamePanel.requestFocus();//获取焦点}public static void main(String[] args) {SwingUtilities.invokeLater(() -> {TankWar_UI tankWarUi = new TankWar_UI();tankWarUi.initUI();});} }
-
-
2.2 位置选择
- 由于JFrame承载着本游戏的界面, 是最重要的底层框架, 我们把它直接编写在主类的初始化界面方法中, 一是防止创建的类过于冗杂, 二是便于我们后续直接对初始化方法进行修改
-
2.3 关键难点
-
2.3.1 gamePanel的焦点获取
- 我们在创建完JFrame后, 将游戏Panel添加到了其上面, 这样既有可能会令游戏盘失去系统控制的焦点
-
gamePanel.requestFocus();//获取焦点
-
2.3.2 通过EDT线程初始化游戏
- a. 为什么不直接在主线程(main函数)里启动?
- 1. 主线程与EDT的博弈
-
public static void main(String[] args) {// 此时运行在主线程(非EDT)SwingUtilities.invokeLater(() -> {// 此处代码会在EDT执行new TankWar_UI().initUI(); }); }
- 2. Swing 的线程规则
- 黄金法则: 所有访问/修改 Swing组件的代码必须在EDT上执行
- 例如: UI 初始化通过
invokeLater
托管
- b. SwingUtilities.invokeLater()是什么?
- 1. 事件分发线程(EDT)的守护者
-
SwingUtilities.invokeLater(Runnable doRun);
- 核心功能: 将任务异步提交到Swing的事件分发线程(Event Dispatch Thread)执行
- 2. 与直接调用的区别
- c. 底层机制图解
- d. Event Dispatch Thread (EDT)
- 1. EDT的本质:
- 单线程规则: Swing的所有UI操作必须在EDT上执行
- 消息队列机制: 采用事件队列(EventQueue)处理用户输入、绘制请求等
- 生命周期: 随JVM启动而创建,直到最后一个Swing组件销毁才结束
- 2. EDT的工作流程
- 3. 关键特性
- 独占性: 所有Swing组件只能在EDT上创建修改
- 阻塞敏感性: EDT被阻塞=整个UI冻结
- 绘制触发:
repaint()
只是提交请求,实际绘制由EDT调度执行
- 1. EDT的本质:
- 记住:在 Swing 的世界里,EDT 是王,而
invokeLater
是你效忠的誓言。
- a. 为什么不直接在主线程(main函数)里启动?
-
-
-
3. 移动物体类
-
3.1 完整代码展示
-
import java.awt.*;public class MoveObjects {private int width;private int height;private int speedX;private int speedY;private int x, y;private int direction;public MoveObjects(int x, int y) {this.x = x;this.y = y;}public void move() {x += speedX;y += speedY;}public Rectangle getBounds() {return new Rectangle(x, y, width, height);}public int getX() {return x;}public int getY() {return y;}public void setX(int x) {this.x = x;}public void setY(int y) {this.y = y;}public void setSpeedX(int speedX) {this.speedX = speedX;}public void setSpeedY(int speedY) {this.speedY = speedY;}public void setWidth(int width) {this.width = width;}public void setHeight(int height) {this.height = height;}public int getWidth() {return width;}public int getHeight() {return height;}public int getDirection() {return direction;}public void setDirection(int direction) {this.direction = direction;} }
-
-
3.2 设计理由
- 游戏内容的本质都是物体的移动和变化, 因此如果有一个我们自己设计的承载着基础属性和基本方法的父类, 即可以充分利用Java本身继承的特性大大减少代码量
-
3.3 关键难点
- 我们在编写有参构造时, 仅传入x,y参数?
- 因为: 任何物体的不论任何时候都必须有一个可能在变化的坐标, 而长/宽是我们提前确定下的, 速度参数也是根据x,y坐标 通过增减speedX/speedY而实现的
-
-
4. 坦克类
-
4.1 完整代码展示
-
import javax.swing.*; import java.awt.*;public class TankA extends MoveObjects {private final ImageIcon[] imgArr = new ImageIcon[4];public TankA(int x, int y) {super(x, y);setWidth(45);setHeight(35);setDirection(1);for (int i = 0; i < 4; i++) {imgArr[i] = new ImageIcon("D:\\桌面\\Xing\\Photos\\TankA" + i + ".png");}}public void drawTankA(Graphics2D g2d) {switch (getDirection()) {case (0)://左g2d.drawImage(imgArr[0].getImage(), getX(), getY(), getWidth(), getHeight(), null);break;case (1)://上g2d.drawImage(imgArr[1].getImage(), getX(), getY(), getHeight(), getWidth(), null);break;case (2)://右g2d.drawImage(imgArr[2].getImage(), getX(), getY(), getWidth(), getHeight(), null);break;case (3)://下g2d.drawImage(imgArr[3].getImage(), getX(), getY(), getHeight(), getWidth(), null);break;}}public Rectangle getBounds() {//获取边界方方法return new Rectangle(getX(), getY(), getWidth(), getHeight());}}
-
-
4.2 核心思路
- a. 我们首先继承刚才编写的MoveObject类以获取父类的方法, 为了能根据键盘的操作实时显示对应方向的坦克图片,我们创建一个ImageIcon[]的对象imgArr 用于存储(实时取出)对应的图片, 通过将我们本地的坦克图片的地址作为参数传给ImageIcon实现绑定
-
private final ImageIcon[] imgArr = new ImageIcon[4];public TankA(int x, int y) {super(x, y);setWidth(45);setHeight(35);setDirection(1);for (int i = 0; i < 4; i++) {imgArr[i] = new ImageIcon("D:\\桌面\\Xing\\Photos\\TankA" + i + ".png");}}
- b. 利用上面的ImageIcon图像数组, 根据后续方法传来的坦克方向代号, 利用Graphics2D绘制对应的图片
- c. 编写一个获取坦克边界的方法用于后续进行碰撞检测
-
4.3 难点解析
-
super(x, y);
super(x, y);
是 Java 中调用父类构造方法的语法,在面向对象编程中扮演着重要角色。- 作用: 直接调用父类的构造方法
- 位置要求: 必须是子类构造方法中的第一条语句(继承规则)
- 语法形式: super(参数列表);
- 执行流程解析:
- 隐式调用规则: 如果省略super() ,编译器会自动插入无参super()
- 当父类没有无参构造方法时,必须显式调用super()
- 参数传递机制:
-
-
-
5. 地图类
-
5.1 完整源码展示
-
import javax.swing.*; import java.awt.*;public class BattleMaps {public static final int Brick = 1;public static int rows = 50;public static int cols = 60;public final ImageIcon[] brickImage = new ImageIcon[1];int[][] map1 = {{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};public void paintMap(Graphics2D g2d) {brickImage[0] = new ImageIcon("D:\\桌面\\Xing\\Photos\\Bricks.png");int mapX = 120;int mapY = 60;for (int x = 0; x < rows; x++) {for (int y = 0; y < cols; y++) {int blockType = map1[x][y];if (blockType == Brick) {g2d.drawImage(brickImage[0].getImage(), mapX, mapY, 15, 15, null);}mapX += 15;}mapY += 15;mapX = 120;}}public boolean isCollidingWithWall(Rectangle rect) {//计算地图块起始坐标(地图绘制起始点)int mapStartX = 120;int mapStartY = 60;int tileSize = 15;// 获取子弹/坦克在地图上的位置 将坦克的矩形转换为单位地图块索引范围int startCol = (rect.x - mapStartX) / tileSize;int startRow = (rect.y - mapStartY) / tileSize;int endCol = (rect.x + rect.width - mapStartX - 1) / tileSize;int endRow = (rect.y + rect.height - mapStartY - 1) / tileSize;//防止索引越界startCol = Math.max(startCol, 0);startRow = Math.max(startRow, 0);endCol = Math.min(endCol, cols - 1);endRow = Math.min(endRow, rows - 1);for (int row = startRow; row <= endRow; row++) {for (int col = startCol; col <= endCol; col++) {if (row < map1.length && col < map1[row].length) {if (map1[row][col] == Brick) {Rectangle wallRect = new Rectangle(// 创建墙体的矩形区域mapStartX + col * tileSize,mapStartY + row * tileSize,tileSize,tileSize);if (rect.intersects(wallRect)) {// 检查对象是否与墙体相交return true;}}}}}return false;}}
-
-
5.2 项目核心算法: 碰撞检测
- a. 考虑到我们的地图是依据遍历二维数组填充绘制砖块的基本逻辑, 我们通过将坦克的矩形坐标转化为以地图块为最小单位的坐标系的坐标
-
// 获取子弹/坦克在地图上的位置 将坦克的矩形转换为单位地图块索引范围 int startCol = (rect.x - mapStartX) / tileSize; int startRow = (rect.y - mapStartY) / tileSize; int endCol = (rect.x + rect.width - mapStartX - 1) / tileSize; int endRow = (rect.y + rect.height - mapStartY - 1) / tileSize;
- 这个难以脑补我们用一个游戏模拟界面分析一下:
- 上图极其清晰的展示了坐标转化的过程和结果
- b. 防止索引越界:始终保证遍历的范围在map之内
-
//防止索引越界startCol = Math.max(startCol, 0);startRow = Math.max(startRow, 0);endCol = Math.min(endCol, cols - 1);endRow = Math.min(endRow, rows - 1);
- c. 遍历整个地图, 判断该地图块是否是砖块, 如果是则根据当前的row与col创建墙体对象并调用intersects方法判断坦克是否与墙体碰撞
-
if (map1[row][col] == Brick) {Rectangle wallRect = new Rectangle(// 创建墙体的矩形区域mapStartX + col * tileSize,mapStartY + row * tileSize,tileSize,tileSize);if (rect.intersects(wallRect)) {// 检查对象是否与墙体相交return true;} }
-
-
--下一篇我们会着重分析游戏引擎和子弹类的设计与开发