add-3d-models について
Decentralandのシーン制作者向けに、3Dモデルの配置と設定を案内するスキルです。静的・実行時エンティティ、コライダー、モデルカタログ検索、読み込み時の問題を扱います。
主な機能
- 01main-entities.tsへの静的モデル配置
- 02GLTFとメッシュコライダーの設定
- 03カタログモデルの検索とダウンロード
- 04モデルの変換と親エンティティの設定
- 05読み込みや表示の問題の診断
活用シーン
- 01Decentralandシーンへの静的な小道具の追加
- 02実行時のモデル生成
- 03シーン用モデルの検索
- 04モデルが見えない・クリックできない問題の診断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill add-3d-modelsインストール後、Claude Code で/add-3d-modelsを入力して読み込まれたか確認します。
詳細
Decentraland シーンへの 3D モデルの追加
モデルの配置先:main-entities.ts
作成時に配置する 3D モデルは、静的な表示エンティティです。src/index.ts で engine.addEntity() を使うのではなく、main-entities.ts で宣言してください。ビルド時に main-entities.ts は main.crdt にコンパイルされ、エンジンは main() の実行前にこれをプリロードするため、エディターでモデルをドラッグしたり、回転させたりできます。
// main-entities.ts
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
my_model: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: {
src: 'models/myModel.glb',
visibleMeshesCollisionMask: 3 // CL_PHYSICS | CL_POINTER
}
}
}
} satisfies Scene
GltfContainerでは、必ずvisibleMeshesCollisionMaskを設定してください。カタログのモデルには個別のコライダーメッシュが含まれていません。表示メッシュをコライダーとして使うことで、モデルが実体を持ち、クリックできるようになります。リテラル内では列挙型を使用できないため、main-entities.tsでは整数値(ColliderLayer.CL_PHYSICS = 1、CL_POINTER = 2、両方の場合は3)を使ってください。
代わりに src/index.ts で engine.addEntity() を使う場合:モデルを動的に生成する場合(ループ内でプロシージャルに配置する、イベント時に出現させる、NFT の所有権を条件とするなど)のみにしてください。静的な小道具には、必ず main-entities.ts を使用してください。
ファイル構成
プロジェクトのルートにある models/ ディレクトリにモデルファイルを配置します。
project/
├── models/
│ ├── building.glb
│ ├── tree.glb
│ └── furniture/
│ ├── chair.glb
│ └── table.glb
├── src/
│ └── index.ts
└── scene.json
コライダー
モデルに組み込まれたコライダーの使用
// main-entities.ts — declared inline with GltfContainer
building: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: {
src: 'models/building.glb',
visibleMeshesCollisionMask: 3, // CL_PHYSICS | CL_POINTER
invisibleMeshesCollisionMask: 1 // CL_PHYSICS
}
}
}
シンプルなコライダーの追加
基本図形(GLTF なし)の場合は、MeshCollider を追加します。
// main-entities.ts
invisible_wall: {
components: {
Transform: { position: { x: 0, y: 1, z: 8 }, scale: { x: 0.1, y: 2, z: 16 } },
MeshCollider: { mesh: { $case: 'box', box: { uvs: [] } } }
}
}
その他の形状:{ $case: 'sphere', sphere: {} }、{ $case: 'plane', plane: { uvs: [] } }、{ $case: 'cylinder', cylinder: {} }。
⚠️ 重要:Transform フィールドに undefined を渡さないでください
Transform フィールド(position、rotation、scale)が存在するにもかかわらず undefined の場合、SDK のシリアライザーはクラッシュします。代わりに、キー自体を省略してください。これは main-entities.ts のリテラルと、実行時のヘルパーの両方に当てはまります。main-entities.ts では、フィールドを記述しなければよいので自然に対応できます。
モデルの一般的な操作
スケーリング
// main-entities.ts
big_statue: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 }, scale: { x: 2, y: 2, z: 2 } },
GltfContainer: { src: 'models/statue.glb' }
}
}
回転(オイラー角を作成時にクォータニオンへ変換)
クォータニオンは { x, y, z, w } です。Quaternion.fromEulerDegrees(0, 90, 0) に相当するリテラルは { x: 0, y: 0.7071, z: 0, w: 0.7071 } です。正確な角度で回転させる必要があり、手計算を避けたい場合は、main-entities.ts で rotation を単位回転に設定し、src/index.ts で実行時に Transform.getMutable(entity).rotation = Quaternion.fromEulerDegrees(0, 90, 0) を使って回転させてください。
親子関係
親は名前(同じ scene オブジェクト内の文字列キー)で参照します。ビルドでは、2 回目の処理で名前がエンティティ ID に解決されます。
// main-entities.ts
character: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/character.glb' }
}
},
hat: {
components: {
Transform: {
position: { x: 0, y: 2, z: 0 }, // 2m above parent's origin
parent: 'character'
},
GltfContainer: { src: 'models/hat.glb' }
}
}
無料の 3D モデル — OpenDCL カタログ(5,700 以上のモデル)
カタログファイルは {baseDir}/references/model-catalog.md にあります。各行の形式は次のとおりです。
slug | dims | tris | size | category/sub | description [tags] [anim: clips] | curl command | preview: thumbnail_url
検索方法
一度に 1 つのキーワードで検索してください。まず最も具体的な語を試します。
grep -i "zombie" {baseDir}/references/model-catalog.md
結果がない場合は、類義語、より広い用語、関連語を試してください。
- "sofa" → "couch" → "seat" → "furniture"
- "car" → "vehicle" → "truck" → "van"
- "wall" → "fence" → "barrier" → "structure"
すべてのカテゴリを確認して、利用可能なものを探します。
grep "^##" {baseDir}/references/model-catalog.md
特定のカテゴリ内を検索します。
grep "^##\|chair" {baseDir}/references/model-catalog.md
モデルの使い方
- 条件に合うモデルが見つかるまで、カタログで異なるキーワードを検索します
- 検索結果を確認します。寸法、三角形の数、アニメーション、説明をチェックしてください
- curl コマンドを使ってモデルを
models/にダウンロードします GltfContainer.create(entity, { src: 'models/{slug}.glb' })でコードから参照します- モデルにアニメーション(
[anim: ...]に記載)がある場合は、Animatorコンポーネントを使って再生します - モデルを配置した後、プレビューサムネイル(
preview:URL)を取得して、モデルの見た目を確認できます
ワークフローの例
# Search for zombie models
grep -i "zombie" {baseDir}/references/model-catalog.md
# Found: zombie-purple | 2.8×2.9×0.5m | 1472 tri | 271KB | character/zombie | ...
# [anim: Tpose, ZombieAttack, ZombieUP, ZombieWalk]
# preview: https://models.dclregenesislabs.xyz/blobs/bafkrei...
# Download the model
curl -o models/zombie-purple.glb "https://models.dclregenesislabs.xyz/blobs/bafybeiffc..."
// main-entities.ts — declare the entity with all its initial state
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
zombie: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/zombie-purple.glb' },
Animator: {
states: [
{ clip: 'ZombieWalk', playing: true, loop: true },
{ clip: 'ZombieAttack', playing: false, loop: false }
]
}
}
}
} satisfies Scene
実行時にアニメーションを切り替える場合(例:クリック時に攻撃を発動させる場合)は、src/index.ts を使用します。
import { engine, Animator } from '@dcl/sdk/ecs'
export function main() {
const zombie = engine.getEntityOrNullByName('zombie')
if (zombie) Animator.playSingleAnimation(zombie, 'ZombieAttack')
}
重要:
GltfContainerが使用できるのはローカルファイルのみです。モデルのsrcフィールドに外部 URL を使わないでください。必ず先にモデルをmodels/にダウンロードしてください。modelsディレクトリにcdしないでください。必ずプロジェクトのルートからcurl -o models/slug.glb "URL"を実行します。cd models && curl -o slug.glbは使用しないでください。
モデルの読み込み状態の確認
読み込み状態のポーリングは実行時の動作です。src/index.ts に記述し、エンティティを名前で参照してください。
import { engine, GltfContainerLoadingState, LoadingState } from '@dcl/sdk/ecs'
export function main() {
engine.addSystem(() => {
const model = engine.getEntityOrNullByName('zombie')
if (!model) return
const state = GltfContainerLoadingState.getOrNull(model)
if (state?.currentState === LoadingState.FINISHED) {
console.log('Model loaded successfully')
} else if (state?.currentState === LoadingState.FINISHED_WITH_ERROR) {
console.log('Model failed to load')
}
})
}
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| モデルが表示されない | ファイルパスが間違っている | プロジェクトのルートからの相対パス(例:models/myModel.glb)にファイルが存在することを確認してください |
| モデルが表示されない | シーンの境界外に配置されている | Transform の位置が区画ごとに 0~16 の範囲内にあることを確認してください。1 区画のシーンの中心は (8, 0, 8) です |
| モデルが表示されない | スケールが 0 または非常に小さい | Transform.scale を確認してください。デフォルトは (1,1,1) です。モデルが非常に小さくエクスポートされている場合は、より大きな値を試してください |
| モデルが表示されない | カメラの後ろにある | アバターを移動するか、モデルの方向を向くように回転させてください |
| モデルは読み込まれるが、見た目がおかしい | Y-up と Z-up の不一致 | Decentraland では Y-up を使用します。Blender から「Y Up」にチェックを入れて再エクスポートしてください |
| 読み込み状態が「FINISHED_WITH_ERROR」 | .glb が破損しているか、サポートされていない | モデルを再エクスポートしてください。.glb(バイナリ GLTF)形式を使用し、サポートされていない拡張機能がないことを確認してください |
| モデルをクリックしても何も起きない | コライダーがない | GltfContainer に visibleMeshesCollisionMask: ColliderLayer.CL_POINTER を追加するか、MeshCollider を追加してください |
シーンの制限に合わせてモデルを最適化する必要がありますか? 三角形ポリゴンの予算と LOD パターンについては、optimize-scene スキルをご覧ください。 モデルにアニメーションを追加する必要がありますか? Animator を使用して GLTF アニメーションクリップを再生する方法については、animations-tweens スキルをご覧ください。
モデルのベストプラクティス
- 読み込み時間を短縮するため、モデルはファイルごとに 50MB 未満にしてください
.glb形式(バイナリ GLTF)を使用してください。.gltfよりサイズが小さくなります- 三角形ポリゴン数を最適化してください。小さな小道具では、モデルあたり 1,500 個未満を目指してください
- 可能な場合はテクスチャアトラスを使用して、描画呼び出しを減らしてください
- アニメーションが埋め込まれたモデルは、
Animatorコンポーネントで再生できます - モデルの向きをテストしてください。Decentraland では Y-up 座標系を使用します
- 最良の結果を得るには、モデルのマテリアルに PBR(物理ベースレンダリング)を使用してください