关于 threejs-animation
面向 Three.js 开发者的 3D 动画实现技能,涵盖导入动画、骨架和程序化动作。重点是按 delta 时间更新、妥善切换动作,并随模型生命周期清理状态。
主要功能
- 01使用 AnimationMixer 管理动画片段
- 02混合并切换动画动作
- 03驱动骨骼与形态目标
- 04按 delta 时间实现帧率无关的动作
- 05清理混合器状态和监听器
适用场景
- 01播放、暂停、定位或循环导入的动画
- 02修复依赖帧率的动画问题
- 03平滑混合动作,避免画面跳变
- 04模型移除后清除残留动画状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ akillness/ jeo-skills --skill threejs-animation安装后在 Claude Code 中输入/threejs-animation确认已加载。
附带文件SKILL.toonevals/evals.json
详细介绍
Three.js 动画
使用此技能实现动画本身:片段选择、混合器所有权、混合、骨架/变形驱动,以及时间步长的正确性。使用 threejs-loaders 加载模型;如果主要问题是游戏状态或战斗设计,而非动画 API,请使用 web-game-development。
适用场景
- 通过
AnimationMixer播放、暂停、跳转、循环或混合AnimationClip数据 - 将 GLTF 片段附加到已加载的场景,并随模型生命周期清理混合器
- 为骨骼、变形目标影响值或轻量级程序化运动添加动画
- 修复动画无法播放、过渡时跳变、播放速度随帧率变化或发生泄漏的问题
说明
步骤 1:确定动画的所有者和时钟
- 检查模型的
gltf.animations、根对象和当前渲染循环的所有者。 - 每个带动画的根对象创建一个混合器,并在每帧推进一次,使用经过的增量时间,而非固定的每帧时间量。
- 按语义名称保留动作引用;不要盲目按索引访问导出的片段数组,因为资源版本更新可能会改变片段顺序。
const mixer = new THREE.AnimationMixer(gltf.scene);
const clips = new Map(gltf.animations.map((clip) => [clip.name, clip]));
const idle = mixer.clipAction(clips.get("Idle"));
idle.reset().fadeIn(0.2).play();
const clock = new THREE.Clock();
function update() {
mixer.update(clock.getDelta());
renderer.render(scene, camera);
requestAnimationFrame(update);
}
update();
步骤 2:平滑过渡动作,避免视觉不连续
明确设置循环和钳制行为;播放新动作前重置该动作;只有片段共用兼容的姿势/骨架时,才使用交叉淡化。
function transition(from, to, duration = 0.2) {
to.reset().setEffectiveWeight(1).setEffectiveTimeScale(1).play();
from.crossFadeTo(to, duration, false);
}
对于单次播放的动作,监听混合器的 finished 事件,并切换回已知的待机动作。功能拆除时移除监听器。
步骤 3:选择最精简的动画机制
| 需求 | 默认机制 |
|---|---|
| 已制作的变换/属性关键帧 | AnimationClip + 关键帧轨道 |
| 导入的角色动画 | AnimationMixer + clipAction |
| 逐帧待机动作或 UI 响应 | 基于增量时间的程序化更新 |
| 面部表情或网格变形 | morphTargetInfluences |
| 运行时骨骼调整 | 混合器更新后的骨骼变换 |
| 分层叠加姿势 | 仅在验证参考姿势后使用叠加片段 |
在 mixer.update(delta) 之后应用程序化骨骼偏移,以免已制作的片段覆盖这些偏移。将开销较大的 IK、物理或重定向处理限制在经过测量的预算内。
步骤 4:随功能一同释放动画状态
带动画的根对象被永久移除时,调用 mixer.stopAllAction() 和 mixer.uncacheRoot(root)。如果根对象由其他场景或实例共享,则不要取消其缓存。
步骤 5:验证动画行为
- 在低帧率和高帧率下测试;动画时长和移动距离必须保持一致。
- 测试过渡中断、单次播放完成和快速切换动作。
- 确认命名片段存在,并在资源导出内容发生变化时报告有用的错误。
- 反复挂载/卸载场景来验证清理效果,确认监听器或保留的混合器动作不会增加。
示例
为变形目标添加动画
const face = gltf.scene.getObjectByName("Face");
face.morphTargetInfluences[0] = 0.75;
确认网格包含变形目标;如果导出内容包含多个表情,请使用资源中的命名索引映射,而非魔法数字。
驱动简单的程序化上下浮动
const elapsed = clock.getElapsedTime();
mesh.position.y = baseY + Math.sin(elapsed * 2) * 0.1;
单独存储 baseY。累加位置变化会造成漂移,也会使其难以与已制作的动画混合。
最佳实践
- 每帧使用以秒为单位的
delta,且恰好更新混合器一次。 - 已制作的动作优先使用导出的片段;程序化偏移应为叠加式且局部化。
- 在导出时为片段命名,并在加载时验证名称。
- 克隆骨架或混合器前,确认骨架和材质的共享所有权。
- 在目标硬件上分析蒙皮网格和变形目标的性能,再增加其数量。
参考资料
评论
支持 Markdown