Bevy Resource

15 阅读3分钟

Resource-Management.jpeg

Bevy中的资源是用来实现存储某种数据类型单个全局实例的方式,它和Entity最大的不同就是,资源是全局的。通常,游戏中的配置或者设置项,都可以用Resource来实现。

创建资源

想要创建新的资源类型,派生Resource trait即可:

#[derive(Resource)]
struct GoalsReached {
    main_goal: bool,
    bonus: u32,
}

使用资源的时候,同一个类型只能有一个实例。如果想要有多个实例,可能Entity会更合适。

访问资源

如果要从System中访问资源,需要使用Res或者ResMut

fn my_system(
    // 获得 GoalsReached 的可变访问,如果不存在会 panic 
    mut goals: ResMut<GoalsReached>,
    // 获得 MyOtherResource 的只读访问,如果不存在会 panic 
    other: Res<MyOtherResource>,
    // 使用 Option 防止 MyFancyResource 不存在导致的 panic 问题
    mut fancy: Option<ResMut<MyFancyResource>>,
) {
    if let Some(fancy) = &mut fancy {
        // 如果 MyFancyResource 存在
    }
}

管理资源

如果我们需要在运行时增加或者删除资源,可以使用Commands

fn my_setup(mut commands: Commands) {
    // 添加一个资源,如果资源已经存在,会被覆盖
    commands.insert_resource(GoalsReached { main_goal: false, bonus: 100 });
    // 如果资源不存在,会插入,如果存在,会忽略,资源的默认值参考 Default 实现
    commands.init_resource::<MyFancyResource>();
    // 移除资源
    commands.remove_resource::<MyOtherResource>();
}

当然,我们可以在创建App的时候,初始化资源,这样在App的整个生命周期,资源都会存在。

App::new()
    .add_plugins(DefaultPlugins)
    .insert_resource(StartingLevel(3))
    .init_resource::<MyFancyResource>()
    // ...

初始化资源

在使用init_resource初始化资源的时候,我们需要实现Default trait来给资源提供默认值:

// 直接 derive,每个值均会使用其默认值,rust中很多类型都已经实现了 Default trait
#[derive(Resource, Default)]
struct GameProgress {
    game_completed: bool,
    secrets_unlocked: u32,
}

#[derive(Resource)]
struct StartingLevel(usize);

// 实现 Default trait 来自定义默认值
impl Default for StartingLevel {
    fn default() -> Self {
        StartingLevel(1)
    }
}

如果实现逻辑比较复杂,可以考虑使用FromWorld

#[derive(Resource)]
struct MyFancyResource { /* code */ }

impl FromWorld for MyFancyResource {
    fn from_world(world: &mut World) -> Self {
        
        // 可以通过 world 访问别的资源
        {
            let mut x = world.resource_mut::<MyOtherResource>();
            x.do_mut_stuff();
        }

        // 可以记载 asset
        let font: Handle<Font> = world.resource::<AssetServer>().load("myfont.ttf");

        MyFancyResource { /* code */ }
    }
}

这里需要注意,过多的使用FromWorld会造成代码的混乱,因为如果一个资源的初始化依赖别的资源或者逻辑,必然会造成一条依赖链路,这样在修改其他资源或者逻辑的时候,会影响整个链路。

使用建议

  • 通常,游戏的全局设置会是使用Resource的一个不错的选择。
  • 可能有时候对于使用实体或者资源不是很明确,一般我们有这样一个原则:如果是全局访问,则使用资源;如果是通过ECS系统访问,则使用实体。即使游戏中只有一件事(例如单人游戏中的玩家),使用实体可能很合适,而不是资源,因为实体由多个组件组成,其中一些可能与其他实体通用。这可以使游戏逻辑更灵活。例如,游戏角色中“生命值/伤害等”,这些在玩家和敌人中都会使用。