引言
太空侵略者(Space Invaders)是街机射击游戏的鼻祖。其核心算法包括:敌人编队的阵型移动、有限状态机驱动的行为决策、弹幕生成模式,以及玩家的自动躲避策略。本文用Java实现完整的敌人AI系统与智能躲避算法。
核心算法设计
1. 敌人有限状态机(FSM)
每个敌人有多个状态:巡逻、瞄准、射击、撤退。状态转移由游戏条件触发。
import java.util.*;
/**
* 敌人状态枚举
*/
enum EnemyState {
PATROL, // 巡逻:随编队左右移动
AIMING, // 瞄准:暂停移动,锁定玩家
SHOOTING, // 射击:发射弹幕
RETREAT, // 撤退:向下移动并加速
DIVE // 俯冲:脱离编队冲向玩家
}
/**
* 状态转移条件
*/
interface StateTransition {
boolean canTransition(EnemyAI enemy, GameContext ctx);
EnemyState targetState();
}
/**
* 游戏上下文:提供状态机决策所需的全局信息
*/
class GameContext {
double playerX, playerY; // 玩家位置
List<EnemyAI> allEnemies; // 所有敌人
int frameCount; // 当前帧数
double difficulty; // 难度系数(随关卡提升)
public GameContext(double px, double py, List<EnemyAI> enemies, int frame, double diff) {
this.playerX = px; this.playerY = py;
this.allEnemies = enemies;
this.frameCount = frame;
this.difficulty = diff;
}
}
/**
* 单个敌人的AI控制器
*/
class EnemyAI {
double x, y; // 位置
double speed; // 移动速度
EnemyState state; // 当前状态
int stateTimer; // 状态已持续帧数
double fireCooldown; // 射击冷却
boolean isAlive; // 是否存活
Random rand; // 随机数生成器
// 状态转移表:每个状态对应一组可转移的条件
Map<EnemyState, List<StateTransition>> transitions;
public EnemyAI(double x, double y, double speed, long seed) {
this.x = x; this.y = y;
this.speed = speed;
this.state = EnemyState.PATROL;
this.stateTimer = 0;
this.fireCooldown = 0;
this.isAlive = true;
this.rand = new Random(seed);
initTransitions();
}
private void initTransitions() {
transitions = new HashMap<>();
// PATROL -> AIMING:随机概率触发,难度越高概率越大
transitions.put(EnemyState.PATROL, Arrays.asList(
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return e.stateTimer > 30 && e.rand.nextDouble() < 0.02 * ctx.difficulty;
}
public EnemyState targetState() { return EnemyState.AIMING; }
},
// PATROL -> DIVE:玩家靠近时俯冲
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return Math.abs(e.x - ctx.playerX) < 30 && e.rand.nextDouble() < 0.1;
}
public EnemyState targetState() { return EnemyState.DIVE; }
}
));
// AIMING -> SHOOTING:瞄准足够时间后射击
transitions.put(EnemyState.AIMING, Arrays.asList(
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return e.stateTimer > 20 && Math.abs(e.x - ctx.playerX) < 50;
}
public EnemyState targetState() { return EnemyState.SHOOTING; }
},
// AIMING -> PATROL:玩家跑太远,放弃瞄准
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return e.stateTimer > 60;
}
public EnemyState targetState() { return EnemyState.PATROL; }
}
));
// SHOOTING -> RETREAT:射击后撤退
transitions.put(EnemyState.SHOOTING, Arrays.asList(
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return e.stateTimer > 10;
}
public EnemyState targetState() { return EnemyState.RETREAT; }
}
));
// RETREAT -> PATROL:撤退一段时间后恢复巡逻
transitions.put(EnemyState.RETREAT, Arrays.asList(
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return e.stateTimer > 40;
}
public EnemyState targetState() { return EnemyState.PATROL; }
}
));
// DIVE -> PATROL:俯冲到底部后恢复
transitions.put(EnemyState.DIVE, Arrays.asList(
new StateTransition() {
public boolean canTransition(EnemyAI e, GameContext ctx) {
return e.y < 100 || e.stateTimer > 100; // 到达底部或超时
}
public EnemyState targetState() { return EnemyState.PATROL; }
}
));
}
/**
* 每帧更新:状态转移 + 行为执行
*/
public void update(GameContext ctx) {
if (!isAlive) return;
stateTimer++;
fireCooldown = Math.max(0, fireCooldown - 1);
// 检查状态转移
List<StateTransition> possible = transitions.getOrDefault(state, Collections.emptyList());
for (StateTransition t : possible) {
if (t.canTransition(this, ctx)) {
state = t.targetState();
stateTimer = 0;
break;
}
}
// 执行当前状态的行为
executeStateBehavior(ctx);
}
private void executeStateBehavior(GameContext ctx) {
switch (state) {
case PATROL:
// 随编队左右移动,碰到边界向下
x += speed * Math.sin(ctx.frameCount * 0.02);
break;
case AIMING:
// 微调X位置对准玩家
if (x < ctx.playerX) x += speed * 0.5;
else if (x > ctx.playerX) x -= speed * 0.5;
break;
case SHOOTING:
// 发射弹幕
if (fireCooldown <= 0) {
fireBullet(ctx);
fireCooldown = 30 / ctx.difficulty;
}
break;
case RETREAT:
// 向上撤退并左右躲避
y += speed * 0.3;
x += speed * 0.5 * Math.sin(ctx.frameCount * 0.1);
break;
case DIVE:
// 俯冲向玩家位置
double dx = ctx.playerX - x;
double dy = ctx.playerY - y;
double dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
x += (dx / dist) * speed * 1.5;
y += (dy / dist) * speed * 1.5;
}
break;
}
}
private void fireBullet(GameContext ctx) {
// 通知游戏世界创建子弹
Bullet bullet = new Bullet(x, y - 10, 0, -3 * ctx.difficulty);
ctx.allEnemies.add(null); // 占位:实际应加入世界的子弹列表
System.out.printf("敌人在(%.0f, %.0f)发射子弹!\n", x, y);
}
}
2. 弹幕生成模式
敌人的射击不是随机的,而是遵循特定模式增加可玩性。
/**
* 弹幕对象
*/
class Bullet {
double x, y;
double vx, vy; // 速度向量
boolean active;
public Bullet(double x, double y, double vx, double vy) {
this.x = x; this.y = y;
this.vx = vx; this.vy = vy;
this.active = true;
}
public void update() {
x += vx;
y += vy;
if (y < 0 || y > 600 || x < 0 || x > 800) active = false;
}
}
/**
* 弹幕模式生成器
*/
class BulletPatternGenerator {
Random rand = new Random();
/**
* 生成扇形散射弹幕
*/
public List<Bullet> generateFan(double x, double y, int count, double baseAngle, double spread) {
List<Bullet> bullets = new ArrayList<>();
double speed = 4.0;
for (int i = 0; i < count; i++) {
double angle = baseAngle - spread / 2 + (spread / (count - 1)) * i;
bullets.add(new Bullet(x, y,
speed * Math.cos(angle),
speed * Math.sin(angle)));
}
return bullets;
}
/**
* 生成追踪弹幕(带轻微追踪效果)
*/
public List<Bullet> generateHoming(double x, double y, double playerX, double playerY) {
List<Bullet> bullets = new ArrayList<>();
double dx = playerX - x;
double dy = playerY - y;
double dist = Math.sqrt(dx * dx + dy * dy);
double speed = 3.5;
if (dist > 0) {
// 添加随机偏移,使追踪不是100%准确
double noiseX = rand.nextDouble() * 2 - 1;
double noiseY = rand.nextDouble() * 2 - 1;
bullets.add(new Bullet(x, y,
(dx / dist) * speed + noiseX,
(dy / dist) * speed + noiseY));
}
return bullets;
}
/**
* 生成环形弹幕
*/
public List<Bullet> generateRing(double x, double y, int count) {
List<Bullet> bullets = new ArrayList<>();
double speed = 3.0;
for (int i = 0; i < count; i++) {
double angle = 2 * Math.PI * i / count;
bullets.add(new Bullet(x, y,
speed * Math.cos(angle),
speed * Math.sin(angle)));
}
return bullets;
}
}
3. 玩家自动躲避算法
AI玩家需要分析弹幕轨迹,找到安全位置。
/**
* 玩家自动躲避AI
*/
class DodgeAI {
double playerX, playerY;
double playerWidth = 30;
double moveSpeed = 5.0;
/**
* 计算最佳躲避方向
* @param bullets 所有活跃弹幕
* @return 建议移动方向(-1左, 0停, 1右)
*/
public int calculateDodgeDirection(List<Bullet> bullets) {
// 方法:对每个候选位置(左/停/右)评估危险度,选择最安全的
double[] dangerScores = new double[3]; // 左, 停, 右
double[] candidateX = {playerX - moveSpeed, playerX, playerX + moveSpeed};
for (int i = 0; i < 3; i++) {
dangerScores[i] = evaluateDanger(candidateX[i], playerY, bullets);
}
// 选择危险度最低的方向
int bestDir = 1; // 默认不动
double minDanger = dangerScores[1];
if (dangerScores[0] < minDanger) { minDanger = dangerScores[0]; bestDir = -1; }
if (dangerScores[2] < minDanger) { bestDir = 1; }
return bestDir;
}
/**
* 评估某位置的未来危险度
* 核心思想:模拟未来几帧,统计会进入玩家碰撞盒的弹幕数量
*/
private double evaluateDanger(double testX, double testY, List<Bullet> bullets) {
double danger = 0.0;
int predictionFrames = 30; // 预测未来30帧
for (Bullet b : bullets) {
if (!b.active) continue;
Bullet sim = new Bullet(b.x, b.y, b.vx, b.vy);
for (int f = 0; f < predictionFrames; f++) {
sim.update();
// 检查是否进入玩家碰撞区域
if (Math.abs(sim.x - testX) < playerWidth &&
Math.abs(sim.y - testY) < playerWidth) {
// 越近的弹幕危险度越高
danger += 10.0 / (f + 1);
break;
}
}
}
return danger;
}
/**
* 高级躲避:同时考虑射击窗口(在安全前提下尽量对准敌人)
*/
public int calculateDodgeAndAim(List<Bullet> bullets, List<EnemyAI> enemies) {
double[] scores = new double[3];
double[] candidateX = {playerX - moveSpeed, playerX, playerX + moveSpeed};
for (int i = 0; i < 3; i++) {
double danger = evaluateDanger(candidateX[i], playerY, bullets);
double aimBonus = calculateAimBonus(candidateX[i], enemies);
scores[i] = aimBonus - danger * 2; // 安全优先,危险权重更高
}
int bestDir = 1;
double bestScore = scores[1];
if (scores[0] > bestScore) { bestScore = scores[0]; bestDir = -1; }
if (scores[2] > bestScore) { bestDir = 1; }
return bestDir;
}
private double calculateAimBonus(double x, List<EnemyAI> enemies) {
double bonus = 0;
for (EnemyAI e : enemies) {
if (!e.isAlive) continue;
double dist = Math.abs(e.x - x);
if (dist < 20) bonus += 5; // 正对敌人,射击加分
else bonus += 1.0 / (dist + 1);
}
return bonus;
}
}
演示程序
public class SpaceInvadersDemo {
public static void main(String[] args) {
// 创建5个敌人
List<EnemyAI> enemies = new ArrayList<>();
for (int i = 0; i < 5; i++) {
enemies.add(new EnemyAI(100 + i * 80, 400, 2.0, i));
}
BulletPatternGenerator patternGen = new BulletPatternGenerator();
List<Bullet> bullets = new ArrayList<>();
DodgeAI dodgeAI = new DodgeAI();
dodgeAI.playerX = 250;
dodgeAI.playerY = 50;
System.out.println("=== 太空侵略者AI模拟 ===");
for (int frame = 0; frame < 100; frame++) {
GameContext ctx = new GameContext(dodgeAI.playerX, dodgeAI.playerY, enemies, frame, 1.0);
// 更新所有敌人
for (EnemyAI e : enemies) {
e.update(ctx);
}
// 随机生成弹幕(模拟敌人射击)
if (frame % 20 == 0 && !enemies.isEmpty()) {
EnemyAI shooter = enemies.get(frame / 20 % enemies.size());
if (shooter.state == EnemyState.SHOOTING) {
bullets.addAll(patternGen.generateFan(shooter.x, shooter.y, 3, -Math.PI/2, 0.5));
}
}
// 更新弹幕位置
for (Bullet b : bullets) b.update();
bullets.removeIf(b -> !b.active);
// AI躲避决策
int dir = dodgeAI.calculateDodgeDirection(bullets);
dodgeAI.playerX += dir * dodgeAI.moveSpeed;
dodgeAI.playerX = Math.max(20, Math.min(480, dodgeAI.playerX));
if (frame % 10 == 0) {
System.out.printf("帧%3d: 玩家(%.0f, %.0f), 弹幕数:%d, 敌人状态:",
frame, dodgeAI.playerX, dodgeAI.playerY, bullets.size());
for (EnemyAI e : enemies) {
System.out.print(e.state + " ");
}
System.out.println();
}
}
}
}
复杂度分析
- FSM状态转移:O(t)每敌人每帧,t为转移条件数
- 弹幕更新:O(b)每帧,b为弹幕数量
- 躲避评估:O(b × f)每帧,b为弹幕数,f为预测帧数
- 整体:可控制在O(b × f + n × t),n为敌人数量
总结
本文从有限状态机、弹幕模式、躲避策略三个角度构建太空侵略者AI。FSM赋予敌人多样化的战术行为;模式化弹幕增加游戏节奏感;预测性躲避算法让AI玩家展现出类人的反应。读者可在此基础上扩展更复杂的编队行为、Boss多阶段状态机与弹幕编织算法。