资源

可参考的官方例子: asset_loading.


Bevy 有一个灵活的系统来异步加载和管理你的游戏资源(运行在后台,不会在你的游戏中造成尖峰时延)。

你加载的资源的数据被存储在 Assets<T> 资源 中(资源类型列表)。

资源是通过 句柄 的设计模式来追踪的。句柄仅仅只是特定资源的轻量级 ID。

使用 AssetServer 加载资源

要从文件中加载资源,请使用 AssetServer

struct UiFont(Handle<Font>);

fn load_ui_font(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    let handle: Handle<Font> = server.load("font.ttf");

    // we can store the handle in a resource:
    //  - to prevent the asset from being unloaded
    //  - if we want to use it to access the asset later
    commands.insert_resource(UiFont(handle));
}

这将使资源加载在后台排队进行。被加载的资源需要稍等一些时间才可以被使用。资源就绪之前,在同一系统中,你可以通过资源句柄来使用资源,但实际资源数据仍需等就绪后才能真正变得有效果。

在资产加载之前你就可以使用资源句柄来生成你的 2D 精灵、3D 模型和用户界面,当资源准备就绪时,它们就会 "跳进来"。

需要知道的是,你可以随意多次地调用 asset_server.load 来加载同一资源,即使资源目前正在加载或已经加载,都只会为你返回相同的句柄。如果资源处于不可用或未加载的状态,则它将会被重新开始加载。

创建你自己资源

你也可以 Assets<T> 来手动添加资源。

如果你想用代码来创建它们(比如程序化生成),或者你已经通过其他方式得到了数据,这种方式就会很有用。

fn add_material(
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let new_mat = StandardMaterial {
        base_color: Color::rgba(0.25, 0.50, 0.75, 1.0),
        unlit: true,
        ..Default::default()
    };

    let handle = materials.add(new_mat);

    // do something with the handle
}

热重载

Bevy 可以监测到你何时修改了你的资源文件,并在游戏运行的过程中重新加载它们。更多信息请见本页面

句柄

句柄(Handle<T>)是引用特定资源的典型方式。当你在游戏中生成一些东西时,比如 2D 精灵、3D 模型或 UI,它们在各自的组件中将需要通过句柄来引用它们使用到的资源。

你可以把你的句柄存放在你认为方便的地方(比如 resources) 中),或者直接存放在实体的组件中。

如果你没有把句柄存储在任何地方,你仍可以通过调用 asset_server.load 从一个路径获得一个句柄。这样你就可以在需要访问资源的时候简单地调用,而不必费力地存储句柄。

访问资源

要从系统中访问实际资源数据,请使用 Assets<T> resource

你可以使用句柄或资源路径来获取你想要的资源:

struct SpriteSheets {
    map_tiles: Handle<TextureAtlas>,
}

fn use_sprites(
    handles: Res<SpriteSheets>,
    atlases: Res<Assets<TextureAtlas>>,
    images: Res<Assets<Image>>,
) {
    // Could be `None` if the asset isn't loaded yet
    if let Some(atlas) = atlases.get(&handles.map_tiles) {
        // do something with the texture atlas
    }

    // Can use a path instead of a handle
    if let Some(map_tex) = images.get("map.png") {
        // if "map.png" was loaded, we can use it!
    }
}

资源路径和标签

来自文件系统的资源可以通过 AssetPath 来识取,它由文件路径外加一个标签组成。标签用于区分同一文件中包含的多个资源。这方面的一个例子是 GLTF files 文件,它可以包含网格、场景、纹理、材质等等。

资源路径类型可以用一个路径字符串来初始化创建,标签(如果有的话)通过 # 号附在路径字符串之后。

fn load_gltf_things(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    // get a specific mesh
    let my_mesh: Handle<Mesh> = server.load("my_scene.gltf#Mesh0/Primitive0");

    // spawn a whole scene
    let my_scene: Handle<Scene> = server.load("my_scene.gltf#Scene0");
    commands.spawn_scene(my_scene);
}

关于使用 3D 模型的更多信息,请参阅 GLTF 页面

句柄和资源生命周期(垃圾回收)

句柄有内置的引用计数(类似于 Rust 中 的 RcArc)。这使得 Bevy 可以跟踪一个资源并得知它是否仍然正在被引用,并在它不再被需要时自动卸载它。

你可以使用 .clone() 来为同一个资源创建多个句柄。句柄克隆是一个低消耗的操作,但它被设计为明确的显性调用,以确保你知道你的代码中哪些地方创建了额外的句柄,并可能影响资源的生命周期。

弱引用句柄

句柄可以是 "强引用"(默认)或 "弱引用"。只有强引用句柄被计算在资源引用计数中,并能维持资源的被加载状态。弱引用句柄可以让你引用一个资源,同时在该资源没有了强引用句柄后仍会被卸载(即使弱引用句柄仍存在)。

你需要使用 .clone_weak() 而不是 .clone() 来创建弱引用句柄。

非类型化的句柄

Bevy 还有一个 HandleUntyped 类型。如果你需要无视资源类型以能够引用任何资源,请使用这种类型的句柄。

这种类型允许你存储一个包含混合类型资源的集合(如 VecHashMap)。

你可以使用 .clone_untyped() 创建一个非类型化的句柄。

非类型化资源加载

方便的是,如果你不知道文件是什么资源类型,AssetServer 支持非类型化加载。你也可以加载整个文件夹:

struct ExtraAssets(Vec<HandleUntyped>);

fn load_extra_assets(
    mut commands: Commands,
    server: Res<AssetServer>,
) {
    if let Ok(handles) = server.load_folder("extra") {
        commands.insert_resource(ExtraAssets(handles));
    }
}

它将尝试根据文件扩展名来检测每个资产的格式。

资源事件

如果你需要在资源被创建、修改或移除时执行特定的业务逻辑,你可以通过关注 AssetEvent 事件 来做出相应的反应。

struct MyMapImage {
    handle: Handle<Image>,
}

fn fixup_images(
    mut ev_asset: EventReader<AssetEvent<Image>>,
    mut assets: ResMut<Assets<Image>>,
    map_img: Res<MyMapImage>,
) {
    for ev in ev_asset.iter() {
        match ev {
            AssetEvent::Created { handle } |
            AssetEvent::Modified { handle } => {
                // a texture was just loaded or changed!

                let texture = assets.get_mut(handle).unwrap();
                // ^ unwrap is OK, because we know it is loaded now

                if *handle == map_img.handle {
                    // it is our special map image!
                } else {
                    // it is some other image
                }
            }
            AssetEvent::Removed { handle } => {
                // an image was unloaded
            }
        }
    }
}