threejs-game について
threejs-gameは、Three.js製ブラウザーゲームの開発スキルです。プロジェクト構成に加え、レンダリング、入力、モバイルレイアウト、パフォーマンス、検証の指針を示します。
主な機能
- 01ディレクトリー構成とコアモジュールの規定
- 02イベントバスと集中状態管理のパターン
- 03WebGLとWebGPUの選択ガイド
- 04モバイル入力とセーフゾーン配置の指針
- 05描画性能とリソース解放のルール
活用シーン
- 01新しいThree.jsゲームのひな型を作る
- 02ゲーム状態、イベント、システムを整理する
- 03デスクトップとモバイルの操作を実装する
- 04性能と再起動時のクリーンアップを確認する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ playableintelligence/ game-creator --skill threejs-gameインストール後、Claude Code で/threejs-gameを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
core-patterns.mdinput-patterns.mdtsl-guide.mdreference/llms-full.txtllms.txt詳細
Three.js ゲーム開発
Three.js ゲーム開発のエキスパートです。3D ブラウザーゲームを構築する際は、次の一貫したパターンに従ってください。
参考:公式の Three.js LLM ドキュメントについては、
reference/llms.txt(クイックガイド)とreference/llms-full.txt(API と TSL の全編)を参照してください。これらのファイルと異なる場合は、記載されているパターンを優先してください。
パフォーマンスに関する注意事項
- 各ステップに時間をかけてください。速度より品質が重要です。
- 検証ステップを省略しないでください。問題を早期に発見できます。
- 変更を加える前に、各ファイルの全体を読んでください。
- 最適化の前にプロファイリングしてください。ボトルネックは、思っている場所にあるとは限りません。
参考ファイル
詳細については、このディレクトリ内の関連ファイルを参照してください。
core-patterns.md— EventBus、GameState、Constants、Game.js オーケストレーターの完全なコードtsl-guide.md— Three.js シェーディング言語のリファレンス(NodeMaterial クラス、TSL の使用場面)input-patterns.md— ジャイロスコープ入力、仮想ジョイスティック、統合アナログ InputSystem、入力優先順位システム
測定済みの最適化前後のエビデンスを含むパフォーマンス最適化パターンについては、threejs-perf スキル(skills/threejs-perf/SKILL.md)を参照してください。
技術スタック
- レンダラー:Three.js(
[email protected]+、ESM インポート) - ビルドツール:Vite
- 言語:ゲームテンプレートには JavaScript(TypeScript ではありません)— TypeScript は任意です
- パッケージマネージャー:npm
プロジェクトのセットアップ
新しい Three.js ゲームをスキャフォールディングする場合:
mkdir <game-name> && cd <game-name>
npm init -y
npm install three@^0.183.0
npm install -D vite
vite.config.js を作成します。
import { defineConfig } from 'vite';
export default defineConfig({
root: '.',
publicDir: 'public',
server: { port: 3000, open: true },
build: { outDir: 'dist' },
});
package.json のスクリプトに追加します。
{
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
最新のインポートパターン
Vite / npm(デフォルト — テンプレートで使用)
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
Import Maps / CDN(ビルド手順のないスタンドアロン HTML ゲーム)
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
</script>
ビルドツールを使わずに単一の HTML ファイルを配布する場合は、Import Maps を使用してください。Import Map の URL でバージョンを固定してください。
必須のアーキテクチャ
すべての Three.js ゲームは、必ず次のディレクトリ構造を使用してください。
src/
├── core/
│ ├── Game.js # Main orchestrator - init systems, render loop
│ ├── EventBus.js # Singleton pub/sub for all module communication
│ ├── GameState.js # Centralized state singleton
│ └── Constants.js # ALL config values, balance numbers, asset paths
├── systems/ # Low-level engine systems
│ ├── InputSystem.js # Keyboard/mouse/gamepad input
│ ├── PhysicsSystem.js # Collision detection
│ └── ... # Audio, particles, etc.
├── gameplay/ # Game mechanics
│ └── ... # Player, enemies, weapons, etc.
├── level/ # Level/world building
│ ├── LevelBuilder.js # Constructs the game world
│ └── AssetLoader.js # Loads models, textures, audio
├── ui/ # User interface
│ └── ... # Game over, overlays
└── main.js # Entry point - creates Game instance
基本原則
- まずはコアループ — カメラ 1 つ、シーン 1 つ、ゲームプレイループ 1 つを実装します。ビジュアルを作り込む前に、プレイヤー入力と終了条件(勝利/敗北)を追加してください。最初のスコープは小さく保ちます。メカニクス 1 つ、失敗条件 1 つ、スコアシステム 1 つにします。
- ゲームプレイの分かりやすさ > 視覚的な複雑さ — 3D は複雑さを求めるものではなく、スタイルの選択肢として扱ってください。シンプルなマテリアルを使った分かりやすいゲームのほうが、視覚的に複雑でも分かりにくいゲームより優れています。
- 安全に再起動可能 — ゲームプレイは完全に安全に再起動できる必要があります。
GameState.reset()は状態を初期状態に戻さなければなりません。クリーンアップ時にジオメトリ、マテリアル、テクスチャを破棄してください。再起動後に古い参照やリスナーが残らないようにしてください。
基本パターン(必須)
すべての Three.js ゲームで、次の 4 つの基本モジュールが必要です。完全な実装コードは core-patterns.md にあります。
1. EventBus シングルトン
モジュール間の通信はすべて EventBus(core/EventBus.js)を介して行います。モジュール間の通信に、互いを直接インポートしてはいけません。on、once、off、emit、clear メソッドを提供します。イベント名には domain:action 形式(例:player:hit、game:over)を使用します。完全な実装については core-patterns.md を参照してください。
2. 一元管理された GameState
ゲーム状態はすべて、単一のシングルトン(core/GameState.js)で管理します。システムはここから状態を読み取り、イベントによって状態を更新します。再起動時に状態を初期状態に戻す reset() メソッドが必要です。完全な実装については core-patterns.md を参照してください。
3. Constants ファイル
すべてのマジックナンバー、バランス値、アセットパス、設定は core/Constants.js に記述します。ゲームロジックに値をハードコードしてはいけません。PLAYER_CONFIG、ENEMY_CONFIG、WORLD、CAMERA、COLORS、ASSET_PATHS のように、ドメインごとに整理します。完全な実装については core-patterns.md を参照してください。
4. Game.js オーケストレーター
Game クラス(core/Game.js)がすべてを初期化し、レンダーループを実行します。公式の Three.js パターンである renderer.setAnimationLoop() を使用します(WebGPU の非同期処理に正しく対応し、タブが非表示のときは一時停止します)。init() でレンダラー、シーン、カメラ、システム、UI、イベントリスナーをセットアップします。完全な実装については core-patterns.md を参照してください。
レンダラーの選択
WebGLRenderer(デフォルト — すべてのゲームテンプレートで使用)
ブラウザーとの互換性が最も高く、広く普及しており、ほとんどのサンプルやチュートリアルで使用されています。テンプレートではデフォルトで WebGLRenderer を使用します。
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
WebGPURenderer(TSL またはコンピュートシェーダーが必要な場合)
カスタムのノードベースマテリアル(TSL)、コンピュートシェーダー、高度なレンダリングを使用する場合に必要です。注:インポートパスが 'three/webgpu' に変わり、初期化は非同期になります。
import * as THREE from 'three/webgpu';
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init();
WebGPU を選ぶ場合:TSL のカスタムシェーダー、コンピュートシェーダー、またはノードベースマテリアルが必要な場合に使用します。それ以外の場合は、WebGL を使用してください。TSL の詳細については、tsl-guide.md を参照してください。
Play.fun のセーフゾーン
ゲームがモバイル Safari の Play.fun ダッシュボード内で実行されると、SDK はゲーム iframe の document.documentElement に CSS カスタムプロパティを設定します。
--ogp-safe-top-inset— Play.fun ヘッダーの吹き出しの下の余白(モバイルでは約 68px)--ogp-safe-bottom-inset— Safari の下部コントロールの上の余白(モバイルでは約 148px)
ダッシュボード内で実行されていない場合(デスクトップ、スタンドアロン)は、どちらもデフォルトで 0px になります。
定数
// In Constants.js — reads SDK CSS vars with static fallbacks
function _readSafeInsets() {
const s = getComputedStyle(document.documentElement);
return {
top: parseInt(s.getPropertyValue('--ogp-safe-top-inset')) || 0,
bottom: parseInt(s.getPropertyValue('--ogp-safe-bottom-inset')) || 0,
};
}
const _insets = _readSafeInsets();
export const SAFE_ZONE = {
TOP_PX: Math.max(75, _insets.top),
BOTTOM_PX: _insets.bottom,
TOP_PERCENT: 8,
};
CSS ルール
すべての .overlay 要素(ゲームオーバー、ポーズ、メニュー)では、パディングに CSS 変数を使用する必要があります。
.overlay {
padding-top: max(20px, 8vh, var(--ogp-safe-top-inset, 0px));
padding-bottom: var(--ogp-safe-bottom-inset, 0px);
}
画面下部に配置する UI(ジョイスティック、アクションボタン)でも、下部のインセットを考慮する必要があります。
#joystick-zone {
bottom: max(20px, 3vh, var(--ogp-safe-bottom-inset, 0px));
}
.bottom-hud {
margin-bottom: var(--ogp-safe-bottom-inset, 0px);
}
確認事項
- 上部または下部のインセット領域に、テキスト、ボタン、インタラクティブ要素がないこと
- ゲームオーバーのオーバーレイは、画面全体ではなく、両方のインセットの間にある利用可能領域の中央にコンテンツを配置すること
- スコア表示、タイトル、再開ボタンがすべて表示され、ブラウザーの UI に隠れていないこと
- 画面下部に配置するコントロール(ジョイスティック、アクションボタン)が、Safari の下部バーで切れないこと
注:3D キャンバス自体はブラウザーの UI の背後にも描画されますが、問題ありません。ゲームは画面全体に広がるようにしてください。セーフゾーンのオフセットが必要なのは HTML のオーバーレイ UI のみです。ゲーム内の 3D 要素(HUD テクスチャ、フローティングテキスト)は、画面領域の上部 8% と下部のインセットを避けてください。
パフォーマンスに関するルール
- 手動の
requestAnimationFrameの代わりにrenderer.setAnimationLoop()を使用する。タブが非表示のときに一時停止し、WebGPU の非同期処理も適切に扱います。 - デルタタイムに上限を設定する:
Math.min(clock.getDelta(), 0.1)を使用して、致命的な処理ループを防ぎます。 - ピクセル比に上限を設定する:
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))を使用すると、高 DPI 画面での GPU 過負荷を防げます。 - オブジェクトプーリング:GC を最小限に抑えるため、ホットループ内では
Vector3、Box3、一時オブジェクトを再利用します。フレームごとの割り当ては避け、事前に確保して再利用してください。 - 最初の段階ではシャドウを無効にする — 特に必要で、モバイルでテスト済みの場合に限り、シャドウマップを有効にしてください。動的シャドウはレンダリング機能の中で最も負荷が高いものです。
- ドローコールを抑える — 固有のマテリアルやジオメトリが少ないほど、ドローコールも少なくなります。可能な場合は静的ジオメトリを結合してください。繰り返し登場するオブジェクトにはインスタンス化メッシュを使用してください。InstancedMesh のパターンについては
skills/threejs-perf/を参照してください(ドローコールを約 9,000 倍削減し、レンダリング時の CPU 処理を約 57 倍高速化)。 - シンプルなマテリアルを優先する —
MeshBasicMaterialまたはMeshStandardMaterialを使用してください。特に必要な場合を除き、MeshPhysicalMaterial、カスタムシェーダー、複雑なマテリアル設定は避けてください。 - デフォルトではポストプロセスを使わない — 最初の実装では、ブルーム、SSAO、モーションブラーなどのポストプロセスパスを省略してください。これらはモバイルのパフォーマンスを大きく低下させます。ゲームプレイが安定し、パフォーマンス予算に余裕がある場合にのみ追加してください。
- ジオメトリとマテリアルの数を抑える — 固有のマテリアルが 10 個のゲームは、100 個のゲームより高速に描画されます。同じ外観のオブジェクト間でマテリアルを再利用してください。
- レンダラーで
powerPreference: 'high-performance'を使用する - 適切に破棄する:オブジェクトを削除するときは、ジオメトリ、マテリアル、テクスチャに対して
.dispose()を呼び出します。 - 視錐台カリング:Three.js に任せます(デフォルトで有効)。ただし、カスタムジオメトリには境界球を設定してください。
アセットの読み込み
- Vite では、静的アセットを
/public/に配置します。 - 3D モデルには GLB 形式を使用します(ファイルサイズが小さく、単一ファイルです)。
three/addonsのTHREE.TextureLoader、GLTFLoaderを使用します。- コールバックを使って UI に読み込みの進行状況を表示します。
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
function loadModel(path) {
return new Promise((resolve, reject) => {
loader.load(
path,
(gltf) => resolve(gltf.scene),
undefined,
(error) => reject(error),
);
});
}
入力処理(モバイルファースト)
明示的に指定されていない限り、すべてのゲームはデスクトップとモバイルの両方で動作する必要があります。トレードオフを検討する際は、モバイルに60%、デスクトップに40%の労力を配分してください。ゲームのコンセプトごとに、最適なモバイル入力方法を選んでください。
| ゲームの種類 | 主なモバイル入力 | 代替手段 |
|---|---|---|
| マーブル/傾き/バランス | ジャイロスコープ(DeviceOrientation) | 仮想ジョイスティック |
| ランナー/エンドレス | タップゾーン(左右半分) | スワイプジェスチャー |
| パズル/ターン制 | ターゲットをタップ(最小44px) | ドラッグ&ドロップ |
| シューティング/照準 | 仮想ジョイスティック+タップで発射 | デュアルジョイスティック |
| プラットフォーマー | 仮想Dパッド+ジャンプボタン | 移動に傾きを使用 |
統合アナログ InputSystem
キーボード、ジャイロスコープ、タッチを単一のアナログインターフェースに統合する専用のInputSystemを使用してください。ゲームロジックはmoveX/moveZ(-1..1)を読み取り、入力元を認識しません。キーボード入力は常にオーバーライドとして有効です。モバイルでは、システムがジャイロスコープ(iOS 13+の権限リクエストあり)を初期化し、利用できない場合は仮想ジョイスティックにフォールバックします。GyroscopeInput、VirtualJoystick、入力優先度のパターンを含む完全な実装については、input-patterns.mdを参照してください。
機能を追加するとき
- 適切な
src/サブディレクトリに新しいモジュールを作成します EventBus.jsのEventsオブジェクトに、domain:action形式の名前で新しいイベントを定義しますConstants.jsに設定を追加します- 必要に応じて
GameState.jsに状態を追加します Game.jsオーケストレーターで接続します- 他のシステムとの通信には、EventBusのみを使用します
出荷前検証チェックリスト
ゲームの完成と見なす前に、以下を確認してください。
- コアループが動作する — プレイヤーが開始、プレイ、敗北/勝利でき、結果を確認できる
- 問題なく再起動できる —
GameState.reset()で初期状態に戻り、すべてのThree.jsリソースが破棄される - タッチ入力とキーボード入力 — モバイル(ジャイロ/ジョイスティック/タップ)とデスクトップ(キーボード/マウス)の両方で動作する
- キャンバスがレスポンシブである — ウィンドウサイズ変更時にレンダラーがリサイズされ、カメラのアスペクト比が更新される
- すべての値がConstantsにある — ゲームロジックにハードコードされたマジックナンバーがない
- EventBusのみを使用する — 通信のためのモジュール間の直接インポートがない
- リソースをクリーンアップする — シーンから削除するときに、ジオメトリ、マテリアル、テクスチャを破棄する
- ポストプロセスを使用しない — 明示的に必要で、モバイルでテスト済みの場合を除く
- シャドウを無効にする — 明示的に必要で、予算に余裕がある場合を除く
- デルタを上限で制限した移動 — 毎フレーム
Math.min(clock.getDelta(), 0.1)を適用する - ミュート切り替え — 音声をミュート/ミュート解除でき、
isMutedの状態が反映される - セーフエリアを遵守する — すべてのHTMLオーバーレイUIで、Play.funのセーフエリアに
var(--ogp-safe-top-inset)/var(--ogp-safe-bottom-inset)を使用する。画面下部のコントロールは下部インセットより上に配置する - ビルドが成功する —
npm run buildがエラーなしで成功する - コンソールエラーがない — ゲームが未捕捉例外やWebGLの障害なしで動作する