用途
本技能用于在 Bevy 0.20 中判断一份游戏数据应该放在 Component 上还是 Resource 上。当你需要决定数据是“分布在多个实体上”还是“整个应用只保留一份”时,可以加载本技能。它围绕 ECS 的数据归属展开,帮助选择 Query 与 Res/ResMut、insert_resource、Time 与 Timer,以及 RON 解析或资产句柄的存放方式。
主要能力
- 区分 Component 与 Resource:Component 用于需要跨实体查询的数据(Component、Query),适合许多对象携带同类字段的情况;Resource 是全局唯一数据(Resource),官方文档以经过时间、资产集合和渲染器为例。
- 资源访问方式:用 Res<T> 读取,用 ResMut<T> 写入。
- 资源插入:insert_resource 将资源放到 App 上。示例中对 GreetTimer(Timer) 派生 Resource,并插入 Timer::from_seconds(2.0, TimerMode::Repeating)。
- 时间与计时器:Time 由 add_plugins(DefaultPlugins) 插入;time.delta() 是自上次更新以来的经过时间;Timer::tick 与 just_finished 是文档中的计时器方法。
- 调度:示例在 Update 系统中读取 Res<Time> 和 ResMut<GreetTimer>;Startup 与 Update 的区别见 ECS 页面。
- 资产句柄:Resource 可以保存通过 AssetServer 加载的资产句柄;句柄是数据,加载器仍留在资产服务器上。
使用方式
数据归属的判断表:
- 数据属于某个游戏对象:使用 Component。
- 整个应用只需一份:使用 Resource,并通过 insert_resource 插入。
- 共享时钟:使用 DefaultPlugins 提供的 Time,或自定义 Resource。
- 作者编写的配置文件:使用 serde 加 ron::from_str 解析进 Resource,或使用来自 AssetServer 的 Handle<Mesh>。
依赖示例(Cargo.toml):
[package]
name = "data-resources"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.20"
serde = { version = "1", features = ["derive"] }
ron = "0.10"
一个在 Update 中由 Time 驱动的问候计时器资源:
#[derive(Resource)]
struct GreetTimer(Timer);
fn greet(time: Res<Time>, mut timer: ResMut<GreetTimer>) {
timer.0.tick(time.delta());
if timer.0.just_finished() {
let _ready = true;
}
}
pub struct GreetPlugin;
impl Plugin for GreetPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(GreetTimer(Timer::from_seconds(2.0, TimerMode::Repeating)))
.add_systems(Update, greet);
}
}
fn main() {
App::new().add_plugins(DefaultPlugins).add_plugins(GreetPlugin).run();
}
每个实体的速度保留为 Component,共享倍率保留为 Resource:
#[derive(Component)]
struct BaseSpeed(f32);
#[derive(Component)]
struct Speed(f32);
#[derive(Resource)]
struct SpeedScale(f32);
fn scale_speed(scale: Res<SpeedScale>, mut bodies: Query<(&BaseSpeed, &mut Speed)>) {
for (base, mut speed) in &mut bodies {
speed.0 = base.0 * scale.0;
}
}
pub struct SpeedPlugin;
impl Plugin for SpeedPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(SpeedScale(1.0))
.add_systems(Startup, |mut commands: Commands| {
commands.spawn((BaseSpeed(4.0), Speed(4.0)));
})
.add_systems(Update, scale_speed);
}
}
将场景句柄加载进一个资源,并将 RON 表解析进另一个资源:
use serde::Deserialize;
#[derive(Resource)]
struct LevelAssets {
mesh: Handle<Mesh>,
}
#[derive(Deserialize, Resource)]
struct Tuning {
speed: f32,
}
fn load_level(mut commands: Commands, assets: Res<AssetServer>) {
commands.insert_resource(LevelAssets {
mesh: assets.load("models/gate.glb#Mesh0/Primitive0"),
});
}
fn tuning_from_ron(raw: &str) -> Result<Tuning, ron::error::SpannedError> {
ron::from_str(raw)
}
适用场景
- 需要决定某个字段是放在实体上还是全局资源上时。
- 需要实现全应用共享的配置、时钟或调参数据时。
- 需要把 RON 配置文件解析为 Resource 时。
- 需要把 AssetServer 加载的资产句柄保存为 Resource 时。
- 需要理解 Query 与 Res/ResMut 的取舍时。
限制
- 目标版本为 Bevy 0.20+。
- 每个实体的数据文件和存档 blob 不在本技能范围内,参见 bevy-save-load。
- 消息与观察者属于另一个归属槽位,其 API 参见 bevy-events-messaging。
- 不要在文档 URL 中固定 /0.20.0/;链接保持指向最新版本,0.20 目标只在正文中说明一次。
- 不要从两个未排序的系统可变查询同一个组件;调度会并行运行系统,除非用 .chain() 排序。
- 不要在本技能中发明物理、导航、网络或 XR crate;本技能只命名有文档的 Bevy crate 和模块。
- 不要用打包场景或节点脚本替代 Resource;应用级数据是插入到 App 或 Commands 上的 Resource。
- 不要发明资产或序列化 API,不要使用第三方 crate;Bevy 代码保持惯用 ECS 并只使用有文档的 Bevy API。
相关技能
前置:bevy-master(Component 与 resource 的归属)、bevy-ecs-composition(组件如何生成与查询)。
互补:bevy-app-resources(围绕这些资源的调度与插件)。
下游/消费者:Inventory、stats 和 save 技能覆盖物品、属性与持久化。
主技能:bevy-master(Bevy 路由)。