Latest commit 4c0b78b

资产

相关的官方文档:asset_loading


Bevy用一个灵活的系统来异步加载和管理你的游戏资产(在后台,不会造成游戏的滞后高峰)。

加载资产时获得的数据存储在Assets<T>资源。

Bevy用句柄来跟踪资产。句柄只是特定资产的轻量级ID。

用AssetServer加载资产

AssetServer资源从文件中加载资产。

#![allow(unused)]
fn main() {
struct UiFont(Handle<Font>);

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

    // 可以把句柄存储在资源中,既可以防止资产被卸载,又能在以后访问
    commands.insert_resource(UiFont(handle));
}

}

这会使资产加载在后台进行排队,所以资产将需要一些时间才能使用。不能在相同的系统中立即访问实际数据,但是可以使用句柄。

可以在资产加载之前,就使用句柄生成2D精灵、3D模型和UI,当资产准备好后它们就会“弹”进来。

请注意,可以多次调用asset_server.load,即使资产正在加载或已经加载。它只是提供相同的句柄。如果资产不可用,它会开始加载。[TODO:不理解]

创建自定义资产

可以手动添加资产到Assets<T>

Assets<T>可以用代码来创建资产(比如用于程序生成),或者用某些数据来创建资产。

#![allow(unused)]
fn main() {
fn add_material(
    mut material: ResMut<Assets<StandardMaterial>>,
) {
    let new_mat = StandardMaterial {
        base_color: Color::rgba(0.25, 0.50, 0.75, 1.0),
        unlit: true,
        ..Default::default()
    };

    material.add(new_mat);
}
}

热加载

Bevy可以实时检测资源文件的改变,在游戏运行时重载资源。查看[Hot-Reloading Assets]。

句柄

句柄是引用特定资产的典型方法。 把事物(例如2D精灵,3D模型或UI)生成到游戏中时,它们各自的组件会需要它们所用资产的句柄。

可以把句柄存储在方便使用的地方(比如[资源])。

如果没有在任何地方存储句柄,可以通过调用asset_server.load从路径中生成句柄。如果不需要存储句柄,可以随时随地生成句柄。

知识点:

访问资产

Assets<T>资源从系统中访问实际资产数据。

可以使用句柄或资产路径来标记所需的资产:

#![allow(unused)]
fn main() {
struct SpriteSheets {
    map_tiles: Handle<TextureAtlas>,
}

fn use_sprites(
    handles: Res<SpriteSheets>,
    atlases: Res<Assets<TextureAtlas>>,
    textures: Res<Assets<Texture>>,
) {
    // 如果资产未加载,返回`None`
    if let Some(atlas) = atlases.get(&handles.map_tiles) {
        // 做一些事
    }

    // 可以用路径代替句柄
    if let Some(map_tex) = textures.get("map.png") {
        // 做一些事
    }
}
}

资产路径和标签

文件系统中的资产可以通过AssetPath来标记,AssetPath由文件路径+标签组成。标签用于同一文件中包含多个资产的情况。一个例子是GLTF文件,它可以包含meshes、场景、纹理、材质等。

资产路径可以由一个字符串创建,并在#符号后附加标签(如果有)。

#![allow(unused)]
fn main() {
fn load_gltf_things(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    // 获取特定的网格
    let my_mesh: Handle<Mesh> = server.load("my_scene.gltf#Mesh0/Primitive0");

    // 生成整个场景
    let my_scene: Handle<Scene> = server.load("my_scene.gltf#Scene0");
    commands.spawn_scene(my_scene);
}
}

(对于GLTF,Bevy会根据文件中每个对象的索引生成像Scene0这样的标签,但如果GLTF文件包括从3D建模软件导出的名称/标签,Bevy也会提供这些标签) 有关使用三维模型的更多信息,请查看[GLTF]页面。

句柄和资产生命周期(垃圾收集)

句柄有内置的引用计数(类似于Rust中的Rc/Arc)。这使得Bevy可以跟踪一个资产是否还需要,并在不再需要时自动卸载。

可以使用.clone()来创建同一资产的多个句柄。克隆是一种廉价的操作,但它是显式的,以确保你知道代码中哪些地方会创建额外的句柄,并可能影响资产的生命周期。

弱句柄

句柄可以是“强”(默认)或“弱”。只计算强句柄并导致资产保持加载状态。弱句柄可以引用资产,同时允许它在没有强句柄时进行垃圾收集。

可以使用.clone_weak()代替.clone()来创建弱句柄。

无类型句柄

Bevy有HandleUntyped类型,用来引用任何类型的资产。

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

.clone_untyped()创建一个无类型句柄。

无类型加载

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

#![allow(unused)]
fn main() {
struct ExtraAssets(Vec<HandleUntyped>);

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

它会尝试根据文件扩展名来检测每个资产的格式。[TODO:“它”指什么?]

资产事件

可以用AssetEvent在资产完成加载、修改或删除时执行特定的操作。

#![allow(unused)]
fn main() {
struct MapTexture {
    handle: Handle<Texture>,
}

fn fixup_textures(
    mut ev_asset: EventReader<AssetEvent<Texture>>,
    mut assets: ResMut<Assets<Texture>>,
    map_tex: Res<MapTexture>,
) {
    for ev in ev_asset.iter() {
        match ev {
            AssetEvent::Created { handle } |
            AssetEvent::Modified { handle} => {
                // 刚刚加载或更改了纹理

                let texture = assets.get_mut(handle).unwrap();
                // ^ unwrap返回Ok,因为当前已经加载成功

                if *handle == map_tex.handle {
                    // 这是特殊的地图纹理
                } else {
                    // 这是其他纹理
                }
            }
            AssetEvent::Removed { handle} => {
                // 纹理被卸载
            }
        }
    }
}
}