mcpField
T
threejs-impl-animation

threejs-impl-animation

github.com/impertio-studio开发23140 次安装30 周前更新
代码仓库

关于 threejs-impl-animation

面向 Three.js 开发者的动画实现参考,涵盖动画片段、动作与混合器,并说明播放、混合、计时及常见实现问题。

主要功能

  1. 01混合器与动作生命周期指南
  2. 02片段播放与循环模式
  3. 03淡入淡出和交叉淡化模式
  4. 04叠加与变形目标动画
  5. 05基于 Clock 的帧计时

适用场景

  1. 01播放 GLTF 角色动画
  2. 02切换不同动画状态
  3. 03叠加呼吸或受击动作

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/impertio-studio/three.js-claude-skill-package --skill threejs-impl-animation

安装后在 Claude Code 中输入/threejs-impl-animation确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.gitkeep
references/
anti-patterns.md
examples.md
methods.md

详细介绍

threejs-impl-animation

快速参考

架构
AnimationClip        (data: array of KeyframeTrack objects)
  └── AnimationAction  (playback controller: play, pause, fade, crossfade)
        └── AnimationMixer (master scheduler: one per animated root object)
              └── Clock    (provides delta time for mixer.update)

每个动画根对象始终只创建一个 AnimationMixer。 每帧始终在渲染循环中调用 mixer.update(delta)。 切勿直接实例化 AnimationAction——始终使用 mixer.clipAction(clip)。

必要导入
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
最简动画设置
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const clock = new THREE.Clock();
let mixer;

const loader = new GLTFLoader();
loader.load('character.glb', (gltf) => {
  scene.add(gltf.scene);
  mixer = new THREE.AnimationMixer(gltf.scene);

  // Play all animations from the GLTF file
  gltf.animations.forEach((clip) => {
    mixer.clipAction(clip).play();
  });
});

function animate() {
  const delta = clock.getDelta();
  if (mixer) mixer.update(delta);
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
重要警告

切勿忘记在渲染循环中调用 mixer.update(delta)——否则动画不会播放。

切勿使用 new Date() 或 performance.now() 手动计算 delta——始终使用 THREE.Clock 或 renderer.setAnimationLoop,它们能提供稳定的帧时间。

切勿在渲染循环中反复调用 mixer.clipAction(clip)——虽然它会在内部缓存,但查找会带来不必要的开销。始终将返回的 action 存储在变量中。

切勿每帧都调用 .play()——只需调用一次即可开始播放。对正在播放的 action 再次调用 .play() 不会产生效果,但这表明你对其用法理解有误。

重新启动已停止或已结束的 action 时,始终先调用 .reset() 再调用 .play(),否则 action 可能会从上次停止的位置继续播放。

使用 LoopOnce 时,始终设置 action.clampWhenFinished = true——否则 action 结束时会重置到第一帧。


AnimationMixer

用于驱动单个对象层级中所有动画 action 的主调度器。

构造函数
const mixer = new THREE.AnimationMixer(rootObject);

rootObject 是动画模型的根 Object3D(通常为 gltf.scene)。

属性
属性类型默认值描述
.timenumber0全局混合器时间,单位为秒
.timeScalenumber1全局速度倍率;设为 0 会暂停所有 action
方法
方法返回值描述
.clipAction(clip, root?, blendMode?)AnimationAction返回或创建 clip 对应的 action
.existingAction(clip, root?)AnimationAction | null返回之前创建的 action,或返回 null
.update(delta)this将混合器推进 delta 秒——必须每帧调用
.setTime(seconds)this设置全局时间并更新所有 action
.stopAllAction()this停用所有已调度的 action
.getRoot()Object3D返回混合器的根对象
.uncacheAction(clip, root?)void释放缓存的 action
.uncacheClip(clip)void释放 clip 数据
.uncacheRoot(root)void释放根对象数据
事件

通过 mixer.addEventListener(type, callback) 监听:

事件触发时机
'finished'action 播放完成(仅当使用 LoopOnce 且 clampWhenFinished = true 时)
'loop'action 完成一轮循环

事件对象属性:{ action, loopDelta, type }。

混合模式

将其作为第三个参数传递给 mixer.clipAction(clip, root, blendMode):

常量行为
THREE.NormalAnimationBlendMode标准混合(默认)
THREE.AdditiveAnimationBlendMode叠加在基础动画之上

AnimationAction

控制单个动画 clip 的播放。切勿直接实例化。

属性
属性类型默认值描述
.blendModenumberNormalAnimationBlendMode混合策略
.clampWhenFinishedbooleanfalse完成后暂停在最后一帧
.enabledbooleantrue禁用但不重置
.loopnumberLoopRepeat循环模式
.pausedbooleanfalse冻结播放
.repetitionsnumberInfinity循环次数
.timenumber0本地时间(秒)
.timeScalenumber1速度:0 表示暂停,负值表示倒放
.weightnumber1混合影响 [0, 1]
.zeroSlopeAtEndbooleantrue在循环结束时平滑插值
.zeroSlopeAtStartbooleantrue在循环开始时平滑插值
循环模式
常量行为
THREE.LoopOnce播放一次后停止
THREE.LoopRepeat每次循环都从头开始
THREE.LoopPingPong在正向和反向之间交替播放
播放方法
方法描述
.play()开始播放
.stop()停止并重置到开头
.reset()将时间、权重和速度重置为初始状态
.startAt(mixerTime)延迟到指定的混合器时间后开始
淡入淡出和交叉淡化方法
方法描述
.fadeIn(duration)将权重从 0 淡入到 1
.fadeOut(duration)将权重从 1 淡出到 0
.crossFadeFrom(fadeOutAction, duration, warp)从另一个动作交叉淡化到此动作
.crossFadeTo(fadeInAction, duration, warp)从此动作交叉淡化到另一个动作
.stopFading()取消所有正在进行的淡化
速度和计时方法
方法描述
.halt(duration)在指定时长内将 timeScale 减速至 0
.warp(startScale, endScale, duration)平滑过渡播放速度
.stopWarping()取消所有正在进行的速度渐变
.setDuration(seconds)调整 timeScale,使一次循环恰好持续 seconds 秒
.setEffectiveTimeScale(scale)设置有效时间缩放
.setEffectiveWeight(weight)设置有效权重
.setLoop(mode, repetitions)设置循环模式和次数
.syncWith(otherAction)与另一个动作同步时间
查询方法
方法返回值描述
.isRunning()boolean仅当正在播放时返回 true
.isScheduled()boolean如果已调用 .play(),则返回 true
.getClip()AnimationClip关联的片段
.getMixer()AnimationMixer所属的混合器
.getRoot()Object3D根对象
.getEffectiveTimeScale()number计算后的时间缩放
.getEffectiveWeight()number计算后的权重

AnimationClip

一组可重复使用的关键帧轨道。通常从 GLTF 文件加载。

构造函数
const clip = new THREE.AnimationClip(name, duration, tracks, blendMode);
  • name -- 字符串标识符(GLTF 片段使用文件中的名称)
  • duration -- 秒数;-1 表示根据轨道自动计算
  • tracks -- KeyframeTrack 对象数组
  • blendMode -- 可选的混合模式常量
主要静态方法
方法描述
AnimationClip.findByName(arrayOrObject, name)按名称查找片段
AnimationClip.CreateFromMorphTargetSequence(name, targets, fps, noLoop)根据变形目标创建片段
AnimationClip.parse(json)从 JSON 反序列化

KeyframeTrack 类型

轨道类型值类型用途
VectorKeyframeTrackVector3位置、缩放
QuaternionKeyframeTrackQuaternion旋转(使用球面线性插值)
NumberKeyframeTracknumber不透明度、强度
BooleanKeyframeTrackboolean可见性切换
ColorKeyframeTrackColor颜色动画
StringKeyframeTrackstring离散字符串值
插值模式
常量行为
THREE.InterpolateDiscrete阶跃函数,不进行平滑
THREE.InterpolateLinear线性插值(默认)
THREE.InterpolateSmooth三次样条插值
PropertyBinding 路径格式
"meshName.position"                    // animate position
"meshName.material.opacity"            // animate material property
"meshName.morphTargetInfluences[0]"    // animate morph target
"boneName.quaternion"                  // animate bone rotation

时钟

构造函数
const clock = new THREE.Clock(autoStart); // autoStart defaults to true
方法
方法返回值描述
.getDelta()number距上次调用 getDelta() 的秒数
.getElapsedTime()number总经过时间(秒)
.start()void启动时钟
.stop()void停止时钟,但不重置

淡入淡出模式(角色状态机)

const actions = {};
gltf.animations.forEach((clip) => {
  actions[clip.name] = mixer.clipAction(clip);
});

let currentAction = actions['Idle'];
currentAction.play();

function switchAction(toName, duration = 0.5) {
  const toAction = actions[toName];
  toAction.reset();
  toAction.setEffectiveTimeScale(1);
  toAction.setEffectiveWeight(1);
  toAction.crossFadeFrom(currentAction, duration, true);
  toAction.play();
  currentAction = toAction;
}

交叉淡化前,务必对即将进入的动作调用 .reset()。 务必保存当前动作的引用,以用于下一次过渡。


加法动画混合

const baseAction = mixer.clipAction(baseClip);
const additiveAction = mixer.clipAction(
  additiveClip, undefined, THREE.AdditiveAnimationBlendMode
);

baseAction.play();
additiveAction.play();
additiveAction.setEffectiveWeight(0.5);

分层效果(如呼吸、受伤反应、瞄准偏移)请使用加法混合。


变形目标动画

// Manual control
mesh.morphTargetInfluences[0] = Math.sin(elapsed) * 0.5 + 0.5;

// Via GLTF animation clip (preferred)
const morphAction = mixer.clipAction(morphClip);
morphAction.play();

参考链接

官方来源

评论

支持 Markdown