🎯 本章目标

学完这一章,你将能够:

  • 理解为什么需要事件系统
  • 创建和发送自定义事件
  • 在系统中接收和处理事件
  • 避免事件使用中的常见陷阱

🤔 为什么需要事件?

想象一下我们的学校系统:当学生成绩发生变化时,需要:

  • 通知家长系统发送短信
  • 更新排行榜系统
  • 记录到日志系统
  • 检查是否需要奖励

如果用传统方式,我们需要在成绩更新系统中调用所有这些功能。这样会让代码很混乱:

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");
}

🎯 事件使用的最佳实践

  1. 事件名称要清晰GradeChangedEventEvent1
  2. 包含必要信息:事件应该包含处理所需的所有数据
  3. 避免过度使用:不是所有通信都需要事件
  4. 及时处理:确保监听事件的系统能定期运行

🤔 检查理解

  1. 事件和直接函数调用的区别是什么?
  2. 为什么事件只存在两帧?
  3. 什么情况下可能会丢失事件?
  4. EventWriterEventReader 分别用来做什么?

💪 练习挑战

试着为学校系统添加:

  1. 一个 StudentAbsentEvent(学生缺勤事件)
  2. 处理这个事件的系统,记录缺勤次数
  3. 当缺勤次数过多时,发送 ParentNotificationEvent

常见错误解析

错误1:忘记注册事件

rust

// ❌ 忘记这行会导致程序崩溃
.add_event::<MyEvent>()

错误2:在条件系统中处理关键事件

rust

// ❌ 可能错过事件
fn handle_important_events(mut events: EventReader<ImportantEvent>) {
    // 如果这个系统因为某种条件不运行,事件会丢失
}

下一步

下一章我们将学习状态管理,了解如何让游戏在不同的”模式”之间切换!