evomeme-dev 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -0
- package/bin/evomeme +28 -0
- package/dist/canvas-public/app.js +5857 -0
- package/dist/canvas-public/cycle-detect.js +166 -0
- package/dist/canvas-public/index.html +395 -0
- package/dist/canvas-public/sequence-sheet.js +59 -0
- package/dist/canvas-public/smart-split.js +185 -0
- package/dist/canvas-public/styles.css +2276 -0
- package/dist/canvas-public/vendor/painterro.min.js +3 -0
- package/dist/index.js +25586 -0
- package/package.json +69 -0
- package/skills/evomeme-cloud-fs/SKILL.md +176 -0
- package/skills/evomeme-cloud-fs/agents/openai.yaml +7 -0
- package/skills/evomeme-cover/SKILL.md +132 -0
- package/skills/evomeme-cover/agents/openai.yaml +7 -0
- package/skills/evomeme-game-maker/SKILL.md +214 -0
- package/skills/evomeme-game-maker/agents/openai.yaml +4 -0
- package/skills/evomeme-login/SKILL.md +92 -0
- package/skills/evomeme-login/agents/openai.yaml +7 -0
- package/skills/evomeme-native-bridge-sdk/SKILL.md +286 -0
- package/skills/evomeme-native-bridge-sdk/agents/openai.yaml +7 -0
- package/skills/evomeme-tos-upload/SKILL.md +46 -0
- package/skills/evomeme-tos-upload/agents/openai.yaml +4 -0
- package/skills/game-art/SKILL.md +121 -0
- package/skills/game-art/agents/openai.yaml +7 -0
- package/skills/game-asset-image-generator/SKILL.md +104 -0
- package/skills/game-asset-image-generator/agents/openai.yaml +7 -0
- package/skills/game-asset-image-generator/references/background.md +32 -0
- package/skills/game-asset-image-generator/references/static-game-assets.md +38 -0
- package/skills/game-audio/SKILL.md +82 -0
- package/skills/game-audio/agents/openai.yaml +4 -0
- package/skills/game-audio/references/bgm-cdn.md +127 -0
- package/skills/game-audio/references/sfx-cdn.md +77 -0
- package/skills/game-demo/SKILL.md +103 -0
- package/skills/game-demo/agents/openai.yaml +7 -0
- package/skills/game-maker/SKILL.md +111 -0
- package/skills/game-maker/agents/openai.yaml +4 -0
- package/skills/game-test/SKILL.md +58 -0
- package/skills/game-test/agents/openai.yaml +7 -0
- package/skills/game-test/references/checklists/playtest-qa.md +12 -0
- package/skills/game-ui/SKILL.md +169 -0
- package/skills/game-ui/agents/openai.yaml +4 -0
- package/skills/game-ui/references/checklists/game-ui-quality.md +14 -0
- package/skills/game-ui/references/checklists/hud-readability.md +12 -0
- package/skills/game-ui/references/checklists/mobile-input.md +12 -0
- package/skills/game-ui/references/checklists/responsive-ui-fit.md +12 -0
- package/skills/game-ui/references/ui-patterns.md +140 -0
- package/skills/game-ui/references/ui-prompt-templates.md +63 -0
- package/skills/phaser-2d-game/SKILL.md +127 -0
- package/skills/phaser-2d-game/agents/openai.yaml +7 -0
- package/skills/phaser-2d-game/references/engine-performance.md +64 -0
- package/skills/phaser-2d-game/references/engine-physics.md +139 -0
- package/skills/phaser-2d-game/references/engine-runtime.md +129 -0
- package/skills/phaser-2d-game/references/engine-spritesheets.md +99 -0
- package/skills/phaser-2d-game/references/engine-tilemaps.md +80 -0
- package/skills/phaser-2d-game/references/file-structure.md +94 -0
- package/skills/phaser-2d-graphics-builder/SKILL.md +68 -0
- package/skills/phaser-2d-graphics-builder/agents/openai.yaml +7 -0
- package/skills/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-1.jpeg +0 -0
- package/skills/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-2.jpeg +0 -0
- package/skills/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-3.jpg +0 -0
- package/skills/phaser-2d-graphics-builder/references/checklists/compose-quality.md +10 -0
- package/skills/phaser-2d-graphics-builder/references/checklists/performance-safe-visual-detail.md +9 -0
- package/skills/phaser-2d-graphics-builder/references/checklists/premium-game-quality-gate.md +15 -0
- package/skills/phaser-2d-graphics-builder/references/checklists/sprite-quality.md +10 -0
- package/skills/phaser-2d-graphics-builder/references/checklists/technical-art-quality.md +9 -0
- package/skills/phaser-2d-graphics-builder/references/checklists/visual-scorecard.md +9 -0
- package/skills/phaser-2d-graphics-builder/references/compose-recipes.md +71 -0
- package/skills/phaser-2d-graphics-builder/references/graphics-prompt-templates.md +33 -0
- package/skills/phaser-2d-graphics-builder/references/implementation-blueprint.md +82 -0
- package/skills/phaser-2d-graphics-builder/references/sprite-recipes.md +90 -0
- package/skills/phaser-2d-graphics-builder/references/technical-art.md +67 -0
- package/skills/phaser-2d-graphics-builder/references/vfx-recipes.md +67 -0
- package/skills/phaser-2d-graphics-builder/references/visual-scorecard.md +129 -0
- package/skills/threejs-3d-game/SKILL.md +113 -0
- package/skills/threejs-3d-game/agents/openai.yaml +7 -0
- package/skills/threejs-3d-game/references/engine-asset-pipeline.md +116 -0
- package/skills/threejs-3d-game/references/engine-audio.md +70 -0
- package/skills/threejs-3d-game/references/engine-input-camera.md +84 -0
- package/skills/threejs-3d-game/references/engine-performance.md +96 -0
- package/skills/threejs-3d-game/references/engine-physics.md +142 -0
- package/skills/threejs-3d-game/references/engine-runtime.md +199 -0
- package/skills/threejs-3d-game/references/file-structure.md +353 -0
- package/skills/threejs-aaa-graphics-builder/SKILL.md +62 -0
- package/skills/threejs-aaa-graphics-builder/agents/openai.yaml +7 -0
- package/skills/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-1.jpg +0 -0
- package/skills/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-2.jpg +0 -0
- package/skills/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-3.jpg +0 -0
- package/skills/threejs-aaa-graphics-builder/references/checklists/aaa-game-quality-gate.md +23 -0
- package/skills/threejs-aaa-graphics-builder/references/checklists/aaa-visual-scorecard.md +19 -0
- package/skills/threejs-aaa-graphics-builder/references/checklists/material-lighting-quality.md +14 -0
- package/skills/threejs-aaa-graphics-builder/references/checklists/performance-safe-visual-detail.md +14 -0
- package/skills/threejs-aaa-graphics-builder/references/checklists/procedural-model-quality.md +15 -0
- package/skills/threejs-aaa-graphics-builder/references/checklists/technical-art-quality.md +17 -0
- package/skills/threejs-aaa-graphics-builder/references/graphics-prompt-templates.md +236 -0
- package/skills/threejs-aaa-graphics-builder/references/implementation-blueprint.md +161 -0
- package/skills/threejs-aaa-graphics-builder/references/model-recipes.md +146 -0
- package/skills/threejs-aaa-graphics-builder/references/render-recipes.md +90 -0
- package/skills/threejs-aaa-graphics-builder/references/shader-cookbook.md +292 -0
- package/skills/threejs-aaa-graphics-builder/references/technical-art.md +167 -0
- package/skills/threejs-aaa-graphics-builder/references/visual-scorecard.md +147 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 引擎:性能
|
|
2
|
+
|
|
3
|
+
卡顿、掉帧、生成尖峰、移动端低帧时加载。
|
|
4
|
+
|
|
5
|
+
画像流程与验收证据见 `../game-test`;AAA 渲染预算 / LOD 美术侧见 `../threejs-aaa-graphics-builder/references/technical-art.md`。本文件只管**实现期引擎约束**。
|
|
6
|
+
|
|
7
|
+
## 优先做的几件事
|
|
8
|
+
|
|
9
|
+
1. **对象池**:子弹、拾取、碎片、短效 VFX 载体 — 热路径禁止狂 `new` / `dispose` 网格
|
|
10
|
+
2. **共享资源**:重复件用 `InstancedMesh` 或共享 geometry / material / texture
|
|
11
|
+
3. **DPR 上限**:默认 cap `2`(见 `engine-runtime.md` Canvas 配置);移动端可再降
|
|
12
|
+
4. **热路径零分配**:`useFrame` / simulation 不每帧 `{}` / `new Vector3`;复用临时向量
|
|
13
|
+
5. **物理先减成本**:简单 collider、sleeping、少动态体、窄 sensor;CCD 只给需要的 body(见 `engine-physics.md`)
|
|
14
|
+
|
|
15
|
+
## 渲染侧
|
|
16
|
+
|
|
17
|
+
| 手段 | 何时 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `InstancedMesh` | 车道标、螺栓、窗户、草地、碎屑、重复道具 |
|
|
20
|
+
| 共享 geometry/material | clone 实体时勿各自 clone 材质链 |
|
|
21
|
+
| 视锥 / 距离剔除 | 远景与屏外动态体 |
|
|
22
|
+
| `LOD` | 背景件、密集世界套件 |
|
|
23
|
+
| 阴影减负 | 少 caster、小 shadow map、接触阴影/blob 替代 |
|
|
24
|
+
| 后处理限流 | 只保留可读性必需的 pass |
|
|
25
|
+
| 贴图纪律 | 尺寸可控、复用、mipmap;避免同屏大量唯一大贴图 |
|
|
26
|
+
|
|
27
|
+
优化顺序:**先查 DPR / post / shadow / 实例化 / 剔除**,再砍视觉密度;不要一上来删英雄件可读性。
|
|
28
|
+
|
|
29
|
+
用户向性能以 **production preview**(`pnpm build` + preview)为准,勿用 dev server 帧率当证据。
|
|
30
|
+
|
|
31
|
+
## 对象池(3D)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// 示意:池内持有已构建的 Object3D + 可选物理 body 句柄
|
|
35
|
+
function acquireBullet(): BulletView {
|
|
36
|
+
const b = pool.pop() ?? createBulletView();
|
|
37
|
+
b.root.visible = true;
|
|
38
|
+
// 启用 collider / 重置 transform
|
|
39
|
+
return b;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function releaseBullet(b: BulletView): void {
|
|
43
|
+
b.root.visible = false;
|
|
44
|
+
// 停 mixer、关 body、摘监听;不要 dispose 共享几何
|
|
45
|
+
pool.push(b);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
重开关卡:归还池内全部活跃实例,再清未池化的关卡专属资源。
|
|
50
|
+
|
|
51
|
+
## CPU 与降频
|
|
52
|
+
|
|
53
|
+
- 移动 / 相机 / mixer 每帧;AI、寻路、远距清理可 5–10Hz。
|
|
54
|
+
- 多个 `AnimationMixer` 只更新可见 / 近距实体。
|
|
55
|
+
- DOM HUD 用事件/store 推送,避免每帧强制 React 重渲染整棵树。
|
|
56
|
+
|
|
57
|
+
## 物理成本(摘要)
|
|
58
|
+
|
|
59
|
+
- 视觉 mesh ≠ 碰撞体;用原始体 / 复合体。
|
|
60
|
+
- 屏外或休眠体可 sleeping;窄化 sensor。
|
|
61
|
+
- 诊断里暴露 body / collider / CCD / sensor 计数(见 runtime 钩子)。
|
|
62
|
+
|
|
63
|
+
## 内存与切关
|
|
64
|
+
|
|
65
|
+
- 换关 dispose:本关独有几何、材质、贴图、render target、mixer、物理 body。
|
|
66
|
+
- 公共材质库 / 共享 atlas 可留。
|
|
67
|
+
- 音频资源清理见 `engine-audio.md`。
|
|
68
|
+
|
|
69
|
+
## 诊断字段(实现侧)
|
|
70
|
+
|
|
71
|
+
在 `__THREE_GAME_DIAGNOSTICS__` 中尽量提供:
|
|
72
|
+
|
|
73
|
+
- `renderer.info`:`render.calls`、`triangles`、`memory.geometries`、`memory.textures`
|
|
74
|
+
- DPR、阴影/后处理开关摘要
|
|
75
|
+
- 实体计数、池大小、活跃 mixer 数
|
|
76
|
+
- 物理(若有):engine、timestep、bodies、colliders、sensors、ccdBodies
|
|
77
|
+
|
|
78
|
+
## 快检表
|
|
79
|
+
|
|
80
|
+
| 症状 | 常因 | 处理 |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| 越玩越慢 | 未 dispose / 订阅叠加 | 查 restart 与池归还 |
|
|
83
|
+
| 周期性顿 | GC | 池化、减 `useFrame` 分配 |
|
|
84
|
+
| 一直低帧 | draw call / 三角面 / DPR | 实例化、LOD、cap DPR、减 shadow |
|
|
85
|
+
| 生成尖峰 | 当场 new 网格 | 预池 / 预加载 |
|
|
86
|
+
| 物理卡 | body/接触过多 | 简 collider、sleeping、减动态体 |
|
|
87
|
+
| 导入后暴跌 | 重模/大贴图 | 降面、缩贴图(生成侧 + 导入体检) |
|
|
88
|
+
|
|
89
|
+
## 引擎反模式
|
|
90
|
+
|
|
91
|
+
- 在未量测前先砍美术细节
|
|
92
|
+
- 用 dev server FPS 当发布性能证据
|
|
93
|
+
- 每帧 new 几何 / 材质 / `Audio`
|
|
94
|
+
- 大量唯一 mesh 本可用 `InstancedMesh`
|
|
95
|
+
- 精细导入 mesh 当碰撞体
|
|
96
|
+
- 移动端不 cap DPR
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# 引擎:物理与碰撞
|
|
2
|
+
|
|
3
|
+
Three.js / R3F **只负责渲染**;物理是并行世界,固定步长推进后,在一处把 body 变换同步到 `Object3D`。
|
|
4
|
+
|
|
5
|
+
添加/更换物理、载具、弹珠、刚体、传感器、高速碰撞时加载本文件。玩法向说明可与 `../game-designer/references/physics-engine-selection.md` 对照;**实现以本文件 + `file-structure.md` 分层为准**。
|
|
6
|
+
|
|
7
|
+
## 选型阶梯
|
|
8
|
+
|
|
9
|
+
1. **自定义碰撞**:街机触发、拾取、车道、子弹、简单 overlap、确定性轨道;手感靠作者而非仿真。
|
|
10
|
+
2. **Rapier**(`@dimforge/rapier3d-compat`):默认严肃 3D 物理——刚体、传感器、多接触、高速、球类、斜坡、移动平台、可靠浏览器/移动表现。
|
|
11
|
+
3. **cannon-es**:小规模刚体、希望避开 WASM 时的轻量 JS 回退。
|
|
12
|
+
4. **Jolt**:明确需要其特性且能接受集成成本时再考虑。
|
|
13
|
+
5. **Ammo.js**:仅存量项目或强依赖时;新项目不要默认。
|
|
14
|
+
6. **Matter.js**:仅 2D 玩法(即便用 Three 渲染)。
|
|
15
|
+
|
|
16
|
+
多数需要「真物理」的新 3D 项目选 Rapier。
|
|
17
|
+
|
|
18
|
+
## 按类型速查
|
|
19
|
+
|
|
20
|
+
| 用自定义 | 用 Rapier |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| 无尽跑酷、车道躲避、简单射击、拾取门 | 迷你高尔夫、台球、弹珠、弹球、物理谜题 |
|
|
23
|
+
| 脚本化障碍、简单球/盒 overlap | 平台、移动平台、斜坡、角色控制器 |
|
|
24
|
+
| 手感定制的赛车护栏 | 碎片堆、箱子、高速需 CCD 的物体 |
|
|
25
|
+
| | 大量 sensor / 碰撞事件 |
|
|
26
|
+
|
|
27
|
+
避免用精细视觉 mesh 做碰撞:用原始体、复合体、凸包;静态关卡几何才考虑简化三角网。生成 GLB 必须另做 **collision proxy**。
|
|
28
|
+
|
|
29
|
+
## 本项目中的所有权
|
|
30
|
+
|
|
31
|
+
| 事项 | 落点 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| World / body / collider 创建与销毁 | `game/simulation` 下物理模块,或明确的 `PhysicsWorld` 门面 |
|
|
34
|
+
| 固定步长 `step` | 模拟更新(由 `useFrame` 调进 simulation) |
|
|
35
|
+
| 视觉同步 | `useFrame` 内**唯一**一处:body → `Object3D` |
|
|
36
|
+
| Debug 画 collider | `game/three` 或诊断层;发布关闭 |
|
|
37
|
+
|
|
38
|
+
禁止:在渲染组件里 `createRigidBody`;多处写 `mesh.position` 与 body 抢所有权;重启后残留 body。
|
|
39
|
+
|
|
40
|
+
## 更新顺序(含物理)
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
input intents → fixed physics step → game state / collision events → VFX / camera / UI → render
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Rapier 接入模式
|
|
47
|
+
|
|
48
|
+
安装(本仓库包管理为 pnpm):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm add @dimforge/rapier3d-compat
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
初始化一次后再开玩法:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import RAPIER from '@dimforge/rapier3d-compat';
|
|
58
|
+
|
|
59
|
+
await RAPIER.init();
|
|
60
|
+
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
固定步长累加器:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const fixedDt = 1 / 60;
|
|
67
|
+
let accumulator = 0;
|
|
68
|
+
|
|
69
|
+
function stepPhysics(deltaSeconds: number) {
|
|
70
|
+
accumulator += Math.min(deltaSeconds, 0.1);
|
|
71
|
+
while (accumulator >= fixedDt) {
|
|
72
|
+
world.timestep = fixedDt;
|
|
73
|
+
world.step();
|
|
74
|
+
accumulator -= fixedDt;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
刚体 + 碰撞体 + 同步:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const body = world.createRigidBody(
|
|
83
|
+
RAPIER.RigidBodyDesc.dynamic()
|
|
84
|
+
.setTranslation(0, 2, 0)
|
|
85
|
+
.setLinearDamping(0.25)
|
|
86
|
+
.setAngularDamping(0.5),
|
|
87
|
+
);
|
|
88
|
+
world.createCollider(
|
|
89
|
+
RAPIER.ColliderDesc.ball(0.5).setRestitution(0.6).setFriction(0.4),
|
|
90
|
+
body,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// 唯一同步点
|
|
94
|
+
const t = body.translation();
|
|
95
|
+
const r = body.rotation();
|
|
96
|
+
mesh.position.set(t.x, t.y, t.z);
|
|
97
|
+
mesh.quaternion.set(r.x, r.y, r.z, r.w);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
高速开启 CCD(有成本,只给需要的 body):
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
RAPIER.RigidBodyDesc.dynamic().setCcdEnabled(true);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
传感器 / 触发器:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
RAPIER.ColliderDesc.ball(1)
|
|
110
|
+
.setSensor(true)
|
|
111
|
+
.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 实现规则
|
|
115
|
+
|
|
116
|
+
- 移动平台 / 脚本障碍用 **kinematic** body,视觉与物理一起动。
|
|
117
|
+
- 拾取、终点、检查点、伤害区用 **sensor**。
|
|
118
|
+
- 角色控制器优先胶囊、锁旋转、运动学移动(除非要布娃娃)。
|
|
119
|
+
- 街机载具:不要只靠裸刚体;控制逻辑 + 碰撞响应组合。
|
|
120
|
+
- 使用 sleeping,重启时移除/dispose body,避免陈旧仿真。
|
|
121
|
+
- 调参单位与场景尺度一致;显式设摩擦、恢复、阻尼、质量/密度、重力缩放、碰撞组。
|
|
122
|
+
- Debug:body/collider 计数、接触对、速度;发布构建关闭。
|
|
123
|
+
|
|
124
|
+
## 验证(物理改动)
|
|
125
|
+
|
|
126
|
+
- `pnpm build` / 类型检查
|
|
127
|
+
- 真实输入改变 body 状态
|
|
128
|
+
- 碰撞 / 触发路径通
|
|
129
|
+
- 重启无残留 body
|
|
130
|
+
- 高速不隧道穿模
|
|
131
|
+
- 低帧尖峰下仿真不崩
|
|
132
|
+
- 诊断中记录:引擎、步长、body/collider 数、CCD、sensor、危险 collider
|
|
133
|
+
|
|
134
|
+
## 常见失败
|
|
135
|
+
|
|
136
|
+
- 变步长物理 → 手感漂
|
|
137
|
+
- 视觉与物理多处同步 → 漂移
|
|
138
|
+
- 重启残留 body
|
|
139
|
+
- 用导入 mesh 碰撞 → 慢且错
|
|
140
|
+
- 高速无 CCD → 穿透
|
|
141
|
+
- Sensor 未开 active events
|
|
142
|
+
- Kinematic 平台只动了 mesh 没动物理
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# 引擎运行时(R3F / Three.js)
|
|
2
|
+
|
|
3
|
+
本文件为 R3F / Three.js **引擎层**约定(见 `file-structure.md`)。不含策划、关卡、手感 juice 配方。
|
|
4
|
+
|
|
5
|
+
写/改运行时、Canvas/gl 默认、循环、生命周期、白屏/双循环、dispose、诊断钩子、R3F 加载边界时加载本文件。
|
|
6
|
+
|
|
7
|
+
## 目录与所有权映射
|
|
8
|
+
|
|
9
|
+
| 职责 | 本项目落点 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| DOM 启动、CSS | `main.tsx` / `App.tsx` / `index.css` |
|
|
12
|
+
| 渲染器、Canvas、像素比 | `ui/components/GameCanvas.tsx`(R3F `<Canvas>`) |
|
|
13
|
+
| 帧循环编排 | `GameCanvas` 的 `useFrame`(勿另起 `requestAnimationFrame`) |
|
|
14
|
+
| 输入采集 → 语义动作 | `game/input` |
|
|
15
|
+
| 规则 / 状态 / 固定步物理 | `game/simulation`(无 Three / React) |
|
|
16
|
+
| 相机、世界网格、mixer | `game/three/*`(无 React)+ `useFrame` 同步 |
|
|
17
|
+
| UI 桥 | `game/bridge` + `src/ui` |
|
|
18
|
+
| 资产键 / 加载 | `game/assets` + `game/three` 加载助手 |
|
|
19
|
+
| 诊断 / 测试钩子 | `GameCanvas` 挂载安装,卸载清理 |
|
|
20
|
+
|
|
21
|
+
## Canvas / 渲染器默认
|
|
22
|
+
|
|
23
|
+
**禁止**在 `GameCanvas` 外再 `new THREE.WebGLRenderer`。唯一 WebGL 入口是 R3F `<Canvas>`。
|
|
24
|
+
|
|
25
|
+
推荐默认(可按项目微调,但须显式设定,勿依赖隐式):
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Canvas } from '@react-three/fiber';
|
|
29
|
+
import * as THREE from 'three';
|
|
30
|
+
|
|
31
|
+
<Canvas
|
|
32
|
+
shadows
|
|
33
|
+
dpr={[1, 2]} // 像素比上限 2
|
|
34
|
+
gl={{
|
|
35
|
+
antialias: true,
|
|
36
|
+
alpha: false,
|
|
37
|
+
powerPreference: 'high-performance',
|
|
38
|
+
outputColorSpace: THREE.SRGBColorSpace,
|
|
39
|
+
toneMapping: THREE.ACESFilmicToneMapping,
|
|
40
|
+
toneMappingExposure: 1.05,
|
|
41
|
+
}}
|
|
42
|
+
camera={{ fov: 50, near: 0.1, far: 500, position: [0, 5, 10] }}
|
|
43
|
+
>
|
|
44
|
+
{/* World + useFrame 编排 */}
|
|
45
|
+
</Canvas>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
规则:
|
|
49
|
+
|
|
50
|
+
- resize / aspect 交给 R3F;勿用 CSS `transform: scale` 假装缩放画布。
|
|
51
|
+
- 阴影:`shadows` + 灯光 `castShadow`;移动端可关或减 cascade(见 `engine-performance.md`)。
|
|
52
|
+
- 色域与 tone mapping 与导入 PBR 一致;过暗/过亮先查这里与环境光,再改资产。
|
|
53
|
+
- 更深的光影/后处理配方属 `threejs-aaa-graphics-builder`,本文件只定启动默认。
|
|
54
|
+
|
|
55
|
+
## 帧更新顺序
|
|
56
|
+
|
|
57
|
+
每帧保持显式顺序(R3F 下由 `useFrame` 编排,渲染交给 R3F):
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
input intents
|
|
61
|
+
→ fixed physics(若有,累加器)
|
|
62
|
+
→ gameplay / simulation systems
|
|
63
|
+
→ animation / mixer / VFX sockets
|
|
64
|
+
→ camera
|
|
65
|
+
→ UI bridge(emit 事实 / 写 store)
|
|
66
|
+
→(R3F 自动 render;除非离屏否则不要手调 renderer.render)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
规则:
|
|
70
|
+
|
|
71
|
+
- `useFrame` 只编排,不写核心胜负/战斗规则。
|
|
72
|
+
- **禁止**第二个活跃的 `requestAnimationFrame` 游戏循环与 R3F 抢渲染。
|
|
73
|
+
- 帧间隔按秒使用;对休眠/卡顿做 clamp(脚手架常用上限约 `0.05`–`0.1` 秒)。
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// useFrame 内:delta 已是秒;仍建议 clamp
|
|
77
|
+
const dt = Math.min(state.clock.getDelta(), 0.05);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `useFrame` 优先级(建议)
|
|
81
|
+
|
|
82
|
+
R3F:`useFrame(fn, priority)` 中 **priority 越大越早执行**;默认 `0`;**负数在默认渲染之后**执行。
|
|
83
|
+
|
|
84
|
+
| priority | 内容 |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `> 0`(如 `1`) | 输入采样 → 固定步物理 → simulation |
|
|
87
|
+
| `0`(默认) | `Object3D` 同步、mixer、VFX socket、相机跟随 |
|
|
88
|
+
| `< 0` | 仅诊断采样 / 需读完帧结果的钩子(可选) |
|
|
89
|
+
|
|
90
|
+
同一职责不要注册多个互相抢写的 `useFrame`。物理固定步细节见 `engine-physics.md`。
|
|
91
|
+
|
|
92
|
+
## R3F 政策
|
|
93
|
+
|
|
94
|
+
### 加载边界
|
|
95
|
+
|
|
96
|
+
- **正式游戏资产**(GLB/贴图键、失败占位、manifest):放在 `game/assets` + `game/three` 加载助手;由 `GameCanvas` / 世界根触发一次加载。
|
|
97
|
+
- `useLoader` / `<Suspense>`:仅用于明确的挂载边界原型或局部辅助;**不要**在每个实体组件里各自 `useLoader` 同一 URL 却无统一缓存/dispose 策略。
|
|
98
|
+
- 业务代码不硬编码 glTF URL;走 manifest / `config.json`。
|
|
99
|
+
|
|
100
|
+
### `@react-three/drei` 白名单
|
|
101
|
+
|
|
102
|
+
| 可用(谨慎) | 禁止作正式方案 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| 开发向 Helpers、轻量环境/HDR 辅助(可关) | `<Html>` 堆正式 HUD / 菜单 / 设置 |
|
|
105
|
+
| 明确需要的 `OrbitControls`(编辑/观察关,玩法关须可关) | 用 drei 控件绕过 `game/input` 语义动作 |
|
|
106
|
+
| | 用 Canvas 内 DOM 替代 `src/ui` |
|
|
107
|
+
|
|
108
|
+
正式 HUD、虚拟摇杆、设置面板 → React DOM(`src/ui`)。
|
|
109
|
+
|
|
110
|
+
### 其他
|
|
111
|
+
|
|
112
|
+
- `game/three/*` **禁止** `import` React / R3F hooks。
|
|
113
|
+
- StrictMode 双挂载:订阅与 WebGL 资源创建必须幂等可清理。
|
|
114
|
+
|
|
115
|
+
## 首个可运行切片(引擎最小集)
|
|
116
|
+
|
|
117
|
+
引擎侧先具备这些,再谈内容密度:
|
|
118
|
+
|
|
119
|
+
1. Canvas + 场景根 + resize / 相机 aspect(含上节 gl/dpr 默认)
|
|
120
|
+
2. 单一更新路径(`useFrame`)
|
|
121
|
+
3. 输入 → 语义动作 → 模拟一步
|
|
122
|
+
4. 至少一个可动实体的 `Object3D` 同步
|
|
123
|
+
5. 碰撞或触发的最小检测(自定义或物理)
|
|
124
|
+
6. 失败/重开时的资源与监听清理
|
|
125
|
+
7. 诊断钩子(见下)
|
|
126
|
+
|
|
127
|
+
拒绝:只能看不能控;重开后残留实体/监听/物理体;多循环互抢。
|
|
128
|
+
|
|
129
|
+
## 生命周期与 dispose
|
|
130
|
+
|
|
131
|
+
StrictMode 会双挂载;卸载必须可重复执行且幂等。
|
|
132
|
+
|
|
133
|
+
必须清理:
|
|
134
|
+
|
|
135
|
+
- 几何、材质、纹理(含材质上挂的 map)
|
|
136
|
+
- `AnimationMixer` 与 clip actions
|
|
137
|
+
- 物理 world / body / collider
|
|
138
|
+
- `gameBus` 订阅、DOM / window 监听
|
|
139
|
+
- 诊断与测试钩子(`window.__THREE_GAME_*`)
|
|
140
|
+
- 局内音频 loop / 短效(见 `engine-audio.md`)
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import * as THREE from 'three';
|
|
144
|
+
|
|
145
|
+
export function disposeObject3D(root: THREE.Object3D): void {
|
|
146
|
+
root.traverse((object) => {
|
|
147
|
+
const mesh = object as THREE.Mesh;
|
|
148
|
+
if (mesh.geometry) mesh.geometry.dispose();
|
|
149
|
+
const materials = Array.isArray(mesh.material)
|
|
150
|
+
? mesh.material
|
|
151
|
+
: mesh.material
|
|
152
|
+
? [mesh.material]
|
|
153
|
+
: [];
|
|
154
|
+
for (const material of materials) {
|
|
155
|
+
for (const value of Object.values(material as unknown as Record<string, unknown>)) {
|
|
156
|
+
if (value && typeof value === 'object' && (value as { isTexture?: boolean }).isTexture) {
|
|
157
|
+
(value as THREE.Texture).dispose();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
material.dispose();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 诊断钩子
|
|
167
|
+
|
|
168
|
+
在 `GameCanvas` 挂载时安装、卸载时清理(类型写在 `vite-env.d.ts`):
|
|
169
|
+
|
|
170
|
+
- `window.__THREE_GAME_DIAGNOSTICS__`:renderer info、模拟快照、输入状态、实体计数、物理体/碰撞体计数(若有)、DPR/阴影摘要
|
|
171
|
+
- `window.__THREE_GAME_TEST_HOOKS__`:`seed` / `setState` / `setPausedForScreenshot` / `setReducedMotion` / `hideDebugUi`
|
|
172
|
+
|
|
173
|
+
确定性随机放 `simulation` 或 `lib/random`;业务路径禁止裸 `Math.random`。
|
|
174
|
+
|
|
175
|
+
## 引擎故障分流(运行时)
|
|
176
|
+
|
|
177
|
+
| 现象 | 优先检查 |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| 白屏 / 空画布 | Canvas DOM 与 CSS 尺寸;WebGL 上下文;是否只有一个循环;相机 near/far/朝向;场景是否有可见物体与灯光;overlay 是否盖住 Canvas;resize 是否更新 aspect;色域/曝光是否把物体「隐掉」 |
|
|
180
|
+
| 能进但不能动 | 输入是否产出意图;模拟是否步进;`Object3D` 是否从模拟同步;是否写错层 |
|
|
181
|
+
| 越玩越卡 / 泄漏 | dispose;是否每帧 new 几何/材质/音源;总线订阅是否叠加 → `engine-performance.md` |
|
|
182
|
+
| 重开异常 | 实体、timer、mixer、物理体、监听、音频是否在 restart 路径清理 |
|
|
183
|
+
| 低帧抖动 | delta clamp;物理是否误用变步长;DPR 是否过高 |
|
|
184
|
+
| 无声 | 是否未 unlock AudioContext → `engine-audio.md` |
|
|
185
|
+
|
|
186
|
+
更细的资产/音频/性能验收清单见 `../game-test`;本文件只保留引擎归属判断。
|
|
187
|
+
|
|
188
|
+
## 引擎反模式
|
|
189
|
+
|
|
190
|
+
- 在 `useFrame` 或 HUD 写规则真相
|
|
191
|
+
- 第二套 rAF 循环 + R3F 双渲染;或额外 `new WebGLRenderer`
|
|
192
|
+
- `game/three` import React
|
|
193
|
+
- 用 `<Html>` 替代正式 DOM HUD
|
|
194
|
+
- 实体组件各自 `useLoader` 且无统一 dispose
|
|
195
|
+
- 视觉 mesh 当碰撞体
|
|
196
|
+
- 多处同步物理→视觉变换
|
|
197
|
+
- 重启不 dispose / 不摘监听
|
|
198
|
+
- 可调参数硬编码,不走 `public/config.json`
|
|
199
|
+
- 业务代码硬编码 glTF/贴图 URL(应走 manifest / config)
|