You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判断Bevy引擎中关卡生成命令的完成时机?

如何判断Bevy引擎中关卡生成命令的完成时机?

我懂你这种纠结!用Bevy做小行星这类游戏的关卡生成时,最头疼的就是明明发了生成命令,却不知道什么时候实体真的落地,不敢随便启动游戏逻辑,怕胜负条件误触发。结合你的场景,给你几个实用的方案,都是我自己做项目时试过的:

方案一:临时标记组件+查询验证

这是最直观的方法,给所有关卡生成的实体加个临时标记,等Bevy完成生成后,通过查询这个标记来判断时机:

  1. 先定义一个临时的标记组件,不需要任何数据,只是用来标识“这是当前关卡正在生成的实体”:
#[derive(Component)]
struct LevelSpawnInProgress;
  1. 在你的setup_new_level系统里,给新生成的飞船和小行星都挂上这个标记:
fn setup_new_level(
    mut start_level_event_reader: EventReader<StartLevelEvent>,
    mut commands: Commands,
) {
    for start_level_event in start_level_event_reader.read() {
        // 给飞船加标记
        commands.spawn((new_ship, LevelSpawnInProgress));
        // 给每个小行星也加标记
        for _ in 1..start_level_event.level {
            commands.spawn((new_asteroid_bundle(), LevelSpawnInProgress));
        }
    }
}
  1. 写一个专门的检查系统,用来检测这些标记实体是否都已生成完成:
// 假设你有一个管理游戏状态的资源,比如区分“生成中”“游戏中”
#[derive(Resource, Default)]
struct GameState {
    current: GameStateEnum,
}

#[derive(Clone, Eq, PartialEq, Debug)]
enum GameStateEnum {
    Spawning,
    Playing,
    // 其他状态比如菜单、游戏结束
}

fn check_level_spawn_complete(
    mut commands: Commands,
    spawned_entities: Query<Entity, With<LevelSpawnInProgress>>,
    mut game_state: ResMut<GameState>,
) {
    // 只要查询到带标记的实体,就说明Bevy已经完成了它们的生成
    if !spawned_entities.is_empty() {
        // 移除所有临时标记,避免影响后续逻辑
        for entity in spawned_entities.iter() {
            commands.entity(entity).remove::<LevelSpawnInProgress>();
        }
        // 切换到游戏状态,启动胜负判断等核心逻辑
        game_state.current = GameStateEnum::Playing;
    }
}

原理很简单:Bevy的命令队列会在当前帧的系统执行结束后批量处理,所以下一个帧的查询系统能拿到这些带标记的实体,就意味着所有生成操作都完成了。

方案二:状态机+实体数量验证

如果不想加临时组件,也可以用Bevy的状态系统配合实体查询来做:

  1. 先定义游戏的状态枚举:
#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
    #[default]
    MainMenu,
    Spawning,
    Playing,
    GameOver,
}
  1. 在发送StartLevelEvent后,先把游戏状态切换到Spawning,然后写一个只在这个状态下运行的检查系统:
// 假设你有一个资源存储当前关卡需要的小行星数量
#[derive(Resource, Default)]
struct CurrentLevel(u32);

fn check_level_ready(
    mut next_state: ResMut<NextState<GameState>>,
    ship_query: Query<Entity, With<Ship>>,
    asteroid_query: Query<Entity, With<Asteroid>>,
    current_level: Res<CurrentLevel>,
) {
    // 验证两个条件:飞船存在,且小行星数量符合当前关卡要求
    if ship_query.get_single().is_ok() && asteroid_query.iter().count() == current_level.0 as usize {
        // 满足条件就切换到游戏状态
        next_state.set(GameState::Playing);
    }
}
  1. 把这个检查系统添加到Spawning状态的Update阶段后期,让它每帧自动检查,直到条件满足就启动游戏。

方案三:用命令队列的延迟执行标记

Bevy的commands.add()方法可以让你在当前帧的所有命令执行完成后,再运行一段逻辑,我们可以用这个来做生成完成的标记:

  1. 定义一个用来标记生成完成的资源:
#[derive(Resource, Default)]
struct LevelSpawnComplete(bool);
  1. 在setup_new_level系统里,添加一个延迟执行的命令来设置标记:
fn setup_new_level(
    mut start_level_event_reader: EventReader<StartLevelEvent>,
    mut commands: Commands,
    mut current_level: ResMut<CurrentLevel>,
) {
    for start_level_event in start_level_event_reader.read() {
        current_level.0 = start_level_event.level;
        commands.spawn(new_ship);
        for _ in 1..start_level_event.level {
            commands.spawn(new_asteroid_bundle());
        }
        // 添加一个延迟执行的逻辑,在所有spawn命令完成后设置标记
        commands.add(|world: &mut World| {
            world.resource_mut::<LevelSpawnComplete>().0 = true;
        });
    }
}
  1. 写一个系统检测这个标记,切换游戏状态:
fn start_gameplay_when_ready(
    mut level_complete: ResMut<LevelSpawnComplete>,
    mut next_state: ResMut<NextState<GameState>>,
) {
    if level_complete.0 {
        next_state.set(GameState::Playing);
        // 重置标记,方便下一次关卡生成使用
        level_complete.0 = false;
    }
}

这个方案的优势是不需要额外的组件,直接利用Bevy命令队列的执行顺序来判断时机。

额外小提示

不管用哪种方案,都强烈建议用Bevy的状态机来管理游戏的不同阶段:把胜负条件检测、玩家输入这类核心逻辑,只在Playing状态下运行,这样即使生成阶段有短暂的实体缺失,也不会触发误判。另外你原代码里的事件处理loop其实没问题,EventReader的read()方法会自动处理所有未读取的事件,哪怕只有一个事件,用for循环也完全合理,不用特意改。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:45:29