diff --git a/Docs/动画系统需求整理.md b/Docs/动画系统需求整理.md index d936b98a5..3a5ad36bd 100644 --- a/Docs/动画系统需求整理.md +++ b/Docs/动画系统需求整理.md @@ -1289,46 +1289,110 @@ float Speed { get; } ## 8. 分阶段实施计划 -### 8.1 第一阶段:原型验证 +实施过程由同一套整体架构约束,但按可独立验证的阶段推进。第一阶段即确定完整序列化数据模型,后续阶段不得通过改变既有字段语义完成扩展;如需调整已落地资产结构,必须先补充迁移方案。 + +每个阶段完成后应至少执行代码编译、该阶段针对性测试和对应验收,再进入下一阶段。阶段交付可以暂时不具备完整第一版编辑体验,但不得以一次性临时代码绕过资产所有权、引用稳定性、Undo / Redo 或运行时语义。 + +### 8.1 第一阶段:数据模型与运行时核心 目标: -- 实现最小 FrameClip。 -- 实现 SpriteRenderer / Image 播放。 -- 实现基础 Inspector 预览。 -- 从一个简单 PNG / JSON 生成 Clip。 +- 确定并实现第一版完整序列化数据模型,包括 FrameClip、FrameAnimationGraph、AnimationNode、AnimationEdge、AnimationFlow、ImportSource、Settings 和 EditorData。 +- 实现 Graph 的统一 playable id 解析、Flow 可达路径计算和基础数据校验。 +- 实现 FrameAnimationPlayer 对 SpriteRenderer / Image 的播放支持。 +- 实现直接 Clip 播放和线性 AnimationFlow 播放。 +- 实现播放替换、暂停、恢复、停止、结束行为、速度计算和大 deltaTime 跨帧推进。 +- 实现 FrameAnimationPlaybackHandle、完成回调、协程等待、标准 Task 等待和结构化失败结果。 验收: -- 不依赖 AnimatorController 播放序列帧。 -- 编辑器中可直接预览 Clip。 -- 修改来源后能刷新 Clip。 +- 可以使用手工构造的 FrameAnimationGraph,不依赖 AnimatorController 播放 Clip 和线性 Flow。 +- SpriteRenderer 与 Image 的首帧显示、空帧、结束行为和 HideTarget 语义一致。 +- 同一播放请求只结算一次,Completed / Replaced / Stopped / Failed 结果符合文档定义。 +- 大 deltaTime 可以跨过多帧和多个 Flow 节点并落到正确结果。 +- 无效 Graph、playable id、显示目标、空 Clip 和非法速度返回对应结构化错误。 -### 8.2 第二阶段:FrameAnimationGraph 与运行时播放 +### 8.2 第二阶段:Aseprite 导入与稳定刷新 目标: -- 实现 FrameAnimationGraph。 -- 实现 Graph 内 Clip、AnimationNode、AnimationEdge、AnimationFlow 的基础组织。 -- 实现按统一 playable id 播放 Clip 或 AnimationFlow。 -- 实现 FrameAnimationGraph 编辑器。 +- 实现 Aseprite JSON Object / Array 两种 frames 格式解析,并完整保留源顺序与 UTF-8 名称。 +- 实现 forward、reverse、pingpong 和 pingpong_reverse 的确定性帧序列展开。 +- 实现来源、Tag、Frame、Texture 尺寸、trimmed / rotated 和 playable id 冲突校验。 +- 实现新增、更新、Missing、不变和错误的导入差异模型。 +- 实现按 importSourceId + sourceTagName 对 Imported Clip 进行原地刷新。 +- 实现 Imported Clip sub-asset 的创建、Missing 恢复和字段所有权保护。 +- 实现 manageSpriteSlicing=false 时的只读 Sprite 匹配。 +- 实现 manageSpriteSlicing=true 时的 TextureImporter 差异确认、写入所有权检查和稳定 Sprite ID 更新。 +- 实现来源 hash 与单来源 / 全部来源的原子刷新流程。 验收: -- 代码可通过统一 playable id 播放 Graph 内 Clip 或 AnimationFlow。 -- 编辑器可集中预览和检查 Graph 内 Clip、节点图与 AnimationFlow。 +- Object / Array 两种 Aseprite JSON 均能得到相同语义的有序 SourceFrame。 +- 中文 Tag 和 frameName 在导入、刷新和资产保存后保持不变。 +- 新增、消失、重新出现和用户已重命名的 Tag 对应 Clip 均按稳定匹配规则处理。 +- 刷新 Imported Clip 不替换其 sub-asset,不破坏已有 Node 和 Flow 引用。 +- 自动切图更新同名 frameName 时保留 Sprite ID,并阻止同一 Texture 出现多个写入所有者。 +- 任一阻断错误出现时,不产生部分 Graph / Clip 更新;TextureImporter 更新失败时也不应用 Graph 变化。 -### 8.3 第三阶段:AnimationFlow 与演出工作流 +### 8.3 第三阶段:Graph 资产管理工作台 目标: -- 实现最小节点式 AnimationFlow。 -- 支持“播放一次 -> 进入 Idle loop”。 -- 提供 AnimationFlow 预览。 +- 实现单 Graph FrameAnimationGraph EditorWindow 的工具栏、资源浏览区、属性区、导入差异区和校验结果区。 +- 实现 Clips / Flows / Sources 浏览、搜索、筛选、排序、分组和对象定位。 +- 实现 Manual Clip 创建与帧表编辑,以及 Imported Clip 只读展示和复制为 Manual Clip。 +- 实现 Clip / Flow 正式重命名流程、Graph 内引用原子更新和外部字符串强警告。 +- 实现 Clip 移除、sub-asset 删除、共享外部 Manual Clip 提示和引用保护。 +- 实现 ImportSource 编辑、差异查看、单来源刷新和全部来源刷新入口。 +- 实现可定位的 Error / Warning / Info 校验面板。 +- 确保全部资产修改正确接入 SerializedObject、Undo / Redo、dirty 标记和保存恢复。 验收: -- 使用者可以不写代码配置并预览基础帧动画演出序列。 +- 可以在主工作台中创建、打开、保存和管理 FrameAnimationGraph 的 Clip、Flow 与 ImportSource。 +- Manual / Imported、外部资产 / Graph sub-asset 的编辑和删除权限符合资产所有权规则。 +- 重命名、复制和删除操作不会静默留下无效 Graph 内引用。 +- 导入差异和校验问题可以定位到具体 Clip、Flow、Node 或 ImportSource。 +- Undo / Redo、窗口重开和脚本重编译不会丢失已经保存的数据。 + +### 8.4 第四阶段:节点画布与 AnimationFlow 编排 + +目标: + +- 实现 FrameAnimationGraph 的唯一全局节点画布。 +- 实现从 Clip 拖入或右键创建 Clip 节点,以及节点创建、移动、删除、连接和断开。 +- 实现每个节点最多一个后继 Edge、自连接 / 多节点环路阻止和显式终点行为约束。 +- 实现从单个节点创建 Flow、修改 Flow 入口和入口引用保护。 +- 实现“显示全部”和“聚焦 Flow”,并按入口可达关系高亮共享节点与连线。 +- 实现节点引用定位、基础自动布局和节点位置共享保存。 +- 使画布操作全部支持 Undo / Redo,并与资源浏览区、属性区和校验区同步选择。 + +验收: + +- 使用者可以不写代码配置“播放一次 -> 进入 Idle loop”等基础线性演出。 +- 多个 Flow 可以从不同入口共享后续节点,且不保存额外节点成员列表。 +- 非法连接在创建时被阻止或形成可定位 Error,完整 Flow 预览不会执行非法路径。 +- 删除入口节点、被引用 Clip 或连线时会显示影响范围,不会静默破坏 Flow。 +- 节点位置、连接和 Flow 入口在保存、窗口重开和脚本重编译后保持一致。 + +### 8.5 第五阶段:预览、恢复与完整验收 + +目标: + +- 实现 FrameClip 独立预览、AnimationNode 内嵌预览和 AnimationFlow 图上预览。 +- 让编辑器预览复用或严格对齐运行时的帧推进、速度和结束行为求值逻辑。 +- 实现播放、暂停、停止、从头播放、逐帧、时间拖动、缩放、棋盘格和空帧显示。 +- 实现 NodePreviewPolicy、Flow 预览期间的自动预览切换和本机工作区状态保存。 +- 完成面板尺寸、画布视图、选择、搜索、筛选和预览偏好的恢复。 +- 对第一版完整验收标准执行回归验证,并补充必要的使用说明与测试资产。 + +验收: + +- Clip、节点和 Flow 的编辑器预览结果与相同数据的运行时播放一致。 +- Flow 预览能在全局画布中正确驱动当前节点并展示节点切换。 +- Domain Reload、窗口重开、切换 Graph 和刷新来源不会造成已保存数据或工作区状态异常丢失。 +- 文档第 9 节全部验收标准通过,第一版形成可用于实际动画资产生产和运行时播放的完整闭环。 ## 9. 验收标准 diff --git a/Docs/帧动画系统第一二阶段手动测试.md b/Docs/帧动画系统第一二阶段手动测试.md new file mode 100644 index 000000000..29987754c --- /dev/null +++ b/Docs/帧动画系统第一二阶段手动测试.md @@ -0,0 +1,416 @@ +# 帧动画系统第一、二阶段手动测试方案 + +## 1. 测试目标 + +本文档用于手动验收帧动画系统前两个阶段: + +- 第一阶段:数据模型、Graph 解析、运行时播放器、统一求值器和播放 Handle。 +- 第二阶段:Aseprite JSON 解析、ImportSource、差异预览、稳定刷新、只读 Sprite 匹配和自动切图。 + +测试重点是验证实际工作流和资产稳定性,不验收第三阶段的完整 Graph 工作台、节点画布或正式删除流程。 + +## 2. 测试前准备 + +### 2.1 环境 + +- Unity:2022.3.7f1c1。 +- 打开项目后等待脚本编译完成。 +- 清空 Console,确认没有编译错误。 +- 打开 `Window > General > Test Runner`,确认可以看到: + - `AibisDream.FrameAnimation.Tests.EditMode` + - `AibisDream.FrameAnimation.Tests.PlayMode` + +### 2.2 生成测试样例 + +依次执行: + +1. `Tools > Frame Animation > Rebuild Runtime Sample` +2. `Tools > Frame Animation > Rebuild Import Sample` + +生成内容: + +- 运行时样例场景:`Assets/Scenes/FrameAnimationRuntimeTest.unity` +- 第一阶段样例:`Assets/GameContent/Test/FrameAnimation/` +- 第二阶段样例:`Assets/GameContent/Test/FrameAnimation/Import/` + +第二阶段测试中可以修改 Import 文件夹内的测试 JSON。测试结束后再次执行 `Rebuild Import Sample` 即可恢复,不要修改生产动画资源。 + +### 2.3 测试记录 + +每个用例记录: + +| 项目 | 内容 | +|---|---| +| 结果 | 通过 / 失败 / 阻塞 | +| 实际表现 | 简述观察结果 | +| Console | 是否出现 Error/Exception | +| 证据 | 截图、录屏或相关资产路径 | +| 缺陷 | 可复现步骤和预期/实际差异 | + +## 3. 第一阶段:运行时播放测试 + +### RT-01 样例场景基础播放(P0) + +操作: + +1. 打开 `FrameAnimationRuntimeTest.unity`。 +2. 进入 Play Mode。 +3. 同时观察左侧 SpriteRenderer 和右侧 UI Image。 +4. 选中两个对象,在 `FrameAnimationPlayer` Inspector 中观察 Runtime State。 + +预期: + +- 左侧 `Flow Sample - SpriteRenderer` 立即显示 Intro 第 0 帧。 +- 左侧约 1.25 秒后从 Intro 自动进入 Idle,之后持续循环 Idle。 +- 左侧 Playable 始终为 `IntroToIdle`,Clip 从 `Intro` 变为 `Idle`。 +- 右侧 `Direct Idle - Image` 立即显示 Idle 第 0 帧并持续循环。 +- 右侧 Playable 和 Clip 均为 `Idle`,Node 为空。 +- 两个目标均不依赖 Animator 或 AnimatorController。 +- Console 没有 Error 或 Exception。 + +### RT-02 长时间循环稳定性(P0) + +操作: + +1. 保持样例场景运行至少 60 秒。 +2. 观察两个目标和 Inspector 中的帧索引。 + +预期: + +- Idle 每约 4 秒循环一次。 +- 不出现停播、闪空、越界帧或明显累计漂移。 +- 内存和 Console 不持续产生异常或日志刷屏。 + +### RT-03 动态速度(P0) + +操作: + +1. Play Mode 中将 Player 的 Speed 改为 `0`。 +2. 等待数秒,再改为 `0.5`、`2`,最后恢复 `1`。 + +预期: + +- Speed 为 `0` 时停在当前帧,但 State 仍为 Playing。 +- 恢复正数后从当前进度继续,不从头重播。 +- `0.5` 明显变慢,`2` 明显变快。 +- 不丢失 Flow 节点或当前 playable。 + +### RT-04 Pause/Resume、Stop 和 Handle 语义(P0) + +操作: + +1. 在 Test Runner 中运行第一阶段 EditMode 测试。 +2. 重点查看 PlaybackSession、PlaybackHandle、Stop、Pause/Resume 和替换请求相关用例。 + +预期: + +- Pause 不改变当前帧,Resume 从原进度继续。 +- 新的合法 Play 将旧请求以 Replaced 结算。 +- Stop 将活动请求以 Stopped 结算。 +- 正常播完以 Completed 结算。 +- 无效请求以 Failed 结算,且不会打断正在播放的合法请求。 +- Task、协程和回调观察到同一个结果,每个请求只结算一次。 + +说明:当前样例 Inspector 只展示状态,没有完整运行时控制面板,因此这一组 API 语义以 Test Runner 的可重复结果作为验收依据。 + +### RT-05 Disable/Enable 生命周期(P0) + +操作: + +1. Play Mode 中等待动画进入非首帧。 +2. 禁用 `FrameAnimationPlayer` 组件。 +3. 观察显示目标和 Runtime State。 +4. 重新启用 Player。 +5. 再对整个 GameObject 执行一次禁用和启用。 + +预期: + +- 禁用时活动请求以 Stopped 结束。 +- 当前 Sprite 保留,不自动 Clear 或 Hide。 +- 重新启用时,因为 `playOnEnable = true`,默认 playable 从头播放。 +- 左侧重新从 Intro 第 0 帧开始,右侧重新从 Idle 第 0 帧开始。 + +### RT-06 目标配置校验(P0) + +在临时场景或复制对象上执行,测试后不要保存改动。 + +| 配置 | 预期 | +|---|---| +| Player,无 SpriteRenderer/Image | Inspector 显示缺失目标错误,Play 返回 Failed | +| Player + SpriteRenderer + Image | Inspector 显示目标冲突,Play 返回 Failed | +| Player + 唯一目标,但 Graph 为空 | Inspector 显示 Graph 缺失,Play 返回 Failed | +| Speed 为负数 | Inspector 显示非法速度;运行时 SetSpeed 不接受负值 | + +失败请求不得清空或替换其他对象上正在播放的合法动画。 + +### RT-07 Clear 与 HideTarget(P1) + +操作: + +1. 在 PlayMode Test Runner 中运行 Clear/HideTarget 相关用例。 +2. 检查 SpriteRenderer 和 Image 两套目标的结果。 + +预期: + +- Clear:Sprite 变为 null,但显示组件仍启用。 +- HideTarget:只禁用 SpriteRenderer 或 Image。 +- Hide 后再次成功 Play 会重新启用目标,并立即显示第 0 帧。 +- SpriteRenderer 与 Image 语义一致。 + +## 4. 第二阶段:Aseprite 导入测试 + +### IMP-01 样例与中文数据(P0) + +操作: + +1. 执行 `Rebuild Import Sample`。 +2. 选中 `ImportSampleGraph.asset`。 +3. 展开 Import Sources。 +4. 点击 `Preview All Enabled`。 + +预期: + +- 存在 Object/只读切图和 Array/自动切图两个来源。 +- internalId 是只读 GUID,并可复制。 +- 中文来源名、Tag 和 frameName 没有乱码。 +- 可看到 `待机`、`眨眼`、`转身`、`惊讶` 等 Imported Clip。 +- 初始预览不出现 Error;成功构建后 Clip 状态应为 Unchanged。 +- Imported Clip 显示为 Graph 的 sub-asset,而不是独立 `.asset`。 + +### IMP-02 Object/Array 与 direction(P0) + +操作: + +1. 在 Test Runner 中运行第二阶段 Parser 和 TagExpansion 测试。 +2. 查看 Object、Array 和四种 direction 用例。 + +预期: + +- Object 和 Array 得到相同语义的 SourceFrame。 +- Object 帧顺序保持 JSON 属性原始顺序,不按名称重新排序。 +- `forward`、`reverse`、`pingpong`、`pingpong_reverse` 顺序正确。 +- pingpong 不重复首尾端点,单帧 Tag 只产生一帧。 + +### IMP-03 普通内容更新与原地刷新(P0) + +操作: + +1. 在 Project 窗口展开 `ImportSampleGraph`,选中任意 Imported Clip并保持 Inspector 锁定。 +2. 打开对应测试 JSON,将该 Tag 使用帧的 `duration` 改为另一个正整数。 +3. 回到 Unity,等待 JSON 重新导入。 +4. 点击对应来源的 `Preview Source`。 +5. 确认显示 SourceChanged Warning 和 Updated Clip。 +6. 点击 `Refresh Source`。 + +预期: + +- Preview 不直接修改 Clip。 +- Refresh 后仍是原来的 Clip sub-asset,锁定的引用不丢失。 +- Clip 的帧时长更新。 +- `displayName`、`speed` 和 `defaultEndBehavior` 不被覆盖。 +- 刷新后再次 Preview,状态变为 Unchanged,来源变化 Warning 消失。 + +### IMP-04 用户重命名 Clip id 后刷新(P0) + +操作: + +1. 选择一个未作为默认 playable 的 Imported Clip。 +2. 将 Clip `id`、`displayName` 和 `speed` 改为自定义值。 +3. 修改其源 JSON 的 duration。 +4. 对来源执行 Preview 和 Refresh。 + +预期: + +- 刷新仍通过 `importSourceId + sourceTagName` 找到原 Clip。 +- frames 被更新。 +- 用户设置的 id、displayName、speed 和结束行为保持不变。 +- 不会因为 Clip id 已改变而创建重复 Clip。 + +测试完成后执行 `Rebuild Import Sample` 恢复样例,避免重命名影响默认 playable 或 Flow 引用。 + +### IMP-05 Tag Missing 与恢复(P0) + +操作: + +1. 从 Object 测试 JSON 的 `frameTags` 中暂时删除 `待机` 条目,不删除 frames。 +2. 点击 `Preview Source`。 +3. 确认 `待机` 显示 Missing,再执行 Refresh。 +4. 检查 Graph sub-assets。 +5. 将原 Tag 完整恢复,再次 Preview 和 Refresh。 + +预期: + +- Tag 消失时原 Clip 保留,只设置 `isMissingFromSource = true`。 +- 原 Clip 的 Sprite 和帧表不被自动删除。 +- Missing Clip 在完整 Graph 校验中报告错误。 +- Tag 恢复后更新同一个 Clip,并清除 Missing。 +- Node、Flow 或其他对象对原 Clip 的引用不丢失。 + +### IMP-06 Tag 改名规则(P1) + +操作: + +1. 将测试 JSON 中一个 Tag 改为全新的、不冲突的名称。 +2. Preview 并 Refresh。 + +预期: + +- 旧 Tag 对应 Clip 变为 Missing。 +- 新 Tag 创建新的 Imported Clip sub-asset。 +- 系统不猜测两者关联,也不迁移旧引用。 + +执行 `Rebuild Import Sample` 恢复样例。 + +### IMP-07 只读 Sprite 匹配(P0) + +操作: + +1. 选择 Object/只读切图来源,确认 `manageSpriteSlicing = false`。 +2. 记录对应 PNG `.meta` 的 Git diff 状态。 +3. Preview 和 Refresh 一次未变化的来源。 +4. 将 JSON 中一个 frame rect 改成无法匹配已有 Sprite 的位置,再 Preview。 + +预期: + +- 正常情况下按 frameName 和 rect 唯一匹配现有 Sprite。 +- 未变化刷新不会修改 TextureImporter 或 PNG `.meta`。 +- rect 无法匹配时出现 SpriteMatchFailed Error。 +- 系统不会自动改切图,也不会应用 Graph/Clip 修改。 + +### IMP-08 自动切图与稳定 Sprite ID(P0) + +操作: + +1. 选择 Array/自动切图来源,确认 `manageSpriteSlicing = true`。 +2. 将某个 Sprite 拖到临时场景中的 SpriteRenderer,形成真实序列化引用。 +3. 修改 JSON 中该 frame 的 rect,但保持 frameName 不变且 rect 合法。 +4. 点击 `Preview Source`。 +5. 检查 Sprite diff 后点击 `Refresh Source`,确认 TextureImporter 修改对话框。 + +预期: + +- 差异明确显示 Updated SpriteRect。 +- 必须确认后才修改 TextureImporter。 +- 刷新后同名 Sprite 保持原 spriteID。 +- 临时 SpriteRenderer 的 Sprite 引用不变,不出现 Missing。 +- rect/pivot 更新为 JSON 和 ImportSource 设置。 +- JSON 中消失的 frameName 对应旧 SpriteRect仍保留。 + +### IMP-09 多写入所有者冲突(P0) + +操作: + +1. 在样例 Graph 中点击 `Add ImportSource`。 +2. 启用新来源,绑定自动切图来源正在使用的同一 Texture 和任意有效 JSON。 +3. 设置 `manageSpriteSlicing = true`。 +4. 点击 `Preview All Enabled`。 + +预期: + +- 出现 TextureOwnershipConflict Error,并列出冲突来源。 +- Refresh 不修改 TextureImporter、Graph 或 Clip。 +- 将新增来源禁用后冲突消失。 + +测试完成后执行 `Rebuild Import Sample` 清理额外来源。 + +### IMP-10 阻断错误与单来源原子性(P0) + +依次测试以下任一错误: + +- duration 改为 `0`。 +- frame rect 越出 Texture。 +- `trimmed = true`。 +- `rotated = true`。 +- direction 改为未知值。 +- Tag 范围越界。 + +操作: + +1. 先记录当前 Clip 帧表和 Texture `.meta`。 +2. 制造错误并点击 `Preview Source`。 +3. 再点击 `Refresh Source`。 + +预期: + +- Preview 显示具体结构化 Error。 +- 不出现部分 Clip 更新。 +- TextureImporter 和 `.meta` 不变化。 +- `lastSourceHash` 不更新。 +- 现有 Clip 和 sub-asset 引用保持原样。 + +### IMP-11 全部来源原子性(P0) + +操作: + +1. 在一个来源中修改合法 duration,使其产生 Updated。 +2. 在另一个来源中制造 duration 为 `0` 的阻断错误。 +3. 点击 `Preview All Enabled`,然后点击 `Refresh All Enabled`。 + +预期: + +- 预览同时显示合法变化和阻断错误。 +- 因任一启用来源失败,整批刷新不应用。 +- 合法来源的 Clip 也保持刷新前状态。 +- 两个来源的 hash 均不更新。 + +### IMP-12 命名冲突(P1) + +分别制造: + +- 两个启用 ImportSource 使用同名 Tag。 +- 新 Tag 与现有 Clip id 同名。 +- 新 Tag 与现有 Flow id 同名。 + +预期: + +- Preview 显示 TagConflict 或 PlayableIdConflict。 +- 错误信息能够指出来源和冲突名称。 +- 系统不自动加前缀、不自动改名、不创建部分 Clip。 + +### IMP-13 Undo/Redo 与保存稳定性(P1) + +操作: + +1. 执行一次成功的 duration 刷新。 +2. 使用 Undo,检查 Clip 帧表和来源 hash。 +3. 使用 Redo,再次检查。 +4. 保存项目,关闭并重新打开 Unity。 +5. 重新检查 Graph、Imported Clip、ImportInfo 和中文名称。 + +预期: + +- Undo/Redo 对 Graph 和 Clip 修改成组生效。 +- 新建 sub-asset 不留下孤立对象。 +- 重启后中文名称、sourceTagName、internalId 和引用不变化。 +- 再次 Preview 能正确判断当前来源是否已刷新。 + +## 5. 自动测试回归 + +手动测试完成前至少执行: + +1. Test Runner > EditMode > Run All。 +2. Test Runner > PlayMode > Run All。 + +通过标准: + +- 第一阶段既有测试全部通过。 +- 第二阶段 Parser、direction、只读匹配、稳定 sub-asset、Missing 和所有权测试全部通过。 +- 没有新增 Console Error 或未处理 Exception。 + +## 6. 总体验收标准 + +以下项目全部满足后,第一、二阶段可视为通过: + +- 不依赖 AnimatorController 即可播放直接 Clip 和线性 Flow。 +- SpriteRenderer 与 Image 的首帧、循环、速度、生命周期和显示语义正确。 +- 播放请求完成原因和 Failed 隔离符合定义。 +- Object/Array Aseprite JSON 均能正确解析,中文数据稳定。 +- Imported Clip 能新增、原地更新、Missing 和恢复。 +- 用户拥有字段不会被刷新覆盖。 +- 只读来源绝不修改 TextureImporter。 +- 自动切图为同名 frameName 保留稳定 Sprite ID。 +- 多写入所有者、素材错误和命名冲突能够阻断刷新。 +- 单来源和全部来源刷新都不产生部分 Graph/Clip 更新。 +- 保存、Undo/Redo 和重新打开 Unity 后资产引用保持稳定。 + +若任一 P0 用例失败,先停止进入第三阶段并记录缺陷;P1 问题可以评估后决定是否阻断,但不得破坏资产引用、原子刷新或运行时播放语义。 diff --git a/Docs/帧动画系统第三阶段手动测试.md b/Docs/帧动画系统第三阶段手动测试.md new file mode 100644 index 000000000..9588bd74f --- /dev/null +++ b/Docs/帧动画系统第三阶段手动测试.md @@ -0,0 +1,177 @@ +# 帧动画系统第三阶段手动测试 + +## 1. 测试目标 + +验证 `FrameAnimationGraph Editor` 的资源浏览、Manual Clip 编辑、正式重命名、安全删除、ImportSource 集成、校验定位、Undo/Redo 和本机工作区状态。 + +本阶段中央区域仍是画布占位区,不测试节点创建、连线、Flow 创建或动画预览。 + +## 2. 测试前准备 + +1. 使用 Unity 2022.3.7f1c1 打开项目,等待脚本和资产导入完成。 +2. 备份需要保留的测试 Graph;删除 sub-asset 和刷新 Imported Clip 都会修改资产。 +3. 可使用现有 `ImportSampleGraph`,也可以通过 `Tools > Frame Animation > Rebuild Import Sample` 重建标准样例。 +4. 若重建样例,确认 `Assets/GameContent/Test/FrameAnimation/Import/` 下没有需要保留的手工改动。 +5. 打开 `Window > Aibis Dream > Frame Animation Graph Editor`。 + +## 3. 打开与布局 + +### 3.1 打开入口 + +分别验证以下入口会打开同一个工作台并选中正确 Graph: + +- 菜单打开后,从顶部 ObjectField 选择 Graph。 +- 在 Project 中双击 `FrameAnimationGraph`。 +- 在 Graph Inspector 点击 `Open Frame Animation Graph Editor`。 +- 对仅被一个 Graph 引用的 FrameClip,在 Clip Inspector 点击打开按钮或双击资产。 + +预期:窗口一次只编辑一个 Graph;切换 Graph 后,前一个 Graph 的属性修改不会丢失。 + +### 3.2 布局持久化 + +1. 拖动左侧、右侧和底部面板尺寸。 +2. 切换资源标签、搜索词、筛选、排序和底部标签。 +3. 关闭并重新打开窗口,再重载脚本或重启 Unity。 + +预期:面板尺寸、折叠状态、资源标签、搜索筛选、排序、Graph 和有效选择得到恢复;不同 Graph 的状态互不覆盖。 + +## 4. 资源浏览与定位 + +### 4.1 Clip 查询 + +1. 在 Clips 中依次搜索 id、displayName、sourceTagName 和来源名称。 +2. 测试 Manual、Imported、Normal、Missing、Referenced、Unused 筛选。 +3. 测试名称、来源、帧数、时长、Missing 和引用数排序。 +4. 清空搜索,确认按 Manual 和 ImportSource 分组;输入搜索词后确认结果变为扁平列表。 + +预期:每行显示帧数、时长、来源、Missing、Node 引用数和问题标记,结果顺序稳定。 + +### 4.2 引用定位 + +1. 选择无 Node 引用的 Clip,点击 `Locate References`。 +2. 选择单引用 Clip,再次定位。 +3. 选择多引用 Clip,再次定位并从菜单选择一个 Node。 + +预期:无引用时显示提示;单引用直接定位;多引用显示选择菜单。Node 在右侧只读显示,中央占位区同步显示当前定位对象。 + +### 4.3 校验定位 + +人为制造一个安全的校验问题,例如将 Manual Frame 的 durationMs 改为 0,然后点击 `Validate`。 + +预期:Validation 出现结构化条目;点击条目会切换到正确资源标签并显示对应对象和处理建议。 + +## 5. Manual Clip + +### 5.1 创建 Graph 内 Manual Clip + +1. 点击 `New Manual`。 +2. 输入唯一 id 和 displayName,保持“外部独立 .asset”关闭。 +3. 创建后检查 Project 资产和 Clip 属性。 + +预期:Clip 是当前 Graph 的 sub-asset;首始帧表为空;结束行为取 Graph 的 `New Manual End Behavior`。 + +### 5.2 创建外部 Manual Clip + +重复创建流程并勾选外部资产,保存到测试目录。 + +预期:生成独立 `.asset`,同时加入当前 Graph;取消保存对话框不会产生半成品。 + +### 5.3 添加已有外部 Clip + +点击 `Add Existing`,分别尝试: + +- 合法的外部 Manual Clip。 +- Imported Clip。 +- 其他 Graph 的 sub-asset Clip。 +- 当前 Graph 已引用的 Clip。 +- id 与当前 Clip 或 Flow 冲突的 Clip。 + +预期:仅第一种成功,其余给出明确原因。 + +### 5.4 编辑帧表 + +对 Manual Clip 执行新增、复制、删除和拖拽排序,并编辑 Sprite 与 durationMs。 + +预期:新帧默认为 Sprite 空、durationMs 100、frameName 空、sourceIndex -1;空 Sprite 合法;frameName/sourceIndex 只读。Undo/Redo 能恢复每一步。 + +### 5.5 Imported 转 Manual + +选择 Imported Clip,点击 `Copy As Manual`,分别测试 Graph sub-asset 和外部资产。 + +预期:帧表和用户字段被复制,Sprite 引用复用;新 Clip 的 ImportInfo 为空,之后刷新来源不会修改它。Imported Clip 原帧表始终只读。 + +## 6. 正式重命名 + +### 6.1 Clip ID + +1. 选择被 Node 和 defaultPlayableId 引用的 Clip。 +2. 点击 `Rename Clip ID`,观察警告和受影响引用数。 +3. 尝试空 id、与 Clip 冲突、与 Flow 冲突和合法 id。 +4. 成功后执行 Undo/Redo。 + +预期:普通属性区不能直接编辑 id;合法重命名会原子更新 Clip、全部 Node.clipId 和 defaultPlayableId。Undo/Redo 不产生一半新一半旧的状态。 + +对被两个 Graph 共享的外部 Manual Clip 重复测试。 + +预期:禁止重命名,并列出引用它的 Graph。 + +### 6.2 Flow 与 Graph ID + +分别通过正式按钮重命名 Flow 和 Graph。 + +预期:Flow 同步更新 defaultPlayableId 和 FlowEditorData;Graph 只更新自身 id。两个对话框均明确提示外部字符串契约无法自动迁移。 + +## 7. 删除与所有权 + +### 7.1 Clip + +依次测试: + +- 被 Node 引用的 Clip。 +- 被 defaultPlayableId 引用的 Clip。 +- 未引用的外部 Manual Clip。 +- 未引用的 Graph sub-asset Manual Clip。 +- 未引用且非 Missing 的 Imported Clip。 + +预期:前两种在确认前被阻止并可据提示定位;外部 Clip 仅移除当前 Graph 引用且 `.asset` 保留;sub-asset 从 Graph 和资产文件中删除;非 Missing Imported Clip 会提示下次刷新可能重建。 + +### 7.2 Flow + +测试删除默认 Flow 和非默认 Flow。 + +预期:默认 Flow 被阻止;非默认 Flow 仅删除 Flow 定义,不删除 Node 或 Edge。 + +### 7.3 ImportSource + +1. 删除仍有关联 Imported Clip 的来源。 +2. 将关联 Clip 删除或复制为 Manual 后再次删除来源。 + +预期:有关联 Clip 时阻止并列出 Clip;允许删除时不会删除 Texture、JSON、Sprite 或 SpriteRect。 + +## 8. 导入与差异 + +1. 在 Sources 中编辑 displayName、启用状态、Texture、JSON、pivot、切图模式和新 Clip 默认结束行为。 +2. 检查 internalId 只读且可复制。 +3. 分别执行单来源和全部来源 Preview、Refresh。 +4. 对自动切图来源制造 SpriteRect 变化并拒绝确认,再重新刷新并确认。 +5. 制造非法 JSON 后 Preview/Refresh。 + +预期:预览不修改资产;差异按 Added、Updated、Missing、Unchanged、Error 分组并可定位;切图变化必须确认;失败保留完整差异和错误,Graph、Clip、TextureImporter 不出现部分更新。 + +## 9. 保存、Undo 与异常恢复 + +1. 连续执行创建、帧编辑、重命名、删除和 Source 修改。 +2. 逐步 Undo,再逐步 Redo。 +3. 点击 Save,关闭 Unity 后重开项目。 +4. 删除当前窗口正在编辑的测试 Graph。 + +预期:每个资产操作以具名事务恢复;列表、引用数和校验随 Undo/Redo 重建;保存和 Domain Reload 后数据稳定;资产删除后窗口自动清理失效 Graph 和选择,不抛出持续异常。 + +## 10. 通过标准 + +- 工作台所有入口、布局恢复和统一定位正常。 +- Manual、Imported、外部资产和 Graph sub-asset 的所有权行为符合预期。 +- id 只能正式重命名,引用迁移原子且可撤销。 +- 删除不会留下悬空引用,也不会误删外部资产或导入源资源。 +- Import Preview/Refresh、差异和综合校验能在同一窗口完成并准确定位。 +- Console 无持续异常;重新打开项目后资产内容和引用保持稳定。 diff --git a/Docs/帧动画系统第三阶段手动测试.md.meta b/Docs/帧动画系统第三阶段手动测试.md.meta new file mode 100644 index 000000000..c9a33753f --- /dev/null +++ b/Docs/帧动画系统第三阶段手动测试.md.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a31bfb3b2581476db5b2d152cd8e9b33 diff --git a/Docs/帧动画系统第四阶段手动测试.md b/Docs/帧动画系统第四阶段手动测试.md new file mode 100644 index 000000000..960c168ba --- /dev/null +++ b/Docs/帧动画系统第四阶段手动测试.md @@ -0,0 +1,186 @@ +# 帧动画系统第四阶段手动测试 + +## 1. 测试目标 + +验证第四阶段新增的节点画布、顺序连线、Flow 编排、选择定位、删除影响分析、自动布局和状态恢复。导入刷新可按第一、二阶段文档做回归;动画预览、时间轴和逐帧播放不属于本阶段。 + +## 2. 测试准备 + +1. 备份或提交当前工作区改动。 +2. 在 Unity 中等待脚本编译完成,确认 Console 没有编译错误。 +3. 如需标准样例,执行 `Tools > Frame Animation > Upgrade Phase 4 Sample (Non-Destructive)`。 +4. 打开 `Assets/GameContent/Test/FrameAnimation/Import/ImportSampleGraph.asset`。 +5. 点击 Inspector 中的 `Open Graph Editor`,或使用 `Window > Aibis Dream > Frame Animation Graph Editor`。 + +样例升级是幂等、非重建操作:它只补充固定 ID 的三个 Node、两条 Edge、两个 Flow 及其 EditorData;不会删除或重建 Graph,不会移除已有来源或 Clip。重复执行后数量不应继续增加。 + +## 3. 基础画布 + +### 3.1 打开与导航 + +1. 用鼠标滚轮缩放画布。 +2. 按住中键拖动画布。 +3. 拖出矩形框选多个节点。 +4. 点击 `Frame Selection`。 + +预期:缩放、平移、框选正常;`Frame Selection` 将选中元素完整放入视野;多选时右侧显示数量摘要,不提供批量属性编辑。 + +### 3.2 已有数据加载 + +检查样例中的 `待机 Node`、`眨眼 Node` 和 `Shared Idle Node`,以及两条指向共享节点的 Edge。 + +预期:节点位置稳定;共享节点显示多个 Flow 标记;`Shared Idle Node` 显示 Loop 结束行为且输出端不可连接;打开窗口本身不会新增 Node 或 Edge。 + +## 4. Node 创建与编辑 + +### 4.1 从 Clip 列表拖入 + +1. 在左侧选择 `Clips`。 +2. 将同一个 Clip 分别拖到画布两个不同位置。 + +预期:创建两个不同 internalId 的 Node,二者引用同一 Clip;释放位置被保存;Undo 一次只撤销最近创建的一个 Node。 + +### 4.2 右键创建 + +1. 在画布空白处右键,选择 `Create Clip Node`。 +2. 在搜索框查找一个 Clip 并创建。 + +预期:节点出现在右键位置;displayName 默认取 Clip displayName,空时取 Clip id。 + +### 4.3 属性编辑 + +选中 Node,在右侧依次测试: + +- 修改 displayName。 +- 从 Clip 下拉切换引用。 +- 开启速度覆盖并输入 `0`、正数、负数。 +- 开启和关闭结束行为覆盖。 +- 复制 internalId。 + +预期:修改立即反映到画布;速度 `0` 被保留,负数被保留并产生可定位校验错误;失效 Clip 显示 `` 并可通过下拉修复;普通属性编辑支持 Undo/Redo。 + +## 5. Edge 连接约束 + +分别尝试以下操作: + +1. 从无后继、无结束覆盖的 Node 连接到另一个 Node。 +2. 再从同一起点连接第二个后继。 +3. 节点连接自身。 +4. 连接一条会形成多节点环路的 Edge。 +5. 从设置了结束行为覆盖的 Node 拉出连接。 + +预期:第 1 项成功,Edge 固定为 `default / Always`;第 2~5 项被阻止并显示明确原因,Graph 数据不变化。 + +选中已有后继的 Node,再开启结束行为覆盖。 + +预期:出现“删除 Edge 并应用”确认;确认后 Edge 与结束行为在同一个 Undo 事务内变化;取消则两者都不变化。 + +## 6. Flow 创建与入口 + +### 6.1 创建 Flow + +1. 只选中一个非 Flow 入口 Node。 +2. 点击画布工具栏 `Create Flow`,或节点右键 `Create Flow From Node`。 +3. 保持默认 id 创建。 + +预期:默认 id 为“节点显示名 + Flow”;冲突时追加 `2`、`3`;Clip/Flow 同名会阻止创建;已作为其他 Flow 入口的 Node 会阻止创建。 + +### 6.2 修改入口 + +1. 左侧选择一个 Flow。 +2. 在画布选择目标 Node。 +3. 回到 Flow 属性,点击 `Set Selected Node As Entry`。 + +预期:入口只通过该命令修改;目标已是其他 Flow 入口时阻止修改;成功后 Flow 聚焦和可达范围立即更新。 + +### 6.3 聚焦与共享节点 + +1. 从 Flow 下拉分别选择两个样例 Flow。 +2. 点击 `Frame Flow`。 +3. 点击 `Show All`。 + +预期:聚焦时仍保留全部节点和 Edge,只降低无关元素透明度;共享节点在两个 Flow 中均正常高亮;`Frame Flow` 框住当前可达节点;`Show All` 恢复全部显示。 + +## 7. 选择与问题定位 + +1. 从 Clip 行执行“定位引用”。 +2. 点击左侧 Flow。 +3. 执行 `Validate`,点击一个 Node 或 Edge 问题。 +4. 在聚焦某 Flow 时,定位一个不属于该 Flow 的问题。 + +预期:左侧资源、画布和右侧属性同步;Node/Edge 被框入视野;目标不属于当前 Flow 时自动切换 `Show All`,不会仅以弱化状态显示。 + +## 8. 删除与影响分析 + +### 8.1 删除 Edge + +选择一个处于 Flow 可达路径中的 Edge,按 Delete 或点击属性区 `Disconnect Edge`。 + +预期:确认框列出受影响 Flow 和失去可达关系的 Node 数;取消不修改;确认只删除 Edge,不删除 Node 或 Flow。 + +### 8.2 删除普通 Node + +删除一个非入口但位于 Flow 路径中的 Node。 + +预期:确认框列出受影响 Flow;确认后自动删除该 Node 的全部入边、出边和 NodeEditorData,但不删除引用的 Clip。 + +### 8.3 删除入口 Node + +删除一个或多个 Flow 的入口 Node。 + +预期:只提供“同时删除这些 Flow”或取消;确认后 Node、关联 Edge、入口 Flow 及相应 EditorData 在同一 Undo 事务内删除。 + +### 8.4 批量删除 + +框选多个相连 Node 后按 Delete。 + +预期:只出现一次综合确认;关联 Edge 去重,不发生重复删除或异常。 + +## 9. 位置与自动布局 + +1. 拖动一个 Node,关闭并重新打开窗口。 +2. 选中两个以上 Node,点击 `Auto Layout`。 +3. 取消选择、聚焦一个 Flow,再点击 `Auto Layout`。 +4. `Show All` 后再次自动布局。 + +预期:拖动位置被保存且可 Undo;三种布局范围依次为选中集合、当前 Flow 可达节点、全部节点;布局从左到右,同层排序稳定;范围外 Node 不移动;一次布局只产生一个 Undo。 + +## 10. Undo、保存与状态恢复 + +1. 依次执行创建 Node、移动、连线、创建 Flow、改入口和删除。 +2. 连续 Undo,再连续 Redo。 +3. 点击 `Save`,关闭窗口并重新打开。 +4. 触发脚本重新编译或 Domain Reload。 + +预期:每个具名事务完整恢复;不会出现只有画布变化而数据未恢复的情况;internalId、Edge、Flow 入口和节点位置稳定;当前 Flow 聚焦和有效 Node/Edge 选择尽量恢复;失效选择会自动清理。 + +## 11. 综合校验 + +点击 `Validate`,检查以下类型能够显示并定位: + +- 缺失或重复 Node/Edge ID、无效引用、多后继、自连接、环路。 +- 终点结束覆盖与后继冲突。 +- 缺失 NodeEditorData / FlowEditorData。 +- 孤立或重复 EditorData。 +- 第一至第三阶段已有的 Clip、Flow、ImportSource 和资产所有权问题。 + +预期:轻量编辑只触发结构校验;JSON、Texture、Sprite 和 hash 只在 `Validate`、`Preview` 或 `Refresh` 时检查。 + +## 12. 回归检查 + +1. Preview / Refresh 一个 ImportSource,确认画布引用保持。 +2. 重命名 Clip,确认所有 Node.clipId 同步迁移。 +3. 重命名 Flow,确认 FlowEditorData 和默认 playable 同步迁移。 +4. 尝试删除仍被 Node 引用的 Clip。 +5. 进入第一阶段运行时样例场景,验证直接 Clip 和 Flow 播放。 + +预期:导入刷新不重建已有 Clip sub-asset;重命名引用原子迁移;被 Node 引用的 Clip 仍受删除保护;运行时行为与前三阶段一致。 + +## 13. 通过标准 + +- 无 Unity 编译错误或未处理异常。 +- 所有非法新连接和入口冲突均在写入前阻止。 +- 所有确认取消操作保持 Graph 不变。 +- Node、Edge、Flow、EditorData 的 Undo/Redo 和重载后状态一致。 +- 样例升级重复执行不增加重复 Node、Edge 或 Flow,且既有 ImportSource(包括用户添加来源)保持不变。 +- 第一至第三阶段自动测试和 PlayMode 测试无回归。 diff --git a/Docs/帧动画系统第四阶段手动测试.md.meta b/Docs/帧动画系统第四阶段手动测试.md.meta new file mode 100644 index 000000000..92297357c --- /dev/null +++ b/Docs/帧动画系统第四阶段手动测试.md.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 02f6fd1fa87b4ea182bd05d86c637184