🎯 本章目标
学完这一章,你将能够:
- 理解什么是状态以及为什么需要状态管理
- 创建和切换游戏状态
- 让不同的系统在不同状态下运行
- 使用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:恢复正常安全级别
- 自动:系统会根据时间自动切换状态
🎯 状态管理的优势
- 清晰的逻辑分离:不同状态下运行不同的系统
- 自动资源管理:StateScoped实体自动创建和销毁
- 性能优化:不需要的系统完全不运行
- 代码组织:相关功能自然地分组在一起
⚠️ 常见陷阱
陷阱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>) {
// 如果这个系统有状态条件,小心事件丢失
}
🤔 检查理解
- 什么是状态?为什么需要状态管理?
OnEnter
和OnExit
系统什么时候运行?- StateScoped实体有什么好处?
- 如何让系统只在特定状态下运行?
💪 练习挑战
- 添加一个新状态
Exam
(考试状态) - 在考试状态下,创建监考老师实体
- 添加键盘快捷键切换到考试状态
- 让安全系统在考试时自动提升到加强模式
下一步
下一章我们将学习观察者系统,了解如何实现更即时、更精确的响应式逻辑!