Template 与 FromTemplate
Template 是 BSN 的底层机制——一个"带 ECS 上下文的构造器"。FromTemplate 把组件和它的 Template 关联起来。
Default + Clone vs FromTemplate
两种方式让组件在 bsn! 里可用:
| 方式 | 适用场景 | 示例 |
|---|---|---|
Default + Clone | 所有字段都有合理默认值 | Health(i32)、Score(usize) |
FromTemplate | 有字段需要运行时解析 | Handle<T>、Entity 引用 |
规则:不能同时 derive FromTemplate 和 Default——两者都会提供 FromTemplate 实现,会冲突。
FromTemplate derive
当组件有 Handle<T> 字段时,必须用 FromTemplate:
fn setup(world: &mut World) {
world.spawn_scene(bsn! {
#Icon
SpriteIcon {
image: "textures/icon.png",
tint: Color::WHITE,
}
}).unwrap();
println!("已生成带 Handle 的实体");
}Listing 32-5:FromTemplate 与 Handle 字段
运行:
cargo run -p ch32-scenes --example listing-32-05已生成带 Handle 的实体"textures/icon.png" 是一个字符串字面量,bsn! 宏检测到 image 字段是 Handle<Image> 类型,自动将其转换为 HandleTemplate<Image>。在 Scene::resolve 阶段,HandleTemplate 调用 AssetServer::load 完成实际加载。
HandleTemplate
HandleTemplate 是 Handle<T> 的 Template 实现。它接受字符串路径,在解析时调用 AssetServer::load:
// 在 bsn! 里直接写字符串路径
Sprite { image: "player.png" }
// 等价于手写
let handle: HandleTemplate<Image> = "player.png".into();如果资产已加载,AssetServer::load 返回已有 Handle,不会重复加载。
EntityTemplate
#Name 引用在组件字段中产生 EntityTemplate。组件用 Entity 字段时,FromTemplate 自动把 EntityTemplate 解析为实际 Entity:
#[derive(Component, FromTemplate)]
struct TargetEntity {
target: Entity, // #Name 引用自动解析为 Entity
}
bsn! {
#Player
Children [
#Minion
TargetEntity { target: #Player }
]
}FontSourceTemplate
字体资产也有专门的 Template:
bsn! {
Text("Hello")
TextFont {
font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
font_size: px(24.0),
}
}FontSourceTemplate 处理字体路径到 FontSource 的转换。
自定义 Template
对于需要 World 访问的复杂构造,用 template(|ctx| { ... }) 语法:
bsn! {
#Foo
template(|ctx| {
Foo(ctx.resource::<MyAssetCollection>().get("generated_asset_name"))
})
}ctx 是 TemplateContext,提供 EntityWorldMut 和命名实体引用。
Template 值注入
已有组件实例可以用 template_value(...) 注入:
let transform = Transform::from_translation(translation);
bsn! {
#Foo
template_value(transform)
}这绕过了 Template 系统,直接插入值。适合已经有构造好的组件实例的场景。