game-design について
game-designはDecentralandのシーンやゲームを作る人向けの設計・最適化スキルです。共有ワールドの制約、リソース予算、パフォーマンス、操作設計に加え、MVPの計画や関連実装スキルを案内します。
主な機能
- 01Decentralandの共有シーン設計原則
- 02区画数に応じたシーン予算
- 03テクスチャとアセットの事前読み込み指針
- 04プーリング、LOD、描画呼び出しの最適化
- 05入力、状態、UIの設計指針
- 06コアループとMVPのテスト計画
活用シーン
- 01プレイヤーが随時参加・退出できるゲーム設計
- 02対象区画数に応じたシーンリソースの見積もり
- 03シーンの描画呼び出しや物理負荷の削減
- 04プロトタイプ、テスト、反復の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill game-designインストール後、Claude Code で/game-designを入力して読み込まれたか確認します。
詳細
Decentraland のゲームデザインとシーンの最適化
1. DCL のゲームデザイン哲学
Decentraland は途切れることのない共有の 3D ワールドです。次の制約を踏まえてデザインしてください。
- 起動画面はありません:シーンは常に稼働しています。プレイヤーは隣接する区画から歩いて入ってきます。スプラッシュ画面も「スタートを押してください」という画面もありません。プレイヤーが到着した瞬間から、シーンに意味がある状態にしてください。
- 強制的な終了はありません:「ゲームオーバー」状態を強制することはできません。プレイヤーは歩いて立ち去るか、テレポートしていつでも退出できます。途中参加や途中退出にも自然に対応できるループを設計してください。
- プレイヤーを退出させることはできません:シーンからプレイヤーを追い出す API はありません。プレイヤーをテレポートさせることはできますが、本人の同意が必要です(プロンプトを承諾する必要があります)。問題のあるプレイヤーには、退出させるのではなく、ゲームの仕組みで対処してください。
- 境界への配慮:区画の外に立っているプレイヤーからも、区画の中を見ることができます。シーンは常に人目に触れています。隣接するシーンも見えるため、視覚的な調和も考慮してください。
- 共有スペース:複数のプレイヤーが同時に存在する可能性が常にあります。「シングルプレイヤー」パズルでさえ、ほかのプレイヤーに見られます。これを活用するか、考慮に入れてください。
2. シーンの制限値の計算式
すべての制限値は区画数 n に応じて増加します。デプロイ時のファイルサイズ上限を除き、ほかの制限値はすべて超過してもかまいません。シーンがクラッシュすることはありませんが、パフォーマンスが低下し、低性能のデバイスでは使用できなくなる場合があります。この表は強制的な上限ではなく、目安として扱ってください。
| リソース | 計算式 | 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 |
| ドローコール数 | n x 300(目標値) | 300 | 600 | 1,200 | 2,700 | 4,800 |
ファイルの制限: 区画ごとに 15 MB、合計最大 300 MB、区画ごとに 200 ファイル、ファイルごとに最大 50 MB です。
3. テクスチャの要件
- 寸法は 2 の累乗である必要があります:256、512、1024、2048
- 推奨サイズ:シーンオブジェクトには 1024x1024、ウェアラブルには 512x512
- テクスチャアトラスを使用して複数の小さなテクスチャを 1 つにまとめ、ドローコール数とマテリアル数を減らしてください
- 可能な場合は、非圧縮の PNG より圧縮形式(WebP)を優先してください
- 複数のマテリアルでテクスチャ参照を共有し、テクスチャファイルを重複させないでください
4. アセットの事前読み込み(AssetLoad コンポーネント)
表示時にポップインが目立つような大きなアセットには、AssetLoad を使ってレンダリング前にダウンロードしてください。
import { engine, AssetLoad, LoadingState, GltfContainer, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const preloadEntity = engine.addEntity()
AssetLoad.create(preloadEntity, { src: 'models/large-model.glb' })
function assetLoadingSystem(dt: number) {
for (const [entity] of engine.getEntitiesWith(AssetLoad)) {
const state = AssetLoad.get(entity)
if (state.loadingState === LoadingState.FINISHED) {
GltfContainer.create(entity, { src: 'models/large-model.glb' })
Transform.create(entity, { position: Vector3.create(8, 0, 8) })
AssetLoad.deleteFrom(entity)
}
}
}
engine.addSystem(assetLoadingSystem)
約 1 MB を超えるモデルや、ゲームのフェーズ開始前に準備しておく必要があるアセットには、このパターンを使用してください。
5. パフォーマンスのパターン
オブジェクトプーリング
エンティティを作成・破棄する代わりに、再利用してください。
const pool: Entity[] = []
function getFromPool(): Entity {
const existing = pool.pop()
if (existing) return existing
return engine.addEntity()
}
function returnToPool(entity: Entity) {
Transform.getMutable(entity).position = Vector3.create(0, -100, 0)
pool.push(entity)
}
LOD(詳細度)
プレイヤーからの距離に応じて、モデルを切り替えるかエンティティを非表示にしてください。
function lodSystem() {
const playerPos = Transform.get(engine.PlayerEntity).position
for (const [entity, transform] of engine.getEntitiesWith(Transform, GltfContainer)) {
const distance = Vector3.distance(playerPos, transform.position)
VisibilityComponent.createOrReplace(entity, { visible: distance <= 30 })
}
}
engine.addSystem(lodSystem)
ドローコール数の削減
- エクスポート前に Blender でメッシュを結合する
- テクスチャアトラスを使用する(多くのオブジェクトで 1 つのマテリアルを共有する)
- 独自のマテリアルの数を抑え、エンティティ間で再利用する
- 可能な場合は透明度を避ける(透明なオブジェクトには追加のドローコールが必要です)
システムの最適化
- 重い処理を毎フレーム実行しないでください。タイマーを使用してください:
let timer = 0 function heavySystem(dt: number) { timer += dt if (timer < 0.5) return // Run every 500ms, not every frame timer = 0 // ... expensive work here } engine.getEntitiesWith()のクエリを最小限に抑え、エンティティの集合が安定している場合は結果をキャッシュしてください- 毎フレーム実行されるシステム内で、新しいオブジェクト(Vector3.create、配列)を割り当てないでください
未使用のコライダーを無効化する
プレイヤーが操作しない装飾オブジェクトからコリジョンメッシュを削除してください。これにより、物理ボディ数を大幅に減らせます。
6. 入力システムの設計
| 入力 | アクション | 備考 |
|---|---|---|
| Eキー | 主要アクション(IA_PRIMARY) | 主なインタラクション |
| Fキー | 副次アクション(IA_SECONDARY) | 代替インタラクション |
| ポインタークリック | IA_POINTER | マウスの左クリック/タップ |
| 1~4キー | IA_ACTION_3 から IA_ACTION_6 | アクションバーのスロット |
設計上の考慮事項
- マウスホイールは入力として利用できません
- デスクトップとモバイルの両方を想定して設計してください。モバイルにはキーボードがないため、ポインターと画面上のボタンを利用します
- シーンをまたいだインタラクションを防ぐため、ポインターイベントに
maxDistanceを設定してください(一般的には8~10メートル) - プレイヤーが実行を確定する前に、インタラクションの内容が伝わるよう
hoverTextを使用してください
7. 状態管理のパターン
モジュールレベルの状態(シンプルなゲーム)
// game-state.ts
export let score = 0
export let gamePhase: 'waiting' | 'playing' | 'ended' = 'waiting'
export function addScore(points: number) { score += points }
コンポーネントベースの状態(複雑なゲーム)
構造化されたデータコンテナとしてカスタムコンポーネントを使用します。
import { engine, Schemas } from '@dcl/sdk/ecs'
const EnemyState = engine.defineComponent('EnemyState', {
health: Schemas.Number,
speed: Schemas.Number,
target: Schemas.Entity
})
ステートマシン
ゲームのフェーズを、明確な遷移を持つ明示的な状態としてモデル化します。
type GameState = 'lobby' | 'countdown' | 'active' | 'cooldown'
let currentState: GameState = 'lobby'
function gameStateSystem(dt: number) {
switch (currentState) {
case 'lobby': handleLobby(dt); break
case 'countdown': handleCountdown(dt); break
case 'active': handleActive(dt); break
case 'cooldown': handleCooldown(dt); break
}
}
8. UX/UI ガイドライン
- UIは最小限にする:メタバースの魅力は3D空間での存在感であり、2Dのオーバーレイではありません。世界を覆い隠す大きなHUDは避けてください。
- 空間UIを優先する:情報が場所やオブジェクトに結び付いている場合は、スクリーン空間のUIよりも、エンティティ上の
TextShapeや3Dの看板を使用してください。 - 分かりやすい操作の手がかり:インタラクティブなオブジェクトは、見た目から操作できることが分かるようにします。グロー効果、輪郭線、浮遊するインジケーター、さりげないアニメーションを使い、「クリックできます」と伝えてください。
- サウンドによるフィードバック:プレイヤーの重要なアクションには、すべて音声フィードバックを付けてください。アクションが登録されたことを伝え、仕上がりも向上します。
- 段階的な情報開示:情報を一度にすべて提示しないでください。プレイヤーの行動に合わせて、ゲームの仕組みや物語を明らかにします。まずシンプルに始め、徐々に複雑さを加えてください。
- 即時のフィードバック:プレイヤーがインタラクションを行ったら、同じフレーム内に応答してください。トゥイーン、サウンド、UIポップアップを使い、プレイヤーが「うまくいったのか」と迷わないようにします。
- アクセシビリティ:コントラストの高いテキスト、読みやすいフォントサイズ(画面UIでは
fontSize >= 16)、視覚的な合図と併せた音声キューを使用してください。
9. MVPの計画
コアループから始める
次のように問いかけます。プレイヤーは何をするのか? 答えは一文にまとめてください。
- 「プレイヤーは部屋を探索し、隠されたオブジェクトを見つけます。」
- 「プレイヤーは障害物コースでほかのプレイヤーと競争します。」
- 「プレイヤーはリソースを集め、建造物を作ります。」
素早くプロトタイプを作る
- 最終的なシーンがもっと大規模になる場合でも、まずは1~2区画で作成します
- プレースホルダーには基本的な形状(ボックス、球体)を使い、最終アートを待たないでください
- 副次的な機能を追加する前に、コアループを動作させます
早い段階でテストする
- テスト用のワールドにデプロイし、自分でプレイしてみます
- 実際のプレイヤーを2~3人招待して、様子を観察します(ゲームの説明はせず、説明なしで理解できるかを確認します)
- 計測する内容:プレイヤーは30秒以内に何をすべきか理解できますか?
面白さを追求して反復する
- 仕上げは最後です。プレースホルダーのアートでコアループが面白くなければ、アートを良くしても解決しません
- 機能は思い切って削ります。広大で未完成の体験より、コンパクトでまとまりのある体験のほうが優れています
- DCLでは、コンテンツの量よりリプレイ価値が重要です(プレイヤーは気に入ったシーンに戻ってきます)
MVPチェックリスト
- コアループを60秒以内にプレイできる
- 1人でも、5人以上が同時に参加しても動作する
- 対象の区画数に応じたシーン制限内に収まる
- すべてのインタラクションに明確な視覚・音声フィードバックがある
- 外部からの説明がなくても、プレイヤーが目標を理解できる
ゼロから始めますか? ゲームを設計する前にプロジェクトのひな形を作成するため、まず create-scene スキルを参照してください。
10. 相互参照
| トピック | スキル | 使用する場面 |
|---|---|---|
| インタラクティブ性、入力処理、レイキャスティング | add-interactivity | クリックハンドラー、トリガー、入力を実装するとき |
| マルチプレイヤーの同期、サーバー通信 | multiplayer-sync | ネットワーク経由のゲーム状態、リアルタイム同期 |
| 画面 UI、React-ECS、HUD 要素 | build-ui | メニュー、スコアボード、ダイアログを構築するとき |
| パフォーマンスの最適化、エンティティ/三角形の予算 | optimize-scene | 詳細な最適化手法を確認するとき |
このスキルでは、実装を形作る設計上の判断と最適化の制約に重点を置いています。具体的なコードパターンについては、参照先のスキルをご覧ください。