
本教程详解如何在processing游戏中实现子弹与敌人的碰撞检测、敌人销毁及得分更新,通过状态标记法安全移除对象,避免遍历集合时的并发修改问题。
在基于Processing的2D射击游戏中,实现“子弹击中即消灭敌人并加分”是核心交互逻辑。但直接在checkCollision()中删除Enemy对象极易引发运行时错误(如ConcurrentModificationException),尤其当敌人存储在ArrayList<Enemy>中并在主循环中遍历渲染时。正确做法是分离“检测”与“清理”逻辑:先标记敌人状态,再统一处理。
✅ 正确实现步骤
1. 为 Enemy 类添加存活状态字段
class Enemy {
int x, y;
float size; // 推荐用 size 表示半径或直径,比直接用 x/y 更语义清晰
boolean alive = true; // 关键:默认存活
Enemy(int x, int y, float size) {
this.x = x;
this.y = y;
this.size = size;
}
}2. 修正 Bullet.checkCollision():仅标记,不删除
void checkCollision(Enemy e) {
float distance = dist(bulletX, bulletY, e.x, e.y);
// ✅ 修正:使用 e.size 作为碰撞半径(假设 size 是直径,则半径为 size/2)
if (distance < e.size / 2) {
e.alive = false; // 标记为死亡,不在此处移除!
score++; // 同步增加玩家得分(需确保 score 是 Bullet 实例变量或全局变量)
}
}⚠️ 注意:原代码中 distance < e.x / 2 是错误的——e.x 是横坐标,不是尺寸!务必改用 e.size(或 e.radius)。
3. 在主游戏循环中统一清理与渲染
// 假设 enemies 是 ArrayList<Enemy>,bullets 是 ArrayList<Bullet>
void draw() {
// 更新并检查所有子弹与所有敌人碰撞
for (Bullet b : bullets) {
b.update();
for (Enemy e : enemies) {
b.checkCollision(e); // 此处只标记 e.alive = false
}
}
// 渲染:只画存活的敌人和子弹
for (Enemy e : enemies) {
if (e.alive) {
image(EnemyImg, e.x, e.y);
}
}
for (Bullet b : bullets) {
b.show();
}
// 【关键】清理死亡敌人(倒序遍历,避免索引错位)
for (int i = enemies.size() - 1; i >= 0; i--) {
if (!enemies.get(i).alive) {
enemies.remove(i); // 安全移除
}
}
// 可选:清理飞出屏幕的子弹
for (int i = bullets.size() - 1; i >= 0; i--) {
if (bullets.get(i).offScreen()) {
bullets.remove(i);
}
}
}? 进阶建议
- 性能优化:若敌人数量多,可引入空间分区(如四叉树)减少每帧碰撞检测次数。
- 视觉反馈:在 e.alive = false 后播放爆炸动画或音效,再延迟移除(需额外计时器)。
- 得分同步:若 score 是全局变量,建议封装为 GameStats 类管理,提升可维护性。
通过“标记-清除”两阶段设计,你既能保证逻辑清晰、线程安全(单线程下无竞态),又能避免因误删对象导致的崩溃。这是游戏开发中处理动态对象生命周期的经典范式。










