动画系统必须用模板参数控制类型,支持Animation<vec4>和Animation<quat>共享插值逻辑与生命周期管理,要求类型提供static lerp或特化基础路径,播放状态与采样解耦,关键帧用连续内存存储,组合靠BlendAnimation和事件驱动,资源数据与播放实例分离。

动画系统要不要用模板参数控制类型
必须用。硬编码 float 或 vec3 会导致后续支持颜色、四元数、自定义结构体时反复重构。模板参数不是为了炫技,是让 Animation<vec4> 和 Animation<quat> 共享同一套插值逻辑和生命周期管理,但各自调用正确的 lerp 或 slerp。
常见错误是把插值函数塞进类内部写死,结果加个 std::array<float, 4> 就得复制一遍类。正确做法是要求类型提供 static T lerp(const T& a, const T& b, float t),或通过 std::is_arithmetic_v<T> 特化基础类型路径。
- 别在模板参数里塞策略类(比如
Animation<T, Interpolator>),90% 场景用不到,反而增加调用方理解成本 - 对非 POD 类型(如
Transform),必须显式特化lerp,否则编译失败比运行时插值错更早暴露问题 - 模板实例化后,每个类型生成独立 vtable,注意链接时重复符号问题——把实现全放在头文件里
如何统一管理时间与播放状态
不要用全局帧计数器或 std::chrono::steady_clock::now() 每帧重算。动画组件必须持有自己的 elapsed_time 和 play_state(Playing / Paused / Stopped),否则多个动画之间会因帧率抖动不同步,也无法做时间缩放(slow-mo)或倒播。
关键点在于:播放控制和采样必须解耦。你调用 animation.update(dt) 只负责推进内部时间,而 animation.sample() 才返回当前时刻的值——这样才支持“预览某时刻”或“离线烘焙”等需求。
立即学习“C++免费学习笔记(深入)”;
-
update(float dt)中对dt做 clamping:若dt > max_dt_per_frame(例如 1/10 秒),截断防止跳跃。物理引擎常犯这个错 - 暂停时不清零
elapsed_time,只停更新;恢复时从原位置继续,而不是跳回起点 - 循环模式(
Loop/PingPong/Clamp)应在sample()内部计算归一化t,不污染时间轴逻辑
关键帧数据怎么存才不卡主线程
运行时绝不存 std::vector<Keyframe<T>> 并每帧遍历查找。固定数量关键帧(比如最多 64)直接用 std::array + 二分查找;动态数量则用两个平行数组:std::vector<float> times 和 std::vector<T> values,保证内存连续、缓存友好。
导入工具链(如 FBX 解析器)应提前把贝塞尔控制点烘焙成等距采样点,避免运行时求解三次方程。动画数据本质是查表,不是实时计算。
- 关键帧时间必须升序存储,且首尾显式包含
0.0f和duration,省去边界判断 - 不要为每个关键帧存完整
T—— 对vec3位移动画,可拆成三个独立 float 数组,SIMD 加速时更友好 - 若支持曲线编辑(如 Unity 的 AnimationCurve),运行时只需存烘焙后的 32–64 点浮点序列,而非 Bezier 控制点
怎么让动画能组合又不爆炸复杂度
靠继承或虚函数做「动画树」是条死路。真正可扩展的方式只有两种:复合动画(BlendAnimation)、事件驱动(AnimationEvent)。前者用于叠加(如呼吸+挥手),后者用于触发逻辑(“脚落地时播放音效”)。
BlendAnimation<T> 必须支持权重渐变(fade in/out),而不是简单加权平均——否则两段动画切换时会出现速度突变。权重本身也得是可动画的,即用另一个 Animation<float> 驱动。
- 所有组合类必须保持和基础
Animation<T>完全一致的接口:同样有update()、sample()、seek(),否则上层代码要写两套逻辑 - 事件不放在动画数据里,而是由外部注册回调:
animation.on_event("foot_hit", []{ play_sound(); });—— 动画组件只负责在精确时间点触发,不持有业务逻辑 - 避免「动画状态机」这种重量级方案,除非真有几十个状态要流转;多数 UI 或角色局部动画,用 if-else 切换几个
Animation<T>实例更轻量
最易被忽略的是资源生命周期:动画数据(关键帧数组)和播放实例(当前时间、状态)必须分离。一份数据可被多个实例引用,但每个实例有自己的时间轴——这点没想清楚,后面多对象同播同一段动画时就会出现时间互相覆盖。



















