optimize-scene について
Decentraland のシーン開発者向けに、エンティティ、三角形、テクスチャ、システム、読み込みの負荷を扱う最適化スキルです。リソース予算の目安とシーンコード向けの手法を紹介します。
主な機能
- 01区画数に応じたシーン予算の見積もり
- 02オブジェクトプールとエンティティ階層の活用
- 03低ポリゴンモデルと LOD による形状負荷の制御
- 04マテリアルの再利用とテクスチャの最適化
- 05システムの実行間隔調整とアセットの事前読み込み
活用シーン
- 01シーン内のエンティティ数と三角形数の削減
- 02低性能デバイスでのシーン動作の改善
- 03大型モデル読み込み時の表示遅延の軽減
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill optimize-sceneインストール後、Claude Code で/optimize-sceneを入力して読み込まれたか確認します。
詳細
Decentraland シーンの最適化
作成内容の分離
このスキルで扱うオブジェクトプール、LOD、アセットの事前読み込み、システムのスロットリングといったパターンは、すべて実行時の仕組みであり、src/index.ts に記述します。静的なレイアウト(椅子、壁、ランプ、LOD が適用される小道具など)は、引き続き main-entities.ts で宣言してください。最適化コードは、名前でエンティティを取得する(engine.getEntityOrNullByName)か、コンポーネントクエリ(engine.getEntitiesWith(Transform))を使ってそれらのエンティティを読み取ります。
親子関係の最適化(多数の子を持つ静的なコンテナ)は、main-entities.ts で Transform.parent: 'parent_name' を使って表現するのが最適です。これにより、エディターでグループ全体をひとまとまりとして移動できます。
シーンの上限(区画数ごと)
すべての上限は区画数 n に応じて変化します。三角形、エンティティ、物理ボディの数は線形に増加します。マテリアル、テクスチャ、高さは対数的に増加します。デプロイ時のハードな MB サイズ上限を除き、その他の上限はすべて超過できます。シーンがクラッシュすることはありませんが、パフォーマンスが低下し、性能の低いデバイスではシーンが使いにくくなる場合があります。数値は強制的な制限ではなく、目安として扱ってください。
| リソース | 計算式 | 1 区画 | 2 区画 | 4 区画 | 9 区画 | 16 区画 |
|---|---|---|---|---|---|---|
| 三角形 | n x 10,000 | 10,000 | 20,000 | 40,000 | 90,000 | 160,000 |
| エンティティ | n x 200 | 200 | 400 | 800 | 1,800 | 3,200 |
| 物理ボディ | n x 300 | 300 | 600 | 1,200 | 2,700 | 4,800 |
| マテリアル | log2(n+1) x 20 | 20 | 31 | 46 | 66 | 81 |
| テクスチャ | log2(n+1) x 10 | 10 | 15 | 23 | 33 | 40 |
| 高さの上限 | log2(n+1) x 20m | 20m | 31m | 46m | 66m | 81m |
ファイルの上限: 1 区画あたり 15 MB、合計最大 300 MB、1 区画あたり 200 ファイル、ファイル 1 個あたり最大 50 MB。
エンティティ数の最適化
エンティティを再利用する
// BAD: Creating new entity each time
function spawnBullet() {
const bullet = engine.addEntity() // Creates entity every call
// ...
}
// GOOD: Object pooling
const bulletPool: Entity[] = []
function getBullet(): Entity {
const existing = bulletPool.find(e => !ActiveBullet.has(e))
if (existing) return existing
const newBullet = engine.addEntity()
bulletPool.push(newBullet)
return newBullet
}
未使用のエンティティを削除する
engine.removeEntity(entity) // Frees the entity slot
親子関係を使う
子ごとに個別のトランスフォームを設定する代わりに、エンティティの階層構造を使います。
const parent = engine.addEntity()
Transform.create(parent, { position: Vector3.create(8, 0, 8) })
// Children inherit parent transform
const child1 = engine.addEntity()
Transform.create(child1, { position: Vector3.create(0, 1, 0), parent })
const child2 = engine.addEntity()
Transform.create(child2, { position: Vector3.create(1, 1, 0), parent })
三角形数の最適化
ポリゴン数の少ないモデルを使う
- 小さな小道具:100~500 三角形
- 中サイズのオブジェクト:500~1,500 三角形
- 大きな建物:1,500~5,000 三角形
- 主役となるオブジェクト:最大 10,000 三角形
LOD(詳細度)を使う
遠くでは、より単純なモデルを表示します。
engine.addSystem(() => {
// Check distance to player and swap models
const playerPos = Transform.get(engine.PlayerEntity).position
const objPos = Transform.get(myEntity).position
const distance = Vector3.distance(playerPos, objPos)
const gltf = GltfContainer.getMutable(myEntity)
if (distance > 30) {
gltf.src = 'models/building_lod2.glb' // Low poly
} else if (distance > 15) {
gltf.src = 'models/building_lod1.glb' // Medium poly
} else {
gltf.src = 'models/building_lod0.glb' // High poly
}
})
モデルの代わりにプリミティブを使う
単純な形状の場合、MeshRenderer は .glb の読み込みよりも軽量です。
MeshRenderer.setBox(entity) // Very cheap
MeshRenderer.setSphere(entity) // Cheap
MeshRenderer.setPlane(entity) // Very cheap
テクスチャの最適化
- 寸法は 2 の累乗にする必要があります:256、512、1024、2048
- 推奨サイズ:ほとんどのオブジェクトでは 512x512、主役となるオブジェクトでは最大 1024x1024
- 透明度が必要な UI/スプライトには
.pngを使います - 写真や透明度のないテクスチャには
.jpgを使います - 可能な場合は、未加工の PNG より圧縮形式(WebP)を優先します
- テクスチャアトラス(複数のテクスチャを 1 枚の画像にまとめたもの)を使って、ドローコールとマテリアル数を減らします
- マテリアル間でテクスチャ参照を共有し、テクスチャファイルを重複させないようにします
- エンティティ間でマテリアルを再利用します。
// GOOD: Define material once, apply to many
Material.setPbrMaterial(entity1, { texture: Material.Texture.Common({ src: 'images/wall.jpg' }) })
Material.setPbrMaterial(entity2, { texture: Material.Texture.Common({ src: 'images/wall.jpg' }) })
// Same texture URL = shared in memory
システムの最適化
フレームごとのメモリ確保を避ける
// BAD: Creates new Vector3 every frame
engine.addSystem(() => {
const target = Vector3.create(8, 1, 8) // Allocation!
})
// GOOD: Reuse constants
const TARGET = Vector3.create(8, 1, 8)
engine.addSystem(() => {
// Use TARGET
})
負荷の高い処理をスロットリングする
let lastCheck = 0
engine.addSystem((dt) => {
lastCheck += dt
if (lastCheck < 0.5) return // Only run every 0.5 seconds
lastCheck = 0
// Expensive operation here
})
不要になったシステムを削除する
const systemFn = (dt: number) => { /* ... */ }
engine.addSystem(systemFn)
// When no longer needed:
engine.removeSystem(systemFn)
アセットの事前読み込み(AssetLoad コンポーネント)
表示時に目立つ遅延が発生する可能性のある大きなアセットには、描画前にダウンロードするため AssetLoad を使います。
import { engine, AssetLoad, LoadingState, GltfContainer, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
// Create a preload entity at scene startup
const preloadEntity = engine.addEntity()
AssetLoad.create(preloadEntity, { src: 'models/large-model.glb' })
// System to track loading progress
function assetLoadingSystem(dt: number) {
for (const [entity] of engine.getEntitiesWith(AssetLoad)) {
const state = AssetLoad.get(entity)
if (state.loadingState === LoadingState.FINISHED) {
// Asset is cached — now safe to create the visible entity
GltfContainer.create(entity, { src: 'models/large-model.glb' })
Transform.create(entity, { position: Vector3.create(8, 0, 8) })
AssetLoad.deleteFrom(entity) // Remove preload component
}
}
}
engine.addSystem(assetLoadingSystem)
このパターンは、約 1 MB を超えるモデルや、ゲームフェーズの開始前に準備しておきたいアセットに使います。
読み込み時間の最適化
- 3D モデルは遅延読み込みします(シーン開始時にすべて読み込まず、必要に応じて読み込みます)
- 圧縮された .glb ファイルを使います(Draco 圧縮)
- アセットの合計サイズを最小限に抑えます
- 共有する大きなアセットには、可能な場合 CDN URL を使います
- 重要なアセットは
AssetLoadで事前に読み込み、重要でないアセットは読み込みを遅らせます
よくあるパフォーマンス上の落とし穴
- システムが多すぎる:各システムは毎フレーム実行されます。関連するロジックをまとめてください。
- 不要なコンポーネントのクエリ:対象のセットが変化しない場合は、
engine.getEntitiesWith()の結果をキャッシュしてください。 - 大きな GLTF ファイル:エクスポート前に Blender で最適化してください(ポリゴン数の削減、隠れた面の削除)。
- 非圧縮のオーディオ:音楽には .wav ではなく .mp3 を使用してください(ファイルサイズが 10 分の 1 になります)。
- 継続的なレイキャスト:毎フレームのレイキャストが必要でない限り、
continuous: falseに設定してください。 - テキストのレンダリング:
TextShapeは処理負荷が高くなります。3D 空間に配置する必要のないテキストには、(UI の)Labelを使用してください。
関連項目
- add-3d-models — モデルの読み込み、コライダー、ファイルの整理
- game-design — パフォーマンス予算、デザインパターン、MVP の計画
- advanced-rendering — テクスチャモード、マテリアルの再利用、VisibilityComponent を使った LOD