每日算法 — 使用java实现捕鱼达人:抛物线射击角度计算与物理模拟

引言

捕鱼达人是一款经典的街机休闲游戏,玩家通过控制炮台发射渔网来捕捉游动的鱼群。本文用Java实现捕鱼达人的核心射击系统,重点讲解抛物线轨迹的几何建模、角度预测算法以及物理碰撞检测,提供完整的项目代码。

游戏机制与数学建模

坐标系与运动模型

游戏场景采用二维平面坐标系。鱼的位置随时间变化:

fish_x(t) = fish_x0 + vx * t
fish_y(t) = fish_y0 + vy * t

炮台位于屏幕底部中央 (originX, originY),发射的渔网呈抛物线运动:

net_x(t) = originX + v0 * cos(theta) * t
net_y(t) = originY + v0 * sin(theta) * t - 0.5 * g * t^2

其中 theta 为发射角度,v0 为初速度,g 为重力加速度。

最优角度计算:解析法

给定目标鱼的当前位置 (tx, ty) 和速度 (vx, vy),我们需要计算炮台的发射角度 theta,使渔网在飞行时间 t 后命中鱼。

联立方程:

tx + vx * t = originX + v0 * cos(theta) * t
ty + vy * t = originY + v0 * sin(theta) * t - 0.5 * g * t^2
public class BallisticCalculator {
    private static final double G = 9.8; // 重力加速度

    /**
     * 计算命中移动目标的最优发射角度
     * @param origin 炮台位置
     * @param target 目标当前位置
     * @param targetVel 目标速度
     * @param v0 炮弹初速度
     * @return 发射角度(弧度),无解返回Double.NaN
     */
    public double calculateAngle(Point2D origin, Point2D target, 
                                  Point2D targetVel, double v0) {
        // 相对运动:将目标速度分解到炮弹坐标系
        double dx = target.getX() - origin.getX();
        double dy = target.getY() - origin.getY();
        double vx = targetVel.getX();
        double vy = targetVel.getY();

        // 迭代求解飞行时间t(二分搜索)
        double tLow = 0.1, tHigh = 10.0;
        double bestAngle = Double.NaN;
        double minError = Double.MAX_VALUE;

        for (int iter = 0; iter < 50; iter++) {
            double t = (tLow + tHigh) / 2;
            double rx = dx + vx * t;
            double ry = dy + vy * t + 0.5 * G * t * t; // 补偿重力下落

            double dist = Math.sqrt(rx * rx + ry * ry);
            double requiredV = dist / t;

            if (requiredV < v0) {
                tHigh = t;
                double angle = Math.atan2(ry, rx);
                double error = Math.abs(requiredV - v0);
                if (error < minError) {
                    minError = error;
                    bestAngle = angle;
                }
            } else {
                tLow = t;
            }
        }

        return bestAngle;
    }

    /**
     * 预测渔网与鱼的碰撞点
     */
    public Point2D predictCollision(Point2D origin, Point2D fishPos, 
                                     Point2D fishVel, double angle, double v0) {
        double vx_net = v0 * Math.cos(angle);
        double vy_net = v0 * Math.sin(angle);

        // 相对位置方程求解
        double dx = fishPos.getX() - origin.getX();
        double dy = fishPos.getY() - origin.getY();
        double vx_rel = fishVel.getX() - vx_net;
        double vy_rel = fishVel.getY() - vy_net;

        // 近似求解碰撞时间(忽略重力对碰撞检测的微小影响)
        double a = vx_rel * vx_rel + vy_rel * vy_rel;
        double b = 2 * (dx * vx_rel + dy * vy_rel);
        double c = dx * dx + dy * dy;

        double discriminant = b * b - 4 * a * c;
        if (discriminant < 0) return null;

        double t = (-b - Math.sqrt(discriminant)) / (2 * a);
        if (t < 0) t = (-b + Math.sqrt(discriminant)) / (2 * a);
        if (t < 0) return null;

        return new Point2D.Double(
            fishPos.getX() + fishVel.getX() * t,
            fishPos.getY() + fishVel.getY() * t
        );
    }
}

碰撞检测与捕获判定

渔网到达目标区域后,需要检测与鱼的碰撞。我们使用圆形碰撞盒简化:

public class CollisionDetector {
    /**
     * 圆形碰撞检测
     * @param netCenter 渔网中心
     * @param netRadius 渔网半径
     * @param fishCenter 鱼中心
     * @param fishRadius 鱼半径
     * @return 是否发生碰撞
     */
    public boolean checkCollision(Point2D netCenter, double netRadius,
                                   Point2D fishCenter, double fishRadius) {
        double dx = netCenter.getX() - fishCenter.getX();
        double dy = netCenter.getY() - fishCenter.getY();
        double distance = Math.sqrt(dx * dx + dy * dy);
        return distance < (netRadius + fishRadius);
    }

    /**
     * 计算捕获概率:距离越近概率越高
     */
    public double captureProbability(Point2D netCenter, Point2D fishCenter,
                                      double effectiveRadius) {
        double dx = netCenter.getX() - fishCenter.getX();
        double dy = netCenter.getY() - fishCenter.getY();
        double dist = Math.sqrt(dx * dx + dy * dy);
        if (dist > effectiveRadius) return 0.0;
        return 1.0 - (dist / effectiveRadius);
    }
}

多目标最优射击策略

当屏幕上有多个鱼时,AI需要选择最优射击目标:

public class TargetSelector {
    /**
     * 贪心选择最优射击目标:综合考虑捕获概率、鱼的价值和弹药消耗
     */
    public Fish selectBestTarget(List<Fish> fishes, Point2D origin, 
                                  double v0, double ammoCost) {
        Fish bestTarget = null;
        double bestScore = Double.NEGATIVE_INFINITY;
        BallisticCalculator calc = new BallisticCalculator();

        for (Fish fish : fishes) {
            double angle = calc.calculateAngle(origin, fish.getPosition(), 
                                                fish.getVelocity(), v0);
            if (Double.isNaN(angle)) continue; // 无法命中

            Point2D collision = calc.predictCollision(origin, fish.getPosition(),
                                                       fish.getVelocity(), angle, v0);
            if (collision == null) continue;

            double prob = new CollisionDetector().captureProbability(
                collision, fish.getPosition(), fish.getRadius() * 2);

            // 评分 = 期望收益 - 成本
            double expectedValue = fish.getValue() * prob;
            double score = expectedValue - ammoCost;

            if (score > bestScore) {
                bestScore = score;
                bestTarget = fish;
            }
        }

        return bestTarget;
    }
}

完整项目结构

fishing-master/
├── src/
│   ├── model/
│   │   ├── Fish.java            # 鱼类定义(位置、速度、价值、半径)
│   │   ├── Cannon.java          # 炮台定义
│   │   └── Net.java             # 渔网定义
│   ├── physics/
│   │   ├── BallisticCalculator.java # 弹道计算
│   │   └── CollisionDetector.java   # 碰撞检测
│   ├── strategy/
│   │   └── TargetSelector.java      # 目标选择策略
│   └── game/
│       └── FishingGame.java     # 游戏主循环
└── Main.java

复杂度分析

模块 时间复杂度 说明
角度计算 O(log T) 二分搜索求解飞行时间
碰撞检测 O(1) 圆形距离计算
多目标选择 O(N log T) N为鱼的数量,每个目标做一次角度计算

总结

本文通过Java实现了捕鱼达人的核心射击系统。抛物线弹道计算利用解析几何与二分搜索求解最优发射角度,碰撞检测采用圆形包围盒简化物理模型,多目标选择策略基于期望值最大化进行贪心决策。这些算法组合构成了捕鱼游戏AI的完整射击决策链。