变更检测

可参考的官方例子: component_change_detection.


Bevy 允许你轻松地检测数据何时发生了改变。你可以用它来执行响应变更的行为。

组件

使用查询过滤器

  • Added<T>: 检测新组件实例
    • 如果该组件被添加到一个现有的实体中
    • 如果一个带有该组件的新实体被创建出来
  • Changed<T>: 检测被变更的组件实例
    • 当组件被写访问时触发
    • 如果组件是新添加的,也会触发(同 Added)。

(如果你想对移除组件作出反应,请参阅移除检测页面。它的工作方式不同,使用起来也更麻烦)。

/// Print the stats of friendly players when they change
fn debug_stats_change(
    query: Query<
        // components
        (&Health, &PlayerXp),
        // filters
        (Without<Enemy>, Or<(Changed<Health>, Changed<PlayerXp>)>),
    >,
) {
    for (health, xp) in query.iter() {
        eprintln!(
            "hp: {}+{}, xp: {}",
            health.hp, health.extra, xp.0
        );
    }
}

/// detect new enemies and print their health
fn debug_new_hostiles(
    query: Query<(Entity, &Health), Added<Enemy>>,
) {
    for (entity, health) in query.iter() {
        eprintln!("Entity {:?} is now an enemy! HP: {}", entity, health.hp);
    }
}

Changed 检测是由 DerefMut 触发的。仅仅通过可变查询访问组件,而不实际执行 &mut 访问,将不会触发它。

这使得变更检测相当准确。你可以依靠它来优化你的游戏性能,或者故意来触发变更事件的发生。

还要注意的是,当你变更一个组件时,Bevy 并不跟踪新值是否真的与旧值不同。它总是会触发变更检测。如果你想避免这种情况,只需自己判断数据前后是否一致:

fn update_player_xp(
    mut query: Query<&mut PlayerXp>,
) {
    for mut xp in query.iter_mut() {
        let new_xp = maybe_lvl_up(&xp);

        // avoid triggering change detection if the value is the same
        if new_xp != *xp {
            *xp = new_xp;
        }
    }
}

变更检测是可靠的 -- 它将检测自你的检测系统上一次运行以来发生的所有变更。如果你的系统只是偶尔运行(如状态执行条件),你不必担心错过变化。

资源

对于资源,变更检测是通过 Res/ResMut 系统参数的方法提供的。

fn check_res_changed(
    my_res: Res<MyResource>,
) {
    if my_res.is_changed() {
        // do something
    }
}

fn check_res_added(
    // use Option, not to panic if the resource doesn't exist yet
    my_res: Option<Res<MyResource>>,
) {
    if let Some(my_res) = my_res {
        // the resource exists

        if my_res.is_added() {
            // it was just added
            // do something
        }
    }
}

这与组件的变化检测的工作方式相同。

请注意,变化检测目前不能用于检测状态变化(即通过 State 资源)(bug)。

可能的陷阱

注意帧延迟和滞后一帧的问题。如果 Bevy 在变更系统之前运行了检测系统,就会发生这种情况。检测系统将只能在下次运行时看到变化,通常是在下一帧更新时。

如果你需要确保变化被立即(在同一帧中)处理,你可以使用显式的系统排序。

然而,当用 Added<T> 来检测组件的添加时(通常是用 Commands 来完成),这还不够,你需要使用阶段