引言
捕鱼达人是一款经典的街机休闲游戏,玩家通过控制炮台发射渔网来捕捉游动的鱼群。本文用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的完整射击决策链。