Skip to content

动画系统职责三分与行为模型总结

本文是对当前讨论中逐步沉淀出的动画系统设计思想的系统化整理,目标是:

  • 明确职责边界,避免状态污染
  • 区分声明、语义、运行时三个层级
  • BT(行为树)视角统一理解动画与行为

一、总体设计目标

我们希望构建的是一套:

  • 可声明(Declarative):动画如何发生,而不是每一帧怎么改值
  • 可推导(Derived):所有动态结果都应由函数计算得出
  • 无污染(Stateless Runtime):运行时不维护“额外真相”
  • 可解耦(Decoupled):配置、语义、执行三者相互独立

核心思想一句话概括:

状态是输入,行为是规则,结果永远是算出来的。


二、职责三分法(Three-Layer Responsibility Model)

1️⃣ Animation Config(声明层)

它是什么?
动画“意图”的描述文件,是一种 配置 / 数据结构

它负责什么?

  • 描述有哪些对象(hero / enemy / parts)
  • 描述对象有哪些可被行为作用的属性声明
  • 描述时间轴(timeline)与事件(events)

它不负责什么?

  • ❌ 不做任何计算
  • ❌ 不存储运行时数据
  • ❌ 不关心逐帧变化

Config 的本质

Config 不是状态,而是“可被求值的前提条件集合”。

它只说明:

  • 有哪些可能(capability)
  • 在什么条件下(when)
  • 用什么参数(params)

2️⃣ Animation Library(语义层)

它是什么?
一套稳定、可复用的行为语义定义集合

它负责什么?

  • 定义行为类型(move / rotate / fade ...)
  • 定义参数结构(schema)
  • 提供求值函数(evaluator)

它的关键特性

  • 行为是纯函数语义
  • 不依赖具体动画实例
  • 不随动画运行而变化

Behavior Declaration 示例(概念)

  • 行为类型:move
  • 所需参数:targetX, duration, easing
  • 求值规则:
    • 输入:起始值、时间、参数
    • 输出:某一时刻的结果值

Library 决定“怎么算”,而不是“什么时候算”。


3️⃣ Loader / Runtime(运行时层)

它是什么?
动画的执行引擎,负责把声明 + 语义“跑起来”。

它负责什么?

  • 维护世界状态(World State)
  • 根据 timeline / event 创建行为实例
  • 在每一帧进行求值并渲染

World State 的原则

  • 只存储最小真实状态(如 time、输入信号)
  • 不存“结果性状态”(如 x 是多少)

World State 是输入源,不是结果缓存。


三、参数维度的统一理解

我们区分参数的两个正交维度:

1️⃣ 动态参数 vs 静态参数

类型含义
静态参数不随时间变化(或不被行为作用)
动态参数会被行为求值函数计算

是否“动态”,取决于是否由 evaluator 计算,而不是是否写在 config 里。


2️⃣ 常态参数 vs 非常态参数

类型含义
常态参数对该对象长期成立的能力或默认值
非常态参数仅在某次行为实例中存在

关键结论

  • 常态参数 → 放在 Config 中作为声明
  • 非常态参数 → 放在 Behavior Instance 中作为一次性输入

Config 只描述“可能性”,Instance 才携带“这一次”。


四、Timeline 与 Event 的正确理解

Timeline 的本质

json
{ "at": 0, "behavior": "move", "params": { ... } }

并不是“初始状态”,而是:

在某个时间点,满足条件时,创建一个行为实例。

行为的生命周期

  1. 到达时间点 / 触发事件
  2. Loader 创建 Behavior Instance(快照)
  3. 每一帧由 evaluator 求值
  4. 行为自然结束或被更高优先级行为覆盖

行为不是“切换状态”,而是“被选择执行”。


五、FSM 与 BT 的核心差异(结合动画语境)

一句话区分

FSM 管“我现在是什么状态”
BT 管“我现在要不要做这件事”


场景一:鱼的整体运动

  • 默认:绕点转圈
  • 点击:摆尾 → 移动 → 围绕新点

FSM 思路

  • Orbit → TailWhip → Move → Orbit
  • 行为被强制串成状态

BT 思路

  • 如果有点击 → 执行点击序列
  • 否则 → 执行默认绕圈

绕圈是 fallback 行为,而不是状态。


场景二:尾巴行为(并行维度)

  • 默认慢摆
  • 点击快摆
  • 快速移动时不摆

FSM 的问题

  • 需要额外 FSM
  • 状态组合爆炸

BT 的优势

text
Priority Selector
├─ 如果正在快速移动 → 不摆尾
├─ 如果有点击 → 快速摆尾
└─ 默认 → 慢摆

尾巴不是状态,而是持续决策。


六、与你当前模型的对应关系

你的概念BT 对应
World State黑板(Blackboard)
Behavior Declaration行为节点定义
Evaluator节点执行逻辑
Timeline / Event条件节点
无污染运行时每帧重新决策

核心共识

所有“结果”都应该是被计算出来的,而不是被维护的。


七、最终总结

  • Config:声明可能性

  • Library:定义语义与规则

  • Runtime:在时间中反复求值

  • FSM 适合阶段剧情

  • BT 适合持续动画与角色行为

而你正在构建的系统,本质上是:

一个用动画语言实现的行为树执行器。

只是你是从工程直觉,而不是从 AI 教科书出发走到这里的。

这不是绕路,这是更干净的那条路。