threejs-animation について
Three.js の 3D シーンでアニメーションを実装する開発者向けスキルです。インポートしたクリップやリグ、プロシージャルな動きを扱い、delta 時間での更新、遷移、ライフサイクルに沿った後処理を重視します。
主な機能
- 01AnimationMixer によるクリップ管理
- 02アクションのブレンドと遷移
- 03ボーンとモーフターゲットの駆動
- 04delta 時間によるフレームレート非依存の動き
- 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、ルートオブジェクト、現在のレンダーループの所有者を確認します。 - アニメーションするルートごとにミキサーを 1 つ作成し、固定の 1 フレームあたりの値ではなく、経過した delta 時間で毎フレーム 1 回更新します。
- アクションの参照は意味のある名前で保持します。アセットの改訂によってクリップの順序が変わる可能性があるため、エクスポートされたクリップ配列を無条件にインデックス指定しないでください。
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 の応答 | delta 時間によるプロシージャル更新 |
| 表情やメッシュの変形 | 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秒を使って毎フレームちょうど 1 回更新します。 - 作成済みの動作にはエクスポートされたクリップを優先し、プロシージャルなオフセットは加算的かつ局所的にします。
- エクスポート時にクリップへ名前を付け、読み込み時に名前を検証します。
- 共有されているスケルトンやマテリアルの所有権を確認せずに、リグやミキサーを複製しないでください。
- 数を増やす前に、対象ハードウェアでスキンメッシュとモーフターゲットの性能をプロファイリングします。