用途
本技能用于 Bevy ECS 中的事件与消息传递。当某个系统需要感知“发生了某事”,而又不想把这一事实存储在每个实体上时,加载本技能。它帮助你在不同机制之间做出选择,并明确要求:不要自行发明第三种通道。
主要能力
消息(拉取式)
Message 是用于拉取式处理的缓冲消息。MessageWriter 负责写入,MessageReader 负责读取并跟踪自己已经看到的内容。Messages 保存最近两次更新内发生的内容。通过 App::add_message 注册类型。引擎负责更新缓冲区;游戏逻辑系统只负责写入和读取。
事件(推送式)
派生 Event,添加观察者,然后触发。Commands::trigger 和 World::trigger 会在调用内部立即运行观察者。On<E> 是系统参数。对于派生事件,Event::Trigger 默认为 GlobalTrigger。
观察者
Observer 是一个持有系统的组件。EntityEvent 针对单个实体:事件携带 entity 字段,On::event_target 返回该实体。
状态转换
StateTransitionEvent<S> 在状态 S 变化时触发。该类型随状态一起存在,可参考 bevy-state-machines。
使用方式
消息示例
依赖:
[dependencies]
bevy = "0.20"
命中是一个供其他系统轮询的事实。注册消息,在攻击系统中写入,并在同一调度中稍后读取。add_message 会维护双缓冲队列。
#[derive(Message)]
struct Hit {
amount: i32,
}
fn install(app: &mut App) {
app.add_message::<Hit>()
.add_systems(Update, (deal, apply).chain());
}
fn deal(mut hits: MessageWriter<Hit>) {
hits.write(Hit { amount: 12 });
}
fn apply(mut hits: MessageReader<Hit>) {
for hit in hits.read() {
info!("hit {}", hit.amount);
}
}
事件与观察者示例
喊叫应当立即运行监听者。派生 Event,用 On<Shout> 观察,然后触发。观察者在 trigger 内部、系统返回之前运行。
#[derive(Event)]
struct Shout {
text: String,
}
fn listen(shout: On<Shout>) {
info!("{}", shout.event().text);
}
fn setup(mut commands: Commands) {
commands.spawn(Observer::new(listen));
commands.trigger(Shout {
text: "round".into(),
});
}
实体定向事件示例
实体定向事件会指明其目标。event_target 就是那个实体;观察者仍然立即运行。
#[derive(EntityEvent)]
struct Stagger {
entity: Entity,
}
fn on_stagger(hit: On<Stagger>) {
info!("stagger {:?}", hit.event_target());
}
fn strike(mut commands: Commands, fighters: Query<Entity>) {
for entity in &fighters {
commands.trigger(Stagger { entity });
}
}
适用范围与约束
- 在任何 MessageWriter 或 MessageReader 之前,先用 App::add_message 注册消息。
- 实体定向触发使用 EntityEvent,并通过 Commands::trigger 设置 entity 字段。
- 在同一调度中写入并读取消息时使用 .chain(),以便读取者在缓冲区更新之前看到写入。
专家知识要点
目标:Bevy 0.20+。
核心 ECS 类型:Component、Resource、Query、Commands、Startup、Update、Fixed / FixedMain(bevy::time)、.chain()、Plugin、DefaultPlugins、MinimalPlugins。消息与观察者是一个归属槽位,其 API 见本技能。除非 .chain() 对冲突的系统进行排序,否则系统并行运行。
状态归属
| 部分 | 归属 | 谁可以修改 | 调度 |
|---|---|---|---|
| 单个实体上的数据 | Component | 一个系统通过 Query | Update 或 FixedMain |
| 整个应用的一个值 | Resource | 一个系统 | Startup 插入,随后由读取它的调度 |
| 生成 | Commands | 执行生成的系统 | Startup 用于初始世界;后续调度用于运行时生成 |
| 不能竞态的顺序 | .chain() | 仅被链式连接的系统 | 同一调度 |
| 插件边界 | Plugin | build 注册系统与资源 | 应用运行之前 |
借用规则:两个都可变查询同一组件的系统必须被排序(.chain()),或拆分使只有一个写入。不相关的系统保持并行。这是 ECS 页面规则,不是对系统集 API 的猜测。
决策
| 如果你需要 | 这样做 | 不要 |
|---|---|---|
| 每实体数据 | Component + Query | 一个被每个实体共享的可变大块 |
| 应用级配置或时钟 | Resource | 把值复制到每个实体上 |
| 稳定的模拟步长 | FixedMain 与 Fixed 时钟 | 把模拟绑定到原始帧 Update |
| 启动时生成 | Startup 中的 Commands | 从渲染系统生成 |
| 非共享状态的跨系统通知 | bevy-events-messaging 中的消息或观察者槽位 | 在本文件中发明新的事件类型 |
| 文档集合之外的 API | 阅读官方 Bevy 页面 | 编造的 crate 或 API |
加载 references/ecs-ownership.md 查看归属表。
禁止事项
- 不要在文档 URL 中固定 /0.20.0/。原因:链接保持在最新版本;0.20 目标只在正文中说明一次。
- 不要从两个无序系统可变查询同一组件。原因:除非 .chain() 排序,否则调度并行运行系统。
- 不要在此发明物理、导航、网络或 XR crate。原因:本技能只命名有文档的 Bevy crate 和模块。
- 不要发明消息注册或触发方法。原因:Bevy 代码保持惯用 ECS,只使用有文档的 Bevy API。
- 不要使用第三方 crate。原因:Bevy 代码保持惯用 ECS,只使用有文档的 Bevy API。
参考
bevy-master 上的库固定版本保持 0.20.0。官方页面保持在无版本站点上。
官方文档:
- ECS messages —— 系统、Query,以及缓冲通知在组件旁的位置。
- Observer example —— Event、EntityEvent、Observer 与 trigger。
- Examples —— 最新分支上的示例树。
相关技能
前置:
- bevy-master —— 在添加通知通道之前先了解 ECS 归属。
- bevy-ecs-composition —— 写入或读取通知的系统。
互补:
- bevy-state-machines —— 用于应用级状态变化的 StateTransitionEvent。
下游 / 消费者:
- 游戏玩法技能涵盖战斗、能力及类似循环。
主技能:
- bevy-master —— Bevy 路由器。