442 lines
15 KiB
Markdown
442 lines
15 KiB
Markdown
# 帧动画系统第一、二阶段手动测试方案
|
||
|
||
## 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-08A 合并帧共享 Sprite(P0)
|
||
|
||
操作:
|
||
|
||
1. 使用包含多个 SourceFrame 指向相同 `frame.x/y/w/h` 的 Aseprite JSON。
|
||
2. 确认来源设置为 `manageSpriteSlicing = true`,点击 `Preview Source`。
|
||
3. 检查来源摘要中的逻辑帧、SpriteSlot 和共享别名数量。
|
||
4. 确认刷新后检查对应 Imported Clip 的帧表和 Sprite引用。
|
||
5. 再次执行 Preview 和 Refresh。
|
||
|
||
佩佩素材的预期统计:
|
||
|
||
- 347 个逻辑 SourceFrame。
|
||
- 205 个唯一 SpriteSlot。
|
||
- 142 个共享别名。
|
||
|
||
预期:
|
||
|
||
- 相同 rect 只创建一个有效 SpriteRect。
|
||
- 不同 SourceFrame仍保留各自的 frameName、sourceIndex、duration 和 Tag顺序。
|
||
- 指向同一 rect 的 Clip Frame直接引用同一个 Unity Sprite。
|
||
- 第二次 Preview 中 SpriteSlot 和 Clip均显示 Unchanged,Sprite ID保持稳定。
|
||
- 原始 PNG 和 JSON不被修改。
|
||
|
||
### 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。
|
||
- 合并帧只创建唯一 SpriteSlot,同时保留全部逻辑帧时长与播放顺序。
|
||
- 多写入所有者、素材错误和命名冲突能够阻断刷新。
|
||
- 单来源和全部来源刷新都不产生部分 Graph/Clip 更新。
|
||
- 保存、Undo/Redo 和重新打开 Unity 后资产引用保持稳定。
|
||
|
||
若任一 P0 用例失败,先停止进入第三阶段并记录缺陷;P1 问题可以评估后决定是否阻断,但不得破坏资产引用、原子刷新或运行时播放语义。
|