🎯 本章目标

学完这一章,你将能够:

  • 理解观察者与事件的区别
  • 使用OnAdd和OnRemove观察者响应组件变化
  • 掌握观察者的即时性特点
  • 避免观察者使用中的常见陷阱

🚨 为什么需要观察者?

还记得我们的学校系统吗?想象这样的场景:

场景1:学生请病假

  • 传统方式:每帧检查所有学生的出勤状态,看谁请了病假
  • 观察者方式:当学生被标记为”病假”的瞬间,立即通知相关系统

场景2:新老师入职

  • 传统方式:人事系统定期检查是否有新老师
  • 观察者方式:新老师实体创建时,立即分配办公室、更新花名册

观察者就像学校里的智能监控系统,不是定时巡逻,而是有事情发生就立即响应。

🔍 观察者 vs 事件:关键区别

// 事件方式:需要主动"拉取"
fn handle_student_events(mut events: EventReader<StudentSickEvent>) {
    for event in events.read() {
        // 处理事件(可能延迟1-2帧)
    }
}
 
// 观察者方式:被动"推送",立即响应
fn on_student_sick_added(
    trigger: Trigger<OnAdd<SickLeave>>, // 当SickLeave组件被添加时立即触发
    mut commands: Commands,
    query: Query<&Name>,
) {
    let student_entity = trigger.target();
    if let Ok(name) = query.get(student_entity) {
        println!("🤒 学生{}请病假了,立即处理!", name.0);
        // 立即执行相关逻辑
    }
}

🏗️ 设置观察者系统

use bevy::prelude::*;
 
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<SchoolState>()
        .insert_resource(SchoolRegistry::default())
        
        // 注册观察者(不是事件!)
        .add_observer(on_student_added)
        .add_observer(on_teacher_added)
        .add_observer(on_sick_leave_added)
        .add_observer(on_sick_leave_removed)
        .add_observer(on_student_removed)
        .add_observer(on_special_student_added)
        
        .add_systems(Startup, (setup_school, print_instructions))
        .add_systems(Update, (
            handle_keyboard_input,
            simulate_random_events,
            display_registry.run_if(resource_changed::<SchoolRegistry>),
        ))
        .run();
}
 
// 学校注册表资源
#[derive(Resource, Default)]
struct SchoolRegistry {
    total_students: u32,
    total_teachers: u32,
    sick_students: Vec<String>,
    office_assignments: Vec<String>,
}
 
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)]
enum SchoolState {
    #[default]
    Normal,
    Emergency,
}

👨‍🎓 学生相关的观察者

// 基础组件
#[derive(Component)]
struct Student {
    id: u32,
    class: String,
}
 
#[derive(Component)]
struct Name(String);
 
#[derive(Component)]
struct SickLeave {
    reason: String,
    days: u8,
}
 
#[derive(Component)]
struct SpecialNeeds {
    requirement: String,
}
 
// 观察者:当学生实体被创建时
fn on_student_added(
    trigger: Trigger<OnAdd<Student>>,
    mut registry: ResMut<SchoolRegistry>,
    query: Query<&Name>,
) {
    let student_entity = trigger.target();
    
    if let Ok(name) = query.get(student_entity) {
        registry.total_students += 1;
        println!("📝 新学生{}已登记注册(学生总数:{})", 
                name.0, registry.total_students);
        
        // 可以在这里添加更多初始化逻辑
        // 比如分配学号、创建学籍档案等
    }
}
 
// 观察者:当学生请病假时
fn on_sick_leave_added(
    trigger: Trigger<OnAdd<SickLeave>>,
    mut registry: ResMut<SchoolRegistry>,
    student_query: Query<&Name, With<Student>>,
    sick_query: Query<&SickLeave>,
) {
    let student_entity = trigger.target();
    
    if let (Ok(name), Ok(sick_leave)) = (
        student_query.get(student_entity),
        sick_query.get(student_entity)
    ) {
        let student_name = name.0.clone();
        registry.sick_students.push(student_name.clone());
        
        println!("🤒 学生{}请病假{}天(原因:{})", 
                student_name, sick_leave.days, sick_leave.reason);
        println!("   立即通知:班主任、校医、家长");
        
        // 如果是传染病,提升安全级别
        if sick_leave.reason.contains("发烧") || sick_leave.reason.contains("咳嗽") {
            println!("⚠️  可能传染病,建议提升防护级别!");
        }
    }
}
 
// 观察者:当学生病假结束时
fn on_sick_leave_removed(
    trigger: Trigger<OnRemove<SickLeave>>,
    mut registry: ResMut<SchoolRegistry>,
    query: Query<&Name, With<Student>>,
) {
    let student_entity = trigger.target();
    
    if let Ok(name) = query.get(student_entity) {
        let student_name = &name.0;
        
        // 从病假列表中移除
        if let Some(pos) = registry.sick_students.iter().position(|x| x == student_name) {
            registry.sick_students.remove(pos);
            println!("😊 学生{}病假结束,欢迎回校!", student_name);
            println!("   需要:健康检查、补课安排、心理关怀");
        }
    }
}
 
// 观察者:当学生被删除时(转学、毕业等)
fn on_student_removed(
    trigger: Trigger<OnRemove<Student>>,
    mut registry: ResMut<SchoolRegistry>,
) {
    registry.total_students = registry.total_students.saturating_sub(1);
    println!("👋 一名学生离校(转学/毕业),总数更新为:{}", registry.total_students);
    
    // 这里可以添加离校手续:
    // - 清理学籍档案
    // - 退还押金
    // - 发放证明文件
}
 
// 观察者:特殊需求学生
fn on_special_student_added(
    trigger: Trigger<OnAdd<SpecialNeeds>>,
    query: Query<(&Name, &SpecialNeeds), With<Student>>,
) {
    let student_entity = trigger.target();
    
    if let Ok((name, special_needs)) = query.get(student_entity) {
        println!("🌟 特殊需求学生{}需要:{}", name.0, special_needs.requirement);
        println!("   立即安排:专门座位、辅助设备、个性化教学计划");
    }
}

👨‍🏫 教师相关的观察者

#[derive(Component)]
struct Teacher {
    subject: String,
    experience_years: u8,
}
 
// 观察者:新教师入职
fn on_teacher_added(
    trigger: Trigger<OnAdd<Teacher>>,
    mut registry: ResMut<SchoolRegistry>,
    mut commands: Commands,
    query: Query<(&Name, &Teacher)>,
) {
    let teacher_entity = trigger.target();
    
    if let Ok((name, teacher)) = query.get(teacher_entity) {
        registry.total_teachers += 1;
        let office_info = format!("{}老师 - {}办公室", name.0, teacher.subject);
        registry.office_assignments.push(office_info);
        
        println!("🎓 新教师{}入职({}),教师总数:{}", 
                name.0, teacher.subject, registry.total_teachers);
        
        // 根据经验自动分配责任
        if teacher.experience_years >= 5 {
            commands.entity(teacher_entity).insert(SeniorTeacher);
            println!("   资深教师,自动分配导师职责");
        }
        
        // 立即安排入职流程
        println!("   立即安排:办公室分配、教学设备、课程表制定");
    }
}
 
#[derive(Component)]
struct SeniorTeacher;

🎮 交互和模拟系统

// 键盘输入处理
fn handle_keyboard_input(
    mut commands: Commands,
    keyboard_input: Res<ButtonInput<KeyCode>>,
) {
    // 添加新学生
    if keyboard_input.just_pressed(KeyCode::KeyS) {
        let student_id = 1000 + rand::random::<u8>() as u32;
        let student_name = format!("学生{}", student_id);
        
        commands.spawn((
            Student { 
                id: student_id, 
                class: "三年级".to_string() 
            },
            Name(student_name),
        ));
    }
    
    // 添加新教师
    if keyboard_input.just_pressed(KeyCode::KeyT) {
        let subjects = ["数学", "语文", "英语", "科学"];
        let subject = subjects[rand::random::<usize>() % subjects.len()];
        let teacher_name = format!("{}老师{}", subject, rand::random::<u8>());
        
        commands.spawn((
            Teacher { 
                subject: subject.to_string(),
                experience_years: rand::random::<u8>() % 10 + 1,
            },
            Name(teacher_name),
        ));
    }
    
    // 让第一个健康学生请病假
    if keyboard_input.just_pressed(KeyCode::KeyI) {
        // 这里需要实现查找健康学生并添加病假组件的逻辑
        println!("🤒 模拟学生请病假...");
    }
    
    // 让第一个病假学生康复
    if keyboard_input.just_pressed(KeyCode::KeyH) {
        // 这里需要实现移除病假组件的逻辑
        println!("😊 模拟学生康复...");
    }
}
 
// 自动模拟随机事件
fn simulate_random_events(
    mut commands: Commands,
    time: Res<Time>,
    mut timer: Local<Timer>,
    student_query: Query<Entity, (With<Student>, Without<SickLeave>)>,
    sick_query: Query<Entity, With<SickLeave>>,
) {
    timer.tick(time.delta());
    
    if timer.finished() {
        *timer = Timer::from_seconds(8.0, TimerMode::Once);
        
        let random_event = rand::random::<f32>();
        
        if random_event < 0.3 {
            // 30%概率:学生请病假
            if let Some(student_entity) = student_query.iter().next() {
                let reasons = ["发烧", "咳嗽", "肚子疼", "头痛"];
                let reason = reasons[rand::random::<usize>() % reasons.len()];
                
                commands.entity(student_entity).insert(SickLeave {
                    reason: reason.to_string(),
                    days: rand::random::<u8>() % 3 + 1,
                });
            }
        } else if random_event < 0.5 {
            // 20%概率:学生康复
            if let Some(sick_entity) = sick_query.iter().next() {
                commands.entity(sick_entity).remove::<SickLeave>();
            }
        } else if random_event < 0.6 {
            // 10%概率:添加特殊需求学生
            if let Some(student_entity) = student_query.iter().next() {
                let requirements = ["无障碍通道", "助听设备", "特殊座椅", "个别辅导"];
                let requirement = requirements[rand::random::<usize>() % requirements.len()];
                
                commands.entity(student_entity).insert(SpecialNeeds {
                    requirement: requirement.to_string(),
                });
            }
        }
    }
}

📊 状态显示系统

fn display_registry(registry: Res<SchoolRegistry>) {
    println!("\n📋 === 学校实时状态 ===");
    println!("👨‍🎓 学生总数:{}", registry.total_students);
    println!("👨‍🏫 教师总数:{}", registry.total_teachers);
    
    if !registry.sick_students.is_empty() {
        println!("🤒 病假学生:");
        for student in &registry.sick_students {
            println!("   - {}", student);
        }
    }
    
    if !registry.office_assignments.is_empty() {
        println!("🏢 办公室分配:");
        for assignment in &registry.office_assignments {
            println!("   - {}", assignment);
        }
    }
    println!("====================\n");
}
 
fn setup_school(mut commands: Commands) {
    // 创建一些初始学生
    for i in 1..=3 {
        commands.spawn((
            Student { 
                id: i, 
                class: "三年级".to_string() 
            },
            Name(format!("张同学{}", i)),
        ));
    }
}
 
fn print_instructions() {
    println!("\n🎮 === 观察者系统演示 ===");
    println!("按键说明:");
    println!("  S - 添加新学生(触发OnAdd<Student>观察者)");
    println!("  T - 添加新教师(触发OnAdd<Teacher>观察者)");
    println!("  I - 模拟学生请病假(触发OnAdd<SickLeave>观察者)");
    println!("  H - 模拟学生康复(触发OnRemove<SickLeave>观察者)");
    println!("自动事件:每8秒随机发生病假/康复/特殊需求事件");
    println!("观察即时响应的效果!\n");
}

⚠️ 观察者的陷阱:无限递归

这是观察者系统最危险的陷阱:

// ❌ 危险!会导致无限递归和栈溢出
fn dangerous_observer(
    trigger: Trigger<OnAdd<SomeComponent>>,
    mut commands: Commands,
) {
    let entity = trigger.target();
    
    // 这会再次触发OnAdd<SomeComponent>,形成无限循环!
    commands.entity(entity).insert(SomeComponent);
}
 
// ✅ 正确的方式:检查条件或使用不同的组件
fn safe_observer(
    trigger: Trigger<OnAdd<Student>>,
    mut commands: Commands,
    query: Query<&Student, Without<StudentId>>,
) {
    let entity = trigger.target();
    
    // 只有当学生没有ID时才添加
    if query.get(entity).is_ok() {
        commands.entity(entity).insert(StudentId(generate_id()));
    }
}
 
#[derive(Component)]
struct StudentId(u32);
 
fn generate_id() -> u32 {
    rand::random()
}

🔄 OnAdd vs OnInsert:重要区别

// OnAdd: 只在组件第一次被添加时触发
fn on_first_add(trigger: Trigger<OnAdd<Health>>) {
    println!("健康组件第一次被添加");
}
 
// OnInsert: 每次插入组件时都触发(包括覆盖)
fn on_every_insert(trigger: Trigger<OnInsert<Health>>) {
    println!("健康组件被插入(可能是覆盖)");
}
 
#[derive(Component)]
struct Health(u32);
 
// 示例:
// commands.spawn(Health(100)); // 触发 OnAdd 和 OnInsert
// commands.entity(e).insert(Health(90)); // 只触发 OnInsert

🚀 观察者的高级用法

// 使用自定义触发器
#[derive(Event)]
struct EmergencyDrill;
 
fn trigger_emergency_drill(
    mut commands: Commands,
    keyboard_input: Res<ButtonInput<KeyCode>>,
) {
    if keyboard_input.just_pressed(KeyCode::KeyE) {
        // 手动触发观察者
        commands.trigger(EmergencyDrill);
    }
}
 
fn handle_emergency_drill(
    trigger: Trigger<EmergencyDrill>,
    mut next_state: ResMut<NextState<SchoolState>>,
) {
    println!("🚨 紧急演练开始!");
    next_state.set(SchoolState::Emergency);
}
 
// 在app中添加观察者
// .add_observer(handle_emergency_drill)

🎯 何时使用观察者?

场景使用观察者使用事件使用变更检测
组件添加/移除的响应✅ 最佳选择❌ 过于复杂❌ 可能延迟
维护数据一致性✅ 即时保证⚠️ 可能延迟⚠️ 可能延迟
高频事件处理⚠️ 性能考虑✅ 批处理优势✅ 批处理优势
解耦的通信⚠️ 直接耦合✅ 最佳选择❌ 不适用
调试友好性⚠️ 隐式调用✅ 显式发送✅ 显式查询

🤔 检查理解

  1. 观察者与事件的主要区别是什么?
  2. OnAddOnRemove 什么时候被触发?
  3. 为什么观察者可能导致无限递归?
  4. 什么时候应该使用观察者而不是事件?

💪 练习挑战

  1. 创建一个 on_teacher_removed 观察者,处理教师离职
  2. 实现一个复杂的观察者链:学生→班级→年级的级联更新
  3. 使用自定义触发器实现”火警演练”功能
  4. 避免无限递归,实现一个”自动升级”系统

⚡ 性能提示

  • 观察者在组件变化时立即执行,适合低频但重要的事件
  • 避免在观察者中执行耗时操作
  • 考虑使用 commands.trigger_targets() 批量触发

下一步

下一章我们将学习系统调度,了解如何精确控制系统的执行顺序,让复杂的游戏逻辑井然有序!