加载状态与自定义加载器
轮询加载状态
AssetServer::load_state(&handle) 返回资产的当前状态(第 14 章已详细讲过)。对 glTF 这种带依赖的大资产,常用 is_loaded_with_dependencies 判断"连同纹理、网格全部到齐":
rust
use bevy::asset::LoadState;
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, check_loaded)
.run();
}
#[derive(Resource)]
struct Helmet(Handle<WorldAsset>);
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
));
commands.spawn((
DirectionalLight {
contact_shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 25.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let handle = asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
commands.insert_resource(Helmet(handle));
}
fn check_loaded(
mut commands: Commands,
helmet: Res<Helmet>,
asset_server: Res<AssetServer>,
mut done: Local<bool>,
) {
if *done {
return;
}
match asset_server.load_state(&helmet.0) {
LoadState::Loaded => {
*done = true;
commands.spawn(WorldAssetRoot(helmet.0.clone()));
println!("helmet loaded");
}
LoadState::Failed(err) => {
*done = true;
println!("load failed: {err}");
}
_ => {}
}
}Listing 23-7:轮询加载状态,到货后再 spawn 场景(examples/listing-23-07.rs)
console
cargo run -p ch23-gltf --example listing-23-07这个模式在需要延迟 spawn 或显示加载进度时很有用。注意 LoadState 需要从 bevy::asset::LoadState 显式引入。
自定义资产加载器
Bevy 的资产系统是可扩展的。任何格式,只要实现了 AssetLoader trait,就能用 AssetServer::load 加载。下面用一个简单的键值对格式 .cfg 演示:
rust
use bevy::asset::io::Reader;
use bevy::asset::{AssetLoader, LoadContext};
use bevy::prelude::*;
use thiserror::Error;
#[derive(Asset, TypePath, Debug)]
struct Config {
entries: Vec<(String, String)>,
}
#[derive(Debug, Error)]
enum ConfigLoaderError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("not utf-8: {0}")]
NotText(#[from] std::string::FromUtf8Error),
#[error("bad line {line}: {content:?}")]
BadLine { line: usize, content: String },
}
#[derive(Default, TypePath)]
struct ConfigLoader;
impl AssetLoader for ConfigLoader {
type Asset = Config;
type Settings = ();
type Error = ConfigLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>,
) -> Result<Config, ConfigLoaderError> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let text = String::from_utf8(bytes)?;
let mut entries = Vec::new();
for (i, line) in text.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let Some((key, value)) = line.split_once('=') else {
return Err(ConfigLoaderError::BadLine {
line: i + 1,
content: line.to_string(),
});
};
entries.push((key.trim().to_string(), value.trim().to_string()));
}
Ok(Config { entries })
}
fn extensions(&self) -> &[&str] {
&["cfg"]
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_asset::<Config>()
.init_asset_loader::<ConfigLoader>()
.add_systems(Startup, setup)
.add_systems(Update, print_config)
.run();
}
#[derive(Resource)]
struct ConfigHandle(Handle<Config>);
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let handle = asset_server.load("config/app.cfg");
commands.insert_resource(ConfigHandle(handle));
}
fn print_config(
handle: Res<ConfigHandle>,
configs: Res<Assets<Config>>,
mut printed: Local<bool>,
) {
if *printed {
return;
}
let Some(config) = configs.get(&handle.0) else {
return;
};
*printed = true;
for (k, v) in &config.entries {
println!("{k} = {v}");
}
}Listing 23-8:自定义 AssetLoader——教 Bevy 读 .cfg 文本格式(examples/listing-23-08.rs)
console
cargo run -p ch23-gltf --example listing-23-08要点:
Assettrait:#[derive(Asset)]把自定义类型注册为资产。Config结构体存解析后的键值对AssetLoadertrait:三个关联类型——Asset(产出类型)、Settings(加载参数,不用时填())、Error(错误类型)load方法:异步,接收Reader读取原始字节,返回解析后的资产。LoadContext可以用来加载子资产(如纹理)extensions方法:返回这个加载器认领的文件扩展名。.cfg文件会自动路由到ConfigLoader- 注册:
app.init_asset::<Config>().init_asset_loader::<ConfigLoader>()——先给类型上户口,再注册加载器
glTF 加载器本身就是这么实现的——GltfLoader 认领 ["gltf", "glb"] 扩展名,load 方法里调用 gltf crate 解析二进制,产出 Gltf、Scene、Mesh、StandardMaterial 等一串资产。DefaultPlugins 帮你完成了注册,所以前几节的代码不需要这一步。