docs: 动画文档未完成版

This commit is contained in:
2026-07-11 16:17:09 +08:00
parent ea6b75d73d
commit 52d7656c30
+601
View File
@@ -0,0 +1,601 @@
# 自研帧动画系统需求文档
> 当前文档处于需求梳理阶段。目标是先把系统边界、数据结构、编辑器工作流和运行时接入方式说清楚,再进入具体技术设计与实现。
## 1. 背景与问题
### 1.1 项目背景
本项目是 2D 像素风格 AVG / 视觉小说游戏,动画主要服务于角色立绘、场景物件和少量 UI 表现。当前项目中,大部分序列帧动画显示在 `SpriteRenderer` 上,少量显示在 UI `Image` 上。
现阶段主要使用 Unity 原生 Animator / AnimationClip / Timeline 等系统完成表现。其中 Animator 更适合状态机式角色动作,而本项目大量需求更接近“演出编排”:播放一个片段、接另一个片段、最后进入 Idle,或者停留在某一帧。
### 1.2 当前痛点
#### Unity Animator / AnimationClip 相关
1. 状态机模型与项目需求不匹配
Unity Animator 的核心是状态机,适合玩家可控角色在多个状态之间切换;本项目常见需求是线性演出、片段编排、播完后进入 Idle 或停在终帧。
2. 资产结构臃肿
简单序列帧动画也需要 `Animator Controller``Animation Clip`、Sprite 绑定等多层资源。资源关系依赖 Unity 序列化引用,更新和排查成本高。
3. 预览体验差
Animator Controller 难以直接预览,Animation Clip 预览通常需要依附 GameObject。策划或美术无法轻松检查一个片段或一组片段的最终效果。
4. 帧内容不可读、不可维护
Animation Clip 内部通过 Unity 对象引用记录 Sprite,难以直观看到每一帧具体使用了哪个 Sprite、持续多久,也不方便做批量替换或差异检查。
#### 美术资产导入相关
1. Aseprite 导入链路不稳定
当前美术使用 Aseprite 生产资源。Unity Aseprite Importer 存在中文 Tag 支持不佳、资源更新后 Clip 重新生成导致引用关系变化等问题。
2. 现有 AnimationClip 生成器只能部分解决问题
项目中已有 PNG + JSON -> 切图 -> 生成 AnimationClip 的工具,但最终产物仍然是 Unity AnimationClip,仍然保留了 Animator / Clip 体系的维护成本。
3. 美术更新成本高
理想流程应当是:美术更新 PNG / JSON 后,策划或开发点击刷新,已有动画资产保持引用不变,只更新帧数据和 Sprite 映射。
### 1.3 文档目标
本文档希望定义一个独立于 Unity Animator Controller 的自研帧动画系统,重点解决:
- 序列帧动画的数据结构
- PNG / Aseprite JSON 等来源的导入与刷新
- Clip / AnimationSet / Sequence 的组织关系
- 编辑器内预览、检查和编排工作流
- 运行时播放器接口
- 与现有 Actor、Yarn、AnimatorCenter、存档系统的兼容与迁移路径
## 2. 系统目标与非目标
### 2.1 核心目标
1. 支持 SpriteRenderer 和 UI Image 的序列帧播放。
2. 支持单个动画片段独立播放、预览、刷新和检查。
3. 支持同一对象的一组动画片段集中管理。
4. 支持简单演出编排,例如“播放一次动作 -> 进入 Idle 循环 -> 停在最后一帧”。
5. 支持从 Aseprite JSON / PNG 生成动画数据,并在资源更新时保持动画资产引用稳定。
6. 提供不依赖场景 GameObject 的编辑器预览能力。
7. 为运行时代码提供清晰、稳定、可迁移的播放 API。
### 2.2 非目标
第一阶段不尝试替代以下系统:
1. Unity Timeline 的多轨演出能力。
2. DOTween 等 Tween 动效系统。
3. Cinemachine 镜头系统。
4. 骨骼动画、网格变形、复杂 Transform 曲线动画。
5. 完整可视化节点编辑器或通用状态机编辑器。
### 2.3 第一阶段范围建议
第一阶段建议聚焦:
- `FrameClip`:单个序列帧片段。
- `AnimationSet`:同一对象的一组 `FrameClip`
- 简单 `Sequence`:线性片段队列 + 结束行为。
- `FrameAnimationPlayer`:运行时播放器。
- Clip / Set 的基础编辑器和预览。
复杂图编辑器、条件分支、随机播放、批量迁移工具可以放到后续阶段。
## 3. 术语与概念
### 3.1 Frame
单帧数据,描述某一时间段内应该显示的 Sprite。
候选字段:
- `Sprite sprite`
- `float duration`
- `string frameName`
- `int sourceIndex`
待讨论:
- `duration` 使用秒还是毫秒?
- 是否需要记录来源 rect / pivot / tag 等导入信息?
### 3.2 FrameClip
最小可播放动画片段,由一组 Frame 组成。
候选职责:
- 保存帧列表。
- 保存默认播放速度。
- 保存默认结束行为。
- 保存导入来源。
- 提供总时长、帧数等只读信息。
待讨论:
- Clip 是否应当保存 loop,还是只保存 default end behavior
- 导入生成的 Clip 是否允许手动编辑帧表?
- Clip 作为独立 `.asset` 保存,还是作为 AnimationSet 的 sub-asset 保存?
### 3.3 AnimationSet
同一对象的一组动画片段集合。例如一个角色、一个场景物件或一个 UI 元件的所有动画。
候选职责:
- 维护 Clip 名称到 Clip 的映射。
- 维护默认 Idle Clip。
- 维护导入来源列表。
- 支持刷新导入来源后按名称更新已有 Clip。
- 提供编辑器集中预览。
待讨论:
- 原文中的 `Library` 是否改名为 `AnimationSet`
- Set 内 Clip 是否必须唯一命名?
- Set 是否负责 Sequence,还是 Sequence 独立成资产?
### 3.4 Sequence
一段演出编排,描述多个 Clip 的播放顺序和结束策略。
候选能力:
- 线性播放:`Clip A -> Clip B -> Clip C`
- 播完进入 Idle`Intro -> Idle(loop)`
- 播完停在最后一帧
- 播完隐藏目标
待讨论:
- 第一阶段是否只做线性 Sequence?
- Sequence 是否需要独立资产?
- Sequence 是否应该支持分支、随机、条件判断?
### 3.5 Player / Controller
运行时负责推进时间、设置 Sprite、响应代码调用的组件。
候选拆分:
- `FrameAnimationPlayer`:底层播放逻辑。
- `FrameAnimationController`:挂载在 GameObject 上,对外暴露 `Play("clipName")` 等接口。
- `IFrameAnimationTarget`:统一封装 SpriteRenderer / Image。
待讨论:
- 是否需要一个类似 `AnimatorCenter` 的全局注册中心?
- 是否沿用现有 `animatorName + stateName` 的外部调用模型?
## 4. 数据结构需求
### 4.1 FrameClip 数据
候选字段:
```csharp
string clipName;
List<Frame> frames;
float speed;
EndBehavior defaultEndBehavior;
ImportSource importSource;
bool isGenerated;
```
候选结束行为:
- `Stop`
- `HoldLastFrame`
- `Loop`
- `Clear`
- `HideTarget`
待讨论:
- `Stop``HoldLastFrame` 是否需要区分?
- 循环播放是 Clip 的默认属性,还是 Play 请求的属性?
### 4.2 AnimationSet 数据
候选字段:
```csharp
string setName;
List<FrameClip> clips;
string defaultClipName;
List<ImportSource> importSources;
```
待讨论:
- Clip 引用外部资产,还是作为 Set 的子资产?
- 如果刷新后某个 Clip 名称消失,应该删除、标记 missing,还是保留旧数据?
- 如果刷新后出现同名 Clip,如何处理冲突?
### 4.3 Sequence 数据
候选字段:
```csharp
string sequenceName;
List<SequenceStep> steps;
EndBehavior finalBehavior;
string fallbackIdleClipName;
```
待讨论:
- `SequenceStep` 是否只需要 clipName + overrideEndBehavior
- 是否需要 step-level speed、事件、等待时间?
- 是否需要在某一帧触发事件?
### 4.4 ImportSource 数据
候选来源类型:
- `Manual`
- `AsepriteJsonAndTexture`
- `ManualGridJsonAndTexture`
候选字段:
```csharp
ImportSourceType type;
Texture2D texture;
TextAsset json;
Vector2 pivot;
bool generateSpritesIfNeeded;
```
待讨论:
- 是否允许一个 AnimationSet 管理多组 PNG / JSON
- 切图结果是否直接修改原 Texture Importer
- 是否需要保存上次导入摘要,用于显示差异?
## 5. 资产导入与刷新需求
### 5.1 Aseprite JSON 导入
系统应支持读取 Aseprite 导出的 JSON
- 读取 `frames` 中的帧 rect 和 duration。
- 读取 `meta.frameTags` 作为 Clip 名称和帧范围。
- 支持 `forward``reverse``pingpong` 等方向。
- 支持中文 Tag 名称。
待讨论:
- 是否在导入时保留 Aseprite 原始 frameName
- `pingpong` 是否展开成实际帧列表?
### 5.2 手动 Grid JSON 导入
系统可以复用现有生成器里的手动 JSON 思路:
- 配置 rows / columns / frameCount。
- 配置全局 frameDuration。
- 配置每个动画片段的 frameIndices。
待讨论:
- 这个格式是否继续保留?
- 是否需要提供 JSON 模板和校验工具?
### 5.3 刷新策略
刷新时应尽量保持已有资产引用稳定。
候选规则:
1. 以 Clip 名称作为匹配键。
2. 同名 Clip 原地更新帧数据。
3. 新 Clip 自动加入。
4. 消失的 Clip 标记为 missing,等待用户确认是否删除。
5. 手动修改过的字段不被刷新覆盖,除非用户选择强制刷新。
待讨论:
- 哪些字段属于导入生成,哪些字段允许用户覆盖?
- 是否需要刷新前预览差异?
## 6. 编辑器需求
### 6.1 FrameClip Inspector
基础能力:
- 显示 Clip 名称、总时长、帧数、默认结束行为。
- 显示帧表:序号、Sprite、duration、来源 frameName。
- 支持不依赖场景 GameObject 的预览。
- 支持播放、暂停、逐帧、调整预览速度。
手动 Clip
- 帧表可编辑。
- 可增删帧、替换 Sprite、修改 duration。
导入 Clip
- 帧表默认只读。
- 可以跳转到 ImportSource。
- 可以刷新来源。
待讨论:
- 是否允许导入 Clip 局部覆盖某一帧?
- 预览区域是否需要显示透明棋盘格、原始尺寸、缩放倍率?
### 6.2 AnimationSet Editor
基础能力:
- 显示 Set 内所有 Clip。
- 每个 Clip 有小预览窗口。
- 支持搜索、排序、重命名、检查重复名。
- 支持设置默认 Idle / 默认 Clip。
- 支持从导入来源批量刷新。
- 支持侧边预览完整 Sequence 或单个 Clip。
待讨论:
- 第一版是否做独立 EditorWindow,而不是只做 Inspector
- 是否需要拖拽排序?
- Clip 小窗全部实时播放是否会影响编辑器性能?
### 6.3 Sequence Editor
第一阶段建议做轻量列表式编辑:
- 添加 Step。
- 选择 Clip。
- 设置 Step 播放策略。
- 设置最终行为。
- 一键从头预览。
后续再考虑节点图或连线式编辑。
待讨论:
- 是否真的需要节点图?
- Sequence 是否需要与 Yarn / Timeline 联动显示?
### 6.4 校验与错误提示
编辑器应能检查:
- 空 Sprite。
- duration 小于等于 0。
- Clip 重名。
- Sequence 引用不存在的 Clip。
- ImportSource 缺少 texture 或 json。
- JSON 中 Tag 重名。
- 刷新后丢失的 Clip。
## 7. 运行时需求
### 7.1 播放目标
必须支持:
- `SpriteRenderer`
- `UnityEngine.UI.Image`
可选支持:
- 未来扩展到其他自定义 Sprite 显示组件。
### 7.2 播放接口
候选接口:
```csharp
Play(string clipName);
Play(string clipName, EndBehavior endBehavior);
Loop(string clipName);
Queue(string clipName);
Stop();
Pause();
Resume();
Seek(float timeSeconds);
SetSpeed(float speed);
GetCurrentClipName();
GetPlaybackTime();
GetDuration();
```
待讨论:
- `Loop` 是否只是 `Play(..., Loop)` 的快捷方法?
- `Queue` 第一阶段是否需要?
- 是否需要异步协程接口:`IEnumerator PlayAsync(...)`
### 7.3 播放行为
需要明确:
- 播放新 Clip 时是否从第 0 帧开始。
- 播放同一 Clip 时是否重播。
- Stop 后停在哪一帧。
- Pause 是否冻结当前帧。
- HideTarget 是否由播放器设置 GameObject active,还是只清空 Sprite / alpha。
### 7.4 时间推进
待设计:
- 使用 `Update()` 基于 `Time.deltaTime` 推进。
- 是否支持 unscaled time。
- 是否支持手动 Evaluate,供编辑器预览和存档恢复使用。
## 8. 与现有项目系统的关系
### 8.1 Actor 系统
当前 `ActorAnima` 通过 `Animation/{ActorName}` 加载 AnimatorController,并通过状态名播放。
新系统需要考虑:
- 是否新增 `ActorFrameAnima` 类型。
- 是否替换 `ActorAnima` 内部实现。
- 存档中 `stateName` / `stateNormalizedTime` 如何兼容。
- Yarn 中现有 set_actor_state 等命令是否需要调整。
待讨论:
- 第一批迁移对象是否选择角色立绘?
- 还是先选择孤立的场景物件 / UI 动画做试点?
### 8.2 AnimatorCenter / AnimatorHandler
当前通用动画调用以 `animatorName + stateName` 为核心。
新系统可以选择:
1. 新增并行的 `FrameAnimationCenter`
2.`AnimatorCenter` 逐步兼容新播放器。
3. 保持两套系统并存,由 Yarn 命令区分调用。
待讨论:
- 为减少 Yarn 改动,是否应尽量保持类似 API?
- 旧 Animator 动画是否长期保留?
### 8.3 Yarn 命令
候选新命令:
```yarn
<<play_frame_animation name clip>>
<<loop_frame_animation name clip>>
<<stop_frame_animation name>>
```
待讨论:
- 是否复用现有 `play_animation` 命令?
- 命令是否等待播放完成?
- Loop Clip 的等待语义如何定义?
### 8.4 Timeline
第一阶段不替代 Timeline。
新系统只需要考虑:
- Timeline 是否可以调用 FrameAnimationController。
- Frame 动画是否需要在 Timeline 中被录制或控制。
### 8.5 存档系统
需要定义快照语义:
- 当前 Clip 名称。
- 当前播放时间或帧索引。
- 当前播放状态:playing / paused / stopped。
- 当前结束行为。
- 是否保存队列。
待讨论:
- 对角色立绘保存精确播放时间。
- 对一次性演出只保存终态,避免读档后重复播放。
## 9. 迁移计划
### 9.1 第一阶段:原型验证
目标:
- 实现最小 FrameClip。
- 实现 SpriteRenderer / Image 播放。
- 实现基础 Inspector 预览。
- 从一个简单 PNG / JSON 生成 Clip。
验收:
- 不依赖 AnimatorController 播放序列帧。
- 编辑器中可直接预览 Clip。
- 修改来源后能刷新 Clip。
### 9.2 第二阶段:AnimationSet 与运行时接入
目标:
- 实现 AnimationSet。
- 实现按名称播放 Clip。
- 实现 AnimationSet 编辑器。
- 在一个非核心场景物件上试点。
验收:
- 代码可通过名称播放 Set 内 Clip。
- 编辑器可集中预览和检查所有 Clip。
### 9.3 第三阶段:Sequence 与演出工作流
目标:
- 实现简单线性 Sequence。
- 支持“播放一次 -> 进入 Idle loop”。
- 提供 Sequence 预览。
验收:
- 策划可以不写代码配置基础演出序列。
### 9.4 第四阶段:Actor / Yarn 迁移
目标:
- 选择一个角色或一组立绘动画试点。
- 接入 Yarn 命令。
- 接入存档恢复。
验收:
- 角色动画可以通过新系统播放、保存、恢复。
- 不破坏旧 Animator 动画。
## 10. 验收标准
第一版完成时,应满足:
1. 能创建和保存 FrameClip 资产。
2. 能在 Inspector 或 EditorWindow 中直接预览 Clip。
3. 能从 PNG / JSON 生成或刷新 Clip。
4. 能挂载播放器到 SpriteRenderer / Image 并播放 Clip。
5. 能通过 AnimationSet 按名称播放 Clip。
6. 能检查基础错误并给出明确提示。
7. 刷新导入资源时,不破坏已存在 Clip 的引用。
## 11. 待讨论问题清单
优先级较高:
1. `Library` 是否正式命名为 `AnimationSet`
2. Clip 的循环/结束策略放在哪里最合适?
3. Clip 独立资产与 Set 子资产,哪种更适合项目工作流?
4. 第一阶段试点对象选角色立绘、场景物件,还是 UI 动画?
5. 是否保留现有 `AnimatorCenter` API 形状,降低 Yarn 迁移成本?
6. 存档是否需要保存动画播放中间态?
优先级较低:
1. 是否需要节点图式 Sequence 编辑器?
2. 是否需要帧事件?
3. 是否支持随机播放或条件分支?
4. 是否需要 Timeline 轨道扩展?
## 12. 当前倾向
当前建议:
1. 第一版只做序列帧系统,不做通用动画系统。
2. 命名采用 `FrameClip` / `AnimationSet` / `Sequence`
3. Clip 保存默认结束行为,但播放请求可以覆盖。
4. 导入刷新以 Clip 名称为稳定匹配键。
5. 第一版 Sequence 使用列表式编辑,不做节点图。
6. 运行时底层直接按时间设置 Sprite,不使用 Unity Animator / Playables。
7. 先新增并行系统,验证稳定后再讨论替换 ActorAnima / AnimatorCenter。