引言
太空侵略者(Space Invaders)是1978年推出的街机射击游戏,被誉为电子游戏史上的里程碑。玩家控制飞船在屏幕底部移动,射击从上方阵型中逐渐下降的敌人。本文将用Java实现太空侵略者的核心算法,重点讲解敌人阵型的波浪移动模式、AABB碰撞检测系统,以及敌方射击AI的概率触发策略。
问题建模
太空侵略者可抽象为以下算法组件:
- 敌人阵型:M行N列的敌人矩阵,每个敌人有固定间距
- 波浪移动:阵型整体左右移动,触边后下降一层并反向
- 碰撞检测:玩家子弹与敌人、敌人子弹与玩家的命中判定
- 射击AI:敌人何时射击、由哪个敌人射击
- 难度曲线:随着敌人数量减少,移动速度加快
核心算法设计
1. 阵型波浪移动
敌人以矩阵形式排列,整体移动逻辑:
– 所有敌人共享一个基础位置偏移量(formation offset)
– 每帧更新偏移量,使阵型左右移动
– 当最左/最右敌人触边时,偏移量下移并水平反向
– 移动速度 = 基础速度 × 存活敌人数量倒数(越少越快)
2. AABB碰撞检测
所有游戏对象使用轴对齐包围盒(AABB)表示:
– 碰撞判定:两个AABB在X轴和Y轴上的投影均重叠
– 公式:(a.x < b.x + b.w) && (a.x + a.w > b.x) && (a.y < b.y + b.h) && (a.y + a.h > b.y)
3. 射击AI
敌人射击策略:
– 概率触发:每帧每个底部敌人有概率发射子弹
– 底部优先:只有最底部的敌人才有射击权限(避免自伤)
– 难度调整:存活敌人越少,射击频率越高
Java完整实现
import java.util.*;
/**
* 太空侵略者:阵型波浪移动与AABB碰撞检测
*/
public class SpaceInvadersAI {
// 游戏参数
private static final int SCREEN_WIDTH = 800;
private static final int SCREEN_HEIGHT = 600;
private static final int ENEMY_ROWS = 5;
private static final int ENEMY_COLS = 11;
private static final int ENEMY_WIDTH = 40;
private static final int ENEMY_HEIGHT = 30;
private static final int ENEMY_SPACING_X = 50;
private static final int ENEMY_SPACING_Y = 40;
private static final int PLAYER_WIDTH = 50;
private static final int PLAYER_HEIGHT = 30;
private static final int BULLET_WIDTH = 4;
private static final int BULLET_HEIGHT = 10;
private static final int BUNKER_WIDTH = 60;
private static final int BUNKER_HEIGHT = 40;
private static final int BASE_SPEED = 2;
private static final int DROP_DISTANCE = 20;
private Random random;
private int score;
private int lives;
private boolean gameOver;
// 阵型状态
private double formationX; // 阵型左上角X
private double formationY; // 阵型左上角Y
private int direction; // 1=右, -1=左
public SpaceInvadersAI() {
this.random = new Random();
this.score = 0;
this.lives = 3;
this.gameOver = false;
this.formationX = 50;
this.formationY = 50;
this.direction = 1;
initializeGame();
}
/**
* AABB包围盒
*/
static class AABB {
double x, y, w, h;
AABB(double x, double y, double w, double h) {
this.x = x; this.y = y; this.w = w; this.h = h;
}
/**
* AABB碰撞检测核心算法
*/
boolean intersects(AABB other) {
return this.x < other.x + other.w &&
this.x + this.w > other.x &&
this.y < other.y + other.h &&
this.y + this.h > other.y;
}
boolean isOffScreen() {
return y + h < 0 || y > SCREEN_HEIGHT || x + w < 0 || x > SCREEN_WIDTH;
}
}
/**
* 敌人
*/
static class Enemy {
int row, col; // 在阵型中的行列位置
boolean alive; // 是否存活
int type; // 敌人类型(不同分数)
AABB bbox; // 包围盒
Enemy(int row, int col, int type) {
this.row = row;
this.col = col;
this.alive = true;
this.type = type;
this.bbox = new AABB(0, 0, ENEMY_WIDTH, ENEMY_HEIGHT);
}
void updatePosition(double formX, double formY) {
bbox.x = formX + col * ENEMY_SPACING_X;
bbox.y = formY + row * ENEMY_SPACING_Y;
}
}
/**
* 子弹
*/
static class Bullet {
double x, y;
double speedY;
boolean fromPlayer; // true=玩家子弹, false=敌人子弹
AABB bbox;
boolean active;
Bullet(double x, double y, double speedY, boolean fromPlayer) {
this.x = x; this.y = y;
this.speedY = speedY;
this.fromPlayer = fromPlayer;
this.bbox = new AABB(x, y, BULLET_WIDTH, BULLET_HEIGHT);
this.active = true;
}
void update() {
y += speedY;
bbox.x = x;
bbox.y = y;
if (bbox.isOffScreen()) active = false;
}
}
/**
* 玩家飞船
*/
static class Player {
double x, y;
double speed;
AABB bbox;
int cooldown; // 射击冷却
Player() {
this.x = SCREEN_WIDTH / 2.0;
this.y = SCREEN_HEIGHT - 50;
this.speed = 5;
this.bbox = new AABB(x, y, PLAYER_WIDTH, PLAYER_HEIGHT);
this.cooldown = 0;
}
void moveLeft() { x = Math.max(0, x - speed); }
void moveRight() { x = Math.min(SCREEN_WIDTH - PLAYER_WIDTH, x + speed); }
void update() {
bbox.x = x;
bbox.y = y;
if (cooldown > 0) cooldown--;
}
Bullet shoot() {
if (cooldown > 0) return null;
cooldown = 15;
return new Bullet(x + PLAYER_WIDTH/2, y, -8, true);
}
}
/**
* 掩体(防御工事)
*/
static class Bunker {
AABB bbox;
int health;
Bunker(double x, double y) {
this.bbox = new AABB(x, y, BUNKER_WIDTH, BUNKER_HEIGHT);
this.health = 4;
}
}
// 游戏对象集合
private Enemy[][] enemies;
private List<Bullet> bullets;
private Player player;
private List<Bunker> bunkers;
void initializeGame() {
enemies = new Enemy[ENEMY_ROWS][ENEMY_COLS];
for (int r = 0; r < ENEMY_ROWS; r++) {
for (int c = 0; c < ENEMY_COLS; c++) {
int type = (r == 0) ? 3 : (r <= 2) ? 2 : 1; // 越上面分数越高
enemies[r][c] = new Enemy(r, c, type);
}
}
bullets = new ArrayList<>();
player = new Player();
bunkers = new ArrayList<>();
for (int i = 0; i < 4; i++) {
bunkers.add(new Bunker(100 + i * 200, SCREEN_HEIGHT - 120));
}
}
/**
* 获取存活敌人数量
*/
int getAliveCount() {
int count = 0;
for (int r = 0; r < ENEMY_ROWS; r++) {
for (int c = 0; c < ENEMY_COLS; c++) {
if (enemies[r][c].alive) count++;
}
}
return count;
}
/**
* 获取阵型边界
*/
int getLeftmostCol() {
for (int c = 0; c < ENEMY_COLS; c++) {
for (int r = 0; r < ENEMY_ROWS; r++) {
if (enemies[r][c].alive) return c;
}
}
return 0;
}
int getRightmostCol() {
for (int c = ENEMY_COLS - 1; c >= 0; c--) {
for (int r = 0; r < ENEMY_ROWS; r++) {
if (enemies[r][c].alive) return c;
}
}
return ENEMY_COLS - 1;
}
int getBottommostRow(int col) {
for (int r = ENEMY_ROWS - 1; r >= 0; r--) {
if (enemies[r][col].alive) return r;
}
return -1;
}
/**
* 更新阵型位置(波浪移动核心算法)
*/
void updateFormation() {
int alive = getAliveCount();
if (alive == 0) {
gameOver = true;
return;
}
// 速度随敌人减少而增加
double speed = BASE_SPEED + (ENEMY_ROWS * ENEMY_COLS - alive) * 0.3;
// 水平移动
formationX += direction * speed;
// 检测是否触边
int leftCol = getLeftmostCol();
int rightCol = getRightmostCol();
double leftEdge = formationX + leftCol * ENEMY_SPACING_X;
double rightEdge = formationX + rightCol * ENEMY_SPACING_X + ENEMY_WIDTH;
if (direction > 0 && rightEdge >= SCREEN_WIDTH - 20) {
direction = -1;
formationY += DROP_DISTANCE;
} else if (direction < 0 && leftEdge <= 20) {
direction = 1;
formationY += DROP_DISTANCE;
}
// 更新所有敌人位置
for (int r = 0; r < ENEMY_ROWS; r++) {
for (int c = 0; c < ENEMY_COLS; c++) {
enemies[r][c].updatePosition(formationX, formationY);
}
}
// 检查是否到达底部(游戏结束)
for (int r = 0; r < ENEMY_ROWS; r++) {
for (int c = 0; c < ENEMY_COLS; c++) {
if (enemies[r][c].alive && enemies[r][c].bbox.y + ENEMY_HEIGHT >= player.bbox.y) {
gameOver = true;
return;
}
}
}
}
/**
* 敌人射击AI:概率触发,底部敌人优先
*/
void enemyShoot() {
int alive = getAliveCount();
double shootProb = 0.005 + (55 - alive) * 0.0002; // 基础概率+难度加成
for (int c = 0; c < ENEMY_COLS; c++) {
int bottomRow = getBottommostRow(c);
if (bottomRow >= 0 && enemies[bottomRow][c].alive) {
if (random.nextDouble() < shootProb) {
Enemy e = enemies[bottomRow][c];
bullets.add(new Bullet(e.bbox.x + ENEMY_WIDTH/2, e.bbox.y + ENEMY_HEIGHT, 3, false));
}
}
}
}
/**
* 碰撞检测与处理
*/
void handleCollisions() {
Iterator<Bullet> it = bullets.iterator();
while (it.hasNext()) {
Bullet b = it.next();
if (!b.active) continue;
if (b.fromPlayer) {
// 玩家子弹打敌人
for (int r = 0; r < ENEMY_ROWS; r++) {
for (int c = 0; c < ENEMY_COLS; c++) {
Enemy e = enemies[r][c];
if (e.alive && b.bbox.intersects(e.bbox)) {
e.alive = false;
b.active = false;
score += e.type * 10;
break;
}
}
if (!b.active) break;
}
// 玩家子弹打掩体
if (b.active) {
for (Bunker bunker : bunkers) {
if (bunker.health > 0 && b.bbox.intersects(bunker.bbox)) {
bunker.health--;
b.active = false;
break;
}
}
}
} else {
// 敌人子弹打玩家
if (b.bbox.intersects(player.bbox)) {
b.active = false;
lives--;
if (lives <= 0) gameOver = true;
}
// 敌人子弹打掩体
if (b.active) {
for (Bunker bunker : bunkers) {
if (bunker.health > 0 && b.bbox.intersects(bunker.bbox)) {
bunker.health--;
b.active = false;
break;
}
}
}
}
}
bullets.removeIf(b -> !b.active);
}
/**
* AI玩家策略:左右移动瞄准最近敌人,适时射击
*/
void updatePlayerAI() {
// 找到最近的存活敌人
Enemy nearest = null;
double minDist = Double.MAX_VALUE;
for (int r = 0; r < ENEMY_ROWS; r++) {
for (int c = 0; c < ENEMY_COLS; c++) {
Enemy e = enemies[r][c];
if (e.alive) {
double dx = e.bbox.x - player.x;
double dy = e.bbox.y - player.y;
double dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
nearest = e;
}
}
}
}
if (nearest != null) {
double targetX = nearest.bbox.x;
// 向目标移动
if (Math.abs(player.x - targetX) > 5) {
if (player.x < targetX) player.moveRight();
else player.moveLeft();
}
}
// 射击
Bullet b = player.shoot();
if (b != null) bullets.add(b);
}
/**
* 主游戏循环
*/
void gameLoop(int maxFrames) {
System.out.println("=== 太空侵略者模拟开始 ===");
for (int frame = 0; frame < maxFrames && !gameOver; frame++) {
// 更新阵型
updateFormation();
// 敌人射击
enemyShoot();
// AI玩家操作
updatePlayerAI();
// 更新子弹
for (Bullet b : bullets) b.update();
bullets.removeIf(b -> !b.active);
// 碰撞检测
handleCollisions();
// 更新玩家
player.update();
// 每30帧输出状态
if (frame % 30 == 0) {
System.out.printf("Frame %d: Score=%d, Lives=%d, Enemies=%d%n",
frame, score, lives, getAliveCount());
}
}
System.out.println("=== 模拟结束 ===");
System.out.println("最终得分: " + score);
System.out.println("剩余生命: " + lives);
System.out.println("存活敌人: " + getAliveCount());
}
public static void main(String[] args) {
SpaceInvadersAI game = new SpaceInvadersAI();
game.gameLoop(5000);
}
}
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 阵型更新 | O(M × N) | O(1) | M行N列敌人 |
| AABB碰撞检测 | O(B × E) | O(1) | B为子弹数,E为敌人数 |
| 敌人射击AI | O(N) | O(1) | 每列检查底部敌人 |
| 玩家AI瞄准 | O(M × N) | O(1) | 寻找最近敌人 |
对于标准55个敌人的阵型,每帧处理在可接受范围内。
算法优化方向
- 空间哈希:将屏幕划分为网格,加速碰撞检测从O(n²)降到O(n)
- 预测射击:AI预判敌人移动轨迹,提前射击
- 群体行为:敌人添加蜂拥、散开等群体AI行为
- Boss模式:大型敌人的多部位AABB检测与弱点系统
总结
太空侵略者是学习游戏AI与碰撞检测的经典案例。波浪移动算法展示了如何用简单的偏移量控制群体行为,AABB碰撞检测是2D游戏开发的基础技能,而概率射击AI则提供了入门级的行为决策模型。通过Java实现,我们掌握了从游戏循环设计到空间碰撞检测的完整开发流程。