Skip to content

自定义动画(Custom Animation)

除了系统提供的预定义轨道(如平移、旋转、缩放等)外,
Zephyr3D 还允许开发者通过继承 AnimationTrack 类创建自定义轨道
从而实现任意效果的自定义动画(如 UV 滚动、材质变化、颜色淡入淡出等)。


自定义轨道的核心原理

自定义轨道(Custom AnimationTrack)是通过继承基类 AnimationTrack 实现的。
你需要重写以下四个关键方法,以定义轨道如何计算状态、混合状态并应用到目标上。

方法说明
calculateState(target, currentTime)计算指定时刻的轨道状态(如位置、旋转、数值等),返回一个状态对象,可为 Vector3QuaternionNumber 或任何类型。
mixState(stateA, stateB, t)在两个状态间插值,用于动作融合。必须返回新状态对象。
applyState(target, state)将当前轨道状态应用到目标对象上。目标对象通常为 SceneNode
getBlendId()返回轨道的唯一标识。具有相同 blendId 的轨道被认为可相互混合,但不能同时存在于一个动画片段中。

💡 注意:
同一 AnimationClip 中,不允许添加两个具有相同 blendId 的轨道, 否则系统会认为它们控制的是同一属性。


示例:创建自定义 UV 动画轨道

下面示例演示如何编写一个自定义动画轨道,使模型材质的贴图产生循环滚动的 UV 动画效果。

javascript
// 自定义动画轨道:实现UV动画
class MyAnimationTrack extends AnimationTrack {
  _state; // 当前轨道状态(Float32Array)
  _interpolator; // 插值器,用于计算关键帧间的状态

  // 构造函数:接收一个关键帧插值器
  constructor(interpolator) {
    super();
    this._interpolator = interpolator;
    this._state = new Float32Array(1);
  }

  // 计算并返回给定时刻的轨道状态
  calculateState(target, currentTime) {
    this._interpolator.interpolate(currentTime, this._state);
    return this._state;
  }

  // 混合两个动画状态(用于融合)
  mixState(a, b, t) {
    const result = new Float32Array(1);
    result[0] = a[0] + (b[0] - a[0]) * t;
    return result;
  }

  // 将状态应用至节点
  applyState(target, state) {
    // 遍历节点下的所有Mesh
    target.iterate((child) => {
      if (child.isMesh()) {
        // 应用UV动画(平移UV坐标)
        child.material.albedoTexCoordMatrix = Matrix4x4.translation(
          new Vector3(state[0], 0, 0)
        );
      }
    });
  }

  // Blend ID用于区分轨道类型
  getBlendId() {
    return 'uv-animation';
  }
}

应用自定义轨道

完整应用流程包括以下几步:

  1. 加载模型
  2. 创建动画片段
  3. 构建插值器(Interpolator)
  4. 创建并添加自定义轨道
  5. 播放动画
javascript
// 步骤 1:加载模型
const model = await getEngine().resourceManager.instantiatePrefab(
  scene.rootNode,
  '/assets/BoxTextured.zprefab'
);

// 步骤 2:创建一个动画片段
const animation = model.animationSet.createAnimation('UserTrackTest');

// 步骤 3:创建一个关键帧插值器
const interpolator = new Interpolator(
  'linear',                  // 插值模式('linear' / 'step' / 'cubicspline')
  null,                      // 自动计算关键帧维度
  new Float32Array([0, 2]),  // 时间关键帧:0s → 2s
  new Float32Array([0, 1])   // 值关键帧:UV 从 0 滚动到 1
);

// 步骤 4:创建并添加自定义轨道
const track = new MyAnimationTrack(interpolator);
animation.addTrack(model, track);

// 步骤 5:播放动画
model.animationSet.playAnimation('UserTrackTest', {
  repeat: 0,     // 循环次数(0 表示无限循环)
  speedRatio: 1, // 播放速度
  weight: 1,     // 融合权值(多个动画叠加时使用)
  fadeIn: 0,     // 淡入时间
});

方法实现说明

calculateState()

用于根据当前播放时间计算轨道状态,如位置或数值变化。
系统每帧调用此方法,然后通过 applyState() 将结果应用到目标节点。

mixState()

当两个具有相同 blendId 的动画同时播放时调用此方法,
返回两个状态按比例 t 混合后的新状态,用于动画融合。

applyState()

实际将计算得到的轨道状态作用于节点,例如:

  • 修改位移、旋转、缩放;
  • 调整材质或颜色属性;
  • 更新自定义属性(如灯光强度或贴图偏移)。

getBlendId()

用于定义轨道类型唯一性。
系统通过 blendId 判定轨道能否融合或是否冲突。


实用技巧

使用场景建议实现方式
自定义材质动画(UV滚动、闪烁)通过材质属性实现(如 UVemission
摄像机特效(焦距、景深变化)轨道直接修改摄像机参数
灯光动画(明暗变化)轨道修改光照强度或颜色
环境变化(雾效、曝光)控制全局渲染参数实现

小结

  • 通过继承 AnimationTrack 可实现各种自定义动画;
  • 必须实现 calculateState()mixState()applyState()getBlendId() 四个核心方法;
  • 可结合 Interpolator 实现精准的关键帧控制;
  • 自定义轨道的强大之处在于——几乎可以对引擎中任何可动画参数进行控制

建议:

  • 保持各轨道的 blendId 唯一以避免冲突;
  • 自定义动画适合用于高级材质、环境或特效系统;
  • Zephyr3D 编辑器 支持为大部分节点对象属性创建关键帧动画;

Released under the MIT License.