引言
拳击是最古老的竞技运动之一,其核心在于出拳时机、角度选择与防守策略。本文将用Java实现一个简化版拳击模拟系统,涵盖物理碰撞检测、打击力计算与基于决策树的AI对手。读者将理解如何将刚体物理、AABB碰撞与决策树融合到游戏AI中。
一、物理模型设计
1.1 拳击手状态定义
拳击手在二维平面内移动,状态包括位置、速度、体力与姿态:
public class Boxer {
// 位置与速度
public double x, y;
public double vx, vy;
// 身体属性
public double mass; // 质量(kg)
public double reach; // 臂展(m)
public double stamina; // 体力 0-100
public double health; // 生命值 0-100
// 姿态枚举
public enum Stance { GUARD, JAB, HOOK, UPPERCUT, DODGE_LEFT, DODGE_RIGHT }
public Stance currentStance = Stance.GUARD;
// 攻击冷却
public int attackCooldown = 0;
public Boxer(double x, double y, double mass, double reach) {
this.x = x; this.y = y;
this.mass = mass;
this.reach = reach;
this.stamina = 100;
this.health = 100;
}
}
1.2 打击碰撞检测
每次出拳生成一个AABB碰撞框,检测是否与对手躯干或头部相交:
public class Punch {
public double x, y, width, height;
public double damage; // 基础伤害
public double forceX, forceY; // 冲击力向量
public PunchType type;
public enum PunchType { JAB, CROSS, HOOK, UPPERCUT }
public boolean intersects(Boxer opponent) {
// 对手受击区域: 以对手位置为中心的区域
double oppLeft = opponent.x - 0.3;
double oppRight = opponent.x + 0.3;
double oppBottom = opponent.y;
double oppTop = opponent.y + 1.8;
return x < oppRight && x + width > oppLeft &&
y < oppTop && y + height > oppBottom;
}
}
1.3 冲量与动量守恒
击中时根据动量定理计算受击位移与伤害衰减:
public class PhysicsEngine {
private static final double FIST_MASS = 0.5; // 拳头质量约0.5kg
/**
* 计算打击后的动量交换
* @param punch 打击信息
* @param target 被击中的拳击手
* @return 实际造成的伤害值
*/
public static double applyPunch(Punch punch, Boxer target) {
// 打击速度 v = sqrt(2*E/m),假设单次打击能量与体力相关
double punchVelocity = Math.sqrt(2 * punch.damage / FIST_MASS);
// 动量 p = m*v
double momentum = FIST_MASS * punchVelocity;
// 受击后目标速度变化 delta_v = p / M_target
double deltaVX = punch.forceX * momentum / target.mass;
double deltaVY = punch.forceY * momentum / target.mass;
target.vx += deltaVX * 0.1; // 衰减系数
target.vy += deltaVY * 0.1;
// 伤害计算: 考虑防守姿态减伤
double damageMultiplier = getDamageMultiplier(target.currentStance, punch.type);
double actualDamage = punch.damage * damageMultiplier;
target.health -= actualDamage;
target.stamina -= actualDamage * 0.3; // 受击消耗体力
return actualDamage;
}
private static double getDamageMultiplier(Boxer.Stance stance, Punch.PunchType type) {
switch (stance) {
case GUARD: return 0.4; // 格挡大幅减伤
case DODGE_LEFT:
case DODGE_RIGHT:
// 闪避对直拳效果好,对勾拳效果差
if (type == Punch.PunchType.JAB || type == Punch.PunchType.CROSS) return 0.1;
return 0.8;
default: return 1.0;
}
}
}
二、决策树AI设计
2.1 决策树节点结构
AI根据当前局势选择最优动作。决策树采用行为树变体,每层评估不同维度:
public interface DecisionNode {
Boxer.Stance evaluate(Boxer self, Boxer opponent, double distance);
}
/**
* 复合节点: 按优先级顺序评估子节点
*/
public class PrioritySelector implements DecisionNode {
private final List<DecisionNode> children = new ArrayList<>();
public void addChild(DecisionNode child) { children.add(child); }
@Override
public Boxer.Stance evaluate(Boxer self, Boxer opponent, double distance) {
for (DecisionNode child : children) {
Boxer.Stance result = child.evaluate(self, opponent, distance);
if (result != null) return result;
}
return Boxer.Stance.GUARD;
}
}
2.2 具体决策节点实现
/**
* 血量危急时优先防守
*/
public class HealthCheckNode implements DecisionNode {
@Override
public Boxer.Stance evaluate(Boxer self, Boxer opponent, double distance) {
if (self.health < 30 && self.stamina > 20) {
// 随机左右闪避
return Math.random() > 0.5 ? Boxer.Stance.DODGE_LEFT : Boxer.Stance.DODGE_RIGHT;
}
return null; // 不满足条件,继续评估
}
}
/**
* 距离内出拳决策
*/
public class AttackNode implements DecisionNode {
@Override
public Boxer.Stance evaluate(Boxer self, Boxer opponent, double distance) {
if (distance > self.reach * 1.2) return null; // 距离太远
if (self.attackCooldown > 0) return null; // 冷却中
if (self.stamina < 15) return null; // 体力不足
double rand = Math.random();
// 根据对手姿态选择最优打击方式
if (opponent.currentStance == Boxer.Stance.GUARD) {
// 对手格挡时,优先勾拳/上勾拳破防
return rand > 0.5 ? Boxer.Stance.HOOK : Boxer.Stance.UPPERCUT;
} else if (opponent.currentStance == Boxer.Stance.DODGE_LEFT) {
// 对手左闪,打右侧勾拳
return Boxer.Stance.HOOK;
} else {
// 直拳试探
return Boxer.Stance.JAB;
}
}
}
/**
* 拉开距离的移动策略
*/
public class MovementNode implements DecisionNode {
@Override
public Boxer.Stance evaluate(Boxer self, Boxer opponent, double distance) {
if (self.stamina < 20) {
// 体力不足,保持防守距离
return Boxer.Stance.GUARD;
}
if (distance < 0.5) {
// 太近,后撤
self.vx = (self.x > opponent.x) ? 0.5 : -0.5;
}
return null;
}
}
2.3 AI决策树组装
public class BoxingAI {
private final DecisionNode root;
public BoxingAI() {
PrioritySelector selector = new PrioritySelector();
selector.addChild(new HealthCheckNode()); // 优先级1: 保命
selector.addChild(new AttackNode()); // 优先级2: 攻击
selector.addChild(new MovementNode()); // 优先级3: 移动
this.root = selector;
}
/**
* 每帧调用,更新AI拳击手的姿态
*/
public void update(Boxer ai, Boxer player) {
double distance = Math.abs(ai.x - player.x);
Boxer.Stance newStance = root.evaluate(ai, player, distance);
ai.currentStance = newStance != null ? newStance : Boxer.Stance.GUARD;
// 根据姿态生成打击
if (isAttackStance(ai.currentStance) && ai.attackCooldown == 0) {
createPunch(ai, player);
ai.attackCooldown = getCooldown(ai.currentStance);
}
if (ai.attackCooldown > 0) ai.attackCooldown--;
}
private boolean isAttackStance(Boxer.Stance s) {
return s == Boxer.Stance.JAB || s == Boxer.Stance.HOOK || s == Boxer.Stance.UPPERCUT;
}
private int getCooldown(Boxer.Stance s) {
switch (s) {
case JAB: return 10;
case HOOK: return 20;
case UPPERCUT: return 25;
default: return 0;
}
}
private void createPunch(Boxer ai, Boxer player) {
Punch punch = new Punch();
punch.x = ai.x + (ai.x < player.x ? ai.reach : -ai.reach);
punch.y = ai.y + 1.2; // 击打高度
punch.width = 0.3;
punch.height = 0.3;
punch.damage = 8 + Math.random() * 7; // 8-15点伤害
punch.forceX = (player.x - ai.x) > 0 ? 1 : -1;
punch.forceY = 0.2;
if (punch.intersects(player)) {
double actual = PhysicsEngine.applyPunch(punch, player);
System.out.printf("AI击中! 造成 %.1f 点伤害,玩家剩余血量: %.1f%n", actual, player.health);
}
}
}
三、主循环与渲染框架
public class BoxingGame {
private final Boxer player;
private final Boxer ai;
private final BoxingAI aiController;
public BoxingGame() {
player = new Boxer(2.0, 0, 85, 1.8);
ai = new Boxer(8.0, 0, 90, 1.9);
aiController = new BoxingAI();
}
/**
* 单步模拟,可扩展为带UI的完整游戏
*/
public void step() {
// 更新位置 (简单的欧拉积分)
player.x += player.vx * 0.016;
player.y += player.vy * 0.016;
ai.x += ai.vx * 0.016;
ai.y += ai.vy * 0.016;
// 速度衰减 (摩擦力)
player.vx *= 0.9; player.vy *= 0.9;
ai.vx *= 0.9; ai.vy *= 0.9;
// 边界限制
player.x = Math.max(0, Math.min(10, player.x));
ai.x = Math.max(0, Math.min(10, ai.x));
// AI决策
aiController.update(ai, player);
// 体力自然恢复
player.stamina = Math.min(100, player.stamina + 0.1);
ai.stamina = Math.min(100, ai.stamina + 0.1);
}
public boolean isOver() {
return player.health <= 0 || ai.health <= 0;
}
public static void main(String[] args) {
BoxingGame game = new BoxingGame();
int round = 0;
while (!game.isOver() && round < 10000) {
game.step();
round++;
if (round % 100 == 0) {
System.out.printf("Round %d: Player HP=%.1f Stamina=%.1f | AI HP=%.1f Stamina=%.1f%n",
round/100, game.player.health, game.player.stamina,
game.ai.health, game.ai.stamina);
}
}
System.out.println(game.player.health > game.ai.health ? "玩家胜利!" : "AI胜利!");
}
}
四、算法复杂度分析
| 模块 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 碰撞检测 | O(1) | O(1) | 单次AABB相交判断为常数时间 |
| 决策树评估 | O(k) | O(1) | k为决策树节点数,通常<10 |
| 单帧模拟 | O(1) | O(1) | 每帧固定计算量 |
| 整体对局 | O(n) | O(1) | n为总帧数,与对局时长成正比 |
五、扩展方向
- 神经网络评估: 用多层感知机替代决策树,通过自对弈训练更智能的AI
- Monte Carlo模拟: 模拟未来多步打击结果,选择期望收益最大的动作
- 物理精细化: 引入旋转、重心偏移,实现更真实的击倒判定
总结
本文通过Java实现了一个完整的拳击物理模拟与AI决策系统。核心算法包括基于AABB的碰撞检测、动量守恒的打击力学计算,以及基于优先级决策树的AI策略选择。读者可以在此基础上扩展出完整的拳击游戏,并尝试用更高级的AI算法(如Minimax、MCTS)替代决策树,提升对手的智能水平。