引言
太空侵略者是街机射击游戏鼻祖,核心机制为玩家操控飞船射击从上方阵列降落的入侵者。本文用Java实现太空侵略者的核心AI,重点讲解队列调度算法管理敌机编队阵型,以及弹幕轨迹预测算法让敌机子弹更具威胁性。
核心算法概览
- 敌机编队队列调度:入侵者按行列组成编队,以整体阵型左右平移并逐行下降
- 射击决策算法:计算与玩家的夹角,预测子弹飞行轨迹
- 动态难度调整:随敌机数量减少加快移动速度,维持游戏紧张感
敌机编队队列模型
将入侵者抽象为二维队列,每行同类型敌机,整体按队形规则移动。
import java.util.*;
public class InvaderFormation {
// 单个入侵者
static class Invader {
int row, col; // 编队中的行列位置
double x, y; // 屏幕实际坐标
int type; // 敌机类型(1=小型,2=中型,3=大型)
boolean alive;
long lastShotTime; // 上次射击时间
Invader(int row, int col, double x, double y, int type) {
this.row = row; this.col = col;
this.x = x; this.y = y;
this.type = type;
this.alive = true;
this.lastShotTime = 0;
}
}
private List<Invader> invaders;
private int rows = 5, cols = 11; // 标准5行11列编队
private double spacingX = 48, spacingY = 36; // 敌机间距
private double startX = 60, startY = 50; // 编队起始位置
private int direction = 1; // 1=右移,-1=左移
private double speed = 0.5; // 基础移动速度
private double dropDistance = 20; // 触边后下降距离
public InvaderFormation() {
invaders = new ArrayList<>();
initFormation();
}
/**
* 初始化标准编队阵型
* 第0行:大型敌机(3分)
* 第1-2行:中型敌机(2分)
* 第3-4行:小型敌机(1分)
*/
private void initFormation() {
for (int r = 0; r < rows; r++) {
int type = (r == 0) ? 3 : (r <= 2) ? 2 : 1;
for (int c = 0; c < cols; c++) {
double x = startX + c * spacingX;
double y = startY + r * spacingY;
invaders.add(new Invader(r, c, x, y, type));
}
}
}
/**
* 队列调度更新:整队左右平移,触边后下降换向
*/
public void updateFormation() {
boolean hitEdge = false;
// 先检测是否有存活敌机触边
for (Invader inv : invaders) {
if (!inv.alive) continue;
if ((direction == 1 && inv.x >= 750) ||
(direction == -1 && inv.x <= 10)) {
hitEdge = true;
break;
}
}
// 触边:全队下降并换向
if (hitEdge) {
direction *= -1;
for (Invader inv : invaders) {
if (inv.alive) inv.y += dropDistance;
}
} else {
// 正常平移:按存活数量动态加速
double currentSpeed = speed * speedMultiplier();
for (Invader inv : invaders) {
if (inv.alive) inv.x += direction * currentSpeed;
}
}
}
/**
* 动态速度倍率:存活越少,速度越快
* 公式:speedMult = 1 + (1 - aliveRatio) * 2
* 满编时1.0倍,剩1只时约3.0倍
*/
private double speedMultiplier() {
int aliveCount = 0;
for (Invader inv : invaders) if (inv.alive) aliveCount++;
double ratio = (double) aliveCount / invaders.size();
return 1.0 + (1.0 - ratio) * 2.0;
}
public List<Invader> getInvaders() { return invaders; }
}
弹幕轨迹预测算法
敌机发射的子弹需要预判玩家位置,使用简单的线性插值预测。
public class BulletTrajectory {
private double x, y; // 子弹当前位置
private double vx, vy; // 速度向量
private double speed = 3.0; // 子弹基础速度
/**
* 创建指向玩家当前位置的子弹(简单追踪)
* @param startX, startY 子弹起始坐标
* @param targetX, targetY 玩家当前坐标
*/
public BulletTrajectory(double startX, double startY,
double targetX, double targetY) {
this.x = startX;
this.y = startY;
double dx = targetX - startX;
double dy = targetY - startY;
double dist = Math.sqrt(dx * dx + dy * dy);
// 归一化方向向量并乘以速度
if (dist > 0) {
this.vx = (dx / dist) * speed;
this.vy = (dy / dist) * speed;
} else {
this.vx = 0; this.vy = speed;
}
}
/**
* 每帧更新子弹位置
*/
public void update() {
x += vx;
y += vy;
}
/**
* 预测N帧后的位置(用于碰撞预检和玩家预警)
*/
public double[] predictPosition(int frames) {
return new double[]{ x + vx * frames, y + vy * frames };
}
public boolean isOutOfScreen(double height) {
return y > height || y < 0 || x < 0 || x > 800;
}
public double getX() { return x; }
public double getY() { return y; }
}
射击决策系统
使用概率加权选择发射敌机,最下方的敌机有更高射击权重,避免被上方敌机遮挡。
import java.util.*;
public class ShootingSystem {
private Random random = new Random();
private long lastGlobalShot = 0;
private long minShotInterval = 800; // 全局最小射击间隔(ms)
/**
* 选择射击敌机并生成子弹
* 策略:仅最下方存活敌机可射击,按列随机选择
* @param formation 敌机编队
* @param playerX, playerY 玩家位置
* @param currentTime 当前时间戳
* @return 新生成的子弹,若无则返回null
*/
public EnemyBullet selectShooterAndFire(List<InvaderFormation.Invader> invaders,
double playerX, double playerY,
long currentTime) {
if (currentTime - lastGlobalShot < minShotInterval) return null;
// 找出每列最下方的存活敌机
Map<Integer, InvaderFormation.Invader> bottomInvaders = new HashMap<>();
for (InvaderFormation.Invader inv : invaders) {
if (!inv.alive) continue;
InvaderFormation.Invader existing = bottomInvaders.get(inv.col);
if (existing == null || inv.row > existing.row) {
bottomInvaders.put(inv.col, inv);
}
}
if (bottomInvaders.isEmpty()) return null;
// 随机选择一列进行射击
List<InvaderFormation.Invader> shooters = new ArrayList<>(bottomInvaders.values());
InvaderFormation.Invader shooter = shooters.get(random.nextInt(shooters.size()));
lastGlobalShot = currentTime;
// 生成带轨迹预测的子弹
return new EnemyBullet(shooter.x + 16, shooter.y + 16, playerX, playerY);
}
// 内部类:敌方子弹
static class EnemyBullet {
BulletTrajectory trajectory;
EnemyBullet(double startX, double startY, double targetX, double targetY) {
this.trajectory = new BulletTrajectory(startX, startY, targetX, targetY);
}
}
}
玩家飞船与碰撞检测
public class PlayerShip {
private double x, y; // 位置
private double speed = 5; // 移动速度
private int lives = 3; // 生命数
private double width = 32, height = 24; // 碰撞体积
public PlayerShip(double startX, double startY) {
this.x = startX; this.y = startY;
}
public void moveLeft() { x -= speed; clamp(); }
public void moveRight() { x += speed; clamp(); }
private void clamp() {
if (x < 0) x = 0;
if (x > 800 - width) x = 800 - width;
}
/**
* 圆形-矩形碰撞检测(子弹击中玩家)
*/
public boolean checkHit(double bulletX, double bulletY, double bulletRadius) {
// 找到矩形上离圆心最近的点
double closestX = Math.max(x, Math.min(bulletX, x + width));
double closestY = Math.max(y, Math.min(bulletY, y + height));
double dx = bulletX - closestX;
double dy = bulletY - closestY;
return (dx * dx + dy * dy) < (bulletRadius * bulletRadius);
}
public void loseLife() { lives--; }
public boolean isAlive() { return lives > 0; }
public double getX() { return x; }
public double getY() { return y; }
}
游戏主循环
public class SpaceInvadersGame {
private InvaderFormation formation;
private PlayerShip player;
private ShootingSystem shootingSystem;
private List<BulletTrajectory> enemyBullets;
private List<BulletTrajectory> playerBullets;
public SpaceInvadersGame() {
formation = new InvaderFormation();
player = new PlayerShip(400, 550);
shootingSystem = new ShootingSystem();
enemyBullets = new ArrayList<>();
playerBullets = new ArrayList<>();
}
/**
* 游戏主循环,每帧调用
*/
public void gameLoop(long currentTime) {
// 1. 更新敌机编队位置(队列调度)
formation.updateFormation();
// 2. 敌机射击决策
ShootingSystem.EnemyBullet newBullet = shootingSystem.selectShooterAndFire(
formation.getInvaders(), player.getX(), player.getY(), currentTime);
if (newBullet != null) {
enemyBullets.add(newBullet.trajectory);
}
// 3. 更新所有子弹位置
for (BulletTrajectory bt : enemyBullets) bt.update();
for (BulletTrajectory bt : playerBullets) bt.update();
// 4. 碰撞检测
checkCollisions();
// 5. 清理越界子弹
enemyBullets.removeIf(b -> b.isOutOfScreen(600));
playerBullets.removeIf(b -> b.isOutOfScreen(600));
}
private void checkCollisions() {
// 检测敌方子弹是否击中玩家
for (BulletTrajectory bt : enemyBullets) {
if (player.checkHit(bt.getX(), bt.getY(), 3)) {
player.loseLife();
}
}
// 检测玩家子弹是否击中敌机
for (BulletTrajectory pb : playerBullets) {
for (InvaderFormation.Invader inv : formation.getInvaders()) {
if (!inv.alive) continue;
if (pb.getX() >= inv.x && pb.getX() <= inv.x + 32 &&
pb.getY() >= inv.y && pb.getY() <= inv.y + 24) {
inv.alive = false;
}
}
}
}
public static void main(String[] args) {
SpaceInvadersGame game = new SpaceInvadersGame();
long startTime = System.currentTimeMillis();
// 模拟运行100帧
for (int i = 0; i < 100; i++) {
game.gameLoop(startTime + i * 16); // 假设60FPS,每帧16ms
}
System.out.println("Game simulation completed.");
}
}
复杂度分析
| 模块 | 时间复杂度 | 说明 |
|---|---|---|
| 编队更新 | O(n) | n为敌机总数,每帧遍历所有敌机更新坐标 |
| 射击选择 | O(n) | 遍历敌机找出每列最下方存活者 |
| 碰撞检测 | O(m×n) | m为子弹数,n为敌机数,实际中通过空间分割可优化 |
| 轨迹预测 | O(1) | 每颗子弹的预测为常数时间 |
总结
本文以太空侵略者为场景,讲解了队列调度管理敌机编队阵型、以及基于方向向量的弹幕轨迹预测。读者可扩展:
– 实现更智能的射击预测(考虑玩家移动速度,进行提前量计算)
– 加入敌机俯冲攻击模式,突破队列编队限制
– 使用对象池管理子弹,避免频繁创建销毁对象