ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Amethyst 聚合 PrefabData 定义指南:组合组件与嵌套 Prefab 数据的完整实践

Amethyst 聚合 PrefabData 定义指南:组合组件与嵌套 Prefab 数据的完整实践 【免费下载链接】amethystData-oriented and>项目地址https://gitcode.com/gh_mirrors/ame/amethyst点击查看免费下载导读Prefab预制体是 Amethyst 中把多个Component组合到实体上的资产机制而**聚合 PrefabDataAggregate PrefabData**则是其中的关键一环它用于定义一个封装了其他PrefabData的类型从而把多个组件、嵌套 prefab 甚至子资产打包成一个可序列化的数据结构。本文以 book/src/prefabs/how_to_define_prefabs_aggregate.md 为主体完整讲解聚合 PrefabData 的定义步骤、struct/enum 两种形态、同组件写冲突这一经典陷阱及其层级化解法并结合仓库中的prefab_custom、prefab_multi示例与amethyst_assets源码给出可运行的实战方案。读完本文你将能够为任意由多个已有 PrefabData 组成的场景写出正确的聚合类型与对应 prefab 文件。前置知识本文假设你已经理解 Amethyst 中 prefab 的基本表示存储形式与加载形式与资产加载流程可先阅读 Prefabs in Amethyst 与 Prefabs Technical Explanation。若某个Component还没有对应的PrefabData请先依据 指南选型页 中的表格选择并阅读相应指南Simple / Adapter / Asset / Multi-Handle为它创建PrefabData再来做聚合。一、准备依赖与导入聚合PrefabData依赖三样东西Amethyst 本体、serde的派生支持以及 Amethyst 提供的PrefabData派生宏。在Cargo.toml中声明[dependencies] amethyst .. # Minimum version 0.10 serde { version 1, features [derive] }文档基线为 Amethyst 0.10当前仓库即为 0.15 时代之后的结构amethyst::derive模块在 src/lib.rs 中由pub use amethyst_derive as derive;导出。随后在代码中导入所需条目use amethyst::{ assets::{PrefabData, ProgressCounter}, derive::PrefabData, ecs::Entity, Error, }; use serde::{Deserialize, Serialize};说明PrefabDatatrait与ProgressCounter进度计数器来自amethyst::assetsPrefabDataderive 宏来自amethyst::derive注意与同名 trait 区分Entity来自amethyst::ecs是实例化时的目标实体类型Error是 Amethyst 的全局错误类型Deserialize/Serialize让聚合类型可被 RON/JSON 等格式序列化见 Prefabs Technical Explanation 中RonFormat/JsonFormat对serde::Deserialize的要求。二、定义聚合 PrefabData 类型2.1 struct 形态封装多个组件最典型的聚合是一个实体需要挂多个组件。只要每个字段都实现了PrefabData派生宏就会在加载时递归调用各字段的PrefabData方法把组件依次附加到实体上。在下面的示例中Named、Position、Weapon均各自派生derive了PrefabData#[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)] #[prefab(Component)] #[serde(deny_unknown_fields)] pub struct Position(pub f32, pub f32, pub f32); /// **注意** 聚合类型中的每个字段都必须在 prefab 文件中给出。 /// 如果某个字段未指定prefab 将加载失败。 #[derive(Deserialize, Serialize, PrefabData)] #[serde(deny_unknown_fields)] pub struct Player { name: Named, position: Position, }关键点Player不是Component它只是一个实现了PrefabData的聚合载体聚合类型不需要#[prefab(Component)]属性——该属性只用于此类型本身就是组件的简单派生场景见 Simple 指南。聚合派生生成的代码负责在加载与实例化时调用各字段对应的PrefabData方法将组件附着到实体上#[serde(deny_unknown_fields)]让反序列化遇到未知字段时报错详见第四节。仓库中的prefab_multi示例正是这一形态examples/prefab_multi/main.rs中定义了#[derive(Debug, Deserialize, Serialize, PrefabData)] #[serde(deny_unknown_fields)] pub struct Player { player: Named, position: Position, }并通过PrefabLoader_, Player加载prefab/prefab_multi.ron见 examples/prefab_multi/main.rs。2.2 enum 形态在同一 prefab 中混合多种实体当前 prefab 实现要求entities列表中每一个PrefabEntity的data字段必须是同一类型。因此若想在一个 prefab 文件里同时实例化玩家与武器两种不同的实体就必须定义一个实现PrefabData的枚举每个变体按与 struct 相同的方式被处理。#[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)] #[prefab(Component)] #[serde(deny_unknown_fields)] pub struct Position(pub f32, pub f32, pub f32); #[derive(Clone, Copy, Component, Debug, Deserialize, Serialize, PrefabData)] #[prefab(Component)] pub enum Weapon { Axe, Sword, } /// 所有字段都实现 PrefabData。 /// /// **注意** 如果字段是 Option_ 类型且在 prefab 中未指定 /// 它将默认取值为 None。 #[derive(Debug, Deserialize, Serialize, PrefabData)] #[serde(deny_unknown_fields)] pub enum CustomPrefabData { Player { name: Named, position: OptionPosition, }, Weapon { weapon_type: Weapon, position: OptionPosition, }, }这里出现了两种字段形态对应PrefabData提供的两条特殊规则普通字段如name: Named必须在 prefab 中显式给出否则加载失败Option_字段如position: OptionPosition可以省略缺省为None。OptionT的 blanket 实现是资产系统内置的见 Prefabs Technical Explanation。三、关键限制同一 Component 的写访问冲突这是聚合PrefabData最容易踩的坑尤其是 enum 形态。3.1 问题描述构建PrefabData尤其是 enum 形态时有一个重要限制在同一个PrefabData及其所有嵌套PrefabData中不允许有两个字段访问同一个Component除非全部是只读访问。这一限制即使在枚举的不同变体之间也成立——因为 Amethyst 底层的 ECS 系统基于静态类型决定资源访问无法判断同一时刻只有一个变体会被访问。因此下面的定义在加载时会于运行时失败#[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)] #[prefab(Component)] #[serde(deny_unknown_fields)] pub struct SpecialPower; #[derive(Debug, Deserialize, Serialize, PrefabData)] #[serde(deny_unknown_fields)] pub enum CustomPrefabData { MundaneCreature { sprite: SpriteScenePrefab, }, MagicalCreature { special_power: SpecialPower, sprite: SpriteScenePrefab, }, }问题的根源在于两个变体中的SpriteScenePrefab文档时代的amethyst::renderer::sprite::prefab类型会写入Transform及若干其他公共组件都需要对同一批组件做可变写入。由于 ECS 只能按静态类型推断资源访问它无法确定同一时刻只会访问其中一个SpriteScenePrefab于是尝试对同一个组件进行双重可变借用double mutable borrow最终失败。3.2 解法把 PrefabData 定义成层级结构解决思路是重新组织数据结构让每个组件在整个PrefabData树中只出现一次。把差异部分enum 变体与公共部分共享的SpriteScenePrefab拆开公共部分提升为外层 struct 字段enum 只承载真正互斥的数据#[derive(Clone, Copy, Component, Debug, Default, Deserialize, Serialize, PrefabData)] #[prefab(Component)] #[serde(deny_unknown_fields)] pub struct SpecialPower; #[derive(Debug, Deserialize, Serialize, PrefabData)] #[serde(deny_unknown_fields)] pub enum CreatureDetailsPrefab { MundaneCreature {}, MagicalCreature { special_power: SpecialPower }, } #[derive(Debug, Deserialize, Serialize, PrefabData)] #[serde(deny_unknown_fields)] pub struct CustomPrefabData { sprite: SpriteScenePrefab, creature_details: CreatureDetailsPrefab, }现在SpriteScenePrefab在整个树中只出现一次位于最外层 structSpecialPower也只在MagicalCreature变体中出现一次不再有重叠的可变访问运行时即可正常加载。四、serde 属性default 与 deny_unknown_fields聚合PrefabData的可序列化行为由两个 serde 容器属性控制属性作用缺省行为#[serde(default)]允许字段在 prefab 中缺省反序列化时使用字段类型的Default实现字段必须在 prefab 中显式给出否则加载失败#[serde(deny_unknown_fields)]反序列化时遇到未知字段立即报错未知字段被静默忽略#[derive(Debug, Deserialize, Serialize, PrefabData)] #[serde(default, deny_unknown_fields)] pub struct Player { name: Named, // 缺省时使用 Named::default() position: Position, // 缺省时使用 Position::default() }两点实践建议deny_unknown_fields能帮你暴露 prefab 文件中的拼写错误例如把position写成positoin强烈建议总是加上default与Option_字段二者选其一即可实现可省略但Option表达该组件可选地存在更符合 ECS 语义缺省即不附加组件而default表达用默认值填充。文档给出的聚合示例统一使用deny_unknown_fieldsOption的组合。五、在 prefab 文件中使用聚合类型定义好类型后即可在.ron格式的 prefab 文件中使用。两种形态对应两种写法。5.1 struct 聚合的 prefab 文件#![enable(implicit_some)] Prefab( entities: [ PrefabEntity( data: Player( name: Named(name: Zero), position: Position(1.0, 2.0, 3.0), ), ), ], )5.2 enum 聚合的 prefab 文件enum 聚合可在一个文件里实例化多个不同类型的实体并通过parent建立实体间的父子关系parent的取值是列表内父实体的索引这里0指第一个实体 Player#![enable(implicit_some)] Prefab( entities: [ // Player PrefabEntity( data: Player( name: Named(name: Zero), position: Position(1.0, 2.0, 3.0), ), ), // Weapon PrefabEntity( parent: 0, data: Weapon( weapon_type: Sword, position: Position(4.0, 5.0, 6.0), ), ), ], )#![enable(implicit_some)]让Some(...)可以直接写作...例如OptionPosition字段写作Position(...)而非Some(Position(...))。PrefabEntity的data字段即聚合类型的一个变体parent字段可选缺省为None。仓库中 examples/prefab_custom/assets/prefab/prefab_custom.ron 正是上述 enum 形态的完整实现而 examples/prefab_multi/assets/prefab/prefab_multi.ron 则是 struct 形态的完整实现。注意两个文件头部都带有import指令注释用于在编辑器中定位对应的 Rust 类型定义。六、完整示例运行prefab_custom 与 prefab_multi仓库提供了两个可直接运行的完整示例分别演示聚合 enum 多实体与聚合 struct 单实体cargo run -p prefab_custom # superset prefabenum 聚合含父实体与子实体 cargo run -p prefab_multi # object prefabstruct 聚合单实体多组件6.1 prefab_custom 的加载与实例化流程examples/prefab_custom/main.rs 展示了从定义到实例化的完整链路定义聚合类型CustomPrefabDataenum含Player与Weapon两个变体见 main.rs在状态State中通过PrefabLoader_, CustomPrefabData加载prefab/prefab_custom.ron并把返回的HandlePrefabCustomPrefabData存入状态let prefab_handle data .world .exec(|loader: PrefabLoader_, CustomPrefabData| { loader.load( prefab/prefab_custom.ron, RonFormat, mut self.progress_counter, ) });见 main.rs把 handle 作为组件push到实体上触发实例化(0..1).for_each(|_| { data.world.push((prefab_handle.clone(),)); });见 main.rs。由于 prefab 第一条记录Player对应持有 handle 的主实体后续记录Weapon会自动生成新实体并挂上Parent在update中通过progress_counter.is_complete()等待全部含子资产加载完成随后从AssetStoragePrefabCustomPrefabData读取已加载的 prefab 并打印实体与组件信息见 main.rs注册PrefabLoaderSystemDesc::CustomPrefabData到DispatcherBuilder见 main.rs它负责后台的加载与实例化系统。从源码结构看当前仓库中 prefab 的加载/实例化由amethyst_assets的 prefab 模块承担Prefab被定义为一种Assetamethyst_assets/src/prefab/assets.rs内含raw未烹制的 Legion World、cooked烹制后的世界、dependencies与递增的version字段而prefab_spawning_tickamethyst_assets/src/prefab/system.rs负责扫描持有HandlePrefab的实体将烹制好的世界克隆到当前 World 并维护实体映射。这也解释了文档所述的三阶段生命周期加载 → 子资产加载load_sub_assets通过ProgressCounter追踪→ 实例化add_to_entity详见 Prefabs Technical Explanation。6.2 运行输出示例以prefab_custom为例加载完成后会以表格形式打印持有 prefab handle 的主实体Player含Named与Position以及由parent: 0关联的子实体Weapon含Parent、Position与Weapon组件。如果切换(0..1)为更大的循环次数多个主实体各会生成一套带正确父子关系的实体。七、底层原理速览derive 宏生成了什么聚合PrefabData的 derive 宏amethyst::derive::PrefabData会为该类型生成PrefabDatatrait 的实现核心包括SystemData声明加载/实例化该数据需要从 World 取用的资源集合例如写入哪些Component存储add_to_entity在实例化阶段把数据附着到可能新创建的实体上load_sub_assets可选若字段引用了其他资产如AssetPrefab在此异步触发子资产加载并借助ProgressCounter向上游汇报进度。关于#[prefab(Component)]属性在聚合派生中不需要它只有当某个字段是自身无PrefabData、需要直接插入存储的普通Component时才在该字段上加#[prefab(Component)]让宏执行简单的WriteStorage插入见 Prefabs Technical Explanation 中的MyScenePrefab示例。另外PrefabData提供的 blanket 实现包括OptionT任意T: PrefabData以及元组最大 20 元组这使得(OptionGraphicsPrefab.., OptionTransform, ..)这类元组聚合也能直接作为 prefab 数据类型使用——这也是仓库多个示例 prefab 文件的通用做法。八、关联指南聚合是 prefab 指南体系中的一环配合以下文档可构建完整认知指南选型总览Prelude按组件的序列化形态Self/ 多构造 / 组件子集 /HandleA/ 多Handle选择对应指南How to Define Prefabs: Simple为自身完全可序列化的Component定义PrefabDataHow to Define Prefabs: Adapter为有多种构造方式的Component定义适配器How to Define Prefabs: Asset为需要运行时加载资产HandleA的组件定义PrefabDataHow to Define Prefabs: Multi-Handle为内部持有多个Handle_的组件定义PrefabDataPrefabs in Amethyst 与 Prefabs Technical Explanationprefab 的整体概念与底层实现。小结聚合PrefabData是 Amethyst prefab 体系的粘合剂用 struct 把多个组件打包成一个实体模板用 enum 让一个 prefab 文件能够产出多种实体并建立父子关系。写作时牢记三条铁律聚合类型不加#[prefab(Component)]各字段递归实现PrefabData任何Component在整棵 PrefabData 树中只能出现一次写访问否则运行时双重可变借用失败——用公共字段上提 差异部分下沉到 enum的层级化结构规避deny_unknown_fields常开让 prefab 文件中的拼写错误在加载期暴露。参照 examples/prefab_custom 与 examples/prefab_multi 两个示例动手运行即可完整掌握这一机制。赞分享【免费下载链接】amethystData-oriented and>项目地址https://gitcode.com/gh_mirrors/ame/amethyst点击查看免费下载相关推荐TanStack Table 聚合函数定义 AggregationFnDef 完全指南自定义分组聚合与嵌套结果合并TanStack Table 聚合函数定义 AggregationFnDef 完全指南自定义分组聚合与嵌套结果合并 本文以 TanStack Table 前端UI组件TanStack TablePreact聚合指南行聚合、分组聚合与自定义聚合函数实战TanStack TablePreact聚合指南行聚合、分组聚合与自定义聚合函数实战 本文以 TanStack Table 的 Preact 适配器 t前端UI组件LiteFlow自定义聚合多结果数据聚合的完整指南LiteFlow自定义聚合多结果数据聚合的完整指南 LiteFlow是一款轻量、快速、稳定、可编排的组件式规则引擎和流程引擎它通过声明式语法让复杂的业务逻辑后端流程编排工作流自动化人工智能AI Agent上一篇如何用Drain3实现高效日志流处理5分钟快速上手教程下一篇Feather国际化工作流多语言翻译与更新管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表