threejs-impl-animation について
Three.js のアニメーションクリップ、アクション、ミキサーの実装を案内するリファレンスです。再生、ブレンド、時間管理とよくある実装上の注意点を扱います。
主な機能
- 01ミキサーとアクションの管理指針
- 02クリップ再生とループ設定
- 03フェードとクロスフェードのパターン
- 04加算ブレンドとモーフターゲットアニメーション
- 05Clock によるフレーム時間管理
活用シーン
- 01GLTF のキャラクターアニメーション再生
- 02アニメーション状態の切り替え
- 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 と一緒にインストールされる参考資料やスクリプト。
.gitkeepreferences/anti-patterns.mdexamples.mdmethods.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 をちょうど1つ作成してください。
レンダーループ内で、毎フレーム必ず 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) を呼び出し忘れてはいけません。呼び出さないとアニメーションは再生されません。
delta を手動で計算するために new Date() や performance.now() を使ってはいけません。安定したフレームタイミングを提供する THREE.Clock または renderer.setAnimationLoop を必ず使用してください。
レンダーループ内で mixer.clipAction(clip) を繰り返し呼び出してはいけません。内部でキャッシュされますが、検索は不要なオーバーヘッドになります。返されたアクションは必ず変数に格納してください。
毎フレーム .play() を呼び出してはいけません。再生を開始するときに1回だけ呼び出してください。すでに再生中のアクションに .play() を再度呼び出しても効果はありませんが、誤解を招く使い方です。
停止または終了したアクションを再開するときは、必ず .play() の前に .reset() を呼び出してください。そうしないと、アクションが最後の位置から再開する場合があります。
LoopOnce を使用する場合は、必ず action.clampWhenFinished = true を設定してください。そうしないと、終了時にアクションが最初のフレームに戻ります。
AnimationMixer
単一のオブジェクト階層に属するすべてのアニメーションアクションを駆動する、マスタースケジューラーです。
コンストラクター
const mixer = new THREE.AnimationMixer(rootObject);
rootObject はアニメーション化されたモデルのルート Object3D です(通常は gltf.scene)。
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
.time | number | 0 | ミキサーのグローバル時間(秒) |
.timeScale | number | 1 | グローバル速度倍率。0 にするとすべてのアクションが一時停止します |
メソッド
| メソッド | 戻り値 | 説明 |
|---|---|---|
.clipAction(clip, root?, blendMode?) | AnimationAction | クリップのアクションを返すか、新しく作成します |
.existingAction(clip, root?) | AnimationAction | null | 以前に作成したアクション、または null を返します |
.update(delta) | this | delta 秒分ミキサーを進めます。毎フレーム必ず呼び出してください |
.setTime(seconds) | this | グローバル時間を設定し、すべてのアクションを更新します |
.stopAllAction() | this | スケジュールされているすべてのアクションを無効化します |
.getRoot() | Object3D | ミキサーのルートオブジェクトを返します |
.uncacheAction(clip, root?) | void | キャッシュされたアクションを解放します |
.uncacheClip(clip) | void | クリップデータを解放します |
.uncacheRoot(root) | void | ルートオブジェクトのデータを解放します |
イベント
mixer.addEventListener(type, callback) でリッスンします。
| イベント | 発生するタイミング |
|---|---|
'finished' | アクションが完了したとき(LoopOnce と clampWhenFinished = true の組み合わせの場合のみ) |
'loop' | アクションがループを1回完了したとき |
イベントオブジェクトのプロパティ:{ action, loopDelta, type }。
ブレンドモード
mixer.clipAction(clip, root, blendMode) の第3引数として渡します。
| 定数 | 動作 |
|---|---|
THREE.NormalAnimationBlendMode | 標準的なブレンド(デフォルト) |
THREE.AdditiveAnimationBlendMode | ベースアニメーションに重ねて適用 |
AnimationAction
単一のアニメーションクリップの再生を制御します。直接インスタンス化してはいけません。
プロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
.blendMode | number | NormalAnimationBlendMode | ブレンド方式 |
.clampWhenFinished | boolean | false | 完了時に最後のフレームで一時停止します |
.enabled | boolean | true | リセットせずに無効化します |
.loop | number | LoopRepeat | ループモード |
.paused | boolean | false | 再生を一時停止します |
.repetitions | number | Infinity | ループ回数 |
.time | number | 0 | 秒単位のローカル時間 |
.timeScale | number | 1 | 再生速度:0 で一時停止し、負の値で逆再生します |
.weight | number | 1 | ブレンドの影響度 [0, 1] |
.zeroSlopeAtEnd | boolean | true | ループ終了時の補間を滑らかにします |
.zeroSlopeAtStart | boolean | true | ループ開始時の補間を滑らかにします |
ループモード
| 定数 | 動作 |
|---|---|
THREE.LoopOnce | 1 回再生して停止します |
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) | 1 回のループがちょうど seconds 秒になるように timeScale を調整します |
.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-- 秒数。トラックから自動計算する場合は-1tracks--KeyframeTrackオブジェクトの配列blendMode-- オプションのブレンドモード定数
主な静的メソッド
| メソッド | 説明 |
|---|---|
AnimationClip.findByName(arrayOrObject, name) | 名前でクリップを検索します |
AnimationClip.CreateFromMorphTargetSequence(name, targets, fps, noLoop) | モーフターゲットからクリップを作成します |
AnimationClip.parse(json) | JSON からデシリアライズします |
KeyframeTrack の型
| トラックの型 | 値の型 | 用途 |
|---|---|---|
VectorKeyframeTrack | Vector3 | 位置、スケール |
QuaternionKeyframeTrack | Quaternion | 回転(slerp を使用) |
NumberKeyframeTrack | number | 不透明度、強度 |
BooleanKeyframeTrack | boolean | 表示状態の切り替え |
ColorKeyframeTrack | Color | 色のアニメーション |
StringKeyframeTrack | string | 離散的な文字列値 |
補間モード
| 定数 | 動作 |
|---|---|
THREE.InterpolateDiscrete | ステップ関数。スムージングなし |
THREE.InterpolateLinear | 線形補間(デフォルト) |
THREE.InterpolateSmooth | 3 次スプライン補間 |
PropertyBinding のパス形式
"meshName.position" // animate position
"meshName.material.opacity" // animate material property
"meshName.morphTargetInfluences[0]" // animate morph target
"boneName.quaternion" // animate bone rotation
Clock
コンストラクター
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();
参考リンク
- references/methods.md -- API シグネチャの一覧
- references/examples.md -- 動作するコード例
- references/anti-patterns.md -- 避けるべき実装
公式ソース
- https://threejs.org/docs/#api/en/animation/AnimationMixer
- https://threejs.org/docs/#api/en/animation/AnimationAction
- https://threejs.org/docs/#api/en/animation/AnimationClip
- https://threejs.org/docs/#api/en/animation/KeyframeTrack
- https://threejs.org/docs/#api/en/core/Clock
- https://threejs.org/examples/#webgl_animation_skinning_blending