资源管理
资源管理 — glTF 加载、动画、程序化网格
anvilkit-assets 提供资产加载与管理功能,包括 glTF 模型、骨骼动画和程序化网格生成。
glTF 加载
use anvilkit_assets::gltf_loader::GltfLoader;
let scene = GltfLoader::load("assets/models/character.glb")?;
for mesh in &scene.meshes {
println!("Mesh: {} vertices, {} triangles",
mesh.positions.len(),
mesh.indices.len() / 3
);
}骨骼动画
支持三种插值模式:
| 模式 | 说明 |
|---|---|
Linear | 线性插值(平移/缩放使用 lerp,旋转使用 slerp) |
Step | 阶跃函数,无过渡 |
CubicSpline | 三次样条插值,曲线更平滑 |
use anvilkit_assets::animation::*;
let mut player = AnimationPlayer::new();
player.play(animation_clip, true); // loop = true
player.update(dt);
let joint_transforms = player.joint_transforms();程序化网格生成
MeshData 提供常用几何体的工厂方法:
use anvilkit_assets::mesh::MeshData;
let cube = MeshData::generate_box(1.0); // 24 顶点, 36 索引
let plane = MeshData::generate_plane(10.0); // 4 顶点, 6 索引(XZ 平面, +Y 法线)
let sphere = MeshData::generate_sphere(1.0, 32, 16); // UV 球体
// 转换为 GPU 上传格式
let pbr_vertices = cube.to_pbr_vertices();| 方法 | 参数 | 说明 |
|---|---|---|
generate_box(size) | f32 | 居中立方体,6 面独立法线 |
generate_plane(size) | f32 | 原点居中 XZ 平面,+Y 法线 |
generate_sphere(radius, segments, rings) | f32, u32, u32 | UV 球体 |
to_pbr_vertices() | — | 转换为交错 InterleavedPbrVertex(48 字节/顶点) |
StandardMaterial
高级 PBR 渲染使用 StandardMaterial(位于 anvilkit-render):
use anvilkit_render::renderer::standard_material::StandardMaterial;
commands.spawn((
mesh_handle,
StandardMaterial::new()
.with_base_color([1.0, 0.2, 0.1, 1.0])
.with_metallic(0.0)
.with_roughness(0.7),
Transform::default(),
));render_extract_system 自动从 (MeshHandle, StandardMaterial, GlobalTransform) 查询生成 DrawCommand。
材质
use anvilkit_assets::material::Material;
let material = Material {
base_color: [1.0, 0.8, 0.6, 1.0],
metallic: 0.0,
roughness: 0.7,
albedo_texture: Some(texture_handle),
normal_texture: None,
metallic_roughness_texture: None,
};资产服务器
支持同步和异步加载:
use anvilkit_assets::asset_server::AssetServer;
let mut server = AssetServer::new("assets/");
// 同步:注册加载意图(无 I/O)
let handle = server.load::<Mesh>("models/tree.glb");
// 异步:在后台线程中加载文件(固定线程池,1-4 worker)
let async_handle = server.load_async::<Vec<u8>>("textures/atlas.png");
// 在游戏循环中:轮询已完成的加载
server.process_completed();
for result in server.drain_completed() {
match result.data {
Ok(bytes) => { /* 解析并使用加载的数据 */ },
Err(err) => { /* 处理错误 */ },
}
}DependencyGraph
DependencyGraph 使用类型安全的 AssetId 句柄(而非裸 u64)追踪资产之间的父子关系。它集成在 AssetServer 中,驱动级联卸载。
| 方法 | 签名 | 说明 |
|---|---|---|
add_dependency | (&mut self, parent: AssetId, child: AssetId) | 记录 parent 依赖于 child。 |
remove_asset | (&mut self, id: AssetId) -> Vec<AssetId> | 移除资产并返回孤立的子资产(没有剩余父资产的子资产)。 |
dependents | (&self, id: AssetId) -> &[AssetId] | 列出所有依赖于给定资产的资产。 |
dependencies | (&self, id: AssetId) -> &[AssetId] | 列出给定资产依赖的所有资产。 |
use anvilkit_assets::dependency::DependencyGraph;
let mut graph = DependencyGraph::new();
graph.add_dependency(scene_id, mesh_id);
graph.add_dependency(scene_id, texture_id);
// Query
let deps = graph.dependencies(scene_id); // [mesh_id, texture_id]
let parents = graph.dependents(mesh_id); // [scene_id]AssetCache(内容哈希缓存)
AssetServer 包含基于内容哈希的缓存,用于去重加载:
- 当资产在
process_completed()中完成加载时,计算其内容哈希并存储在缓存中。 - 后续的
load_async调用在缓存命中时完全跳过磁盘读取,立即返回缓存数据。 - 当资产被卸载时,缓存会自动清理。
这对游戏代码是透明的 — 无需修改任何 API 即可享受缓存带来的性能提升。
级联卸载
AssetServer::unload(id) 执行完整的依赖感知清理:
- 在
DependencyGraph上调用remove_and_cascade查找孤立的子资产。 - 清理被卸载资产的资产缓存、解析存储和状态追踪。
- 递归卸载没有其他父资产的子资产。
// Unload a scene — its exclusive meshes and textures are cleaned up automatically
server.unload(scene_id);类型化解析存储
AssetServer 提供类型化存储,用于存放解析后的资产值,将原始字节加载与应用层类型桥接:
| 方法 | 签名 | 说明 |
|---|---|---|
insert_parsed | <T: 'static>(&mut self, id: AssetId, value: T) | 存储任意类型的解析后资产值。 |
get_parsed | <T: 'static>(&self, id: AssetId) -> Option<&T> | 获取之前存储的解析后值。 |
// After loading raw bytes, parse and store the result
let mesh: Mesh = parse_mesh(&raw_bytes);
server.insert_parsed::<Mesh>(mesh_id, mesh);
// Later, retrieve the parsed mesh
if let Some(mesh) = server.get_parsed::<Mesh>(mesh_id) {
// Use the mesh
}热重载
文件监视功能,用于开发期间自动刷新资产(需要 hot-reload feature)。变更事件经过 200ms 防抖处理,同时支持 Remove 事件。
use anvilkit_assets::hot_reload::FileWatcher;
let mut watcher = FileWatcher::new("assets").unwrap();
// 在游戏循环中:
for path in watcher.poll_changes() {
if FileWatcher::is_shader(&path) {
// 使用新着色器重建渲染管线
} else if FileWatcher::is_texture(&path) {
// 将纹理重新上传到 GPU
}
}