animations-tweens について
animations-tweensは、Animator、Tween、TweenSequence、自作システムを使ってエンティティやGLTFモデルをアニメーションさせるDecentraland開発向けスキルです。モデル内蔵クリップ、滑らかなプロパティ変更、連続動作、フレーム単位の制御の使い分けを説明します。
主な機能
- 01GLTFアニメーションクリップの再生と切り替え
- 02位置・回転・スケールのトゥイーン
- 03TweenSequenceでトゥイーンを連結
- 04イージングとループ方式の選択
- 05モーフターゲットのウェイト制御
- 06独自のフレーム単位アニメーションシステム
活用シーン
- 01GLTFクリップでキャラクターをアニメーション
- 02ドアを位置間で移動または回転
- 03巡回経路をループ再生
- 04モーフターゲットでモデルの表情を制御
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill animations-tweensインストール後、Claude Code で/animations-tweensを入力して読み込まれたか確認します。
詳細
Decentraland のアニメーションとトゥイーン
記述の分担
Animator、Tween、TweenSequence はすべて main-entities.ts でサポートされています。エンティティ、そのビジュアルコンポーネント、初期アニメーション状態をリテラルで宣言します。src/index.ts では、実行時に getMutable を使ってクリップを切り替えたり、トゥイーンを置き換えたりします。
アニメーション方式の選び方
| 必要なこと | 方式 | 使用場面 |
|---|---|---|
| .glb モデルに組み込まれたアニメーションを再生する | Animator | キャラクターの歩行、ドアの開閉、旗の揺れなど、Blender/Maya で作成したあらゆるアニメーション |
| エンティティを滑らかに移動/回転/拡大縮小する | Tween | スライド式ドア、浮遊するプラットフォーム、成長するオブジェクトなど、プロシージャルな A から B への動き |
| 複数のアニメーションを順番につなげる | TweenSequence | パトロール経路、複数段階のドア、複雑な振り付け |
| フレームごとに連続して制御する | engine.addSystem() | 物理演算のような動き、ターゲットの追跡、カスタムイージング |
選択の流れ:
- .glb モデルにアニメーションがすでに含まれていますか? →
Animator - 2 つの値の間で単純に移動/回転/拡大縮小するものですか? →
Tween - フレームごとの制御やカスタム計算が必要ですか? →
dtを使うシステム
GLTF アニメーション(Animator)
main-entities.ts でキャラクターとそのアニメーション状態を宣言します。states 配列は Animator の形式の一部であり、JSON と互換性があります。
// main-entities.ts
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
character: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/character.glb' },
Animator: {
states: [
{ clip: 'idle', playing: true, loop: true, speed: 1 },
{ clip: 'walk', playing: false, loop: true, speed: 1 },
{ clip: 'attack', playing: false, loop: false, speed: 1.5 }
]
}
}
}
} satisfies Scene
src/index.ts で実行時にアニメーションをトリガーして切り替えます。
import { engine, Animator } from '@dcl/sdk/ecs'
export function main() {
const character = engine.getEntityOrNullByName('character')
if (!character) return
// Play a specific animation
Animator.playSingleAnimation(character, 'walk')
// Stop all animations
// Animator.stopAllAnimations(character)
}
アニメーションの切り替え
import { Entity, Animator } from '@dcl/sdk/ecs'
function playAnimation(entity: Entity, clipName: string) {
const animator = Animator.getMutable(entity)
for (const state of animator.states) state.playing = false
const state = animator.states.find(s => s.clip === clipName)
if (state) state.playing = true
}
トゥイーン
Tween と TweenSequence は JSON と互換性があります。mode は $case タグ付きのユニオン型です。位置/クォータニオンはプレーンなオブジェクトリテラルで、イージングは数値の列挙型です。
移動
// main-entities.ts
sliding_box: {
components: {
Transform: { position: { x: 2, y: 1, z: 8 } },
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
Tween: {
duration: 2000, // milliseconds
easingFunction: 6, // EasingFunction.EF_EASESINE
mode: {
$case: 'move',
move: { start: { x: 2, y: 1, z: 8 }, end: { x: 14, y: 1, z: 8 } }
}
}
}
}
一般的なイージング値:0 = EF_LINEAR、1 = EF_EASEINQUAD、2 = EF_EASEOUTQUAD、3 = EF_EASEQUAD、6 = EF_EASESINE。実行時にトゥイーンを置き換えたり停止したりするには、src/index.ts で名前付きエンティティに対して Tween.createOrReplace/Tween.deleteFrom を使用します。
回転
// main-entities.ts — quaternions are { x, y, z, w } literals.
// 360° around Y end-state is { x: 0, y: 1, z: 0, w: 0 }
spinning_obj: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
Tween: {
duration: 3000,
easingFunction: 0, // EF_LINEAR
mode: {
$case: 'rotate',
rotate: {
start: { x: 0, y: 0, z: 0, w: 1 },
end: { x: 0, y: 1, z: 0, w: 0 }
}
}
}
}
}
クォータニオンを手計算せずに正確な角度で回転させるには、main-entities.ts からトゥイーンを省き、Quaternion.fromEulerDegrees() が使用できる src/index.ts で実行時に作成します。
// src/index.ts — runtime tween authoring (helpers allowed)
import { engine, Tween, EasingFunction } from '@dcl/sdk/ecs'
import { Quaternion } from '@dcl/sdk/math'
export function main() {
const box = engine.getEntityOrNullByName('spinning_obj')
if (!box) return
Tween.createOrReplace(box, {
mode: Tween.Mode.Rotate({
start: Quaternion.fromEulerDegrees(0, 0, 0),
end: Quaternion.fromEulerDegrees(0, 360, 0)
}),
duration: 3000,
easingFunction: EasingFunction.EF_LINEAR
})
}
拡大縮小
// main-entities.ts
grow_box: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
Tween: {
duration: 1000,
easingFunction: 14, // EF_EASEOUTBOUNCE
mode: {
$case: 'scale',
scale: { start: { x: 1, y: 1, z: 1 }, end: { x: 2, y: 2, z: 2 } }
}
}
}
}
トゥイーンシーケンス(連続アニメーション)
複数のトゥイーンをつなげて、順番に再生します。TweenSequence は main-entities.ts でサポートされています。
// main-entities.ts
patrol_box: {
components: {
Transform: { position: { x: 2, y: 1, z: 8 } },
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
Tween: {
duration: 2000,
easingFunction: 6, // EF_EASESINE
mode: { $case: 'move', move: { start: { x: 2, y: 1, z: 8 }, end: { x: 14, y: 1, z: 8 } } }
},
TweenSequence: {
loop: 0, // TweenLoop.TL_RESTART (1 = TL_YOYO)
sequence: [
{
duration: 2000,
easingFunction: 6,
mode: { $case: 'move', move: { start: { x: 14, y: 1, z: 8 }, end: { x: 2, y: 1, z: 8 } } }
}
]
}
}
}
イージング関数
main-entities.ts 内では整数値(左側)を使用します。src/index.ts では EasingFunction.<NAME> を直接参照できます。
| 値 | 列挙型 | 形状 |
|---|---|---|
| 0 | EF_LINEAR | 等速 |
| 1 | EF_EASEINQUAD | 二次イーズイン |
| 2 | EF_EASEOUTQUAD | 二次イーズアウト |
| 3 | EF_EASEQUAD | 二次イーズイン・アウト |
| 4 | EF_EASEINSINE | 正弦イーズイン |
| 5 | EF_EASEOUTSINE | 正弦イーズアウト |
| 6 | EF_EASESINE | 正弦イーズイン・アウト(滑らか) |
| 7 | EF_EASEINEXPO | 指数イーズイン |
| 8 | EF_EASEOUTEXPO | 指数イーズアウト |
| 9 | EF_EASEEXPO | 指数イーズイン・アウト |
| 10 | EF_EASEINELASTIC | 弾性イーズイン |
| 11 | EF_EASEOUTELASTIC | 弾性イーズアウト |
| 12 | EF_EASEELASTIC | 弾性イーズイン・アウト |
| 13 | EF_EASEINBOUNCE | バウンスイーズイン |
| 14 | EF_EASEOUTBOUNCE | バウンスイーズアウト |
| 15 | EF_EASEBOUNCE | バウンスイーズイン・アウト |
| 16 | EF_EASEINCUBIC | 三次イーズイン |
| 17 | EF_EASEOUTCUBIC | 三次イーズアウト |
| 18 | EF_EASECUBIC | 三次イーズイン・アウト |
| 19 | EF_EASEINQUART | 四次イーズイン |
| 20 | EF_EASEOUTQUART | 四次イーズアウト |
| 21 | EF_EASEQUART | 四次イーズイン・アウト |
| 22 | EF_EASEINQUINT | 五次イーズイン |
| 23 | EF_EASEOUTQUINT | 五次イーズアウト |
| 24 | EF_EASEQUINT | 五次イーズイン・アウト |
| 25 | EF_EASEINCIRC | 円形イーズイン |
| 26 | EF_EASEOUTCIRC | 円形イーズアウト |
| 27 | EF_EASECIRC | 円形イーズイン・アウト |
| 28 | EF_EASEINBACK | オーバーシュートイーズイン |
| 29 | EF_EASEOUTBACK | オーバーシュートイーズアウト |
| 30 | EF_EASEBACK | オーバーシュートイーズイン・アウト |
カスタムアニメーションシステム
複雑なアニメーションには、システムを作成します。
// Continuous rotation system
function spinSystem(dt: number) {
for (const [entity] of engine.getEntitiesWith(Transform, Spinner)) {
const transform = Transform.getMutable(entity)
const spinner = Spinner.get(entity)
// Rotate around Y axis
const currentRotation = Quaternion.toEulerAngles(transform.rotation)
transform.rotation = Quaternion.fromEulerDegrees(
currentRotation.x,
currentRotation.y + spinner.speed * dt,
currentRotation.z
)
}
}
engine.addSystem(spinSystem)
Tween ヘルパーメソッド
エンティティ上に Tween コンポーネントを直接作成または置き換える、省略形のヘルパーを使用します。
import { Tween, EasingFunction } from '@dcl/sdk/ecs'
// Move — signature: Tween.setMove(entity, start, end, duration, easingFunction?)
Tween.setMove(entity,
Vector3.create(0, 1, 0), Vector3.create(0, 3, 0),
1500, EasingFunction.EF_EASEINBOUNCE
)
// Rotate — signature: Tween.setRotate(entity, start, end, duration, easingFunction?)
Tween.setRotate(entity,
Quaternion.fromEulerDegrees(0, 0, 0), Quaternion.fromEulerDegrees(0, 180, 0),
2000, EasingFunction.EF_EASEOUTQUAD
)
// Scale — signature: Tween.setScale(entity, start, end, duration, easingFunction?)
Tween.setScale(entity,
Vector3.One(), Vector3.create(2, 2, 2),
1000, EasingFunction.EF_LINEAR
)
// Combined Move + Rotate + Scale in one Tween
Tween.setMoveRotateScale(entity, {
positionStart: Vector3.create(0, 0, 0), positionEnd: Vector3.create(0, 5, 0),
rotationStart: Quaternion.fromEulerDegrees(0, 0, 0), rotationEnd: Quaternion.fromEulerDegrees(0, 360, 0),
scaleStart: Vector3.One(), scaleEnd: Vector3.create(0.5, 0.5, 0.5)
}, 2000, EasingFunction.EF_EASEINOUTCUBIC)
// Continuous spin — applies a per-frame Quaternion delta. No end state.
Tween.setRotateContinuous(entity, Quaternion.fromEulerDegrees(0, -1, 0), 700)
// Continuous move — applies a per-frame Vector3 delta scaled by speed.
Tween.setMoveContinuous(entity, Vector3.create(0, 0, 1), 0.5)
*Continuous バリアントは終了せず、Tween が削除されるまで一定のデルタを適用します(Tween.deleteFrom(entity))。
ヨーヨーループモード
TL_YOYO は、最初から再生し直す代わりに、各端点で Tween の向きを反転させます。
TweenSequence.create(entity, {
sequence: [{ duration: 1000, ... }],
loop: TweenLoop.TL_YOYO
})
Tween の完了検出
tweenSystem.tweenCompleted() を使用して、このフレームに Tween が完了したかどうかを確認します。
engine.addSystem(() => {
if (tweenSystem.tweenCompleted(entity)) {
console.log('Tween finished on', entity)
}
})
Animator の追加機能
Animator の追加機能:
// Get a specific clip to modify
const clip = Animator.getClip(entity, 'Walk')
// shouldReset: restart animation from beginning when re-triggered
Animator.playSingleAnimation(entity, 'Attack', true) // resets to start
// weight: blend between animations (0.0 to 1.0)
const anim = Animator.getMutable(entity)
anim.states[0].weight = 0.5 // blend walk at 50%
anim.states[1].weight = 0.5 // blend idle at 50%
モーフターゲット(ブレンドシェイプ)
GLTF モデルにモーフターゲット(顔の表情や形状のブレンドなど)がエクスポートされている場合は、各 Animator.state の weights 配列を使って操作します。インデックスは、モーフターゲットがエクスポートされた順序に対応します。値の範囲は 0~1 です。
const anim = Animator.getMutable(entity)
const state = anim.states.find(s => s.clip === 'Idle')
if (state) {
state.weights = state.weights ?? []
state.weights[0] = 0.8 // morph target 0 at 80%
state.weights[1] = 0.3 // morph target 1 at 30%
}
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| GLTF アニメーションが再生されない | Animator.states のクリップ名が間違っている | ビューアー(Blender など)で .glb を開き、正確なクリップ名を確認します。大文字と小文字は区別されます |
| Animator コンポーネントに効果がない | エンティティに GltfContainer がない | Animator は、GLTF モデルが読み込まれたエンティティでのみ動作します |
| Tween で移動しない | 開始位置と終了位置が同じ | Tween.Mode.Move() の start と end の値が異なることを確認します |
| Tween が一度再生した後、停止する | ループする TweenSequence がない | 往復させるには、TweenSequence.create(entity, { sequence: [], loop: TweenLoop.TL_YOYO }) を追加します |
| アニメーションがガタついたり途切れたりする | 毎フレーム新しい Tween を作成している | システム内ではなく、Tween を一度だけ作成します。連続させるには tweenSystem.tweenCompleted() を使用します |
アニメーションさせる 3D モデルが必要ですか? アニメーションクリップを含む GLTF モデルの読み込みについては、add-3d-models スキルをご覧ください。
ベストプラクティス
- ドア、プラットフォーム、UI 要素など、単純な A から B へのアニメーションには Tween を使用します
- GLTF ファイルに組み込まれたキャラクターやモデルのアニメーションには Animator を使用します
- 連続的なアニメーションや物理ベースのアニメーションには Systems を使用します
- Tween の継続時間の単位は ミリ秒 です(1000 = 1 秒)
- 親エンティティと子エンティティに適用して、移動 Tween と回転 Tween を組み合わせます
- ループには、
loop: TweenLoop.TL_RESTARTを指定したTweenSequenceを使用します