You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:07:06