🎯 本章目标

学完这一章,你将能够:

  • 理解什么是状态以及为什么需要状态管理
  • 创建和切换游戏状态
  • 让不同的系统在不同状态下运行
  • 使用StateScoped自动管理实体生命周期

🏫 现实中的状态管理

想象我们的学校在不同时间有不同的”模式”:

  • 上课时间:学生在教室学习,门卫检查来访者
  • 课间休息:学生可以自由活动,食堂开始准备
  • 放学时间:家长可以进入,安全系统加强监控
  • 假期模式:只有值班人员,大部分设施关闭

每种模式下,学校的运行规则完全不同。这就是状态管理的核心思想。

🎮 游戏中的状态

游戏也有类似的需求:

  • 主菜单:显示菜单UI,播放背景音乐
  • 游戏中:处理玩家输入,更新游戏逻辑
  • 暂停:游戏逻辑停止,显示暂停菜单
  • 游戏结束:显示分数,保存进度

让我们用学校管理系统来演示状态管理:

📝 定义学校的状态

rust

use bevy::prelude::*;
 
// 定义学校的运行状态
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)]
enum SchoolState {
    #[default]
    BeforeSchool,  // 上学前
    ClassTime,     // 上课时间
    BreakTime,     // 课间休息
    AfterSchool,   // 放学后
    Holiday,       // 假期
}
 
// 定义安全级别状态(可以和学校状态同时存在)
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)]
enum SecurityLevel {
    #[default]
    Normal,        // 正常
    Heightened,    // 加强
    Emergency,     // 紧急
}
 
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // 初始化状态(从默认状态开始)
        .init_state::<SchoolState>()
        .init_state::<SecurityLevel>()
        // 启用状态作用域实体管理
        .enable_state_scoped_entities::<SchoolState>()
        .add_event::<StateChangeEvent>()
        .insert_resource(SchoolClock::new())
        
        // 状态进入时的设置系统
        .add_systems(OnEnter(SchoolState::BeforeSchool), setup_before_school)
        .add_systems(OnEnter(SchoolState::ClassTime), setup_class_time)
        .add_systems(OnEnter(SchoolState::BreakTime), setup_break_time)
        .add_systems(OnEnter(SchoolState::AfterSchool), setup_after_school)
        .add_systems(OnEnter(SchoolState::Holiday), setup_holiday)
        
        // 状态退出时的清理系统
        .add_systems(OnExit(SchoolState::ClassTime), cleanup_class_time)
        .add_systems(OnExit(SchoolState::BreakTime), cleanup_break_time)
        
        // 运行中的系统(按状态过滤)
        .add_systems(Update, (
            // 通用系统
            update_school_clock,
            handle_manual_state_change,
            auto_state_transition,
            
            // 特定状态的系统
            manage_students
                .run_if(in_state(SchoolState::ClassTime)),
            handle_break_activities
                .run_if(in_state(SchoolState::BreakTime)),
            manage_visitors
                .run_if(in_state(SchoolState::AfterSchool)),
            security_patrol
                .run_if(in_state(SecurityLevel::Heightened)),
            
            // 状态变化监听
            monitor_state_changes,
        ))
        .run();
}

⏰ 学校时钟系统

rust

#[derive(Resource)]
struct SchoolClock {
    current_hour: u8,
    current_minute: u8,
    day_speed: f32, // 1.0 = 正常速度,2.0 = 两倍速度
}
 
impl SchoolClock {
    fn new() -> Self {
        Self {
            current_hour: 7,   // 早上7点开始
            current_minute: 0,
            day_speed: 10.0,   // 10倍速度用于演示
        }
    }
    
    fn time_string(&self) -> String {
        format!("{:02}:{:02}", self.current_hour, self.current_minute)
    }
}
 
// 更新学校时钟
fn update_school_clock(
    mut clock: ResMut<SchoolClock>,
    time: Res<Time>,
) {
    // 根据真实时间和速度倍率更新时钟
    let minutes_passed = time.delta_seconds() * clock.day_speed / 60.0;
    let total_minutes = clock.current_hour as f32 * 60.0 + clock.current_minute as f32 + minutes_passed;
    
    clock.current_hour = ((total_minutes / 60.0) as u8) % 24;
    clock.current_minute = (total_minutes % 60.0) as u8;
}

🔄 自动状态切换

rust

// 根据时间自动切换学校状态
fn auto_state_transition(
    clock: Res<SchoolClock>,
    current_state: Res<State<SchoolState>>,
    mut next_state: ResMut<NextState<SchoolState>>,
    mut event_writer: EventWriter<StateChangeEvent>,
) {
    let current_time = clock.current_hour * 100 + clock.current_minute; // 转换为HHMM格式
    let current = current_state.get();
    
    let new_state = match current_time {
        700..=800 => SchoolState::BeforeSchool,   // 7:00-8:00
        800..=1200 => SchoolState::ClassTime,     // 8:00-12:00
        1200..=1300 => SchoolState::BreakTime,    // 12:00-13:00 (午休)
        1300..=1700 => SchoolState::ClassTime,    // 13:00-17:00
        1700..=1800 => SchoolState::AfterSchool,  // 17:00-18:00
        _ => SchoolState::Holiday,                // 其他时间
    };
    
    // 如果状态需要改变
    if new_state != *current {
        println!("🕐 {}:学校状态从 {:?} 切换到 {:?}", 
                clock.time_string(), current, new_state);
        
        next_state.set(new_state.clone());
        
        // 发送状态变化事件
        event_writer.send(StateChangeEvent {
            from: current.clone(),
            to: new_state,
            reason: "自动时间切换".to_string(),
        });
    }
}
 
#[derive(Event)]
struct StateChangeEvent {
    from: SchoolState,
    to: SchoolState,
    reason: String,
}

🏗️ 状态进入时的设置

rust

// 上学前的准备
fn setup_before_school(mut commands: Commands) {
    println!("🌅 学校开始新的一天!");
    
    // 创建值班老师(绑定到BeforeSchool状态)
    commands.spawn((
        StateScoped(SchoolState::BeforeSchool),
        Teacher { name: "值班老师".to_string(), subject: "管理".to_string() },
        Name("张老师".to_string()),
    ));
    
    // 开启校门
    commands.spawn((
        StateScoped(SchoolState::BeforeSchool),
        SchoolGate { is_open: true },
    ));
}
 
// 上课时间的设置
fn setup_class_time(mut commands: Commands) {
    println!("📚 上课时间开始!");
    
    // 创建任课老师
    let teachers = [
        ("数学", "李老师"),
        ("语文", "王老师"),
        ("英语", "赵老师"),
    ];
    
    for (subject, name) in teachers {
        commands.spawn((
            StateScoped(SchoolState::ClassTime),
            Teacher { 
                name: name.to_string(), 
                subject: subject.to_string() 
            },
            Name(name.to_string()),
        ));
    }
    
    // 创建学生(如果还没有的话)
    commands.spawn((
        StateScoped(SchoolState::ClassTime),
        Student { id: 301, enrollment_date: "2024-09-01".to_string() },
        Name("小明".to_string()),
        Grade(3),
    ));
}
 
// 课间休息的设置
fn setup_break_time(mut commands: Commands) {
    println!("🏃 课间休息时间!");
    
    // 创建食堂工作人员
    commands.spawn((
        StateScoped(SchoolState::BreakTime),
        CafeteriaStaff { name: "食堂阿姨".to_string() },
    ));
    
    // 开放操场
    commands.spawn((
        StateScoped(SchoolState::BreakTime),
        Playground { is_open: true, activity: "自由活动".to_string() },
    ));
}
 
// 放学后的设置
fn setup_after_school(mut commands: Commands) {
    println!("🚪 放学时间!家长可以进入学校");
    
    // 创建安保人员
    commands.spawn((
        StateScoped(SchoolState::AfterSchool),
        SecurityGuard { name: "李保安".to_string() },
    ));
}
 
// 假期模式的设置
fn setup_holiday(mut commands: Commands) {
    println!("🏖️ 假期模式:学校大部分设施关闭");
    
    // 只留值班人员
    commands.spawn((
        StateScoped(SchoolState::Holiday),
        HolidayDutyPerson { name: "值班员".to_string() },
    ));
}

🧹 状态退出时的清理

rust

// 上课时间结束的清理
fn cleanup_class_time() {
    println!("📖 上课时间结束,老师们准备休息");
    // StateScoped实体会自动被清理,这里只需要打印信息
}
 
// 课间休息结束的清理
fn cleanup_break_time() {
    println!("🍽️ 课间休息结束,食堂关闭");
    // StateScoped实体会自动被清理
}

🎯 特定状态的系统

rust

// 上课时间的学生管理
fn manage_students(
    student_query: Query<(&Name, &Grade), With<Student>>,
    teacher_query: Query<&Teacher>,
    clock: Res<SchoolClock>,
) {
    // 每10分钟检查一次(游戏中大约1秒)
    if clock.current_minute % 10 == 0 && clock.current_minute != 0 {
        let student_count = student_query.iter().count();
        let teacher_count = teacher_query.iter().count();
        
        if student_count > 0 && teacher_count > 0 {
            println!("👨‍🏫 {}:{}位老师正在教{}名学生", 
                    clock.time_string(), teacher_count, student_count);
        }
    }
}
 
// 课间休息的活动管理
fn handle_break_activities(
    playground_query: Query<&Playground>,
    cafeteria_query: Query<&CafeteriaStaff>,
) {
    if !playground_query.is_empty() {
        println!("🏃‍♂️ 学生们在操场上自由活动");
    }
    
    if !cafeteria_query.is_empty() {
        println!("🍛 食堂正在供应午餐");
    }
}
 
// 放学后的访客管理
fn manage_visitors(clock: Res<SchoolClock>) {
    // 模拟家长来接学生
    if clock.current_minute % 15 == 0 {
        println!("👪 {}:家长来接学生了", clock.time_string());
    }
}
 
// 加强安全模式下的巡逻
fn security_patrol(
    security_query: Query<&SecurityGuard>,
    clock: Res<SchoolClock>,
) {
    if !security_query.is_empty() && clock.current_minute % 5 == 0 {
        println!("🚨 安保人员正在巡逻(加强安全模式)");
    }
}

🎛️ 手动状态切换

rust

// 手动切换状态(用于测试)
fn handle_manual_state_change(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    current_state: Res<State<SchoolState>>,
    mut next_state: ResMut<NextState<SchoolState>>,
    mut security_state: ResMut<NextState<SecurityLevel>>,
    mut event_writer: EventWriter<StateChangeEvent>,
) {
    let current = current_state.get();
    
    if keyboard_input.just_pressed(KeyCode::Digit1) {
        let new_state = SchoolState::BeforeSchool;
        if new_state != *current {
            next_state.set(new_state.clone());
            event_writer.send(StateChangeEvent {
                from: current.clone(),
                to: new_state,
                reason: "手动切换".to_string(),
            });
        }
    }
    
    if keyboard_input.just_pressed(KeyCode::Digit2) {
        let new_state = SchoolState::ClassTime;
        if new_state != *current {
            next_state.set(new_state.clone());
            event_writer.send(StateChangeEvent {
                from: current.clone(),
                to: new_state,
                reason: "手动切换".to_string(),
            });
        }
    }
    
    if keyboard_input.just_pressed(KeyCode::Digit3) {
        let new_state = SchoolState::BreakTime;
        if new_state != *current {
            next_state.set(new_state.clone());
            event_writer.send(StateChangeEvent {
                from: current.clone(),
                to: new_state,
                reason: "手动切换".to_string(),
            });
        }
    }
    
    // 切换安全级别
    if keyboard_input.just_pressed(KeyCode::KeyS) {
        security_state.set(SecurityLevel::Heightened);
        println!("🚨 安全级别提升到加强模式!");
    }
    
    if keyboard_input.just_pressed(KeyCode::KeyN) {
        security_state.set(SecurityLevel::Normal);
        println!("🔒 安全级别恢复正常");
    }
}

📊 状态监控

rust

// 监控状态变化
fn monitor_state_changes(
    mut state_events: EventReader<StateChangeEvent>,
    school_state: Res<State<SchoolState>>,
    security_state: Res<State<SecurityLevel>>,
    clock: Res<SchoolClock>,
) {
    for event in state_events.read() {
        println!("🔄 {}:状态变化 - {}({})", 
                clock.time_string(),
                format!("{:?} → {:?}", event.from, event.to),
                event.reason);
    }
    
    // 定期显示当前状态
    if clock.current_minute % 30 == 0 && clock.current_minute != 0 {
        println!("\n📋 当前状态报告 [{}]:", clock.time_string());
        println!("   学校状态: {:?}", school_state.get());
        println!("   安全级别: {:?}\n", security_state.get());
    }
}

🏗️ 组件定义

rust

// 基础组件(前面章节已定义)
#[derive(Component)]
struct Student {
    id: u32,
    enrollment_date: String,
}
 
#[derive(Component)]
struct Name(String);
 
#[derive(Component)]
struct Grade(u8);
 
// 新增组件
#[derive(Component)]
struct Teacher {
    name: String,
    subject: String,
}
 
#[derive(Component)]
struct SchoolGate {
    is_open: bool,
}
 
#[derive(Component)]
struct CafeteriaStaff {
    name: String,
}
 
#[derive(Component)]
struct Playground {
    is_open: bool,
    activity: String,
}
 
#[derive(Component)]
struct SecurityGuard {
    name: String,
}
 
#[derive(Component)]
struct HolidayDutyPerson {
    name: String,
}

🎮 使用说明

运行程序后,你可以:

🎮 控制说明:
- 数字键 1:切换到上学前状态
- 数字键 2:切换到上课时间状态  
- 数字键 3:切换到课间休息状态
- 字母键 S:提升安全级别
- 字母键 N:恢复正常安全级别
- 自动:系统会根据时间自动切换状态

🎯 状态管理的优势

  1. 清晰的逻辑分离:不同状态下运行不同的系统
  2. 自动资源管理:StateScoped实体自动创建和销毁
  3. 性能优化:不需要的系统完全不运行
  4. 代码组织:相关功能自然地分组在一起

⚠️ 常见陷阱

陷阱1:忘记启用StateScoped

rust

// ❌ 忘记这行,StateScoped组件不会工作
.enable_state_scoped_entities::<YourState>()

陷阱2:状态切换延迟

rust

// ❌ 状态切换不是立即生效的
next_state.set(NewState);
// 这里NewState还没有生效,要等到下一帧

陷阱3:事件丢失

rust

// ⚠️ 如果事件接收系统只在特定状态下运行,
// 可能会错过在其他状态下发送的事件
fn handle_events(mut events: EventReader<MyEvent>) {
    // 如果这个系统有状态条件,小心事件丢失
}

🤔 检查理解

  1. 什么是状态?为什么需要状态管理?
  2. OnEnterOnExit 系统什么时候运行?
  3. StateScoped实体有什么好处?
  4. 如何让系统只在特定状态下运行?

💪 练习挑战

  1. 添加一个新状态 Exam(考试状态)
  2. 在考试状态下,创建监考老师实体
  3. 添加键盘快捷键切换到考试状态
  4. 让安全系统在考试时自动提升到加强模式

下一步

下一章我们将学习观察者系统,了解如何实现更即时、更精确的响应式逻辑!