🎯 本章目标
学完这一章,你将能够:
- 使用必需组件简化实体创建
- 理解和使用资源管理全局数据
- 掌握现代Bevy的最佳实践
🔧 必需组件:让创建实体更简单
还记得上一章我们创建学生时要写很多组件吗?现在有更简洁的方式:
rust
// 新的方式:使用必需组件
#[derive(Component)]
#[require(Name, Grade)] // Student组件自动需要Name和Grade
struct Student {
student_id: u32, // 学生ID
}
// 现在创建学生变得超级简单
fn setup_students(mut commands: Commands) {
// 只需要提供核心的Student组件,其他会自动添加
commands.spawn(Student { student_id: 001 });
// 如果想自定义某些组件,也可以手动提供
commands.spawn((
Student { student_id: 002 },
Name("特殊学生李华".to_string()),
));
}
为什么这样更好?
- 不会忘记添加必要的组件
- 代码更简洁,专注于核心概念
- 自动有合理的默认值
🌍 资源:管理全局数据
不是所有数据都属于特定实体。比如:
- 学校名称(全校只有一个)
- 当前学期(全校统一)
- 总学生数量(全局统计)
这些用资源来管理:
rust
// 定义一个资源
#[derive(Resource)]
struct SchoolInfo {
name: String,
semester: String,
total_students: u32,
}
// 在App中注册资源
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// 插入资源的初始值
.insert_resource(SchoolInfo {
name: "阳光小学".to_string(),
semester: "2024春季".to_string(),
total_students: 0,
})
.add_systems(Startup, setup_students)
.add_systems(Update, update_student_count)
.run();
}
// 系统中使用资源
fn update_student_count(
student_query: Query<&Student>,
mut school_info: ResMut<SchoolInfo>, // 可变访问资源
) {
let current_count = student_query.iter().count() as u32;
// 只在数量变化时更新(避免无意义的计算)
if school_info.total_students != current_count {
school_info.total_students = current_count;
println!("{}当前有{}名学生",
school_info.name,
school_info.total_students);
}
}
// 只读访问资源
fn print_school_info(school_info: Res<SchoolInfo>) {
println!("学校:{}, 学期:{}",
school_info.name,
school_info.semester);
}
📊 实际应用示例:学生管理系统
让我们创建一个更完整的示例:
rust
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(SchoolStats::default())
.add_systems(Startup, setup_school)
.add_systems(Update, (
monitor_student_changes,
print_stats.run_if(resource_changed::<SchoolStats>),
))
.run();
}
#[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);
#[derive(Resource, Default)]
struct SchoolStats {
total_students: u32,
grade_distribution: [u32; 6], // 1-6年级的人数分布
}
// 默认名称生成函数
fn default_name() -> Name {
Name("新学生".to_string())
}
fn setup_school(mut commands: Commands) {
// 创建不同年级的学生
let students_data = [
("张小明", 3, 101),
("李华", 2, 102),
("王小红", 1, 103),
("赵大壮", 4, 104),
("孙小花", 5, 105),
];
for (name, grade, id) in students_data {
commands.spawn((
Student {
id,
enrollment_date: "2024-09-01".to_string()
},
Name(name.to_string()),
Grade(grade),
));
}
}
fn monitor_student_changes(
student_query: Query<&Grade, With<Student>>,
mut stats: ResMut<SchoolStats>,
) {
// 重新计算统计数据
let mut new_grade_distribution = [0; 6];
let mut total = 0;
for grade in &student_query {
if grade.0 >= 1 && grade.0 <= 6 {
new_grade_distribution[(grade.0 - 1) as usize] += 1;
total += 1;
}
}
// 只在数据变化时更新
if stats.total_students != total ||
stats.grade_distribution != new_grade_distribution {
stats.total_students = total;
stats.grade_distribution = new_grade_distribution;
}
}
fn print_stats(stats: Res<SchoolStats>) {
println!("\n=== 阳光小学统计 ===");
println!("总学生数: {}", stats.total_students);
println!("年级分布:");
for (i, count) in stats.grade_distribution.iter().enumerate() {
if *count > 0 {
println!(" {}年级: {}人", i + 1, count);
}
}
println!("==================\n");
}
🔍 资源 vs 组件:何时使用哪个?
使用场景 | 选择 | 原因 |
---|---|---|
全局唯一的数据 | 资源 | 比如游戏设置、时间、分数 |
属于特定实体的数据 | 组件 | 比如位置、生命值、名称 |
配置信息 | 资源 | 比如窗口设置、音量 |
实体的状态/属性 | 组件 | 比如是否可见、移动速度 |
💡 最佳实践提示
- 使用必需组件减少出错机会
- 资源用于全局数据,组件用于实体数据
- 利用变更检测避免不必要的计算
- 合理命名让代码自解释
🤔 检查理解
- 什么时候使用必需组件?
- 资源和组件的区别是什么?
Res<T>
和ResMut<T>
的区别?
下一步
下一章我们将学习事件系统,了解如何让游戏的不同部分相互通信!