react-three-game について
react-three-gameは、prefabと呼ばれるJSONシーンドキュメントを中心としたWebゲーム制作ツールキットです。開発者やエージェントがシーンを編集し、再利用可能なコンポーネントを追加したり、アプリ独自のキャンバスにシーンを描画したりできます。
主な機能
- 01実行中のエディターAPIでシーン編集
- 02prefab JSONを直接編集
- 03再利用可能なカスタムコンポーネントを追加
- 04Runtimeコンポーネントでシーンスクリプトを実行
- 05PrefabRootでシーンを描画
- 06prefabチャンクを読み込んで有効化
活用シーン
- 01実行中のシーンでオブジェクト配置を調整
- 02シーンJSONの変更を生成またはレビュー
- 03再利用可能な動作やビジュアルコンポーネントを追加
- 04既存のキャンバスにprefabシーンを組み込む
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ prnthh/ react-three-game-skill --skill react-three-gameインストール後、Claude Code で/react-three-gameを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlrules/ADVANCED_PHYSICS.mdLIGHTING.mdPERFORMANCE.mdRUNTIME_INTEGRATIONS.md詳細
React Three Game
React Three Game は、Web 向けのゲーム制作ツールボックスです。prefab は JSON のシーンドキュメントです。ノードがオブジェクトを記述し、コンポーネントが設定や動作を与えます。エージェントは、実行中のエディターを操作したり、ドキュメントを直接編集したり、アプリケーションのソースにコンポーネントを追加したりできます。タスクと利用可能なファイルに合った方法を選んでください。
作業方法を選ぶ
| タスクまたは状況 | 方法 |
|---|---|
| 表示中のシーンを調整して配置を確認する | エディター API を使う |
| 保存済みのシーンを変更する、コンテンツを生成する、またはファイルの差分を確認する | prefab JSON を直接編集する |
| 再利用可能な動作、フック、新しいレンダリング機能を追加する | ソースにカスタムコンポーネントを追加する |
| シーンと一緒に小さなスクリプトを保存する | Runtime コンポーネントを使う |
| アプリ独自のキャンバスコンテンツと並べて JSON をレンダリングする | PrefabRoot を埋め込む |
| ゲームプレイ、物理演算、ライティング、読み込みを接続する | 状態とリソースおよび目的別リファレンス |
これらの方法は組み合わせることができます。ソースにコンポーネントを追加し、JSON でインスタンスを設定してから、エディターまたはビューアーで確認します。独自の規約を作る前に、既存のスキーマと近接するアプリケーションコードを参照してください。
シーンを編集する
接続してオブジェクトを見つける
PrefabEditor を含むフレーム内で JavaScript を実行します。
const scene = window.scene;
scene.info(); // Check mode and how this scene can be saved.
const { node } = scene.find({ query: 'north wall' }); // Requires one match.
await scene.setMode({ mode: 'edit' });
scene.look({ id: node.id });
編集に必要な情報だけを読み取ります。
scene.search({ query: 'wall', limit: 20 }); // Choose an ID when find is ambiguous.
scene.get({ id: node.id, resolved: true }); // Full properties and defaults.
scene.components({ names: ['Material'] }); // Supported settings for a type.
コンポーネントインスタンスのキーはノードに属しており、型名と異なる場合があります。
window.scene が見つからない場合は、フレームを確認し、エディターがマウントされるまで待ちます。agentTools={false} は公開を無効にします。showUI={false} はパネルを非表示にするだけです。ビューアーのみの場合、window API はありません。1 ページでサポートされるエージェント対応エディターは 1 つだけです。ナビゲーション後は再取得してください。
変更を 1 つ加えて確認する
書き込みには Edit モードが必要です。適切な既存オブジェクトから始め、シーンの階層と視覚的な規約を維持してください。
await scene.setMode({ mode: 'edit' });
scene.update({ id: node.id, transform: { position: [2, 1, 0] } });
scene.look({ id: node.id });
const image = await scene.capture({ helpers: false });
アセットを読み込んでから、image.dataUrl を表示して結果を確認します。更新 1 回につき、取り消し操作は 1 ステップです。
scene.undo(); // Reverse an authored edit when needed.
await scene.setMode({ mode: 'play' }); // Check animation or physics.
// Observe the behavior before continuing.
await scene.setMode({ mode: 'edit' });
await scene.reset(); // Restore live objects from current JSON; keep edits/history.
モードを変更するだけでは、ライブ状態はリセットされません。
結果を保存する
if (scene.info().saveMethod === 'save') {
await scene.save(); // Host's onSaveScene callback.
} else {
const json = scene.exportJSON(); // Write this string to the scene's source file.
}
エクスポートだけでは保存されません。変更した内容、確認した内容、保存先を報告してください。
より具体的な操作を見つける
scene.help() はメソッドを一覧表示します。キャプチャオプション、コンポーネントの編集、配置、ダウンロード、パッキング、アトミックな一括処理については、エージェント向けエディターシーンの該当セクションを参照してください。このチェックアウトでは、docs/public/editor-scene-for-agents.md を使用してください。デプロイ済みのドキュメントは古い場合があります。
個々の編集では、現在のリビジョンが自動的に使用されます。以前の読み取り結果を基準に調整する場合は expectedRevision を使用してください。validate() と batch() では指定が必須です。ガード付きの書き込みが競合した場合は、再読み込みして計画を立て直してください。依存する変更をまとめて反映する必要がある場合は、一括処理を優先してください。すべての編集に使う必要はありません。
prefab JSON を直接編集する
アプリが実際に読み込むファイルを見つけます。このリポジトリでは、シーンは docs/public/prefabs にあります。
// In the consuming app: follow this import (or the PrefabInstance URL).
import level from './level.json';
// Edit level.json directly; no running editor is needed.
// Preserve node IDs, component keys, and unrelated fields.
// Check component schemas in source before adding properties.
// Check JSON syntax, unique IDs, and component registration in the app.
// Reload the editor/viewer, inspect the result, and review the file diff.
// File edits do not update an already-open editor's in-memory document.
// Reload before continuing API work so a stale save cannot overwrite the file.
アプリのキャンバスに JSON をレンダリングする
ユーザー独自の JSX やゲームプレイシステムと並べて JSON をレンダリングします。マウントする前にカスタム登録をインポートし、レンダー間で data の値が変わらないようにしてください。
GameCanvas を使う場合
import './components'; // Application component registrations.
import { GameCanvas, PrefabRoot } from 'react-three-game/viewer';
import type { Prefab } from 'react-three-game/core';
import level from './level.json';
<GameCanvas>
<ambientLight intensity={1} />
<PrefabRoot data={level as Prefab} />
<mesh position={[3, 0, 0]}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
</GameCanvas>
既存の R3F Canvas を使う場合
アプリ既存の WebGPU セットアップを維持してください。必要に応じて、最小限のレンダラーコールバックを以下に示します。
import { Canvas } from '@react-three/fiber';
import { WebGPURenderer } from 'three/webgpu';
import { PrefabRoot } from 'react-three-game/viewer';
import type { Prefab } from 'react-three-game/core';
import './components';
import level from './level.json';
<Canvas gl={async ({ canvas }) => {
const renderer = new WebGPURenderer({ canvas });
await renderer.init();
return renderer;
}}>
<PrefabRoot data={level as Prefab} />
{/* Existing JSX, lights, controls, and gameplay systems stay here. */}
</Canvas>
リソースを共有する、または prefab にアクセスする場合
キャンバス内で、アプリの Gameplay コンポーネントを使用します。
import { SceneRuntime, PrefabRoot } from 'react-three-game/viewer';
<SceneRuntime>
<PrefabRoot id="level" data={level}>
<Gameplay /> {/* Can use usePrefab() for this document. */}
</PrefabRoot>
<PrefabRoot id="props" data={propsPrefab} />
{/* Sibling userland content shares runtime resources, not a prefab context. */}
</SceneRuntime>
GameCanvas はすでに SceneRuntime を提供しています。スタンドアロンの PrefabRoot は、必要に応じて独自のランタイムを提供します。これらのプロバイダーはゲームループを提供しません。
エディターや window API は必要ありません。視覚的な編集を行うには、react-three-game/editor から PrefabEditor をマウントしてください。詳しくはエディターガイドを参照してください。
プレハブの規則
以下の注釈付き JSON には、説明用のコメントが含まれています。.json ファイルではコメントを省略してください。
{
"root": {
"id": "world", // IDs are unique within this prefab.
"children": [{
"id": "box", // Keep IDs stable across edits.
"components": {
// Instance keys (left) can differ from registered type names (right).
"transform": {
"type": "Transform",
"properties": {
"position": [0, 1, 0], // Local to the parent; Y is up.
"rotation": [0, 0, 0], // XYZ Euler radians.
"scale": [1, 1, 1] // Scaling also affects children and colliders.
}
},
"geometry": {
"type": "Geometry",
"properties": { "geometryType": "box", "args": [2, 1, 1] }
},
"material": { "type": "Material", "properties": { "name": "stone", "color": "#999999" } }
}
}]
}
}
components 内の共通アセットエントリー:
// Model assets retain their embedded materials; a sibling Material won't override them.
"model": { "type": "Model", "properties": { "filename": "/models/tree.glb" } }
// For primitive meshes: texture is the color map, normalMapTexture the normal map.
"surface": { "type": "Material", "properties": { "texture": "/textures/stone.jpg" } }
// Compose another scene document. Relative asset URLs resolve against basePath.
"building": { "type": "PrefabRef", "properties": { "url": "/prefabs/building.json" } }
既知のアセット URL を使用してください。リモートアセットには CORS が必要です。ブラウザーで読み込みを確認してください。
各シーンで有効な Camera と Fog はそれぞれ 1 つにしてください。CameraFollow のターゲットにはローカルノードを指定します。
そのオフセットはワールド空間です。編集モードではエディターのカメラ操作を使用します。
スクリプトによる動作
JSON またはエディター API を使って、ノードの components の下に次のエントリーを追加します。
"spin": {
"type": "Runtime",
"properties": {
"data": { "speed": 1 },
"setup": "const y = object.rotation.y; return () => { object.rotation.y = y; };",
"update": "object.rotation.y += data.speed * delta;"
}
}
setup は任意のクリーンアップ関数を返し、update は秒単位の delta を受け取ります。どちらも準備完了後、Play で有効な間に実行されます。コードを変更すると setup が再実行されますが、データを変更しても再実行されません。setup で作成したコールバック内では、現在の入力を取得するために context.data を使用してください。
スクリプトは state を共有し、nodeId、node、object、data、prefab、events、context を受け取ります。React フックを使わず、信頼されたページコードとして実行されます。フィールドの詳細は Runtime スキーマを参照してください。フックが必要な場合は、ソースコンポーネントを使用してください。
カスタムコンポーネント
再利用可能な動作、React フック、新しいビジュアルコンポーネントには、アプリケーションソースを使用してください。
設定のみ
name と properties は必須です。View は任意です。設定だけでは動作は実行されません。
import { registerComponent, type Component } from 'react-three-game/viewer';
const Health: Component<{ max: number }> = {
name: 'Health',
properties: { max: { default: 100 } },
};
registerComponent(Health);
動作を伴う設定
React フックとライブのノードオブジェクトを使用するには、View を追加します。合成を維持するには children を返してください。
import { useFrame } from '@react-three/fiber';
import { registerComponent, useNode, useGameObject,
type Component, type ComponentViewProps } from 'react-three-game/viewer';
type SpinProps = { speed: number };
function SpinView({ properties, enabled, children }: ComponentViewProps<SpinProps>) {
const object = useGameObject();
const { editMode, preparing } = useNode();
// Animate the live object; document writes are for authored edits.
useFrame((_, delta) => {
if (enabled && !editMode && !preparing && object.transform) {
object.transform.rotation.y += properties.speed * delta;
}
});
return <>{children}</>;
}
const Spin: Component<SpinProps> = {
name: 'Spin', View: SpinView,
// Defaults feed both the view and the generated inspector.
properties: { speed: { default: 1, step: 0.1 } },
};
registerComponent(Spin);
ノードにアタッチする
エディターまたはビューアーをマウントする前に登録モジュールをインポートし、ノードの components の下にエントリーを追加します。インスタンスキーはノード内でローカルです。type は登録名と一致させます。
"spin": { "type": "Spin", "properties": { "speed": 2 } }
プロパティとレンダリングの規約
コンポーネント定義内のフィールド:
properties: {
speed: { default: 1 }, // Numbers infer their type.
label: { type: 'string', default: 'Box' }, // Other values specify a type.
mode: {
type: 'select', default: 'walk',
options: [{ value: 'walk', label: 'Walk' }, { value: 'run', label: 'Run' }],
},
},
// View receives resolved defaults; don't duplicate them in the view.
// Ordinary fields get an inspector automatically.
// Omit slot for ordinary behavior. When supplying a render-graph part:
slot: 'geometry', // Or 'object' / 'material'; each slot is exclusive on its node.
// Geometry gets an implicit mesh; object views supply their own render object.
// The View must implement attachment and preserve children where appropriate.
カスタムインスペクターを使う場合は、エディター専用モジュール内で次のようにします。
import { registerComponentEditor } from 'react-three-game/editor';
registerComponentEditor(Spin, SpinInspector); // App-defined component and inspector.
// Keep this module out of runtime imports.
状態とリソース
| 必要なもの | 使用するもの |
|---|---|
| 現在のノードオブジェクト | useGameObject() |
| ノード/編集状態 | useNode() |
| ドキュメントとローカルオブジェクト | usePrefab() |
| 共有シーン/モード | useScene() |
| シリアライズ可能な編集 | Prefab の変更 |
| ビューのアニメーション | R3F useFrame 内の Refs/ライブ Three オブジェクト |
| ゲームプレイのシミュレーション | ホストが所有する状態とティックループ |
| URL に基づくチャンク | PrefabInstance |
チャンクの読み込みと有効化
キャンバス内で:
import { PrefabInstance } from 'react-three-game/viewer';
// Mount to load, compile, and activate automatically.
<PrefabInstance id="courtyard" url="/prefabs/courtyard.json" />
// onStatus: preparation status/errors. onActivate: gameplay is active.
// Unmount to release. Use static only when the chunk will remain immutable.
ジオメトリの変更
const Stretch: Component<{ amount: number }> = {
name: 'Stretch',
properties: { amount: { default: 1 } },
modifyGeometry: (source, { amount }) => source.clone().scale(1, amount, 1),
};
registerComponent(Stretch);
新しく所有するジオメトリを返し、ソースには変更を加えないでください。モディファイアーはコンポーネントの順序で実行されます。任意の第 3 引数は、ノードのスケールを context.scale として渡します。
アセットの宣言とマテリアルの共有
アセットを使用するコンポーネント定義では、ビューが使用する各リソースを宣言します。
dependencies: ({ filename }) => [{ kind: 'model', path: filename }],
種類は model、texture、sound、prefab で、パスは basePath に従います。カスタムの共有マテリアルには useSharedMaterialResource を使用し、変更しないでください。
用途に応じたリファレンス
タスクに必要なリファレンスだけを参照してください。