ECS实体移除问题求助:游戏循环删实体触发并发修改异常
关于ECS中安全移除实体的解决方案
嘿,很高兴看到你在尝试ECS架构——这玩意儿上手确实有不少小坑,特别是实体生命周期这块儿!你用toRemove标记的思路完全是正确的方向,直接在遍历实体列表时删除元素确实会触发ConcurrentModificationException,因为Java的普通迭代器不允许在遍历过程中修改集合结构。下面给你完善几种可行的实现方案:
方案一:标记+统一阶段清理(最常用)
核心思路是:所有系统只遍历未标记的实体,在游戏循环的固定阶段统一清理标记的实体,这样就完全避免了遍历和修改的冲突。
示例代码:
首先给实体类添加标记属性:
public class Entity { private boolean markedForRemoval = false; public void markForRemoval() { this.markedForRemoval = true; } public boolean isMarkedForRemoval() { return markedForRemoval; } }
然后调整游戏主循环的流程,把清理步骤放在所有系统更新之后:
// 游戏主循环 while (gameRunning) { // 1. 处理输入系统 inputSystem.update(); // 2. 运行所有逻辑系统(AI、物理、碰撞等) for (GameSystem system : systems) { // 过滤掉标记要删除的实体,传递给系统处理 List<Entity> activeEntities = entities.stream() .filter(e -> !e.isMarkedForRemoval()) .collect(Collectors.toList()); system.update(activeEntities); } // 3. 运行渲染系统(同样只处理活跃实体) drawingSystem.update(entities.stream() .filter(e -> !e.isMarkedForRemoval()) .collect(Collectors.toList())); // 4. 统一清理标记的实体 entities.removeIf(Entity::isMarkedForRemoval); }
方案二:延迟删除队列(更灵活)
如果需要在删除实体前执行一些额外逻辑(比如播放死亡动画、释放组件资源),可以维护一个专门的删除队列,把要移除的实体先加入队列,在清理阶段批量处理:
示例代码:
// 在ECS管理器中维护删除队列 private Queue<Entity> entitiesToRemove = new LinkedList<>(); // 对外提供标记删除的方法 public void scheduleEntityRemoval(Entity entity) { if (!entitiesToRemove.contains(entity)) { // 执行销毁前的钩子方法(比如实体的死亡回调) entity.onDestroy(); entitiesToRemove.add(entity); } } // 清理阶段的处理 private void cleanupEntities() { while (!entitiesToRemove.isEmpty()) { Entity entity = entitiesToRemove.poll(); // 从实体列表中移除 entities.remove(entity); // 清理实体关联的组件资源(比如纹理、音效) for (Component component : entity.getComponents()) { component.dispose(); } } }
然后在游戏主循环的清理步骤调用cleanupEntities()即可。
关键注意点
- 绝对不要让任何系统在遍历实体列表时直接调用
remove()方法,所有删除操作必须通过标记或队列延迟处理。 - 如果你的ECS中有依赖实体的组件(比如引用其他实体的组件),清理时要注意解除引用,避免内存泄漏。
- 很多成熟的ECS框架(比如Artemis-ODB)也是用类似的延迟删除机制,你的思路完全符合行业实践,继续完善就行!
内容的提问来源于stack exchange,提问作者JohnnyGat
相关产品推荐
相关产品推荐

