mcpField
A
animations-tweens

animations-tweens

github.com/dcl-regenesislabs開発1738 回インストール21 週間前に更新
リポジトリ

animations-tweens について

animations-tweensは、Animator、Tween、TweenSequence、自作システムを使ってエンティティやGLTFモデルをアニメーションさせるDecentraland開発向けスキルです。モデル内蔵クリップ、滑らかなプロパティ変更、連続動作、フレーム単位の制御の使い分けを説明します。

主な機能

  1. 01GLTFアニメーションクリップの再生と切り替え
  2. 02位置・回転・スケールのトゥイーン
  3. 03TweenSequenceでトゥイーンを連結
  4. 04イージングとループ方式の選択
  5. 05モーフターゲットのウェイト制御
  6. 06独自のフレーム単位アニメーションシステム

活用シーン

  1. 01GLTFクリップでキャラクターをアニメーション
  2. 02ドアを位置間で移動または回転
  3. 03巡回経路をループ再生
  4. 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()物理演算のような動き、ターゲットの追跡、カスタムイージング

選択の流れ:

  1. .glb モデルにアニメーションがすでに含まれていますか? → Animator
  2. 2 つの値の間で単純に移動/回転/拡大縮小するものですか? → Tween
  3. フレームごとの制御やカスタム計算が必要ですか? → 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> を直接参照できます。

値列挙型形状
0EF_LINEAR等速
1EF_EASEINQUAD二次イーズイン
2EF_EASEOUTQUAD二次イーズアウト
3EF_EASEQUAD二次イーズイン・アウト
4EF_EASEINSINE正弦イーズイン
5EF_EASEOUTSINE正弦イーズアウト
6EF_EASESINE正弦イーズイン・アウト(滑らか)
7EF_EASEINEXPO指数イーズイン
8EF_EASEOUTEXPO指数イーズアウト
9EF_EASEEXPO指数イーズイン・アウト
10EF_EASEINELASTIC弾性イーズイン
11EF_EASEOUTELASTIC弾性イーズアウト
12EF_EASEELASTIC弾性イーズイン・アウト
13EF_EASEINBOUNCEバウンスイーズイン
14EF_EASEOUTBOUNCEバウンスイーズアウト
15EF_EASEBOUNCEバウンスイーズイン・アウト
16EF_EASEINCUBIC三次イーズイン
17EF_EASEOUTCUBIC三次イーズアウト
18EF_EASECUBIC三次イーズイン・アウト
19EF_EASEINQUART四次イーズイン
20EF_EASEOUTQUART四次イーズアウト
21EF_EASEQUART四次イーズイン・アウト
22EF_EASEINQUINT五次イーズイン
23EF_EASEOUTQUINT五次イーズアウト
24EF_EASEQUINT五次イーズイン・アウト
25EF_EASEINCIRC円形イーズイン
26EF_EASEOUTCIRC円形イーズアウト
27EF_EASECIRC円形イーズイン・アウト
28EF_EASEINBACKオーバーシュートイーズイン
29EF_EASEOUTBACKオーバーシュートイーズアウト
30EF_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 を使用します

コメント