ECS 进阶

概述

学习目标

  • 掌握变更检测(Change Detection)的使用方法
  • 理解组件生命周期钩子(Component Hooks)的应用场景
  • 学会使用关系系统(Relationships)建立实体间的关系
  • 掌握并行查询(Parallel Queries)的性能优化技巧
  • 理解查询组合(Query Combinations)的使用方法
  • 学会使用观察者模式(Observers)响应组件变化
  • 了解消息系统、错误处理、动态 ECS 等高级功能

前置知识要求

  • 核心编程框架(ECS)
  • 组件(Components)
  • 实体(Entities)
  • 系统(Systems)
  • 查询(Queries)
  • 资源(Resources)
  • 系统调度(Schedule & App)

核心概念

为什么需要高级 ECS 功能?

在复杂的游戏开发中,我们需要:

  1. 性能优化:通过并行查询提高系统执行效率
  2. 变更响应:检测组件和资源的变化,及时响应
  3. 关系管理:建立和维护实体间的复杂关系
  4. 生命周期管理:在组件的生命周期关键点执行逻辑
  5. 事件驱动:使用消息系统和观察者模式实现事件驱动逻辑

ECS 进阶功能概览

  • 变更检测:检测组件和资源的变化
  • 组件生命周期钩子:在组件添加、插入、替换、移除时执行逻辑
  • 关系系统:建立自定义的实体关系
  • 并行查询:使用并行迭代器提高性能
  • 查询组合:处理实体间的交互
  • 观察者模式:响应组件生命周期事件和自定义事件
  • 消息系统:使用消息实现系统间通信
  • 错误处理:处理系统执行中的错误
  • 动态 ECS:动态创建组件和实体

基础用法

变更检测(Change Detection)

变更检测用于检测组件和资源的变化,是响应式编程的基础。

源代码文件bevy/examples/ecs/change_detection.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
use bevy::prelude::*;

#[derive(Component, PartialEq, Debug)]
struct MyComponent(f32);

#[derive(Resource, PartialEq, Debug)]
struct MyResource(f32);

fn change_component(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
for (entity, mut component) in &mut query {
if rand::rng().random_bool(0.1) {
let new_component = MyComponent(time.elapsed_secs().round());
info!("New value: {new_component:?} {entity}");
// 变更检测发生在可变解引用时,不考虑值是否实际相等
// 为了避免在值未实际改变时触发变更检测,可以使用 `set_if_neq` 方法
// 该方法要求组件实现 PartialEq
component.set_if_neq(new_component);
}
}
}

fn change_detection(
changed_components: Query<Ref<MyComponent>, Changed<MyComponent>>,
my_resource: Res<MyResource>,
) {
for component in &changed_components {
// 默认情况下,你只能知道组件被改变了
// 但如果有多个系统修改同一个组件,如何知道是哪个系统导致的改变?
warn!(
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
component,
component.is_added(),
component.is_changed(),
// 如果启用 `track_location` 特性,可以解锁 `changed_by()` 方法
// 它返回组件或资源被改变的文件和行号
// 不建议在发布的游戏中使用,但对调试很有用!
component.changed_by()
);
}

if my_resource.is_changed() {
warn!(
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
my_resource,
my_resource.is_added(),
my_resource.is_changed(),
my_resource.changed_by() // 与组件一样,需要 `track_location` 特性
);
}
}

关键要点

  • 使用 Changed<T> 过滤器查询已变更的组件
  • 使用 Added<T> 过滤器查询新添加的组件
  • 使用 Ref<T> 系统参数访问变更检测信息,但不过滤查询
  • 使用 set_if_neq() 方法避免不必要的变更检测
  • 使用 is_changed()is_added() 检查变更状态
  • 使用 changed_by() 方法(需要 track_location 特性)获取变更位置

说明
变更检测是 Bevy ECS 的核心功能之一。当组件或资源被修改时,Bevy 会自动跟踪这些变更。使用 Changed<T> 过滤器可以只查询已变更的组件,这对于性能优化很重要。set_if_neq() 方法可以避免在值未实际改变时触发变更检测,这对于实现 PartialEq 的组件很有用。

组件生命周期钩子(Component Hooks)

组件生命周期钩子允许在组件的生命周期关键点执行逻辑。

源代码文件bevy/examples/ecs/component_hooks.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use bevy::{
ecs::component::{Mutable, StorageType},
ecs::lifecycle::{ComponentHook, HookContext},
prelude::*,
};
use std::collections::HashMap;

#[derive(Debug)]
struct MyComponent(KeyCode);

impl Component for MyComponent {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;

fn on_add() -> Option<ComponentHook> {
// 如果没有 on_add 钩子,返回 None
// 注意这是不实现钩子时的默认行为
None
}
}

fn setup(world: &mut World) {
// 为了注册组件钩子,组件必须:
// - 当前未被世界中的任何实体使用
// - 尚未注册该类型的钩子
// 这是为了防止覆盖插件和其他 crate 中定义的钩子,并保持性能
world
.register_component_hooks::<MyComponent>()
// 有 4 种组件生命周期钩子:`on_add`、`on_insert`、`on_replace` 和 `on_remove`
// 钩子有 2 个参数:
// - 一个 `DeferredWorld`,允许访问资源和组件数据以及 `Commands`
// - 一个 `HookContext`,提供以下上下文信息:
// - 触发钩子的实体
// - 触发组件的组件 ID,主要用于动态组件
// - 导致钩子触发的代码位置
//
// `on_add` 将在组件插入到没有该组件的实体上时触发
.on_add(
|mut world,
HookContext {
entity,
component_id,
caller,
..
}| {
// 可以在钩子内访问组件数据
let value = world.get::<MyComponent>(entity).unwrap().0;
println!(
"{component_id:?} added to {entity} with value {value:?}{}",
caller
.map(|location| format!("due to {location}"))
.unwrap_or_default()
);
// 或访问资源
world
.resource_mut::<MyComponentIndex>()
.insert(value, entity);
},
)
// `on_insert` 将在组件插入到实体上时触发,无论实体是否已有该组件
// 如果 `on_add` 运行了,则在 `on_add` 之后运行
.on_insert(|world, _| {
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
})
// `on_replace` 将在组件插入到已有该组件的实体上时触发
// 在值被替换之前运行
// 当组件从实体移除时也会触发,在 `on_remove` 之前运行
.on_replace(|mut world, context| {
let value = world.get::<MyComponent>(context.entity).unwrap().0;
world.resource_mut::<MyComponentIndex>().remove(&value);
})
// `on_remove` 将在组件从实体移除时触发
// 由于它在组件移除之前运行,你仍然可以访问组件数据
.on_remove(
|mut world,
HookContext {
entity,
component_id,
caller,
..
}| {
let value = world.get::<MyComponent>(entity).unwrap().0;
println!(
"{component_id:?} removed from {entity} with value {value:?}{}",
caller
.map(|location| format!("due to {location}"))
.unwrap_or_default()
);
// 也可以通过 `.commands()` 发出命令
world.commands().entity(entity).despawn();
},
);
}

关键要点

  • 有 4 种组件生命周期钩子:on_addon_inserton_replaceon_remove
  • 钩子可以访问 DeferredWorldHookContext
  • 钩子可以访问组件数据、资源和 Commands
  • 钩子可以发送消息
  • 组件必须未被使用且未注册钩子才能注册钩子

说明
组件生命周期钩子用于在组件的生命周期关键点执行逻辑。它们对于维护索引、强制执行结构规则等场景很有用。但要注意,尽可能使用 Bevy 的变更检测或事件来响应组件变化,因为事件通常提供更好的性能和更灵活的集成。

关系系统(Relationships)

关系系统允许建立自定义的实体关系,类似于内置的 ChildOf/Children 关系。

源代码文件bevy/examples/ecs/relationships.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
use bevy::prelude::*;

/// 此实体正在瞄准的实体
///
/// 这是关系的真实来源,可以直接修改以改变目标
#[derive(Component, Debug)]
#[relationship(relationship_target = TargetedBy)]
struct Targeting(Entity);

/// 所有正在瞄准此实体的实体
///
/// 此组件使用派生 [`Relationship`] trait 引入的组件钩子进行响应式更新
/// 我们不应该直接修改此组件,但可以安全地读取其字段
#[derive(Component, Debug)]
#[relationship_target(relationship = Targeting)]
struct TargetedBy(Vec<Entity>);

fn spawning_entities_with_relationships(mut commands: Commands) {
// 调用 `.id()` 在生成实体后将返回生成实体的 `Entity` 标识符
// 即使实体本身尚未在世界中实例化
let alice = commands.spawn(Name::new("Alice")).id();
// 关系只是组件,所以我们可以将它们添加到正在生成的 bundle 中
let bob = commands.spawn((Name::new("Bob"), Targeting(alice))).id();

// `with_related` 和 `with_related_entities` 辅助方法可以更符合人体工程学地添加关系
let charlie = commands
.spawn((Name::new("Charlie"), Targeting(bob)))
// `with_related` 方法将生成一个带有 `Targeting` 关系的 bundle
.with_related::<Targeting>(Name::new("James"))
// `with_related_entities` 方法将自动将 `Targeting` 组件添加到闭包内生成的任何实体
.with_related_entities::<Targeting>(|related_spawner_commands| {
// 我们可以在这里生成多个实体,它们都会瞄准 `charlie`
related_spawner_commands.spawn(Name::new("Devon"));
})
.id();

// 简单地插入 `Targeting` 组件将自动创建并更新目标实体上的 `TargetedBy` 组件
// 我们可以在任何时候这样做;不仅仅是在实体生成时
commands.entity(alice).insert(Targeting(charlie));
}

fn mutate_relationships(name_query: Query<(Entity, &Name)>, mut commands: Commands) {
// 关系组件是不可变的!我们不能可变地查询 `Targeting` 组件并直接修改它
// 但我们可以插入一个新的 `Targeting` 组件来替换旧的
// 这允许 `Targeting` 组件上的钩子正确更新 `TargetedBy` 组件
// `TargetedBy` 组件将自动更新!
let devon = name_query
.iter()
.find(|(_entity, name)| name.as_str() == "Devon")
.unwrap()
.0;

let alice = name_query
.iter()
.find(|(_entity, name)| name.as_str() == "Alice")
.unwrap()
.0;

println!("Making Devon target Alice.\n");
commands.entity(devon).insert(Targeting(alice));
}

关键要点

  • 使用 #[relationship(relationship_target = TargetedBy)] 定义关系组件
  • 使用 #[relationship_target(relationship = Targeting)] 定义关系目标组件
  • 关系组件是真实来源,可以直接修改
  • 关系目标组件是响应式更新的,不应直接修改
  • 使用 with_relatedwith_related_entities 辅助方法添加关系
  • 插入关系组件会自动更新关系目标组件

说明
关系系统允许建立自定义的实体关系。Bevy 内置了 ChildOf/Children 关系用于变换和可见性传播,但你可以定义自己的关系。关系组件是真实来源,可以直接修改,而关系目标组件是响应式更新的,不应直接修改。

并行查询(Parallel Queries)

并行查询使用并行迭代器提高系统执行效率。

源代码文件bevy/examples/ecs/parallel_query.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use bevy::{ecs::batching::BatchingStrategy, prelude::*};

#[derive(Component, Deref)]
struct Velocity(Vec2);

// 根据速度移动精灵
fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) {
// 在 ComputeTaskPool 上并行计算每个精灵的新位置
//
// 此示例仅用于演示目的。对于像加法这样便宜的操作,在只有 128 个元素时
// 使用 ParallelIterator 通常不会比使用普通 Iterator 更快
// 有关何时使用或不使用 ParallelIterator 的更多信息,请参阅 ParallelIterator 文档
sprites
.par_iter_mut()
.for_each(|(mut transform, velocity)| {
transform.translation += velocity.extend(0.0);
});
}

// 在窗口外反弹精灵
fn bounce_system(window: Query<&Window>, mut sprites: Query<(&Transform, &mut Velocity)>) {
let Ok(window) = window.single() else {
return;
};
let width = window.width();
let height = window.height();
// 也可以覆盖默认批次大小
// 在这种情况下,选择批次大小为 32 以限制 ParallelIterator 的开销
sprites
.par_iter_mut()
.batching_strategy(BatchingStrategy::fixed(32))
.for_each(|(transform, mut v)| {
// ...
});
}

关键要点

  • 使用 par_iter_mut() 获取并行迭代器
  • 使用 batching_strategy() 自定义批处理策略
  • 并行查询适用于计算密集型操作
  • 对于简单操作,并行查询可能不会更快

注意事项

  • 并行查询适用于计算密集型操作
  • 对于简单操作,并行查询可能不会更快
  • 可以使用 batching_strategy() 自定义批处理策略

最佳实践

  • 只在计算密集型操作时使用并行查询
  • 对于简单操作,使用普通查询
  • 根据操作复杂度调整批处理策略

查询组合(Query Combinations)

查询组合用于处理实体间的交互,如碰撞检测。

源代码文件bevy/examples/ecs/iter_combinations.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use bevy::prelude::*;

const GRAVITY_CONSTANT: f32 = 0.001;

#[derive(Component, Default)]
struct Mass(f32);
#[derive(Component, Default)]
struct Acceleration(Vec3);

fn interact_bodies(mut query: Query<(&Mass, &GlobalTransform, &mut Acceleration)>) {
let mut iter = query.iter_combinations_mut();
while let Some([(Mass(m1), transform1, mut acc1), (Mass(m2), transform2, mut acc2)]) =
iter.fetch_next()
{
let delta = transform2.translation() - transform1.translation();
let distance_sq: f32 = delta.length_squared();

let f = GRAVITY_CONSTANT / distance_sq;
let force_unit_mass = delta * f;
acc1.0 += force_unit_mass * *m2;
acc2.0 -= force_unit_mass * *m1;
}
}

关键要点

  • 使用 iter_combinations_mut() 获取可变组合迭代器
  • 使用 fetch_next() 获取下一对实体
  • 查询组合会跳过重复的组合(如 (A, B) 和 (B, A))
  • 查询组合用于处理实体间的成对交互

注意事项

  • 查询组合用于处理实体间的成对交互
  • 查询组合会跳过重复的组合
  • 查询组合对于大量实体可能较慢

最佳实践

  • 使用查询组合处理碰撞检测、物理交互等场景
  • 注意性能影响,对于大量实体考虑使用空间分区
  • 考虑使用并行查询组合提高性能

观察者模式(Observers)

观察者模式用于响应组件生命周期事件和自定义事件。

源代码文件bevy/examples/ecs/observers.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use bevy::prelude::*;

#[derive(Component)]
struct Mine {
pos: Vec2,
size: f32,
}

/// 这是一个普通的 [`Event`]。任何观察它的观察者都会在它被触发时运行
#[derive(Event)]
struct ExplodeMines {
pos: Vec2,
radius: f32,
}

/// [`EntityEvent`] 是一种专门的 [`Event`] 类型,可以针对特定实体
/// 除了在触发时运行正常的"顶级"观察者(针对任何爆炸的实体)外
/// 它还会运行针对该事件的特定实体的任何观察者
#[derive(EntityEvent)]
struct Explode {
entity: Entity,
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, (draw_shapes, handle_click))
// 观察者是在事件被"触发"时运行的系统
// 此观察者在 `ExplodeMines` 被触发时运行
.add_observer(
|explode_mines: On<ExplodeMines>,
mines: Query<&Mine>,
index: Res<SpatialIndex>,
mut commands: Commands| {
// 访问资源
for entity in index.get_nearby(explode_mines.pos) {
// 运行查询
let mine = mines.get(entity).unwrap();
if mine.pos.distance(explode_mines.pos) < mine.size + explode_mines.radius {
// 并排队命令,包括触发其他事件
// 这里我们为实体 `e` 触发 `Explode` 事件
commands.trigger(Explode { entity });
}
}
},
)
// 此观察者在 `Mine` 组件添加到实体时运行,并将其放置在简单的空间索引中
.add_observer(on_add_mine)
// 此观察者在 `Mine` 组件从实体移除时运行(包括销毁它)
// 并将其从空间索引中移除
.add_observer(on_remove_mine)
.run();
}

fn on_add_mine(add: On<Add, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
let mine = query.get(add.entity).unwrap();
let tile = (
(mine.pos.x / CELL_SIZE).floor() as i32,
(mine.pos.y / CELL_SIZE).floor() as i32,
);
index.map.entry(tile).or_default().insert(add.entity);
}

fn on_remove_mine(remove: On<Remove, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
let mine = query.get(remove.entity).unwrap();
let tile = (
(mine.pos.x / CELL_SIZE).floor() as i32,
(mine.pos.y / CELL_SIZE).floor() as i32,
);
index.map.entry(tile).and_modify(|set| {
set.remove(&remove.entity);
});
}

关键要点

  • 观察者用于响应组件生命周期事件和自定义事件
  • 使用 On<Add, T> 响应组件添加
  • 使用 On<Remove, T> 响应组件移除
  • 使用 On<Event> 响应自定义事件
  • 观察者可以访问资源、运行查询、发出命令

注意事项

  • 观察者用于响应组件生命周期事件和自定义事件
  • 观察者可以访问资源、运行查询、发出命令
  • 观察者比组件钩子更灵活,但开销更大

最佳实践

  • 使用观察者响应组件生命周期事件
  • 使用观察者响应自定义事件
  • 考虑性能影响,观察者可能比组件钩子更灵活但开销更大

进阶用法

消息系统(Messages)

消息系统用于实现系统间通信。

源代码文件bevy/examples/ecs/message.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use bevy::prelude::*;

/// 这是一个 [`Message`],任何观察它的观察者都会在它被激活时运行
#[derive(Message)]
struct MyMessage;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_message::<MyMessage>()
.add_systems(Startup, setup)
.add_systems(Update, send_message)
.add_observer(receive_message)
.run();
}

fn send_message(mut commands: Commands) {
// 发送消息
commands.write_message(MyMessage);
}

fn receive_message(_message: On<MyMessage>) {
info!("Message received!");
}

关键要点

  • 使用 Message 派生宏定义消息
  • 使用 add_message() 注册消息
  • 使用 write_message() 发送消息
  • 使用观察者接收消息

注意事项

  • 消息系统用于实现系统间通信
  • 消息可以用于事件驱动逻辑
  • 注意消息系统的性能影响

最佳实践

  • 对于系统间通信,使用消息系统
  • 对于事件驱动逻辑,使用消息系统
  • 注意消息系统的性能影响

错误处理

系统可以返回 Result 来处理错误。

源代码文件bevy/examples/ecs/error_handling.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use bevy::ecs::error::warn;

fn main() {
let mut app = App::new();
// 默认情况下,返回错误的可失败系统会 panic
//
// 我们可以通过设置自定义错误处理器来改变这一点
// 它适用于整个应用
// 这里我们使用内置错误处理器之一
app.set_error_handler(warn);

app.add_plugins(DefaultPlugins);
app.add_systems(Startup, setup);

// 单个系统也可以通过管道输出结果来处理:
app.add_systems(
PostStartup,
failing_system.pipe(|result: In<Result>| {
let _ = result.0.inspect_err(|err| info!("captured error: {err}"));
}),
);

app.run();
}

/// 这个系统总是失败验证,因为我们从未创建同时具有 `Player` 和 `Enemy` 组件的实体
fn failing_system(world: &mut World) -> Result {
world
// `get_resource` 返回 `Option<T>`,所以我们使用 `ok_or` 将其转换为 `Result`
// 然后我们可以调用 `?` 来传播错误
.get_resource::<UninitializedResource>()
// 我们可以在这里提供 `str`,因为 `BevyError` 实现了 `From<&str>`
.ok_or("Resource not initialized")?;

Ok(())
}

关键要点

  • 系统可以返回 Result<(), BevyError> 来处理错误
  • 使用 set_error_handler() 设置错误处理器
  • 使用 .pipe() 方法处理系统错误
  • 可失败系统可以用于错误处理

注意事项

  • 默认情况下,返回错误的系统会 panic
  • 可以设置自定义错误处理器
  • 系统错误可以通过管道处理

最佳实践

  • 对于可能失败的操作,使用可失败系统
  • 设置适当的错误处理器
  • 使用系统管道处理错误

动态 ECS

动态 ECS 允许动态创建组件和实体。

源代码文件bevy/examples/ecs/dynamic.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use bevy::prelude::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, query_dynamic_components)
.run();
}

fn setup(mut commands: Commands, mut world: &mut World) {
// 动态创建组件
let component_id = world.register_component_with_descriptor(ComponentDescriptor::new(
"MyDynamicComponent",
StorageType::Table,
));

// 使用动态组件创建实体
let entity = commands.spawn_empty().id();
commands.entity(entity).insert_by_id(component_id, MyData(42));
}

fn query_dynamic_components(world: &mut World, component_id: ComponentId) {
// 查询动态组件
let query = Query::new((component_id,));
for (data,) in query.iter(world) {
// ...
}
}

关键要点

  • 使用 register_component_with_descriptor() 动态创建组件
  • 使用 insert_by_id() 插入动态组件
  • 使用 Query 查询动态组件
  • 动态 ECS 可以用于运行时创建组件

注意事项

  • 动态 ECS 允许运行时创建组件
  • 动态 ECS 可以用于插件系统
  • 注意动态 ECS 的性能影响

最佳实践

  • 对于需要运行时创建组件的场景,使用动态 ECS
  • 对于插件系统,使用动态 ECS
  • 注意动态 ECS 的性能影响

实际应用

在游戏开发中的应用场景

ECS 进阶功能在游戏开发中有广泛的应用:

  1. 性能优化:使用并行查询提高系统执行效率
  2. 变更响应:使用变更检测和观察者响应组件变化
  3. 关系管理:使用关系系统建立实体间的复杂关系
  4. 生命周期管理:使用组件钩子管理组件的生命周期
  5. 事件驱动:使用消息系统和观察者实现事件驱动逻辑

常见问题

问题 1:何时使用变更检测,何时使用观察者?

解决方案

  • 变更检测适用于需要查询已变更组件的场景
  • 观察者适用于需要响应组件生命周期事件的场景
  • 对于简单场景,优先使用变更检测

问题 2:并行查询何时更快?

解决方案

  • 并行查询适用于计算密集型操作
  • 对于简单操作,并行查询可能不会更快
  • 需要根据实际情况测试性能

问题 3:如何优化查询组合的性能?

解决方案

  • 使用空间分区减少需要检查的实体对
  • 使用查询过滤器减少查询的实体数量
  • 考虑使用并行查询组合

性能考虑

  1. 并行查询:只在计算密集型操作时使用
  2. 查询组合:注意性能影响,考虑使用空间分区
  3. 观察者:考虑性能开销,优先使用变更检测
  4. 组件钩子:比观察者开销更小,但灵活性较低

相关资源

相关源代码文件

  • bevy/examples/ecs/change_detection.rs - 变更检测示例
  • bevy/examples/ecs/component_hooks.rs - 组件生命周期钩子示例
  • bevy/examples/ecs/relationships.rs - 关系系统示例
  • bevy/examples/ecs/parallel_query.rs - 并行查询示例
  • bevy/examples/ecs/iter_combinations.rs - 查询组合示例
  • bevy/examples/ecs/observers.rs - 观察者模式示例
  • bevy/examples/ecs/removal_detection.rs - 移除检测示例
  • bevy/examples/ecs/message.rs - 消息系统示例
  • bevy/examples/ecs/error_handling.rs - 错误处理示例
  • bevy/examples/ecs/dynamic.rs - 动态 ECS 示例

官方文档链接

进一步学习建议

  • 学习 Bevy 的其他高级功能,如自定义系统参数、系统调度等
  • 阅读 Bevy ECS 源码,深入理解实现原理
  • 实践编写自己的高级 ECS 系统,加深理解

索引返回上级目录