如何让Java游戏中Rock/Paper/Scissors对象碰撞反弹并优化复杂度
实现对象互斥反弹+优化O(n²)碰撞检测(基于普林斯顿StdDraw)
一、实现对象间碰撞反弹逻辑
首先给每个游戏对象(石头/剪刀/布)定义核心属性:x、y(位置),vx、vy(速度),r(半径),以及唯一id(用于后续去重检测)。
1. 碰撞判定
为了避免开方运算的性能损耗,直接用平方距离判断两个对象是否碰撞:
// 判断当前对象与另一个对象是否碰撞 private boolean isColliding(GameObject other) { double dx = other.x - this.x; double dy = other.y - this.y; double distanceSq = dx * dx + dy * dy; double minDistanceSq = (this.r + other.r) * (this.r + other.r); return distanceSq <= minDistanceSq; }
2. 弹性反弹计算
假设所有对象质量相同,弹性碰撞后交换法向速度分量,切向速度保持不变,同时修正重叠位置避免连续抖动:
public void resolveCollision(GameObject other) { if (!isColliding(other)) return; // 计算碰撞法向量单位向量 double dx = other.x - this.x; double dy = other.y - this.y; double distance = Math.sqrt(dx*dx + dy*dy); double nx = dx / distance; double ny = dy / distance; // 提取法向速度分量 double v1n = this.vx * nx + this.vy * ny; double v2n = other.vx * nx + other.vy * ny; // 交换法向分量(质量相同的弹性碰撞规则) double temp = v1n; v1n = v2n; v2n = temp; // 更新速度:保留切向速度,替换法向速度 this.vx = (this.vx - v1n*nx) + v1n*nx; this.vy = (this.vy - v1n*ny) + v1n*ny; other.vx = (other.vx - v2n*nx) + v2n*nx; other.vy = (other.vy - v2n*ny) + v2n*ny; // 修正重叠位置,避免连续碰撞抖动 double overlap = (this.r + other.r) - distance; this.x -= nx * overlap / 2; this.y -= ny * overlap / 2; other.x += nx * overlap / 2; other.y += ny * overlap / 2; // 这里可以追加石头剪刀布的胜负逻辑,比如碰撞后类型转换/对象销毁 }
二、优化O(n²)碰撞检测
嵌套循环遍历所有对象对的方式,在对象数量超过50后会明显卡顿,推荐用**网格划分(Grid Partitioning)**来减少无效检测,这是实现成本最低、效果最直观的优化方案。
1. 核心思路
将画布划分为固定大小的网格,网格边长设为游戏对象的最大直径(2*MAX_RADIUS),这样每个对象只可能与同一网格或相邻8个网格内的对象发生碰撞,大幅减少需要检测的对象对数量。
2. 具体实现代码
// 全局常量:所有对象的最大半径 private static final double MAX_RADIUS = 0.05; // 网格边长:确保同一网格外的对象不可能碰撞 private static final double GRID_SIZE = 2 * MAX_RADIUS; // 每帧执行的碰撞检测流程 public void checkAllCollisions(List<GameObject> allObjects) { // 第一步:将对象分配到对应网格 Map<Pair<Integer, Integer>, List<GameObject>> grid = new HashMap<>(); for (GameObject obj : allObjects) { int gridX = (int) (obj.x / GRID_SIZE); int gridY = (int) (obj.y / GRID_SIZE); Pair<Integer, Integer> gridKey = new Pair<>(gridX, gridY); grid.computeIfAbsent(gridKey, k -> new ArrayList<>()).add(obj); } // 第二步:仅检查当前对象所在网格及相邻网格的对象 Set<Pair<Integer, Integer>> checkedPairs = new HashSet<>(); for (GameObject obj : allObjects) { int gridX = (int) (obj.x / GRID_SIZE); int gridY = (int) (obj.y / GRID_SIZE); // 遍历当前网格+周围8个网格 for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { Pair<Integer, Integer> neighborGridKey = new Pair<>(gridX + dx, gridY + dy); List<GameObject> neighbors = grid.get(neighborGridKey); if (neighbors == null) continue; for (GameObject neighbor : neighbors) { // 跳过自身,避免重复检测同一对象对 if (obj.id == neighbor.id) continue; Pair<Integer, Integer> pairKey = new Pair<>(Math.min(obj.id, neighbor.id), Math.max(obj.id, neighbor.id)); if (checkedPairs.contains(pairKey)) continue; checkedPairs.add(pairKey); obj.resolveCollision(neighbor); } } } } }
3. 进阶优化选项
如果你的游戏对象数量极大(超过200),可以考虑用**四叉树(QuadTree)**替代网格划分,它能动态划分空间,更适合对象分布不均匀的场景,但实现复杂度略高。
内容的提问来源于stack exchange,提问作者Tijil Saka
相关产品推荐
相关产品推荐

