sapdon 3.2.2 → 3.4.0
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 +255 -121
- package/doc/dev/architecture.md +418 -0
- package/doc/dev/cli.md +467 -0
- package/doc/dev/core.md +751 -0
- package/doc/dev/lr-paradigm.md +85 -0
- package/doc/dev/oc.md +582 -0
- package/doc/dev/workflow.md +257 -0
- package/doc/hello_sapdon/hello_sapdon.md +3 -3
- package/doc/user/api/biome.md +558 -0
- package/doc/user/api/block.md +1530 -0
- package/doc/user/api/entity.md +685 -0
- package/doc/user/api/extra.md +231 -0
- package/doc/user/api/item.md +1125 -0
- package/doc/user/api/neo-guidebook.md +409 -0
- package/doc/user/api/recipe.md +427 -0
- package/doc/user/api/sapdon-ui.md +185 -0
- package/doc/user/api/texture.md +181 -0
- package/doc/user/config/build-config.md +83 -0
- package/doc/user/config/mod-info.md +33 -0
- package/doc/user/faq.md +160 -0
- package/doc/user/quick-start.md +122 -0
- package/doc/user/tutorials/block.md +474 -0
- package/doc/user/tutorials/entity.md +356 -0
- package/doc/user/tutorials/item.md +449 -0
- package/doc/user/tutorials/neo-guidebook-experience.md +381 -0
- package/doc/user/tutorials/neo-guidebook.md +640 -0
- package/doc/user/tutorials/recipe.md +278 -0
- package/doc/user/tutorials/sapdon-ui.md +207 -0
- package/package.json +8 -3
- package/prod/cli/index.js +1 -1
- package/prod/cli/start.js +1 -1458
- package/prod/core/index.d.ts +4564 -2166
- package/prod/core/index.js +1 -59015
- package/prod/core/package.json +7 -0
- package/prod/oc/index.d.ts +350 -108
- package/prod/oc/index.js +1 -1
- package/prod/oc/package.json +7 -0
- package/prod/utils/index.d.ts +20 -9
- package/prod/utils/index.js +1 -1
- package/prod/utils/package.json +7 -0
- package/doc/BlockAPI.md +0 -145
- package/doc/api.md +0 -128
- package/doc/oc/index.md +0 -0
- package/doc/sapdon-ts.md +0 -64
- package/src/templates/js_sapdon/build.config +0 -23
- package/src/templates/js_sapdon/main.mjs +0 -4
- package/src/templates/js_sapdon/mod.info +0 -7
- package/src/templates/js_sapdon/pack_icon.png +0 -0
- package/src/templates/js_sapdon/package.json +0 -20
- package/src/templates/js_sapdon/res/animations/animation_item.animation.json +0 -34
- package/src/templates/js_sapdon/res/animations/large_item.animation.json +0 -27
- package/src/templates/js_sapdon/res/models/blocks/crop.geo.json +0 -48
- package/src/templates/js_sapdon/res/models/entity/animation/animation_item.geo.json +0 -26
- package/src/templates/js_sapdon/res/models/entity/animation/large_item.geo.json +0 -28
- package/src/templates/js_sapdon/res/textures/blocks/none.png +0 -0
- package/src/templates/js_sapdon/res/textures/blocks/test_log_oak.png +0 -0
- package/src/templates/js_sapdon/res/textures/blocks/test_log_top.png +0 -0
- package/src/templates/js_sapdon/res/textures/items/masterball.png +0 -0
- package/src/templates/js_sapdon/scripts/custom_components/cropComponent.js +0 -50
- package/src/templates/js_sapdon/scripts/custom_components/items/gui_book.js +0 -37
- package/src/templates/js_sapdon/scripts/custom_components/registry.js +0 -25
- package/src/templates/js_sapdon/scripts/index.js +0 -0
- package/src/templates/ts_sapdon/build.config +0 -23
- package/src/templates/ts_sapdon/main.ts +0 -11
- package/src/templates/ts_sapdon/mod.info +0 -7
- package/src/templates/ts_sapdon/pack_icon.png +0 -0
- package/src/templates/ts_sapdon/package.json +0 -20
- package/src/templates/ts_sapdon/res/models/blocks/crop.geo.json +0 -48
- package/src/templates/ts_sapdon/res/textures/blocks/test_log_oak.png +0 -0
- package/src/templates/ts_sapdon/res/textures/blocks/test_log_top.png +0 -0
- package/src/templates/ts_sapdon/res/textures/items/masterball.png +0 -0
- package/src/templates/ts_sapdon/scripts/components/cropComponent.ts +0 -44
- package/src/templates/ts_sapdon/scripts/components/items/guiBook.ts +0 -36
- package/src/templates/ts_sapdon/scripts/components/registry.ts +0 -24
- package/src/templates/ts_sapdon/scripts/index.ts +0 -7
- package/src/templates/ts_sapdon/tsconfig.json +0 -117
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# L-R 编程范式(sapdon 存量类 Addon 通用骨架)
|
|
2
|
+
|
|
3
|
+
> 服务对象:`examples/digitCircuit`(数电)、`examples/fluid_pipe`(流体)、`examples/power_grid`(电力)——一个可复用的「装备类/生态类」Addon 编程范式。
|
|
4
|
+
|
|
5
|
+
这三类系统在玩法与数据形态上完全不同,但骨架高度一致:**都走一条"逻辑求解 → 渲染同步"的 L/R 分离流水线**,且都由 **连接(C₁)、源(S)、功能(F)、消费(Cc)、储量(R)** 五元组构成一个可更新的"资源网络"。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 五元组映射
|
|
10
|
+
|
|
11
|
+
| 元组 | digitCircuit 数电 | fluid_pipe 流体 | power_grid 电力 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **连接** C₁ | 导线 Net(逐面 `wire_connect:*` 手臂) | 段 Segment(逐面 `pipe_connect:*`) | 段(正交自动连通) |
|
|
14
|
+
| **源** S | on/off/switch 信号源 | 泵输出(+△) | 燃煤发电机 / 太阳能 |
|
|
15
|
+
| **功能** F | 门 / 分线器 / 合并器 | 阀门 / 三通 | 继电器(可控桥) |
|
|
16
|
+
| **消费** Cc | 显示灯(仅显示) | 罐纯吸收 / 空气汇 | 电力熔炉(真实熔炼) |
|
|
17
|
+
| **储量** R | 寄存器 store / 芯片 | 罐(32 格液位) | 电池(0..MAX 电量) |
|
|
18
|
+
|
|
19
|
+
> 关键洞察:F(功能块)本质是**受控的连接/变换器**——数电的门变换信号、流体的阀透传/断流、电力的继电器合并/分隔电网。绝大多数新生态系统只需把 F 当"可通断的耦合节点"即可起步。
|
|
20
|
+
|
|
21
|
+
## 2. 分层约束(铁的边界)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌───────────────────────────────────────────────────────────┐
|
|
25
|
+
│ L 层 scripts/core/(纯逻辑,禁止 import @minecraft/server)│
|
|
26
|
+
│ graph.ts 建立"连接块图":洪水填充成段/网,收设备端点 │
|
|
27
|
+
│ resource.ts/ settle.ts 资源在场上的传播/归并 + 逐 tick 结算│
|
|
28
|
+
│ → 把结果写进 seg.xxx(powered / front / covered / value) │
|
|
29
|
+
├───────────────────────────────────────────────────────────┤
|
|
30
|
+
│ R 层 scripts/engine/(MC 引擎,读世界方块) │
|
|
31
|
+
│ const/world/state/log/diag │
|
|
32
|
+
│ graph.ts 实现 L 层 FloodGraph 接口(世界实现) │
|
|
33
|
+
│ rebuild.ts 放置/破坏/开关 → 重建段 + 加载后渐进重建 │
|
|
34
|
+
│ render.ts 只读 seg.xxx 同步写方块状态(发光/液位/带电) │
|
|
35
|
+
│ persist.ts 只存小状态(设备表/连接位置),图=重建不落盘 │
|
|
36
|
+
│ tick.ts 主循环:L 结算 → R 渲染 → 设备存活检查 │
|
|
37
|
+
│ index.ts 注册:命令 / 自定义组件 / 事件 / 主循环 │
|
|
38
|
+
└───────────────────────────────────────────────────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
铁律:
|
|
42
|
+
1. **L 层零 MC 依赖** → 可在 Node 直接镜像测试。
|
|
43
|
+
2. **L 写 `seg.*`,R 只读** → 状态单一来源,渲染层可缓存去重。
|
|
44
|
+
3. **连接块手臂/朝向 = 方块状态**(世界自动持久化),**图与传播场不落盘**,靠事件 + 加载后 `rebuildPending` 逐批(每 tick 64 个)渐进洪水重建。
|
|
45
|
+
4. **持久化只存小状态**:设备表(储量 level、源燃料、消费进度)+ 连接位置;液位/流动态只进内存 + 方块状态视觉兜底。
|
|
46
|
+
5. **写动态属性不可吞异常**(吞掉 = 重进世界静默丢存档);用 `loaded` 门闩防启动早期空表覆盖存档。
|
|
47
|
+
|
|
48
|
+
## 3. 资源网络结算模板
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
每 tick(如 20t≈1s):
|
|
52
|
+
1. 连接图(L) : 沿连接块洪水 → 段/网
|
|
53
|
+
2. 场/结算(L) : 若含共享设备的耦合(F/S/R 把邻段并网,F 用受控 open/closed 决定是否并)
|
|
54
|
+
→ 网格级(或势场级)结算:源供给侧 vs 消费需求侧 + 储量缓冲
|
|
55
|
+
→ 写 seg.powered / seg.covered 等
|
|
56
|
+
3. 渲染(R) : 读 seg.* → 写方块状态(电线发光、设备液位/燃烧/带电)
|
|
57
|
+
4. 存量(R) : 充电/放电/喂料扣除 → save(仅结构事件)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
三种结算粒度的取舍:
|
|
61
|
+
- **网格级全局**(power_grid):供/需对账 + 全有/全无。最简、稳压直觉,丢物理空间感。
|
|
62
|
+
- **势/距离场**(fluid_pipe):源势沿图传播、衰减,决定"能不能到、到多高"。物理感强但复杂度高(需构思成本函数)。
|
|
63
|
+
- **布尔/位宽**(digitCircuit):信号值 + 位宽固定点,天然适合布尔逻辑合成。
|
|
64
|
+
|
|
65
|
+
## 4. 镜像测试约束(母子同步)
|
|
66
|
+
|
|
67
|
+
L 层用 TS 写、Node 不能直接跑,因此采用**镜像副本**:`test/<name>.test.mjs` 复制 L 层纯逻辑(JS 版)。**改 L 层逻辑必须同步测试副本并跑绿**。这同时约束了 L 层必须保持"纯函数、可独立 import、无副作用",否则镜像就同步不动。
|
|
68
|
+
|
|
69
|
+
## 5. 可复用工具箱(从三项目沉淀)
|
|
70
|
+
|
|
71
|
+
- `world.ts`:`blockKey / keyParts / getBlockByKey / getAdjacent` 一致性 key(`dim:x,y,z`)。
|
|
72
|
+
- `rebuildAround / rebuildPending / rebuildStale`:结构重建三件套(事件重建 + 加载渐进重建 + 失效重建)。
|
|
73
|
+
- `persist.ts`:动态属性分块读写通用骨架(单块/多块 `_chunks`)。
|
|
74
|
+
- 旋转设备:局部参考系语义面 + `ROT_FACE(facing, local)` 映射(见 fluid_pipe AGENTS),杜绝写死世界面。
|
|
75
|
+
- 诊断:`console.warn` ContentLog + 运行日志开关 + dump 就近转储。
|
|
76
|
+
|
|
77
|
+
## 6. 已知坑(三项目通用)
|
|
78
|
+
|
|
79
|
+
- 方块状态 **≤16 有效值**(整数范围 max-min ≤15)。
|
|
80
|
+
- 单元用普通立方体或命名材质 geo;同一方块所有 material instance 必须同一 `render_method`。
|
|
81
|
+
- Molang 变量名统一小写;粒子 `basic_*` 需传 `variable.direction`。
|
|
82
|
+
- 调试证据优先 ContentLog(`console.warn`),`world.sendMessage` 不进日志。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
> 新生态示例(如"供水系统""机械传动""雨洪管网")建议直接套本范式:先定 L 层 graph+settle 并写镜像测试(绿后进 R),再补 R 层引擎与贴图。
|
package/doc/dev/oc.md
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
# OC 运行时模块文档
|
|
2
|
+
|
|
3
|
+
`src/oc/` 是 Sapdon 的 **OC (Object-Component) 运行时**,是一个轻量级 ECS 风格游戏框架,专门用于 Minecraft Bedrock 版 Script API 环境。作为 `@sapdon/runtime` 发布。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 架构总览
|
|
8
|
+
|
|
9
|
+
OC 模块实现了一个简化的 **Entity-Component 架构**:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌──────────────────────────────────────────────────┐
|
|
13
|
+
│ Minecraft 世界 │
|
|
14
|
+
│ │
|
|
15
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
16
|
+
│ │ Entity A │ │ Entity B │ │
|
|
17
|
+
│ │ │ │ │ │
|
|
18
|
+
│ │ ComponentManager │ │ ComponentManager │ │
|
|
19
|
+
│ │ ├─ HealthComp │ │ ├─ MoveComp │ │
|
|
20
|
+
│ │ ├─ InputComp │ │ ├─ RenderComp │ │
|
|
21
|
+
│ │ └─ HudComp │ │ └─ HealthComp │ │
|
|
22
|
+
│ └──────────────┘ └──────────────┘ │
|
|
23
|
+
│ │
|
|
24
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
25
|
+
│ │ Level (实体集合) │ │
|
|
26
|
+
│ │ └─ Scheduler (驱动 tick 循环) │ │
|
|
27
|
+
│ └────────────────────────────────────────────────┘ │
|
|
28
|
+
└──────────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**核心概念:**
|
|
32
|
+
|
|
33
|
+
| 概念 | 说明 |
|
|
34
|
+
|------|------|
|
|
35
|
+
| **Component** | 附着在实体上的数据+行为单元,每个 Component 有自己的 `onTick()` |
|
|
36
|
+
| **ComponentManager** | 每个实体一个,管理其所有 Component 的生命周期 |
|
|
37
|
+
| **Level** | 实体集合,以 string ID 索引,每个 ID 对应一个 ComponentManager |
|
|
38
|
+
| **Scheduler** | 驱动游戏循环,每 tick 遍历所有实体的所有 Component 调用 `onTick()` |
|
|
39
|
+
| **Optional\<T\>** | Monadic 空值包装器 |
|
|
40
|
+
| **EventEmitter** | 自定义发布/订阅事件系统 |
|
|
41
|
+
|
|
42
|
+
### 目录结构
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
src/oc/
|
|
46
|
+
├── index.ts # 包入口,聚合导出
|
|
47
|
+
├── core.ts # 核心 ECS:Component, ComponentManager, lazyGet, RequireComponents
|
|
48
|
+
├── arch.ts # 引导启动:GameInstance, initialize/finalize
|
|
49
|
+
├── optional.ts # Optional Monad
|
|
50
|
+
├── level.ts # Level + Scheduler 接口
|
|
51
|
+
├── events/
|
|
52
|
+
│ └── index.ts # EventEmitter (自定义事件系统)
|
|
53
|
+
├── input/
|
|
54
|
+
│ └── base.ts # PlayerInputComponent (按键/轴输入)
|
|
55
|
+
├── math/
|
|
56
|
+
│ ├── index.ts # 聚合导出
|
|
57
|
+
│ ├── array.ts # ArrayEx (数组基类)
|
|
58
|
+
│ ├── spline.ts # SplineInterpolator (样条插值)
|
|
59
|
+
│ ├── vec/vec3.ts # Vec3 (3D 向量)
|
|
60
|
+
│ ├── vec/vec4.ts # Vec4 (4D 向量)
|
|
61
|
+
│ └── mat/mat4.ts # Matrix (4x4 矩阵)
|
|
62
|
+
├── action/
|
|
63
|
+
│ ├── interface.ts # ActionValueType, IAction, IActionTrigger, IActionModifier
|
|
64
|
+
│ ├── triggers.ts # ActionTriggers (pressed/released/hold 等)
|
|
65
|
+
│ └── modifiers.ts # ActionModifiers (negate/scale)
|
|
66
|
+
├── ui/hud.ts # HudComponent (基类)
|
|
67
|
+
└── minecraft/ # Minecraft 集成层
|
|
68
|
+
├── index.ts # 聚合导出
|
|
69
|
+
├── core.ts # MinecraftTickingScheduler, MinecraftLevel, MinecraftGameInstance
|
|
70
|
+
├── decorator.ts # @Minecraft, @MinecraftMain, @PlayerSpawned, @EntitySpawned, @SpawnFilter
|
|
71
|
+
├── utils.ts # MinecraftMethod 装饰器, utils 单例
|
|
72
|
+
├── scriptEvent.ts # @ScriptEvent 装饰器 + 事件桥接
|
|
73
|
+
├── input/
|
|
74
|
+
│ └── inputComponent.ts # MinecraftPlayerInputComponent
|
|
75
|
+
├── component/
|
|
76
|
+
│ ├── kinematics.ts # EasyKinematicsComponent (力驱动运动)
|
|
77
|
+
│ └── vehicleComponent.ts # EasyVehicleComponent (载具物理)
|
|
78
|
+
└── ui/
|
|
79
|
+
└── actionbar.ts # PlayerHudComponent (actionbar HUD)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 2. Component 系统 (`core.ts`)
|
|
85
|
+
|
|
86
|
+
### 2.1 接口层级
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Component<Actor> (接口)
|
|
90
|
+
├── onTick(dt: number): void
|
|
91
|
+
├── detach(): void
|
|
92
|
+
├── getManager(): ComponentManager<Actor>
|
|
93
|
+
└── getEntity(): Actor
|
|
94
|
+
|
|
95
|
+
BasicComponent<Actor> (接口,extends Component)
|
|
96
|
+
├── onAttach(manager: ComponentManager<Actor>): void
|
|
97
|
+
└── onDetach(manager: ComponentManager<Actor>): void
|
|
98
|
+
|
|
99
|
+
RequiredComponent<Actor> (接口,extends BasicComponent)
|
|
100
|
+
└── getComponent<T>(ctor): T
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2.2 实现层级
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
CustomComponent<Actor> (抽象类,实现 Component)
|
|
107
|
+
└── BaseComponent<Actor> (类,实现 BasicComponent)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**CustomComponent** 使用 `Symbol` 存储 manager 和 entity 引用:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const REFLECT_MANAGER = Symbol('manager')
|
|
114
|
+
const REFLECT_ENTITY = Symbol('entity')
|
|
115
|
+
|
|
116
|
+
class CustomComponent<Actor> {
|
|
117
|
+
onTick(dt: number) {} // 默认空实现
|
|
118
|
+
detach() { this.getManager()?.update() }
|
|
119
|
+
getManager() { return this[REFLECT_MANAGER] }
|
|
120
|
+
getEntity() { return this[REFLECT_ENTITY] }
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**BaseComponent** 添加空的 `onAttach` / `onDetach` 钩子。
|
|
125
|
+
|
|
126
|
+
### 2.3 Component 生命周期
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
构造 (new Component())
|
|
130
|
+
│
|
|
131
|
+
▼
|
|
132
|
+
onAttach(manager) ← 被 ComponentManager.attachComponent() 调用
|
|
133
|
+
│
|
|
134
|
+
▼
|
|
135
|
+
onTick(dt) ← 每帧由 ComponentManager.handleTicks() 调用
|
|
136
|
+
│ (可被多次调用)
|
|
137
|
+
▼
|
|
138
|
+
detach() / onDetach(manager) ← 被 ComponentManager.detachComponent() 调用
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2.4 ComponentManager
|
|
142
|
+
|
|
143
|
+
每个实体拥有一个 `ComponentManager`,管理 `Map<ComponentCtor, Component>`:
|
|
144
|
+
|
|
145
|
+
| 方法 | 说明 |
|
|
146
|
+
|------|------|
|
|
147
|
+
| `attachComponent(component)` | 附加组件,调用 `onAttach`,处理 `RequiredComponent` 依赖 |
|
|
148
|
+
| `detachComponent(ctor)` | 分离组件,调用 `onDetach` |
|
|
149
|
+
| `getComponent<T>(ctor)` | 获取组件,不存在则报错 |
|
|
150
|
+
| `getComponentUnsafe<T>(ctor)` | 安全获取,返回 `Optional<T>` |
|
|
151
|
+
| `getOrCreate<T>(ctor, ...args)` | 获取或创建 |
|
|
152
|
+
| `has(ctor)` | 检查组件是否存在 |
|
|
153
|
+
| `clear()` | 清除所有组件 |
|
|
154
|
+
| `handleTicks(en, dt)` | 遍历所有组件调用 `onTick`,支持 before/after 队列 |
|
|
155
|
+
| `update()` | 触发组件重附加(用于依赖变更后刷新) |
|
|
156
|
+
|
|
157
|
+
**全局单例**:`ComponentManager.global` 用于存放全局(非实体相关)组件。
|
|
158
|
+
|
|
159
|
+
**性能分析**:`ComponentManager.profilerEnable = true` 可启用 tick 耗时日志。
|
|
160
|
+
|
|
161
|
+
### 2.5 RequiredComponent (依赖注入)
|
|
162
|
+
|
|
163
|
+
`RequireComponents(...params)` 是一个 mixin 工厂,创建一个带依赖的 Component 类:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// 用法示例
|
|
167
|
+
const MyComponent = RequireComponents(
|
|
168
|
+
HealthComponent,
|
|
169
|
+
[MovementComponent, 1.0], // 带构造参数的依赖
|
|
170
|
+
TransformComponent
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
class MyComponent extends BaseComponent<Entity> {
|
|
174
|
+
// 通过 this.getComponent(HealthComponent) 访问依赖
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
每次 `attachComponent` 时,如果组件有 `REQUIRED_COMPONENTS` 符号属性,这些依赖会被自动构造并附加。
|
|
179
|
+
|
|
180
|
+
### 2.6 lazyGet
|
|
181
|
+
|
|
182
|
+
`lazyGet(component, ctor)` 返回一个 **Proxy**,在首次访问属性时才通过 manager 查找目标组件。用于组件间交叉引用,避免循环依赖问题。
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 3. Level 与 Scheduler (`level.ts`)
|
|
187
|
+
|
|
188
|
+
### 3.1 Level
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
abstract class Level<T> {
|
|
192
|
+
protected table: Map<string, ComponentManager<T>>
|
|
193
|
+
|
|
194
|
+
addEntity(id: string): ComponentManager<T>
|
|
195
|
+
removeEntity(id: string): void
|
|
196
|
+
getManager(id: string): ComponentManager<T>
|
|
197
|
+
|
|
198
|
+
abstract getScheduler(): Scheduler<T>
|
|
199
|
+
|
|
200
|
+
start() // → scheduler.start(table)
|
|
201
|
+
stop() // → scheduler.stop()
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- 实体用 string ID 标识
|
|
206
|
+
- `getManager(id)` 在不存在时自动创建新 manager
|
|
207
|
+
- `start()` / `stop()` 委托给 scheduler
|
|
208
|
+
|
|
209
|
+
### 3.2 Scheduler
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
interface Scheduler<Actor> {
|
|
213
|
+
currentTick: number
|
|
214
|
+
timeDilation: number
|
|
215
|
+
|
|
216
|
+
start(table: Map<string, ComponentManager<Actor>>): void
|
|
217
|
+
stop(): void
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`timeDilation` 属性可加速/减速游戏时间(默认 1.0)。
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 4. Minecraft 集成
|
|
226
|
+
|
|
227
|
+
### 4.1 整体启动流程
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
Minecraft 启动
|
|
231
|
+
│
|
|
232
|
+
▼
|
|
233
|
+
@MinecraftMain 装饰器
|
|
234
|
+
→ GameInstance(target) 注册游戏类
|
|
235
|
+
→ utils.start() 挂载生命周期钩子
|
|
236
|
+
│
|
|
237
|
+
▼
|
|
238
|
+
system.beforeEvents.startup 触发
|
|
239
|
+
│
|
|
240
|
+
▼
|
|
241
|
+
initialize(GameInstanceClass)
|
|
242
|
+
→ new GameInstanceClass()
|
|
243
|
+
→ instance.onStart()
|
|
244
|
+
│
|
|
245
|
+
▼
|
|
246
|
+
MinecraftGameInstance.onStart()
|
|
247
|
+
→ 创建 MinecraftLevel
|
|
248
|
+
→ 创建 MinecraftTickingScheduler
|
|
249
|
+
→ level.start() → scheduler.start(table)
|
|
250
|
+
→ 安排 afterStart() 下一 tick 执行
|
|
251
|
+
│
|
|
252
|
+
▼
|
|
253
|
+
每游戏 tick:
|
|
254
|
+
scheduler.executeTick(table)
|
|
255
|
+
→ 遍历每个实体的 ComponentManager
|
|
256
|
+
→ manager.handleTicks(entity, dt)
|
|
257
|
+
→ 每个 Component.onTick(dt)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 4.2 MinecraftTickingScheduler
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
@Minecraft
|
|
264
|
+
class MinecraftTickingScheduler implements Scheduler<string> {
|
|
265
|
+
currentTick = 0
|
|
266
|
+
timeDilation = 1
|
|
267
|
+
|
|
268
|
+
start(table: Map<string, ComponentManager<Entity>>) {
|
|
269
|
+
system.runInterval(() => {
|
|
270
|
+
this.executeTick(table)
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
executeTick(table) {
|
|
275
|
+
this.currentTick++
|
|
276
|
+
const dt = this._timeStamp * this.timeDilation
|
|
277
|
+
for (const [id, manager] of table) {
|
|
278
|
+
// 使用 world.getEntity(id) 获取 Minecraft Entity 对象
|
|
279
|
+
manager.handleTicks(entity, dt)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 4.3 MinecraftGameInstance
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
abstract class MinecraftGameInstance implements GameInstance {
|
|
289
|
+
onStart() {
|
|
290
|
+
const level = new MinecraftLevel()
|
|
291
|
+
this.setLevel(level)
|
|
292
|
+
level.start()
|
|
293
|
+
system.run(() => this.afterStart?.()) // 下一 tick 执行 afterStart
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
用户自定义游戏类 extends `MinecraftGameInstance`,实现 `onStart()` 和可选的 `afterStart()`。
|
|
299
|
+
|
|
300
|
+
### 4.4 装饰器
|
|
301
|
+
|
|
302
|
+
#### 类装饰器
|
|
303
|
+
|
|
304
|
+
| 装饰器 | 说明 |
|
|
305
|
+
|--------|------|
|
|
306
|
+
| `@Minecraft` | 代理构造,确保在 Minecraft 环境中运行 |
|
|
307
|
+
| `@MinecraftMain` | 标记为主游戏类,注册 GameInstance + 挂载生命周期 + 设置生成监听 |
|
|
308
|
+
| `@PlayerSpawned(...deps)` | 标记在玩家生成时自动附加的 Component |
|
|
309
|
+
| `@EntitySpawned(...deps)` | 标记在实体生成时自动附加的 Component |
|
|
310
|
+
| `@ActorSpawned(...deps)` | 同时监听玩家和实体生成 |
|
|
311
|
+
| `@SpawnFilter(filter)` | 条件过滤:`(entity) => boolean`,返回 false 则不附加 |
|
|
312
|
+
|
|
313
|
+
#### 方法装饰器
|
|
314
|
+
|
|
315
|
+
| 装饰器 | 说明 |
|
|
316
|
+
|--------|------|
|
|
317
|
+
| `@MinecraftMethod` | 包装方法,执行前断言 Minecraft 环境 |
|
|
318
|
+
| `@ScriptEvent(eventId)` | 绑定方法到 Minecraft script event |
|
|
319
|
+
|
|
320
|
+
### 4.5 生成时自动附加
|
|
321
|
+
|
|
322
|
+
`@MinecraftMain` 内部逻辑:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
world.afterEvents.playerSpawn
|
|
326
|
+
→ 查找所有 @PlayerSpawned 装饰的类
|
|
327
|
+
→ 构造实例 + attachComponent(playerManager)
|
|
328
|
+
→ @SpawnFilter 可过滤
|
|
329
|
+
|
|
330
|
+
world.afterEvents.entitySpawn (排除 player)
|
|
331
|
+
→ 查找所有 @EntitySpawned 装饰的类
|
|
332
|
+
→ 构造实例 + attachComponent(entityManager)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 4.6 ScriptEvent 桥接
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// 方法装饰器用法
|
|
339
|
+
class MySystem {
|
|
340
|
+
@ScriptEvent('myAddon:event')
|
|
341
|
+
handleEvent(ev: ScriptEventCommandMessageAfterEvent) {
|
|
342
|
+
console.log('event received', ev)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 编程式用法
|
|
347
|
+
ScriptEvent.on('myAddon:event', handler)
|
|
348
|
+
ScriptEvent.off('myAddon:event', handler)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
内部通过 `EventEmitter` + `system.afterEvents.scriptEventReceive` 实现。
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## 5. 输入系统 (`input/`)
|
|
356
|
+
|
|
357
|
+
### 5.1 PlayerInputComponent
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
class PlayerInputComponent<Actor> extends BaseComponent<Actor> {
|
|
361
|
+
// 按键状态管理
|
|
362
|
+
inputKey(key: string, pressing: boolean): void
|
|
363
|
+
getKeyState(key: string): IKeyState
|
|
364
|
+
getKeyPressing(key: string): boolean
|
|
365
|
+
getKeyPressTimes(key: string): number
|
|
366
|
+
exhaust(key: string): void // 重置按键次数
|
|
367
|
+
|
|
368
|
+
// 轴输入
|
|
369
|
+
inputAxis(key: string, value: AxisValue): void
|
|
370
|
+
getAxis(key: string): AxisValue
|
|
371
|
+
|
|
372
|
+
// 轴工具
|
|
373
|
+
combineAxis(axis1, axis2): AxisValue
|
|
374
|
+
splitAxis(axis, nDim1, nDim2?): [AxisValue, AxisValue]
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**IKeyState**:`{ pressing: boolean, times: number, consume(): void }`
|
|
379
|
+
|
|
380
|
+
**AxisValue**:`number | [number, number] | [number, number, number]`
|
|
381
|
+
|
|
382
|
+
### 5.2 MinecraftPlayerInputComponent
|
|
383
|
+
|
|
384
|
+
extends `PlayerInputComponent<Entity>`。集成 Minecraft 玩家的按钮输入和摇杆轴:
|
|
385
|
+
|
|
386
|
+
- 通过 `world.afterEvents.playerButtonInput` 监听按钮事件
|
|
387
|
+
- 每 tick 通过 `player.inputInfo.getMovementVector()` 获取移动轴
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 6. 动作系统 (`action/`)
|
|
392
|
+
|
|
393
|
+
| 导出 | 说明 |
|
|
394
|
+
|------|------|
|
|
395
|
+
| `ActionValueType` | 枚举:`BOOL(0)`, `VEC(1)`, `VEC2(2)`, `VEC3(3)` |
|
|
396
|
+
| `IActionModifier<E, V>` | 变换函数:`(type: E, value: V) => V` |
|
|
397
|
+
| `IActionTrigger` | 触发判定:`(type, value, ticks) => boolean` |
|
|
398
|
+
| `IAction<E>` | 组合:`valueType + modifiers[] + triggers[]` |
|
|
399
|
+
| `ActionTriggers` | 工厂:`pressed()`, `released()`, `hold(act, hold)`, `holdThenRelease(act)` |
|
|
400
|
+
| `ActionModifiers` | 工厂:`negate(mask)`, `scale(scalar)` |
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 7. 事件系统 (`events/`)
|
|
405
|
+
|
|
406
|
+
### EventEmitter
|
|
407
|
+
|
|
408
|
+
自定义实现,使用**双向链表**实现 O(1) 的添加/移除:
|
|
409
|
+
|
|
410
|
+
| 方法 | 说明 |
|
|
411
|
+
|------|------|
|
|
412
|
+
| `on(type, handler)` | 订阅事件 |
|
|
413
|
+
| `once(type, handler)` | 一次性订阅 |
|
|
414
|
+
| `prependListener(type, handler)` | 插到监听器列表头部 |
|
|
415
|
+
| `prependOnceListener(type, handler)` | 一次性插到头部 |
|
|
416
|
+
| `off(type, handler)` / `removeListener(type, handler)` | 取消订阅 |
|
|
417
|
+
| `removeAllListeners(type)` | 移除所有 |
|
|
418
|
+
| `emit(type, ...args)` | 触发事件(使用 bind thisArg) |
|
|
419
|
+
| `emitNone(type, ...args)` | 触发事件(不使用 thisArg) |
|
|
420
|
+
| `listenerCount(type)` | 监听器数量 |
|
|
421
|
+
| `listeners(type)` / `rawListeners(type)` | 获取监听器列表 |
|
|
422
|
+
| `eventNames()` | 获取所有事件名 |
|
|
423
|
+
| `setMaxListeners(n)` | 设置最大监听器数 |
|
|
424
|
+
|
|
425
|
+
特点:
|
|
426
|
+
- 双向链表实现,O(1) add/remove
|
|
427
|
+
- 支持 `captureRejections`(捕获 Promise 返回的 handler 的 rejection)
|
|
428
|
+
- emit 过程中的错误会被捕获并重定向到 `'error'` 事件
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## 8. 数学库 (`math/`)
|
|
433
|
+
|
|
434
|
+
### Vec3 / Vec4 / Matrix
|
|
435
|
+
|
|
436
|
+
向量和矩阵继承自 `ArrayEx`(extends `Array`):
|
|
437
|
+
|
|
438
|
+
**Vec3**:`fromXYZ()`、`normalize()`、`dot()`、`cross()`、`add()`、`sub()`、`mul()`、`div()`
|
|
439
|
+
|
|
440
|
+
**Vec4**:同上 + `multiply(Matrix)`(矩阵乘法)
|
|
441
|
+
|
|
442
|
+
**Matrix**(4x4):
|
|
443
|
+
- 静态:`identity()`、`translate()`、`perspective()`、`orthographic()`、`lookAt()`
|
|
444
|
+
- 实例:`setIdentity()`、`setTranslation()`、`setRotation()`、`setRotationX/Y/Z()`、`setScale()`、`transpose()`、`multiply()`
|
|
445
|
+
|
|
446
|
+
### SplineInterpolator
|
|
447
|
+
|
|
448
|
+
五点平滑样条插值(quincunx 算法):
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
const spline = SplineInterpolator.fromPoints(points, lambda)
|
|
452
|
+
spline.interpolate(x) // 插值
|
|
453
|
+
spline.max(step) // 找最大值
|
|
454
|
+
spline.min(step) // 找最小值
|
|
455
|
+
spline.curve(nInterval) // 获取曲线点集
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## 9. HUD 系统 (`ui/`)
|
|
461
|
+
|
|
462
|
+
### HudComponent
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
abstract class HudComponent<Actor, Hud> extends CustomComponent<Actor> {
|
|
466
|
+
hud: Hud
|
|
467
|
+
actor: Optional<Actor>
|
|
468
|
+
|
|
469
|
+
abstract render(): Hud
|
|
470
|
+
abstract draw(): void
|
|
471
|
+
|
|
472
|
+
onTick(dt: number) {
|
|
473
|
+
this.hud = this.render()
|
|
474
|
+
this.draw()
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
每 tick 调用 `render()` 生成 HUD 数据,然后调用 `draw()` 渲染。
|
|
480
|
+
|
|
481
|
+
### PlayerHudComponent
|
|
482
|
+
|
|
483
|
+
extends `HudComponent<Player, string>`。使用 `/title @s actionbar` 命令将字符串渲染到玩家动作栏。
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## 10. 物理组件
|
|
488
|
+
|
|
489
|
+
### EasyKinematicsComponent
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
class EasyKinematicsComponent extends CustomComponent<Entity> {
|
|
493
|
+
mass = 1
|
|
494
|
+
allowRotation = false
|
|
495
|
+
simulated = false // false=applyImpulse, true=tryTeleport
|
|
496
|
+
force: Vec3
|
|
497
|
+
angularVelocity: Vec3
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
每 tick 应用力和角速度。`simulated = false` 时使用 `applyImpulse`,`true` 时使用 `tryTeleport` + 牛顿运动学公式。
|
|
502
|
+
|
|
503
|
+
### EasyVehicleComponent
|
|
504
|
+
|
|
505
|
+
载具物理数据:
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
class EasyVehicleComponent extends CustomComponent<Entity> {
|
|
509
|
+
width = 2, height = 1.5, length = 4.5
|
|
510
|
+
mass = 1500
|
|
511
|
+
inertia: Vec3 // 由 _calcInertia() 自动计算(矩形棱柱公式)
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 11. Optional Monad (`optional.ts`)
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
class Optional<T> {
|
|
521
|
+
static none<T>(): Optional<T> // 空值
|
|
522
|
+
static some<T>(value?: T): Optional<T> // 包装值
|
|
523
|
+
|
|
524
|
+
unwrap(): T // 取值,空则抛错
|
|
525
|
+
isEmpty(): boolean // 是否为 null/undefined
|
|
526
|
+
orElse(other: T): T // 空则返回默认值
|
|
527
|
+
use<R>(fn, self?): R | Optional<R> // monadic bind (flatMap)
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`use()` 是 flatMap 操作:非空时调用 `fn(value)`,如果返回值是 Optional 则直接返回,否则用 `Optional.some()` 包装;空时返回 `Optional.none()`。
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## 12. 类继承关系总图
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
Array (原生)
|
|
539
|
+
└── ArrayEx
|
|
540
|
+
├── Vec3
|
|
541
|
+
├── Vec4
|
|
542
|
+
└── Matrix
|
|
543
|
+
|
|
544
|
+
CustomComponent<Actor> (抽象)
|
|
545
|
+
├── BaseComponent<Actor>
|
|
546
|
+
│ ├── PlayerInputComponent<Actor>
|
|
547
|
+
│ │ └── MinecraftPlayerInputComponent
|
|
548
|
+
│ ├── HudComponent<Actor, Hud> (抽象)
|
|
549
|
+
│ │ └── PlayerHudComponent (抽象)
|
|
550
|
+
│ └── RequireComponents() 生成的类
|
|
551
|
+
├── EasyKinematicsComponent
|
|
552
|
+
└── EasyVehicleComponent
|
|
553
|
+
|
|
554
|
+
Level<T> (抽象)
|
|
555
|
+
└── MinecraftLevel
|
|
556
|
+
|
|
557
|
+
Scheduler<Actor> (接口)
|
|
558
|
+
└── MinecraftTickingScheduler
|
|
559
|
+
|
|
560
|
+
GameInstance<A> (接口)
|
|
561
|
+
└── MinecraftGameInstance (抽象)
|
|
562
|
+
|
|
563
|
+
EventEmitter (独立)
|
|
564
|
+
Optional<T> (独立)
|
|
565
|
+
SplineInterpolator (独立)
|
|
566
|
+
KeyState (独立)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## 13. 包入口 (`index.ts`)
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// src/oc/index.ts
|
|
575
|
+
export * from './core.js' // Component 体系 + ComponentManager
|
|
576
|
+
export * from './optional.js' // Optional<T>
|
|
577
|
+
export * from './input/base.js' // PlayerInputComponent
|
|
578
|
+
export * from './arch.js' // GameInstance, initialize
|
|
579
|
+
export * from './math/index.js' // Vec3, Vec4, Matrix, Spline, MathExt
|
|
580
|
+
export * from './minecraft/index.js' // Minecraft 集成全部
|
|
581
|
+
export * from './ui/hud.js' // HudComponent
|
|
582
|
+
```
|