react-three-game
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesereact-three-game
react-three-game
Instructions for the agent to follow when this skill is activated.
当该技能激活时,Agent需要遵循的说明。
When to use
适用场景
generate 3D scenes, games and physics simulations in React.
在React中生成3D场景、游戏和物理模拟。
Agent Workflow: JSON → GLB
Agent工作流:JSON → GLB
Agents can programmatically generate 3D assets:
- Create a JSON prefab following the GameObject schema
- Load it in to render the Three.js scene
PrefabEditor - Export the scene to GLB format using or
exportGLBexportGLBData
tsx
import { useRef, useEffect } from 'react';
import { PrefabEditor, exportGLBData } from 'react-three-game';
import type { PrefabEditorRef } from 'react-three-game'
const jsonPrefab = {
root: {
id: "scene",
children: [
{
id: "cube",
components: {
transform: { type: "Transform", properties: { position: [0, 0, 0] } },
geometry: { type: "Geometry", properties: { geometryType: "box", args: [1, 1, 1] } },
material: { type: "Material", properties: { color: "#ff0000" } }
}
}
]
}
};
function AgentExporter() {
const editorRef = useRef<PrefabEditorRef>(null);
useEffect(() => {
const timer = setTimeout(async () => {
const sceneRoot = editorRef.current?.rootRef.current?.root;
if (!sceneRoot) return;
const glbData = await exportGLBData(sceneRoot);
// glbData is an ArrayBuffer ready for upload/storage
}, 1000); // Wait for scene to render
return () => clearTimeout(timer);
}, []);
return <PrefabEditor ref={editorRef} initialPrefab={jsonPrefab} />;
}Agent可通过编程方式生成3D资源:
- 遵循GameObject schema创建JSON预制件
- 在中加载该预制件以渲染Three.js场景
PrefabEditor - 使用或
exportGLB将场景导出为GLB格式exportGLBData
tsx
import { useRef, useEffect } from 'react';
import { PrefabEditor, exportGLBData } from 'react-three-game';
import type { PrefabEditorRef } from 'react-three-game'
const jsonPrefab = {
root: {
id: "scene",
children: [
{
id: "cube",
components: {
transform: { type: "Transform", properties: { position: [0, 0, 0] } },
geometry: { type: "Geometry", properties: { geometryType: "box", args: [1, 1, 1] } },
material: { type: "Material", properties: { color: "#ff0000" } }
}
}
]
}
};
function AgentExporter() {
const editorRef = useRef<PrefabEditorRef>(null);
useEffect(() => {
const timer = setTimeout(async () => {
const sceneRoot = editorRef.current?.rootRef.current?.root;
if (!sceneRoot) return;
const glbData = await exportGLBData(sceneRoot);
// glbData是可用于上传/存储的ArrayBuffer
}, 1000); // 等待场景渲染完成
return () => clearTimeout(timer);
}, []);
return <PrefabEditor ref={editorRef} initialPrefab={jsonPrefab} />;
}Core Concepts
核心概念
Asset Paths and Public Directory
资源路径与公共目录
All asset paths are relative to and omit the prefix:
/public/publicjson
{
"texture": "/textures/floor.png",
"model": "/models/car.glb",
"font": "/fonts/font.ttf"
}Path refers to .
"/any/path/file.ext"/public/any/path/file.ext所有资源路径均相对于目录,且需省略前缀:
/public/publicjson
{
"texture": "/textures/floor.png",
"model": "/models/car.glb",
"font": "/fonts/font.ttf"
}路径对应。
"/any/path/file.ext"/public/any/path/file.extGameObject Structure
GameObject结构
Every game object follows this schema:
typescript
interface GameObject {
id: string;
disabled?: boolean;
components?: Record<string, { type: string; properties: any }>;
children?: GameObject[];
}每个游戏对象均遵循以下schema:
typescript
interface GameObject {
id: string;
disabled?: boolean;
components?: Record<string, { type: string; properties: any }>;
children?: GameObject[];
}Prefab JSON Format
预制件JSON格式
Scenes are defined as JSON prefabs with a root node containing children:
json
{
"root": {
"id": "scene",
"children": [
{
"id": "my-object",
"components": {
"transform": { "type": "Transform", "properties": { "position": [0, 0, 0] } },
"geometry": { "type": "Geometry", "properties": { "geometryType": "box" } },
"material": { "type": "Material", "properties": { "color": "#ff0000" } }
}
}
]
}
}场景以JSON预制件形式定义,包含一个根节点和其子节点:
json
{
"root": {
"id": "scene",
"children": [
{
"id": "my-object",
"components": {
"transform": { "type": "Transform", "properties": { "position": [0, 0, 0] } },
"geometry": { "type": "Geometry", "properties": { "geometryType": "box" } },
"material": { "type": "Material", "properties": { "color": "#ff0000" } }
}
}
]
}
}Built-in Components
内置组件
| Component | Type | Key Properties |
|---|---|---|
| Transform | | |
| Geometry | | |
| Material | | |
| Physics | | |
| Model | | |
| SpotLight | | |
| DirectionalLight | | |
| AmbientLight | | |
| Text | | |
| 组件 | 类型 | 关键属性 |
|---|---|---|
| Transform | | |
| Geometry | | |
| Material | | |
| Physics | | |
| Model | | |
| SpotLight | | |
| DirectionalLight | | |
| AmbientLight | | |
| Text | | |
Text Component
Text组件
Requires and a font file (TTF/WOFF) in :
hb.wasm/public/fonts/- hb.wasm: https://github.com/prnthh/react-three-game/raw/refs/heads/main/docs/public/fonts/hb.wasm
- Sample font: https://github.com/prnthh/react-three-game/raw/refs/heads/main/docs/public/fonts/NotoSans-Regular.ttf
Font property:
"font": "/fonts/NotoSans-Regular.ttf"需要和目录下的字体文件(TTF/WOFF格式):
hb.wasm/public/fonts/- hb.wasm:https://github.com/prnthh/react-three-game/raw/refs/heads/main/docs/public/fonts/hb.wasm
- 示例字体:https://github.com/prnthh/react-three-game/raw/refs/heads/main/docs/public/fonts/NotoSans-Regular.ttf
字体属性:
"font": "/fonts/NotoSans-Regular.ttf"Geometry Args by Type
不同几何类型的Args参数
| geometryType | args array |
|---|---|
| |
| |
| |
| |
| geometryType | args数组 |
|---|---|
| |
| |
| |
| |
Material Textures
材质纹理
json
{
"material": {
"type": "Material",
"properties": {
"color": "white",
"texture": "/textures/floor.png",
"repeat": true,
"repeatCount": [4, 4]
}
}
}json
{
"material": {
"type": "Material",
"properties": {
"color": "white",
"texture": "/textures/floor.png",
"repeat": true,
"repeatCount": [4, 4]
}
}
}Rotations
旋转设置
Use radians: = 90°, = 180°, = -90°
1.573.14-1.57使用弧度值: = 90°, = 180°, = -90°
1.573.14-1.57Common Patterns
通用模式
Usage Modes
使用模式
GameCanvas + PrefabRoot: Pure renderer for embedding prefab data in standard R3F applications. Minimal wrapper - just renders the prefab as Three.js objects. Requires manual setup. Physics always active. Use this to integrate prefabs into larger R3F scenes.
<Physics>jsx
import { Physics } from '@react-three/rapier';
import { GameCanvas, PrefabRoot } from 'react-three-game';
<GameCanvas>
<Physics>
<PrefabRoot data={prefabData} />
<CustomComponent />
</Physics>
</GameCanvas>PrefabEditor: Managed scene with editor UI and play/pause controls for physics. Full authoring tool for level design and prototyping. Includes canvas, physics, transform gizmos, and inspector. Physics only runs in play mode. Can pass R3F components as children.
jsx
import { PrefabEditor } from 'react-three-game';
<PrefabEditor initialPrefab={prefabData}>
<CustomComponent />
</PrefabEditor>GameCanvas + PrefabRoot:用于在标准R3F应用中嵌入预制件数据的纯渲染器。轻量封装 - 仅将预制件渲染为Three.js对象。需要手动设置。物理效果始终处于激活状态。适用于将预制件集成到更大的R3F场景中。
<Physics>jsx
import { Physics } from '@react-three/rapier';
import { GameCanvas, PrefabRoot } from 'react-three-game';
<GameCanvas>
<Physics>
<PrefabRoot data={prefabData} />
<CustomComponent />
</Physics>
</GameCanvas>PrefabEditor:带有编辑器UI和物理效果播放/暂停控制的托管场景。用于关卡设计和原型开发的完整创作工具。包含画布、物理系统、变换 gizmo 和检查器。物理效果仅在播放模式下运行。可将R3F组件作为子元素传入。
jsx
import { PrefabEditor } from 'react-three-game';
<PrefabEditor initialPrefab={prefabData}>
<CustomComponent />
</PrefabEditor>Tree Utilities
树结构工具
typescript
import { findNode, updateNode, updateNodeById, deleteNode, cloneNode, exportGLBData } from 'react-three-game';
const node = findNode(root, nodeId);
const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true })); // or updateNodeById (identical)
const afterDelete = deleteNode(root, nodeId);
const cloned = cloneNode(node);
const glbData = await exportGLBData(sceneRoot);typescript
import { findNode, updateNode, updateNodeById, deleteNode, cloneNode, exportGLBData } from 'react-three-game';
const node = findNode(root, nodeId);
const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true })); // 或使用updateNodeById(功能相同)
const afterDelete = deleteNode(root, nodeId);
const cloned = cloneNode(node);
const glbData = await exportGLBData(sceneRoot);Hybrid JSON + R3F Children Pattern
JSON + R3F子元素混合模式
Prefabs define static scene structure, R3F children add dynamic behavior:
tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { PrefabEditor, findNode } from 'react-three-game';
import type { PrefabEditorRef } from 'react-three-game';
function DynamicLight() {
const lightRef = useRef<THREE.SpotLight>(null!);
useFrame(({ clock }) => {
lightRef.current.intensity = 100 + Math.sin(clock.elapsedTime) * 50;
});
return <spotLight ref={lightRef} position={[10, 15, 10]} angle={0.5} />;
}
<PrefabEditor initialPrefab={staticScenePrefab}>
<DynamicLight />
<CustomController />
</PrefabEditor>Use cases: Player controllers, AI behaviors, procedural animation, real-time effects.
预制件定义静态场景结构,R3F子元素添加动态行为:
tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { PrefabEditor, findNode } from 'react-three-game';
import type { PrefabEditorRef } from 'react-three-game';
function DynamicLight() {
const lightRef = useRef<THREE.SpotLight>(null!);
useFrame(({ clock }) => {
lightRef.current.intensity = 100 + Math.sin(clock.elapsedTime) * 50;
});
return <spotLight ref={lightRef} position={[10, 15, 10]} angle={0.5} />;
}
<PrefabEditor initialPrefab={staticScenePrefab}>
<DynamicLight />
<CustomController />
</PrefabEditor>适用场景:玩家控制器、AI行为、程序化动画、实时特效。
Quick Reference Examples
快速参考示例
json
// Static geometry with physics (floor, wall, platform, ramp)
{ "id": "floor", "components": {
"transform": { "type": "Transform", "properties": { "position": [0, -0.5, 0] } },
"geometry": { "type": "Geometry", "properties": { "geometryType": "box", "args": [40, 1, 40] } },
"material": { "type": "Material", "properties": { "texture": "/textures/floor.png", "repeat": true, "repeatCount": [20, 20] } },
"physics": { "type": "Physics", "properties": { "type": "fixed" } }
}}
// Lighting
{ "id": "spot", "components": {
"transform": { "type": "Transform", "properties": { "position": [10, 15, 10] } },
"spotlight": { "type": "SpotLight", "properties": { "intensity": 200, "angle": 0.8, "castShadow": true } }
}}
// 3D Text
{ "id": "title", "components": {
"transform": { "type": "Transform", "properties": { "position": [0, 3, 0] } },
"text": { "type": "Text", "properties": { "text": "Welcome", "font": "/fonts/font.ttf", "size": 1, "depth": 0.1 } }
}}
// GLB Model
{ "id": "tree", "components": {
"transform": { "type": "Transform", "properties": { "position": [0, 0, 0], "scale": [1.5, 1.5, 1.5] } },
"model": { "type": "Model", "properties": { "filename": "/models/tree.glb" } }
}}json
// 带物理效果的静态几何体(地面、墙壁、平台、坡道)
{ "id": "floor", "components": {
"transform": { "type": "Transform", "properties": { "position": [0, -0.5, 0] } },
"geometry": { "type": "Geometry", "properties": { "geometryType": "box", "args": [40, 1, 40] } },
"material": { "type": "Material", "properties": { "texture": "/textures/floor.png", "repeat": true, "repeatCount": [20, 20] } },
"physics": { "type": "Physics", "properties": { "type": "fixed" } }
}}
// 光照
{ "id": "spot", "components": {
"transform": { "type": "Transform", "properties": { "position": [10, 15, 10] } },
"spotlight": { "type": "SpotLight", "properties": { "intensity": 200, "angle": 0.8, "castShadow": true } }
}}
// 3D文本
{ "id": "title", "components": {
"transform": { "type": "Transform", "properties": { "position": [0, 3, 0] } },
"text": { "type": "Text", "properties": { "text": "Welcome", "font": "/fonts/font.ttf", "size": 1, "depth": 0.1 } }
}}
// GLB模型
{ "id": "tree", "components": {
"transform": { "type": "Transform", "properties": { "position": [0, 0, 0], "scale": [1.5, 1.5, 1.5] } },
"model": { "type": "Model", "properties": { "filename": "/models/tree.glb" } }
}}Editor
编辑器
Basic Usage
基础用法
jsx
import { PrefabEditor } from 'react-three-game';
<PrefabEditor initialPrefab={sceneData} onPrefabChange={setSceneData} />Keyboard shortcuts: T (Translate), R (Rotate), S (Scale)
jsx
import { PrefabEditor } from 'react-three-game';
<PrefabEditor initialPrefab={sceneData} onPrefabChange={setSceneData} />键盘快捷键:T(平移)、R(旋转)、S(缩放)
Camera Control
相机控制
By default, uses an orbit camera. Override it by adding a custom camera with :
PrefabEditormakeDefaulttsx
import { PerspectiveCamera } from '@react-three/drei';
import { PrefabEditor } from 'react-three-game';
<PrefabEditor initialPrefab={prefab}>
<PerspectiveCamera makeDefault position={[0, 5, 10]} fov={75} />
</PrefabEditor>Any R3F camera component works: , , or custom camera controllers.
PerspectiveCameraOrthographicCamera默认情况下,使用轨道相机。可通过添加带有属性的自定义相机来覆盖默认设置:
PrefabEditormakeDefaulttsx
import { PerspectiveCamera } from '@react-three/drei';
import { PrefabEditor } from 'react-three-game';
<PrefabEditor initialPrefab={prefab}>
<PerspectiveCamera makeDefault position={[0, 5, 10]} fov={75} />
</PrefabEditor>任何R3F相机组件均可使用:、或自定义相机控制器。
PerspectiveCameraOrthographicCameraProgrammatic Updates
程序化更新
jsx
import { useRef } from 'react';
import { PrefabEditor, updateNodeById } from 'react-three-game';
import type { PrefabEditorRef } from 'react-three-game';
function Scene() {
const editorRef = useRef<PrefabEditorRef>(null);
const moveBall = () => {
const prefab = editorRef.current!.prefab;
const newRoot = updateNodeById(prefab.root, "ball", node => ({
...node,
components: {
...node.components,
transform: {
...node.components!.transform!,
properties: { ...node.components!.transform!.properties, position: [5, 0, 0] }
}
}
}));
editorRef.current!.setPrefab({ ...prefab, root: newRoot });
};
return <PrefabEditor ref={editorRef} initialPrefab={sceneData} />;
}PrefabEditorRef: , , , ,
prefabsetPrefab()screenshot()exportGLB()rootRefjsx
import { useRef } from 'react';
import { PrefabEditor, updateNodeById } from 'react-three-game';
import type { PrefabEditorRef } from 'react-three-game';
function Scene() {
const editorRef = useRef<PrefabEditorRef>(null);
const moveBall = () => {
const prefab = editorRef.current!.prefab;
const newRoot = updateNodeById(prefab.root, "ball", node => ({
...node,
components: {
...node.components,
transform: {
...node.components!.transform!,
properties: { ...node.components!.transform!.properties, position: [5, 0, 0] }
}
}
}));
editorRef.current!.setPrefab({ ...prefab, root: newRoot });
};
return <PrefabEditor ref={editorRef} initialPrefab={sceneData} />;
}PrefabEditorRef:、、、、
prefabsetPrefab()screenshot()exportGLB()rootRefGLB Export
GLB导出
tsx
import { exportGLBData } from 'react-three-game';
const glbData = await exportGLBData(editorRef.current!.rootRef.current!.root);tsx
import { exportGLBData } from 'react-three-game';
const glbData = await exportGLBData(editorRef.current!.rootRef.current!.root);Runtime Animation
运行时动画
tsx
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { PrefabEditor, updateNodeById } from "react-three-game";
function Animator({ editorRef }) {
useFrame(() => {
const prefab = editorRef.current!.prefab;
const newRoot = updateNodeById(prefab.root, "ball", node => ({
...node,
components: {
...node.components,
transform: {
...node.components!.transform!,
properties: { ...node.components!.transform!.properties, position: [x, y, z] }
}
}
}));
editorRef.current!.setPrefab({ ...prefab, root: newRoot });
});
return null;
}
function Scene() {
const editorRef = useRef(null);
return (
<PrefabEditor ref={editorRef} initialPrefab={data}>
<Animator editorRef={editorRef} />
</PrefabEditor>
);
}tsx
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { PrefabEditor, updateNodeById } from "react-three-game";
function Animator({ editorRef }) {
useFrame(() => {
const prefab = editorRef.current!.prefab;
const newRoot = updateNodeById(prefab.root, "ball", node => ({
...node,
components: {
...node.components,
transform: {
...node.components!.transform!,
properties: { ...node.components!.transform!.properties, position: [x, y, z] }
}
}
}));
editorRef.current!.setPrefab({ ...prefab, root: newRoot });
});
return null;
}
function Scene() {
const editorRef = useRef(null);
return (
<PrefabEditor ref={editorRef} initialPrefab={data}>
<Animator editorRef={editorRef} />
</PrefabEditor>
);
}Custom Component
自定义组件
tsx
import { Component, registerComponent, FieldRenderer } from 'react-three-game';
const MyComponent: Component = {
name: 'MyComponent',
Editor: ({ component, onUpdate }) => (
<FieldRenderer fields={[{ name: 'speed', type: 'number', step: 0.1 }]} values={component.properties} onChange={onUpdate} />
),
View: ({ properties, children }) => <group>{children}</group>,
defaultProperties: { speed: 1 }
};
registerComponent(MyComponent);Field types: , , , , , ,
vector3numberstringcolorbooleanselectcustomtsx
import { Component, registerComponent, FieldRenderer } from 'react-three-game';
const MyComponent: Component = {
name: 'MyComponent',
Editor: ({ component, onUpdate }) => (
<FieldRenderer fields={[{ name: 'speed', type: 'number', step: 0.1 }]} values={component.properties} onChange={onUpdate} />
),
View: ({ properties, children }) => <group>{children}</group>,
defaultProperties: { speed: 1 }
};
registerComponent(MyComponent);字段类型:、、、、、、
vector3numberstringcolorbooleanselectcustomGame Events
游戏事件
A general-purpose event system for game-wide communication. Handles physics events, gameplay events, and any custom events.
用于游戏全局通信的通用事件系统。处理物理事件、游戏玩法事件及任何自定义事件。
Core API
核心API
tsx
import { gameEvents, useGameEvent } from 'react-three-game';
// Emit events
gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
gameEvents.emit('score:change', { delta: 100, total: 500 });
// Subscribe (React hook - auto cleanup on unmount)
useGameEvent('player:death', (payload) => {
showGameOver(payload.cause);
}, []);
// Subscribe (manual - returns unsubscribe function)
const unsub = gameEvents.on('score:change', (payload) => {
updateUI(payload.total);
});
unsub(); // cleanuptsx
import { gameEvents, useGameEvent } from 'react-three-game';
// 触发事件
gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
gameEvents.emit('score:change', { delta: 100, total: 500 });
// 订阅(React钩子 - 组件卸载时自动清理)
useGameEvent('player:death', (payload) => {
showGameOver(payload.cause);
}, []);
// 订阅(手动方式 - 返回取消订阅函数)
const unsub = gameEvents.on('score:change', (payload) => {
updateUI(payload.total);
});
unsub(); // 清理Built-in Physics Events
内置物理事件
Physics components automatically emit these events:
| Event | When | Payload |
|---|---|---|
| Something enters a sensor collider | |
| Something exits a sensor collider | |
| A collision starts | |
| A collision ends | |
Collision filtering: By default, kinematic/fixed bodies don't detect each other. For kinematic sensors or projectiles to detect walls/floors, add to the Physics properties.
"activeCollisionTypes": "all"See Advanced Physics for sensor setup and collision handling patterns.
Physics组件会自动触发以下事件:
| 事件 | 触发时机 | 负载 |
|---|---|---|
| 物体进入传感器碰撞体时 | |
| 物体离开传感器碰撞体时 | |
| 碰撞开始时 | |
| 碰撞结束时 | |
碰撞过滤:默认情况下,运动学/固定物体不会互相检测。若要让运动学传感器或抛射物检测墙壁/地面,需在Physics属性中添加。
"activeCollisionTypes": "all"查看高级物理指南了解传感器设置和碰撞处理模式。
TypeScript: Typed Custom Events
TypeScript:类型化自定义事件
Extend for type-safe custom events:
GameEventMaptypescript
declare module 'react-three-game' {
interface GameEventMap {
'player:death': { playerId: string; cause: string };
'score:change': { delta: number; total: number };
'level:complete': { levelId: number; time: number };
}
}扩展以实现类型安全的自定义事件:
GameEventMaptypescript
declare module 'react-three-game' {
interface GameEventMap {
'player:death': { playerId: string; cause: string };
'score:change': { delta: number; total: number };
'level:complete': { levelId: number; time: number };
}
}Common Patterns
通用模式
tsx
// Gameplay controller
function GameController() {
const [score, setScore] = useState(0);
useGameEvent('score:change', ({ total }) => setScore(total), []);
useGameEvent('player:death', () => setGameOver(true), []);
return <ScoreUI score={score} />;
}
// Pickup system
useGameEvent('sensor:enter', (payload) => {
if (payload.sourceEntityId.startsWith('coin-')) {
gameEvents.emit('score:change', { delta: 10, total: score + 10 });
removeEntity(payload.sourceEntityId);
}
}, [score]);tsx
// 游戏玩法控制器
function GameController() {
const [score, setScore] = useState(0);
useGameEvent('score:change', ({ total }) => setScore(total), []);
useGameEvent('player:death', () => setGameOver(true), []);
return <ScoreUI score={score} />;
}
// 拾取系统
useGameEvent('sensor:enter', (payload) => {
if (payload.sourceEntityId.startsWith('coin-')) {
gameEvents.emit('score:change', { delta: 10, total: score + 10 });
removeEntity(payload.sourceEntityId);
}
}, [score]);