引言
射箭游戏是物理与策略结合的经典游戏类型:玩家拉弓射箭,箭矢在空中受重力影响形成抛物线轨迹,同时还需要考虑风力干扰。本文将用 Java 完整实现射箭游戏的核心算法系统,重点讲解抛物线轨迹预测模型与风力补偿贪心瞄准策略两大技术。
抛物线物理模型
运动学方程
箭矢射出后,在水平方向受初速度 vx 和风力 wind 影响,垂直方向受重力 g 影响。设发射角度为 θ,初速度为 v0,则:
- 水平初速度:
vx = v0 × cos(θ) - 垂直初速度:
vy = v0 × sin(θ) - 水平位移:
x(t) = (vx + wind) × t - 垂直位移:
y(t) = vy × t - 0.5 × g × t²
由 y(t) = 0 可解出飞行时间 t = 2 × vy / g,代入水平位移得射程公式:
射程:
R = (v0 × cos(θ) + wind) × 2 × v0 × sin(θ) / g
/**
* 箭矢物理实体
*/
public class Arrow {
private static final double GRAVITY = 9.8; // 重力加速度 m/s²
private static final double DT = 0.016; // 模拟时间步长(约60FPS)
private double x, y; // 位置(米)
private double vx, vy; // 速度分量
private boolean flying; // 是否飞行中
public Arrow(double startX, double startY) {
this.x = startX;
this.y = startY;
this.flying = false;
}
/**
* 发射箭矢
* @param angle 发射角度(弧度)
* @param power 拉弓力度(m/s)
* @param wind 水平风力(m/s,正为顺风)
*/
public void shoot(double angle, double power, double wind) {
this.vx = power * Math.cos(angle) + wind;
this.vy = power * Math.sin(angle);
this.flying = true;
}
/**
* 物理更新
*/
public void update() {
if (!flying) return;
x += vx * DT;
y += vy * DT;
vy -= GRAVITY * DT; // 重力减速
if (y <= 0) {
y = 0;
flying = false; // 落地
}
}
/**
* 预测落点:不模拟直接计算解析解
*/
public static double predictLanding(double startX, double startY,
double angle, double power, double wind) {
double vy0 = power * Math.sin(angle);
double vx0 = power * Math.cos(angle) + wind;
// 飞行时间:解方程 y0 + vy*t - 0.5*g*t² = 0
double discriminant = vy0 * vy0 + 2 * GRAVITY * startY;
if (discriminant < 0) return startX; // 不会落地(理论上不会发生)
double t = (vy0 + Math.sqrt(discriminant)) / GRAVITY;
return startX + vx0 * t;
}
// Getters...
public double getX() { return x; }
public double getY() { return y; }
public boolean isFlying() { return flying; }
}
风力补偿与瞄准策略
贪心最优角度计算
给定目标距离 targetX 和当前风力 wind,需要找到发射角度 θ 使得箭矢恰好命中目标。这是一个单变量非线性方程求根问题,可用二分搜索或牛顿迭代法求解。
/**
* 射箭 AI:贪心策略计算最优发射参数
*/
public class ArcheryAI {
private static final double GRAVITY = 9.8;
private static final double MAX_POWER = 80; // 最大拉弓力度
/**
* 二分搜索求解最佳发射角度
* @param startX 射手X坐标
* @param startY 射手Y坐标
* @param targetX 目标X坐标
* @param power 固定拉弓力度
* @param wind 风力
* @return 最佳发射角度(弧度),无解返回NaN
*/
public static double findOptimalAngle(double startX, double startY,
double targetX, double power, double wind) {
double distance = targetX - startX;
double low = 0.01; // 略高于0度
double high = Math.PI / 2 - 0.01; // 略低于90度
// 检查是否有解:最大射程是否能到达目标
double maxRange = Arrow.predictLanding(startX, startY, high, power, wind);
if (maxRange < distance) return Double.NaN;
// 二分搜索使落点尽可能接近目标
for (int i = 0; i < 50; i++) {
double mid = (low + high) / 2;
double landing = Arrow.predictLanding(startX, startY, mid, power, wind);
if (landing < distance) {
low = mid; // 角度太小,射程不够,增大角度
} else {
high = mid; // 角度太大,射程过远,减小角度
}
}
return (low + high) / 2;
}
/**
* 动态调整力度与角度的贪心策略
* 优先使用较小力度(更精准),力度不足时增大
*/
public static ShotParams computeGreedyShot(double startX, double startY,
double targetX, double wind) {
double bestAngle = Double.NaN;
double bestPower = MAX_POWER;
double minError = Double.MAX_VALUE;
// 贪心搜索:在力度和角度空间中寻找最优组合
for (double power = 30; power <= MAX_POWER; power += 2) {
double angle = findOptimalAngle(startX, startY, targetX, power, wind);
if (Double.isNaN(angle)) continue;
double landing = Arrow.predictLanding(startX, startY, angle, power, wind);
double error = Math.abs(landing - targetX);
// 贪心:优先选择误差小且力度小的方案
if (error < minError || (Math.abs(error - minError) < 0.1 && power < bestPower)) {
minError = error;
bestAngle = angle;
bestPower = power;
}
}
return new ShotParams(bestAngle, bestPower, minError);
}
/**
* 射击参数封装
*/
public static class ShotParams {
public final double angle; // 弧度
public final double power; // 力度
public final double error; // 预期误差(米)
public ShotParams(double angle, double power, double error) {
this.angle = angle;
this.power = power;
this.error = error;
}
@Override
public String toString() {
return String.format("角度=%.2f°, 力度=%.1f, 预期误差=%.3fm",
Math.toDegrees(angle), power, error);
}
}
}
完整游戏系统
import java.util.*;
/**
* 射箭游戏完整实现
*/
public class ArcheryGame {
private double wind; // 当前风力(随机变化)
private double targetX; // 靶子位置
private double targetRadius; // 靶子半径
private int score;
private Random random = new Random();
public ArcheryGame() {
this.targetRadius = 0.5; // 靶心半径0.5米
nextRound();
}
/**
* 生成新一轮目标
*/
public void nextRound() {
this.wind = (random.nextDouble() - 0.5) * 10; // -5 ~ +5 m/s
this.targetX = 30 + random.nextDouble() * 50; // 30-80米
System.out.printf("%n新一轮!目标距离: %.1f米, 风力: %.2f m/s%n", targetX, wind);
}
/**
* AI自动射击
*/
public void aiShoot() {
ArcheryAI.ShotParams params = ArcheryAI.computeGreedyShot(0, 1.5, targetX, wind);
if (Double.isNaN(params.angle)) {
System.out.println("目标超出最大射程,本轮跳过");
return;
}
System.out.println("AI决策: " + params);
// 模拟箭矢飞行
Arrow arrow = new Arrow(0, 1.5);
arrow.shoot(params.angle, params.power, wind);
// 逐步模拟直到落地
while (arrow.isFlying()) {
arrow.update();
}
// 计算得分
double distanceFromCenter = Math.abs(arrow.getX() - targetX);
int points = calculateScore(distanceFromCenter);
score += points;
System.out.printf("落点: %.2f米, 偏离靶心: %.2f米, 得分: %d, 总分: %d%n",
arrow.getX(), distanceFromCenter, points, score);
}
private int calculateScore(double deviation) {
if (deviation <= 0.1) return 10; // 十环
if (deviation <= 0.25) return 9;
if (deviation <= 0.4) return 8;
if (deviation <= targetRadius) return 7;
if (deviation <= targetRadius * 2) return 5;
return 0;
}
/**
* 玩家手动射击(简化版)
*/
public void playerShoot(double angleDeg, double power) {
double angle = Math.toRadians(angleDeg);
Arrow arrow = new Arrow(0, 1.5);
arrow.shoot(angle, power, wind);
while (arrow.isFlying()) arrow.update();
double deviation = Math.abs(arrow.getX() - targetX);
int points = calculateScore(deviation);
score += points;
System.out.printf("落点: %.2f米, 偏离: %.2f米, 得分: %d%n",
arrow.getX(), deviation, points);
}
public static void main(String[] args) {
ArcheryGame game = new ArcheryGame();
// AI演示10轮
for (int i = 0; i < 10; i++) {
game.aiShoot();
game.nextRound();
}
System.out.printf("%n最终得分: %d%n", game.score);
}
}
进阶:多目标追踪与动态修正
/**
* 移动靶追踪:考虑目标速度的提前量计算
*/
public class MovingTargetTracker {
/**
* 计算瞄准提前点
* @param targetX 目标当前位置
* @param targetVx 目标水平速度
* @param arrowSpeed 箭速
* @return 应瞄准的位置
*/
public static double computeLeadPoint(double targetX, double targetVx,
double arrowSpeed, double distance) {
// 估算箭矢飞行时间
double estimatedTime = distance / arrowSpeed;
// 目标在飞行时间内的位移
return targetX + targetVx * estimatedTime;
}
/**
* 迭代修正:考虑箭矢飞行过程中目标持续移动
*/
public static double iterativeLead(double shooterX, double shooterY,
double targetX, double targetY,
double targetVx, double targetVy,
double arrowSpeed) {
double aimX = targetX;
double aimY = targetY;
for (int i = 0; i < 5; i++) {
double dist = Math.hypot(aimX - shooterX, aimY - shooterY);
double flightTime = dist / arrowSpeed;
aimX = targetX + targetVx * flightTime;
aimY = targetY + targetVy * flightTime;
}
return aimX;
}
}
复杂度分析
| 模块 | 时间复杂度 | 说明 |
|---|---|---|
| 落点预测 | O(1) |
解析公式直接计算 |
| 角度二分搜索 | O(log precision) |
约50次迭代达到双精度 |
| 贪心力度搜索 | O((MAX-30)/2 × log precision) |
遍历力度空间 |
| 迭代修正 | O(iterations) |
通常3-5次收敛 |
总结
射箭游戏算法将经典力学与数值优化完美融合:解析公式实现 O(1) 落点预测,二分搜索在微秒级求解最优角度,贪心策略在力度-精度间取得平衡。这种”解析+数值”的混合方法在弹道计算、机器人投掷等工程领域有广泛应用。