🎯 本章目标
学完这一章,你将能够:
- 理解为什么需要事件系统
- 创建和发送自定义事件
- 在系统中接收和处理事件
- 避免事件使用中的常见陷阱
🤔 为什么需要事件?
想象一下我们的学校系统:当学生成绩发生变化时,需要:
- 通知家长系统发送短信
- 更新排行榜系统
- 记录到日志系统
- 检查是否需要奖励
如果用传统方式,我们需要在成绩更新系统中调用所有这些功能。这样会让代码很混乱:
rust
// ❌ 不好的方式:系统之间紧密耦合
fn update_grades(
mut student_query: Query<&mut Grade>,
mut parent_system: ResMut<ParentNotificationSystem>,
mut ranking_system: ResMut<RankingSystem>,
mut log_system: ResMut<LogSystem>,
mut reward_system: ResMut<RewardSystem>,
) {
// 更新成绩的逻辑...
// 然后通知所有其他系统(很混乱!)
parent_system.notify_grade_change();
ranking_system.update_rankings();
log_system.record_change();
reward_system.check_rewards();
}
事件系统的优势:就像学校的广播系统,一个人说话,所有感兴趣的人都能听到,而且说话的人不需要知道谁在听。
📢 创建你的第一个事件
让我们为学校系统创建一些事件:
rust
use bevy::prelude::*;
// 定义事件:学生成绩变化
#[derive(Event)]
struct GradeChangedEvent {
student_id: u32,
old_grade: u8,
new_grade: u8,
subject: String,
}
// 定义事件:学生入学
#[derive(Event)]
struct StudentEnrolledEvent {
student_id: u32,
name: String,
grade: u8,
}
// 定义事件:学生获得奖励
#[derive(Event)]
struct StudentRewardEvent {
student_id: u32,
reward_type: String,
reason: String,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// 注册事件类型(很重要!)
.add_event::<GradeChangedEvent>()
.add_event::<StudentEnrolledEvent>()
.add_event::<StudentRewardEvent>()
.insert_resource(SchoolStats::default())
.add_systems(Startup, setup_school)
.add_systems(Update, (
// 发送事件的系统
simulate_grade_changes,
enroll_new_students,
// 接收事件的系统
handle_grade_changes,
handle_student_enrollment,
handle_rewards,
// 其他系统
print_stats.run_if(resource_changed::<SchoolStats>),
))
.run();
}
📤 发送事件:EventWriter
发送事件就像用广播系统说话:
rust
// 模拟成绩变化的系统
fn simulate_grade_changes(
mut grade_event_writer: EventWriter<GradeChangedEvent>,
mut reward_event_writer: EventWriter<StudentRewardEvent>,
time: Res<Time>,
mut timer: Local<Timer>,
) {
// 每5秒模拟一次成绩变化
timer.tick(time.delta());
if timer.finished() {
// 重置计时器为5秒
*timer = Timer::from_seconds(5.0, TimerMode::Once);
// 发送成绩变化事件
grade_event_writer.send(GradeChangedEvent {
student_id: 101,
old_grade: 85,
new_grade: 90,
subject: "数学".to_string(),
});
// 如果成绩提高了,发送奖励事件
reward_event_writer.send(StudentRewardEvent {
student_id: 101,
reward_type: "进步奖".to_string(),
reason: "数学成绩提高5分".to_string(),
});
println!("📊 发送了成绩变化事件!");
}
}
// 模拟学生入学的系统
fn enroll_new_students(
mut commands: Commands,
mut enrollment_writer: EventWriter<StudentEnrolledEvent>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
// 按空格键添加新学生
if keyboard_input.just_pressed(KeyCode::Space) {
let new_student_id = 200 + rand::random::<u8>() as u32;
let student_name = format!("新学生{}", new_student_id);
// 创建学生实体
commands.spawn((
Student {
id: new_student_id,
enrollment_date: "2024-10-01".to_string()
},
Name(student_name.clone()),
Grade(1),
));
// 发送入学事件
enrollment_writer.send(StudentEnrolledEvent {
student_id: new_student_id,
name: student_name,
grade: 1,
});
println!("🎒 按空格键添加了新学生!");
}
}
📥 接收事件:EventReader
接收事件就像有选择地听广播:
rust
// 处理成绩变化事件的系统
fn handle_grade_changes(
mut grade_events: EventReader<GradeChangedEvent>,
mut stats: ResMut<SchoolStats>,
) {
// 遍历所有收到的成绩变化事件
for event in grade_events.read() {
println!("📈 学生{}的{}成绩从{}变成了{}",
event.student_id,
event.subject,
event.old_grade,
event.new_grade);
// 更新统计信息
if event.new_grade > event.old_grade {
stats.total_improvements += 1;
}
}
}
// 处理学生入学事件的系统
fn handle_student_enrollment(
mut enrollment_events: EventReader<StudentEnrolledEvent>,
mut stats: ResMut<SchoolStats>,
) {
for event in enrollment_events.read() {
println!("🎉 欢迎新学生:{}({}年级)",
event.name,
event.grade);
// 发送欢迎短信给家长
println!("📱 发送欢迎短信给学生{}的家长", event.student_id);
// 更新统计
stats.total_students += 1;
}
}
// 处理奖励事件的系统
fn handle_rewards(
mut reward_events: EventReader<StudentRewardEvent>,
) {
for event in reward_events.read() {
println!("🏆 学生{}获得了{}!原因:{}",
event.student_id,
event.reward_type,
event.reason);
// 这里可以实现实际的奖励逻辑
// 比如更新学生的奖励记录组件
}
}
📊 更新资源结构
rust
#[derive(Resource, Default)]
struct SchoolStats {
total_students: u32,
grade_distribution: [u32; 6],
total_improvements: u32, // 新增:记录进步次数
}
// 其他组件定义保持不变...
#[derive(Component)]
#[require(Name(default_name), Grade(|| Grade(1)))]
struct Student {
id: u32,
enrollment_date: String,
}
#[derive(Component)]
struct Name(String);
#[derive(Component)]
struct Grade(u8);
fn default_name() -> Name {
Name("新学生".to_string())
}
⚠️ 事件的重要特性:双缓冲机制
事件有一个重要特性:它们只存在两帧。这意味着:
rust
// ✅ 正确:这个系统每帧都运行,不会错过事件
fn always_running_system(mut events: EventReader<MyEvent>) {
for event in events.read() {
// 处理事件
}
}
// ⚠️ 危险:如果这个系统因为条件不满足而停止运行,
// 可能会错过在停止期间发送的事件
fn conditional_system(
mut events: EventReader<MyEvent>,
game_state: Res<State<GameState>>,
) {
// 只在特定状态下运行
if !matches!(game_state.get(), GameState::Playing) {
return;
}
for event in events.read() {
// 如果游戏不在Playing状态,这些事件会丢失!
}
}
🛠️ 完整的可运行示例
rust
use bevy::prelude::*;
use std::time::Duration;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_event::<GradeChangedEvent>()
.add_event::<StudentEnrolledEvent>()
.add_event::<StudentRewardEvent>()
.insert_resource(SchoolStats::default())
.add_systems(Startup, (setup_school, print_instructions))
.add_systems(Update, (
simulate_grade_changes,
enroll_new_students,
handle_grade_changes,
handle_student_enrollment,
handle_rewards,
print_stats.run_if(resource_changed::<SchoolStats>),
))
.run();
}
#[derive(Event)]
struct GradeChangedEvent {
student_id: u32,
old_grade: u8,
new_grade: u8,
subject: String,
}
#[derive(Event)]
struct StudentEnrolledEvent {
student_id: u32,
name: String,
grade: u8,
}
#[derive(Event)]
struct StudentRewardEvent {
student_id: u32,
reward_type: String,
reason: String,
}
#[derive(Resource, Default)]
struct SchoolStats {
total_students: u32,
grade_distribution: [u32; 6],
total_improvements: u32,
}
// ... 其他代码保持不变 ...
fn print_instructions() {
println!("\n🎮 互动说明:");
println!("- 按空格键添加新学生");
println!("- 每5秒自动模拟成绩变化");
println!("- 观察事件如何触发不同的反应\n");
}
fn print_stats(stats: Res<SchoolStats>) {
println!("\n=== 📊 学校实时统计 ===");
println!("总学生数: {}", stats.total_students);
println!("总进步次数: {}", stats.total_improvements);
if stats.total_students > 0 {
for (i, count) in stats.grade_distribution.iter().enumerate() {
if *count > 0 {
println!(" {}年级: {}人", i + 1, count);
}
}
}
println!("====================\n");
}
🎯 事件使用的最佳实践
- 事件名称要清晰:
GradeChangedEvent
比Event1
好 - 包含必要信息:事件应该包含处理所需的所有数据
- 避免过度使用:不是所有通信都需要事件
- 及时处理:确保监听事件的系统能定期运行
🤔 检查理解
- 事件和直接函数调用的区别是什么?
- 为什么事件只存在两帧?
- 什么情况下可能会丢失事件?
EventWriter
和EventReader
分别用来做什么?
💪 练习挑战
试着为学校系统添加:
- 一个
StudentAbsentEvent
(学生缺勤事件) - 处理这个事件的系统,记录缺勤次数
- 当缺勤次数过多时,发送
ParentNotificationEvent
常见错误解析
错误1:忘记注册事件
rust
// ❌ 忘记这行会导致程序崩溃
.add_event::<MyEvent>()
错误2:在条件系统中处理关键事件
rust
// ❌ 可能错过事件
fn handle_important_events(mut events: EventReader<ImportantEvent>) {
// 如果这个系统因为某种条件不运行,事件会丢失
}
下一步
下一章我们将学习状态管理,了解如何让游戏在不同的”模式”之间切换!