引言
射箭游戏的核心挑战在于:给定目标距离、高度差与风速,计算最佳发射角度与力度。这本质上是一个物理抛物线问题,加入空气阻力与横风后演变为数值求解问题。本文用Java实现完整的射箭物理引擎与自动瞄准算法。
核心算法设计
1. 抛物线运动学模型
在无风理想条件下,箭的轨迹是标准抛物线。但考虑重力、空气阻力和风速后,需要使用数值积分方法模拟轨迹。
import java.util.*;
/**
* 二维向量类,用于表示位置、速度、力
*/
class Vector2D {
double x, y;
public Vector2D(double x, double y) { this.x = x; this.y = y; }
public Vector2D add(Vector2D v) { return new Vector2D(x + v.x, y + v.y); }
public Vector2D mul(double s) { return new Vector2D(x * s, y * s); }
public double magnitude() { return Math.sqrt(x * x + y * y); }
public Vector2D normalize() {
double m = magnitude();
if (m == 0) return new Vector2D(0, 0);
return new Vector2D(x / m, y / m);
}
@Override
public String toString() { return String.format("(%.2f, %.2f)", x, y); }
}
/**
* 箭矢物理对象
*/
class Arrow {
Vector2D position; // 位置
Vector2D velocity; // 速度
double mass; // 质量(kg)
double dragCoeff; // 阻力系数
double crossArea; // 截面积(m²)
public Arrow(Vector2D pos, Vector2D vel, double mass, double dragCoeff, double crossArea) {
this.position = pos;
this.velocity = vel;
this.mass = mass;
this.dragCoeff = dragCoeff;
this.crossArea = crossArea;
}
}
/**
* 环境参数
*/
class Environment {
double gravity = 9.8; // 重力加速度(m/s²)
double airDensity = 1.225; // 空气密度(kg/m³)
Vector2D wind; // 风速向量(m/s)
public Environment(Vector2D wind) {
this.wind = wind;
}
}
/**
* 物理引擎:使用欧拉法数值积分模拟箭矢轨迹
*/
class PhysicsEngine {
double timeStep = 0.001; // 积分步长(s)
/**
* 计算当前时刻箭矢受到的合力
* 包含:重力、空气阻力、风力
*/
public Vector2D computeForce(Arrow arrow, Environment env) {
// 重力:向下
Vector2D gravity = new Vector2D(0, -env.gravity * arrow.mass);
// 空气阻力:F = 0.5 * ρ * v² * Cd * A,方向与速度相反
Vector2D relVelocity = arrow.velocity.add(env.wind.mul(-1));
double v = relVelocity.magnitude();
double dragMagnitude = 0.5 * env.airDensity * v * v * arrow.dragCoeff * arrow.crossArea;
Vector2D drag = relVelocity.normalize().mul(-dragMagnitude);
return gravity.add(drag);
}
/**
* 模拟箭矢飞行轨迹,直到落地(y<=0)
* @return 轨迹点列表与最终落点
*/
public List<Vector2D> simulate(Arrow arrow, Environment env) {
List<Vector2D> trajectory = new ArrayList<>();
trajectory.add(new Vector2D(arrow.position.x, arrow.position.y));
while (arrow.position.y >= 0) {
Vector2D force = computeForce(arrow, env);
Vector2D acceleration = force.mul(1.0 / arrow.mass);
// 欧拉积分更新速度与位置
arrow.velocity = arrow.velocity.add(acceleration.mul(timeStep));
arrow.position = arrow.position.add(arrow.velocity.mul(timeStep));
trajectory.add(new Vector2D(arrow.position.x, arrow.position.y));
// 安全退出:飞行超过5秒或超出合理范围
if (trajectory.size() > 5000 || Math.abs(arrow.position.x) > 1000) break;
}
return trajectory;
}
}
2. 自动瞄准算法
给定目标位置,需要反推出最佳发射角度。由于空气阻力的存在,无法使用解析解,我们采用数值方法:先忽略阻力求解析初值,再用牛顿迭代法精修。
/**
* 自动瞄准系统
*/
class AutoAimSystem {
PhysicsEngine physics = new PhysicsEngine();
double initialSpeed = 50.0; // 初速度(m/s)
/**
* 计算击中目标所需的最佳发射角度
* @param targetX 目标水平距离(m)
* @param targetY 目标高度(m)
* @param env 环境参数
* @return 推荐发射角度(弧度)
*/
public double calculateAimAngle(double targetX, double targetY, Environment env) {
// 步骤1:用理想抛物线公式求初始估计
double angleGuess = idealAngleEstimate(targetX, targetY, initialSpeed, env.gravity);
// 步骤2:牛顿迭代法精修,考虑空气阻力与风速
double angle = newtonRefine(targetX, targetY, angleGuess, env);
return angle;
}
/**
* 理想抛物线角度估计(无阻力解析解)
* 由公式:y = x*tan(θ) - g*x²/(2*v²*cos²(θ))
* 转化为关于tan(θ)的二次方程求解
*/
private double idealAngleEstimate(double x, double y, double v, double g) {
double discriminant = v * v * v * v - g * (g * x * x + 2 * y * v * v);
if (discriminant < 0) return Math.PI / 4; // 无法到达,返回45度
double tanTheta1 = (v * v + Math.sqrt(discriminant)) / (g * x);
double tanTheta2 = (v * v - Math.sqrt(discriminant)) / (g * x);
// 选择较小的角度(更平坦的轨迹,受风影响小)
double angle1 = Math.atan(tanTheta1);
double angle2 = Math.atan(tanTheta2);
return Math.min(angle1, angle2);
}
/**
* 牛顿迭代法精修角度
* 目标:使落点与目标位置的误差趋于零
*/
private double newtonRefine(double targetX, double targetY, double guess, Environment env) {
double angle = guess;
double delta = 0.001; // 数值微分步长
for (int iter = 0; iter < 20; iter++) {
Vector2D impact = simulateImpact(angle, env);
double errorX = impact.x - targetX;
double errorY = impact.y - targetY;
double error = Math.sqrt(errorX * errorX + errorY * errorY);
if (error < 0.01) break; // 收敛
// 数值微分计算误差对角度的导数
Vector2D impactPlus = simulateImpact(angle + delta, env);
double dError = (Math.sqrt(
(impactPlus.x - targetX) * (impactPlus.x - targetX) +
(impactPlus.y - targetY) * (impactPlus.y - targetY)
) - error) / delta;
if (Math.abs(dError) < 1e-10) break;
angle = angle - error / dError; // 牛顿迭代步
}
return angle;
}
/**
* 模拟给定角度下的落点
*/
private Vector2D simulateImpact(double angle, Environment env) {
Vector2D startPos = new Vector2D(0, 1.5); // 假设射手身高1.5m
Vector2D startVel = new Vector2D(
initialSpeed * Math.cos(angle),
initialSpeed * Math.sin(angle)
);
Arrow arrow = new Arrow(startPos, startVel, 0.02, 0.3, 0.0003);
List<Vector2D> traj = physics.simulate(arrow, env);
return traj.get(traj.size() - 1);
}
}
3. 风速补偿策略
横风会导致箭矢偏离目标。我们通过预瞄偏移量来补偿。
/**
* 风速补偿计算器
*/
class WindCompensator {
PhysicsEngine physics = new PhysicsEngine();
double initialSpeed = 50.0;
/**
* 计算给定风速下的瞄准偏移量
* @param targetX 目标距离
* @param targetY 目标高度
* @param wind 风速向量
* @return 水平方向瞄准偏移量(m)
*/
public double calculateWindOffset(double targetX, double targetY, Vector2D wind) {
// 对比无风与有风两种情况的落点差异
Environment noWind = new Environment(new Vector2D(0, 0));
Environment withWind = new Environment(wind);
AutoAimSystem aim = new AutoAimSystem();
double angleNoWind = aim.calculateAimAngle(targetX, targetY, noWind);
double angleWithWind = aim.calculateAimAngle(targetX, targetY, withWind);
// 计算两种角度下无风轨迹的落点差异
Vector2D impactNoWind = simulateWithAngle(angleNoWind, noWind);
Vector2D impactWithWindAim = simulateWithAngle(angleWithWind, noWind);
return impactWithWindAim.x - impactNoWind.x;
}
private Vector2D simulateWithAngle(double angle, Environment env) {
Vector2D startPos = new Vector2D(0, 1.5);
Vector2D startVel = new Vector2D(
initialSpeed * Math.cos(angle),
initialSpeed * Math.sin(angle)
);
Arrow arrow = new Arrow(startPos, startVel, 0.02, 0.3, 0.0003);
List<Vector2D> traj = physics.simulate(arrow, env);
return traj.get(traj.size() - 1);
}
}
演示程序
public class ArcheryGameDemo {
public static void main(String[] args) {
// 场景:目标在80米外,高度1.2m,有3m/s的右侧横风
double targetX = 80.0;
double targetY = 1.2;
Vector2D wind = new Vector2D(3.0, 0); // 3m/s向右
Environment env = new Environment(wind);
AutoAimSystem aim = new AutoAimSystem();
double angle = aim.calculateAimAngle(targetX, targetY, env);
double degrees = Math.toDegrees(angle);
System.out.printf("目标距离: %.1fm, 高度: %.1fm\n", targetX, targetY);
System.out.printf("风速: %s\n", wind);
System.out.printf("推荐发射角度: %.2f度\n", degrees);
WindCompensator compensator = new WindCompensator();
double offset = compensator.calculateWindOffset(targetX, targetY, wind);
System.out.printf("风速补偿偏移: %.2fm\n", offset);
// 验证:模拟射击
PhysicsEngine physics = new PhysicsEngine();
Vector2D startPos = new Vector2D(0, 1.5);
Vector2D startVel = new Vector2D(
50.0 * Math.cos(angle),
50.0 * Math.sin(angle)
);
Arrow arrow = new Arrow(startPos, startVel, 0.02, 0.3, 0.0003);
List<Vector2D> traj = physics.simulate(arrow, env);
Vector2D impact = traj.get(traj.size() - 1);
System.out.printf("模拟落点: %s\n", impact);
System.out.printf("落点误差: %.3fm\n",
Math.sqrt((impact.x - targetX) * (impact.x - targetX) +
(impact.y - targetY) * (impact.y - targetY)));
}
}
复杂度分析
- 轨迹模拟:O(T/Δt),T为飞行时间,Δt为积分步长
- 角度迭代:O(k × T/Δt),k为牛顿迭代次数(通常<20)
- 整体精度:可通过减小步长提高,但计算量增加
总结
本文从运动学建模、数值积分、非线性方程求解三个层面构建射箭AI。核心思路是将物理问题转化为数值计算:用欧拉法积分轨迹,用牛顿迭代法反解最优角度。读者可进一步扩展三维场景、移动目标预测与多箭连发策略。