From aee8627e9532d5be4b07917c95458b802115a80b Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 26 Oct 2026 20:36:00 +0800 Subject: [PATCH] =?UTF-8?q?docs(10-33):=20=E9=AB=98=E7=BA=A7=20I=20?= =?UTF-8?q?=E7=A7=BB=E5=85=A5=20i/=20=E5=B9=B6=E9=87=8D=E5=BB=BA=E6=80=BB?= =?UTF-8?q?=E5=AF=BC=E8=A7=88=E4=B8=8E=E4=BE=A7=E8=BE=B9=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 九个高级 I 页面 git mv 至 docs/advanced/i/ - docs/advanced/index.md 重建为高级教程总导览(III 暂不提供链接) - 侧边栏新增总导览入口,I 组九项改指 /advanced/i/ - 修正 I 页与高级 II 入链的跨层相对路径 --- docs/.vitepress/config.ts | 19 +-- docs/advanced/{ => i}/data-stores.md | 2 +- .../{ => i}/declarative-vs-imperative.md | 6 +- docs/advanced/{ => i}/face-handler.md | 2 +- docs/advanced/{ => i}/hooks-in-depth.md | 2 +- docs/advanced/i/index.md | 106 ++++++++++++++++ docs/advanced/{ => i}/mover.md | 6 +- docs/advanced/{ => i}/plugin-system.md | 2 +- docs/advanced/{ => i}/replay-consistency.md | 4 +- docs/advanced/{ => i}/saveable-content.md | 4 +- docs/advanced/ii/custom-replay-command.md | 4 +- docs/advanced/ii/index.md | 12 +- docs/advanced/ii/static-vs-dynamic-tiles.md | 2 +- docs/advanced/index.md | 113 ++++-------------- 14 files changed, 163 insertions(+), 121 deletions(-) rename docs/advanced/{ => i}/data-stores.md (97%) rename docs/advanced/{ => i}/declarative-vs-imperative.md (98%) rename docs/advanced/{ => i}/face-handler.md (99%) rename docs/advanced/{ => i}/hooks-in-depth.md (97%) create mode 100644 docs/advanced/i/index.md rename docs/advanced/{ => i}/mover.md (99%) rename docs/advanced/{ => i}/plugin-system.md (98%) rename docs/advanced/{ => i}/replay-consistency.md (96%) rename docs/advanced/{ => i}/saveable-content.md (98%) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c51d483..8c57a55 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -152,39 +152,40 @@ export default defineConfig({ } ], '/advanced/': [ + { text: '高级教程总导览', link: '/advanced/' }, { text: '高级 I · 扩展基础', collapsed: false, items: [ - { text: '高级 I 导览', link: '/advanced/' }, + { text: '高级 I 导览', link: '/advanced/i/' }, { text: '声明式 vs 命令式接口', - link: '/advanced/declarative-vs-imperative' + link: '/advanced/i/declarative-vs-imperative' }, { text: '插件系统', - link: '/advanced/plugin-system' + link: '/advanced/i/plugin-system' }, { text: '可存档内容', - link: '/advanced/saveable-content' + link: '/advanced/i/saveable-content' }, { text: '录像一致性', - link: '/advanced/replay-consistency' + link: '/advanced/i/replay-consistency' }, { text: 'data-common 的 Store', - link: '/advanced/data-stores' + link: '/advanced/i/data-stores' }, { text: '钩子深入', - link: '/advanced/hooks-in-depth' + link: '/advanced/i/hooks-in-depth' }, - { text: '移动 Mover', link: '/advanced/mover' }, + { text: '移动 Mover', link: '/advanced/i/mover' }, { text: 'FaceHandler 系列', - link: '/advanced/face-handler' + link: '/advanced/i/face-handler' } ] }, diff --git a/docs/advanced/data-stores.md b/docs/advanced/i/data-stores.md similarity index 97% rename from docs/advanced/data-stores.md rename to docs/advanced/i/data-stores.md index 93279eb..dd68afa 100644 --- a/docs/advanced/data-stores.md +++ b/docs/advanced/i/data-stores.md @@ -2,7 +2,7 @@ `@user/data-common` 是数据端的 Layer 0 公共层,上面挂着五个内容注册表:`tileStore`、`itemStore`、`mapStore`、`enemyStore`、`eventStore`。它们看起来像「数据表」,实际是内容层原始定义的注册表——图块、道具、地图、怪物的定义登记在这里,游戏运行时从这里查出来,再按定义构造自己的对象;事件则直接以实例登记。 -中级教程的[数据端总览](../intermediate/data-state-overview.md)列过这些入口,[图块存储](../intermediate/tile-store.md)讲了 `tileStore` 的日常用法。这一篇逐个讲每个 Store 存的是什么、有哪些常用成员、它自己能做什么,以及为什么运行期只该读。 +中级教程的[数据端总览](../../intermediate/data-state-overview.md)列过这些入口,[图块存储](../../intermediate/tile-store.md)讲了 `tileStore` 的日常用法。这一篇逐个讲每个 Store 存的是什么、有哪些常用成员、它自己能做什么,以及为什么运行期只该读。 阅读前需要会基础语法:`class`、`new`、`Map`、`import`。 diff --git a/docs/advanced/declarative-vs-imperative.md b/docs/advanced/i/declarative-vs-imperative.md similarity index 98% rename from docs/advanced/declarative-vs-imperative.md rename to docs/advanced/i/declarative-vs-imperative.md index c2a4b14..5db0c6d 100644 --- a/docs/advanced/declarative-vs-imperative.md +++ b/docs/advanced/i/declarative-vs-imperative.md @@ -41,7 +41,7 @@ function healHero(hero: IHeroState): void { - 只在执行到的那一瞬间生效。脚本没被触发,效果就不存在;触发第二次,效果就再来一次。 - 想重新计算只能重跑。怪物移动、周围条件变化之后,旧数值不会自动更新;再跑一遍脚本,又容易把加成叠加两次。 - 每条路径都要自己同步。存档、读档、录像回放、重新进入楼层,这些路径都可能绕开你写的那段脚本,于是状态对不上。 -- 没法凭空给怪物加特殊属性。直接改 `atk` 只是改了一个数字,不能让怪物获得一条领域属性;特殊属性本身的写法见[特殊属性](../intermediate/enemy-specials.md)。 +- 没法凭空给怪物加特殊属性。直接改 `atk` 只是改了一个数字,不能让怪物获得一条领域属性;特殊属性本身的写法见[特殊属性](../../intermediate/enemy-specials.md)。 这不是说命令式一无是处。玩家点一下、走一步、用一次道具,本来就是「现在立刻发生」的动作,用命令式接口最直接。问题只出在:把持续生效的规则也写成了一次性命令。 @@ -90,7 +90,7 @@ export class MyDamageCalculator implements IDamageCalculator< 为什么这比命令式更稳:你只描述 `f(enemy, hero)` 这一条规则,所有调用点共用同一份结果。不需要在「显示」和「结算」两处各写一遍公式,也不会出现显示一个数、实际打另一个数。代价是计算器必须无副作用,下面会单独解释。 -改公式与注册的完整做法见[伤害计算](../intermediate/enemy-calculator.md)。 +改公式与注册的完整做法见[伤害计算](../../intermediate/enemy-calculator.md)。 ### 特殊属性与光环 @@ -98,7 +98,7 @@ export class MyDamageCalculator implements IDamageCalculator< 引擎在哪个时机执行:构建「计算后怪物」时。`state.enemyContext.buildup()` 按固定顺序重建每只怪物,把特殊属性、查询与最终效果写进属性。 -为什么这比命令式更稳:每次重建都从模板的原始值出发,反复重建不会让加成越叠越多;你改的是注册表里的一条定义,所有读取计算后怪物的场景都会自动看到新结果;存档也只需要保存模板。更细的构建流程与特殊属性写法见[战斗总览](../intermediate/combat-overview.md)与[特殊属性](../intermediate/enemy-specials.md)。 +为什么这比命令式更稳:每次重建都从模板的原始值出发,反复重建不会让加成越叠越多;你改的是注册表里的一条定义,所有读取计算后怪物的场景都会自动看到新结果;存档也只需要保存模板。更细的构建流程与特殊属性写法见[战斗总览](../../intermediate/combat-overview.md)与[特殊属性](../../intermediate/enemy-specials.md)。 ### 道具效果 diff --git a/docs/advanced/face-handler.md b/docs/advanced/i/face-handler.md similarity index 99% rename from docs/advanced/face-handler.md rename to docs/advanced/i/face-handler.md index 3efe3f6..456bf04 100644 --- a/docs/advanced/face-handler.md +++ b/docs/advanced/i/face-handler.md @@ -109,7 +109,7 @@ function targetOf( 移动器构造时保存一个处理器,每一步用它把 `moveDirection` 换成落点偏移。内置的 `DynamicTile` 构造自己的 `DynamicTileMover` 时,从 `state.faceManager` 按 `DYNAMIC_MOVER_FACE`(即 `Dir8`)取处理器;自己写移动器时,处理器要作为构造参数传进去(见[移动 Mover](./mover.md#实现自己的移动器))。处理器必须与对象的方向体系一致:四方向对象配八方向处理器,或反过来,偏移与朝向都会对不上。 -寻路复用同一套方向规则。构建有向图时,它按 `mapMovement()` 的条目向相邻格扩展,所以处理器直接决定「能往哪几个方向走」:四方向处理器只扩展上下左右,八方向处理器会扩展到斜向。注入方式是 `finder.useFaceHandler(handler)`;没有注入时构图会失败(找不到图层、谓词或处理器都会失败),详见[自动寻路](./ii/pathfinding.md)。 +寻路复用同一套方向规则。构建有向图时,它按 `mapMovement()` 的条目向相邻格扩展,所以处理器直接决定「能往哪几个方向走」:四方向处理器只扩展上下左右,八方向处理器会扩展到斜向。注入方式是 `finder.useFaceHandler(handler)`;没有注入时构图会失败(找不到图层、谓词或处理器都会失败),详见[自动寻路](../ii/pathfinding.md)。 因为「方向即偏移」被多处共用,改处理器的行为会同时影响移动与寻路:调整四方向处理器的斜向归并,移动器的斜向输入和寻路的斜向分支会一起改变。只希望其中一处变化时,不要直接换掉共享的处理器,而是给那处单独注入一个处理器实例。 diff --git a/docs/advanced/hooks-in-depth.md b/docs/advanced/i/hooks-in-depth.md similarity index 97% rename from docs/advanced/hooks-in-depth.md rename to docs/advanced/i/hooks-in-depth.md index 6bbec20..73fb22a 100644 --- a/docs/advanced/hooks-in-depth.md +++ b/docs/advanced/i/hooks-in-depth.md @@ -1,6 +1,6 @@ # 钩子深入 -中级教程的[钩子](../intermediate/hooks.md)回答的是「什么需求该用哪个钩子」;这一篇讲另一件事:怎么把钩子限定到你关心的那个对象上。做法很简单——建议一个对象一个钩子实例。共享实例是允许的,但不建议;除非你明确了解钩子的内部实现与后果,否则不要这样做。 +中级教程的[钩子](../../intermediate/hooks.md)回答的是「什么需求该用哪个钩子」;这一篇讲另一件事:怎么把钩子限定到你关心的那个对象上。做法很简单——建议一个对象一个钩子实例。共享实例是允许的,但不建议;除非你明确了解钩子的内部实现与后果,否则不要这样做。 钩子接口本身不难,容易出错的是「挂上去了,触发的却不只是你想管的那张地图、那个图层」。下面先讲实例与对象的关系,再给一个完整示例,最后是注册、移除与异步的接口事实。 diff --git a/docs/advanced/i/index.md b/docs/advanced/i/index.md new file mode 100644 index 0000000..506d5d4 --- /dev/null +++ b/docs/advanced/i/index.md @@ -0,0 +1,106 @@ +# 高级 I 导览 + +高级教程写给已经能独立使用中级接口、现在想按自己的规则扩展数据端的人。到了这一层,问题不再是「这个接口怎么调」,而是「这个需求该放在哪一层、该由谁在什么时机执行」。弄错这一层,效果可能当下能跑,却会在存档、读档或录像回放时对不上。 + +这一页说明高级教程分几层、高级 I 包含哪些篇目、先读什么、以及一条贯穿全层的取舍原则:新增系统优先用插件,少改内置。 + +## 高级教程是给谁的 + +高级教程面向有高度定制需求的作者: + +- 你要新增一套有自己状态、生命周期和存档内容的系统,而不只是改一个数值; +- 你要理解扩展机制本身,知道声明式接口、插件、存档与录像之间怎么配合; +- 你愿意读一点源码,但不需要成为引擎的编写者。 + +它不要求你改造引擎内部。高级教程的目标是同时覆盖数据端与渲染端;渲染端尚未完工,当前只写数据端内容,渲染端部分只用占位标记指出归属。所以高级 I 的例子都落在数据端:你写的内容、你注册的对象、你声明的规则。 + +## 高级教程的三层 + +高级教程按难度与主题分成三层。层与顶层的中级教程不是同一套划分:中级讲「怎么用」,高级讲「怎么扩展」。 + +| 层级 | 定位 | 主要主题 | +| ---------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| 高级 I · 扩展基础 | 以插件系统为核心,后续内容都是它的拓展 | 声明式接口、插件系统、可存档内容、录像一致性、data-common 的 Store、钩子深入、移动 Mover、FaceHandler 系列 | +| [高级 II · 深入定制](../ii/) | 深入定制 | 范围、图块、装备、录像指令、寻路、勇士与怪物属性、战斗上下文 | +| 高级 III · 原理与架构 | 原理与架构 | 双端分离约束、数据端 L0–L3、编辑器识别协议、录像深层原理、性能与调试 | + +高级 II 已经可阅读,入口见[高级 II 导览](../ii/);本层收录范围、图块、装备、录像指令、寻路、属性与战斗上下文。高级 III 仍用 `高级 III · <专题>` 这样的占位标记指出归属,暂不提供页面入口。 + +## 高级 I 包含什么 + +高级 I 一共九篇,包含本导览与八篇正文。 + +| 序 | 篇目 | 它能让你做什么 | +| --- | -------------------- | ------------------------------------------------------------ | +| 1 | 高级 I 导览 | 看清分层、阅读路径与取舍原则,决定先读哪篇 | +| 2 | 声明式 vs 命令式接口 | 分清两种思路,知道持续生效的规则与一次性动作各该写在哪 | +| 3 | 插件系统 | 独立写出插件,分清数据端与渲染端的依赖和生命周期 | +| 4 | 可存档内容 | 让自定义状态正确进入存档,并在读档后恢复到存档时的样子 | +| 5 | 录像一致性 | 判断数据端哪些方法该登记、哪些路径不该记,以及检查能保证什么 | +| 6 | data-common 的 Store | 知道五个 Store 各存什么、能做什么,以及为什么运行时只读 | +| 7 | 钩子深入 | 把钩子限定到一个对象上,建议一个对象一个钩子实例 | +| 8 | 移动 Mover | 能排出一次移动、等它走完,并在需要时自己实现移动器 | +| 9 | FaceHandler 系列 | 能选对朝向处理器、算出目标格与反方向,并把朝向映射到图块数字 | + +推荐阅读顺序:声明式 vs 命令式 → 插件系统 → 可存档内容 → 录像一致性 → data-common 的 Store → 钩子深入。移动 Mover 与 FaceHandler 系列偏参考性质,等要写移动或朝向逻辑时再读;它们各自独立,不依赖前面的顺序。 + +插件系统是这一层的核心,可存档内容、录像一致性、data-common 的 Store 与钩子深入都建立在它提供的扩展位置上。也可以按需求跳读,但建议至少先读前两篇:它们决定你是在「声明一条规则」,还是在「写一段一次性脚本」。 + +## 阅读路径与前置 + +高级 I 假设你已经读完中级教程,尤其是数据端与渲染端、代码基础、数据插件这三篇;数据端总览、钩子、录像指令也会在具体页面里被直接引用。前置篇目的链接放在本页末尾。 + +按目标不同,可以走两条路径: + +- 做扩展系统:声明式 → 插件 → 可存档内容 → 录像一致性。适合新增任务、商店、计数器这类有自己状态与生命周期的功能。读完能独立装配一个插件,并让它的数据跟着存档走。 +- 改内容或机制:声明式 → data-common 的 Store → 钩子深入,按需接上移动 Mover 或 FaceHandler 系列。适合在现有机制上加行为、按接口读取内容定义。读完能分清哪些只需要读、哪些由数据端登记,也能把钩子限定到目标对象上;要改移动或朝向规则时,知道该换哪个扩展点。 + +两条路径都建议先读声明式篇。它决定你后面是在「加一条规则」,还是「写一段一次性脚本」;这个判断错了,插件、存档与录像都会跟着别扭。 + +## 优先用插件,少改内置 + +新增系统优先通过插件系统实现;只有不得不修改内置系统时,才去改内部。理由很现实:版本迁移时 `src` 基本不会改,而内置包(`packages` / `packages-user`)有可能会改。直接改内置,升级时就要跟着差异走,维护成本更高;把改动留在自己的 `src` 里,升级时要对照的地方少得多。 + +| 需求类型 | 优先做法 | 什么情况下才改内置 | +| -------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------- | +| 新增一套有自己状态、生命周期与存档内容的系统 | 写成插件,在插件里装配对象 | 现有插件已覆盖大部分能力,只差一个接口或默认值时 | +| 给已有系统补一点行为 | 改内置类的方法体,或加一条分支 | 需要替换整套策略、或改动会破坏内置行为时,才独立成类 | +| 读取内容定义(图块、道具、地图、怪物、事件) | 通过 `state.*Store` 的接口读,不新建 Store | 现有 Store 装不下,且确实需要新的系统级数据表时 | +| 给会改状态的方法补录像标记 | 在内置方法上加一行装饰器 | 需要新的录像入口或自定义录像行为时(见[自定义录像指令](../ii/custom-replay-command.md)) | + +判断标准不是改动多少行,而是改动落在谁的代码里:能由插件或内容定义完成的,就留在 `src`;必须动内置包时,尽量把差异收敛在一个方法或一条分支上,方便升级时对照。 + +## 本层不覆盖什么 + +- 渲染端内容:渲染端尚未完工,高级教程当前只收录数据端内容,渲染端专题待完工后补上。 +- 高级 II 的深入定制内容:范围、图块、装备、录像指令、寻路、勇士与怪物属性、战斗上下文,见[高级 II 导览](../ii/)。 +- 高级 III 的原理与架构:双端分离约束、数据端 L0–L3、编辑器识别协议、录像深层原理、性能与调试。 +- API 参考:接口签名与类型清单不属于教程正文。 + +## 读完这一层应该能做什么 + +- 能用自己的话解释声明式与命令式的区别,并说出一个「仍然该用命令式」的场景。 +- 能拿到一个需求,判断它该做成插件,还是按现有接口读取即可。 +- 能说出一个可存档对象必须满足的三个约束:只存重建必需的数据、`saveState` 返回独立对象(引擎不替你深拷贝)、`loadState` 先清空再重建。 +- 能说明为什么建议一个对象一个钩子实例,并在回调拿到多个对象时用对象引用筛出目标。 +- 能排出一次移动、等到它结束,并在需要时实现自己的移动器或勇士顶层实现。 +- 能选对朝向处理器、算出目标格与反方向,并把朝向映射到图块数字。 + +## 继续阅读 + +高级 I 正文: + +- [声明式 vs 命令式接口](./declarative-vs-imperative.md) +- [插件系统](./plugin-system.md) +- [可存档内容](./saveable-content.md) +- [录像一致性](./replay-consistency.md) +- [data-common 的 Store](./data-stores.md) +- [钩子深入](./hooks-in-depth.md) +- [移动 Mover](./mover.md) +- [FaceHandler 系列](./face-handler.md) + +前置回顾(中级教程): + +- [数据端与渲染端](../../intermediate/data-vs-render.md) +- [代码基础](../../intermediate/code-basics.md) +- [数据插件](../../intermediate/data-plugins.md) diff --git a/docs/advanced/mover.md b/docs/advanced/i/mover.md similarity index 99% rename from docs/advanced/mover.md rename to docs/advanced/i/mover.md index 85c6cdc..1cb5f89 100644 --- a/docs/advanced/mover.md +++ b/docs/advanced/i/mover.md @@ -49,7 +49,7 @@ async function heroStepRight(state: ICoreState): Promise { } ``` -勇士位置对象本身的接口(坐标、楼层、朝向、存档)见[勇士位置](../intermediate/hero-location.md)。 +勇士位置对象本身的接口(坐标、楼层、朝向、存档)见[勇士位置](../../intermediate/hero-location.md)。 ### 动态图块 @@ -70,7 +70,7 @@ async function pushTileRight(layer: IMapLayer): Promise { } ``` -`layer.createDynamic(num, x, y)` 直接新建动态图块,`layer.getDynamicTilesAt(x, y)` 与 `layer.iterateDynamicTiles()` 用来找已存在的动态图块。只是推一格这类简单场景,用 `tile.step(dir, count)` 更省事,它内部就是 `mover.step(...)` 加 `mover.start()`。创建、转换与移动的完整说明见[静态图块与动态图块](./ii/static-vs-dynamic-tiles.md)。 +`layer.createDynamic(num, x, y)` 直接新建动态图块,`layer.getDynamicTilesAt(x, y)` 与 `layer.iterateDynamicTiles()` 用来找已存在的动态图块。只是推一格这类简单场景,用 `tile.step(dir, count)` 更省事,它内部就是 `mover.step(...)` 加 `mover.start()`。创建、转换与移动的完整说明见[静态图块与动态图块](../ii/static-vs-dynamic-tiles.md)。 ### 其他可移动对象 @@ -453,7 +453,7 @@ state.hero.location.mover.useTopImplementation(new NoLeftMoveTopImpl(state)); - `predicate()` 在每个方向步都会被调用,要返回构造时建好的稳定实例,不要在方法里新建对象(内置实现就是把 `DefaultPassPredicateImpl` 存在字段里)。 - `config({ ignoreTerrain: true })` 会让 `HeroMover` 跳过整个谓词判断、只剩边界检查,此时改谓词看不到效果。 -- 寻路用的是同一形状的谓词,但由 `state.pathfinding.finder.usePassPredicate(...)` 独立注入,不会自动跟随勇士的顶层实现。改了勇士的通行规则、又希望寻路一致时,要把同一个谓词再注入给 `finder`,详见[自动寻路](./ii/pathfinding.md)。 +- 寻路用的是同一形状的谓词,但由 `state.pathfinding.finder.usePassPredicate(...)` 独立注入,不会自动跟随勇士的顶层实现。改了勇士的通行规则、又希望寻路一致时,要把同一个谓词再注入给 `finder`,详见[自动寻路](../ii/pathfinding.md)。 ## 边界与注意 diff --git a/docs/advanced/plugin-system.md b/docs/advanced/i/plugin-system.md similarity index 98% rename from docs/advanced/plugin-system.md rename to docs/advanced/i/plugin-system.md index fb80928..f9f5cf9 100644 --- a/docs/advanced/plugin-system.md +++ b/docs/advanced/i/plugin-system.md @@ -1,6 +1,6 @@ # 插件系统 -如果你已经用过中级教程里的[数据插件](../intermediate/data-plugins.md),你会知道插件能把一组功能打包、挂到 `state` 上,并按固定时机初始化。那一篇停在「会用」;这一篇讲怎么自己写一个插件、让插件彼此协作,以及怎么在插件之外调用它。 +如果你已经用过中级教程里的[数据插件](../../intermediate/data-plugins.md),你会知道插件能把一组功能打包、挂到 `state` 上,并按固定时机初始化。那一篇停在「会用」;这一篇讲怎么自己写一个插件、让插件彼此协作,以及怎么在插件之外调用它。 插件分数据端与渲染端两套,写法、注册方式与生命周期一一对应:数据端插件负责逻辑与状态,渲染端插件负责画面与交互。渲染端插件可以依赖数据端插件,反过来不行。下面两边并列讲,遇到不同的地方单独说明。 diff --git a/docs/advanced/replay-consistency.md b/docs/advanced/i/replay-consistency.md similarity index 96% rename from docs/advanced/replay-consistency.md rename to docs/advanced/i/replay-consistency.md index 3496433..d1ca126 100644 --- a/docs/advanced/replay-consistency.md +++ b/docs/advanced/i/replay-consistency.md @@ -2,9 +2,9 @@ 录像验证的用法是:把玩家的操作录下来,之后只跑数据端、按顺序重放,看结果与原始游玩是否一致。要让这件事成立,前提是该记的行为一条都不能少。少记一条,重放时那一步就没有发生,之后每一步都建立在错误的状态上,而第一次出现差异的位置,往往离真正的原因很远。 -中级教程的[录像指令](../intermediate/replay-commands.md)讲了录像是什么、主动与被动的区别、以及怎么新增一条指令。这一篇往下一层:`@shouldReplay` 与 `@ignoreReplay` 这两个装饰器该写在什么方法上,它们能替你查出什么、查不出什么。 +中级教程的[录像指令](../../intermediate/replay-commands.md)讲了录像是什么、主动与被动的区别、以及怎么新增一条指令。这一篇往下一层:`@shouldReplay` 与 `@ignoreReplay` 这两个装饰器该写在什么方法上,它们能替你查出什么、查不出什么。 -阅读前需要会基础语法:`class`、`new`、`import`,以及装饰器写法 `@名字(...)`。装饰器的基础写法见[代码基础](../intermediate/code-basics.md)。 +阅读前需要会基础语法:`class`、`new`、`import`,以及装饰器写法 `@名字(...)`。装饰器的基础写法见[代码基础](../../intermediate/code-basics.md)。 ## 什么该记 diff --git a/docs/advanced/saveable-content.md b/docs/advanced/i/saveable-content.md similarity index 98% rename from docs/advanced/saveable-content.md rename to docs/advanced/i/saveable-content.md index 0a623ee..48763d8 100644 --- a/docs/advanced/saveable-content.md +++ b/docs/advanced/i/saveable-content.md @@ -2,9 +2,9 @@ 存档不是「把内存里的东西倒进文件」,而是游戏状态的一份快照。读档之后,游戏必须从这份快照继续,按同一套规则运行:玩家位置、地图改动、Flag、怪物状态、插件自己的计数,都要回到存档那一刻。只要有一处没回到该有的样子,后面就会逐渐偏掉,而且往往要到很久以后才表现出来。 -中级教程里的 [Flag 变量](../intermediate/flags.md) 已经替你存好了一类简单状态。这一篇讲更一般的情况:当你要自己定义一类需要进存档的数据时,怎么声明、怎么注册、怎么保证读档后的结果和存档时一致。 +中级教程里的 [Flag 变量](../../intermediate/flags.md) 已经替你存好了一类简单状态。这一篇讲更一般的情况:当你要自己定义一类需要进存档的数据时,怎么声明、怎么注册、怎么保证读档后的结果和存档时一致。 -阅读前需要会基础语法:`class`、`new`、`Map`、`import`。如果你还没写过插件,建议先看[数据插件](../intermediate/data-plugins.md)与[插件系统](./plugin-system.md);可存档内容通常就注册在插件的 `awake` 里。 +阅读前需要会基础语法:`class`、`new`、`Map`、`import`。如果你还没写过插件,建议先看[数据插件](../../intermediate/data-plugins.md)与[插件系统](./plugin-system.md);可存档内容通常就注册在插件的 `awake` 里。 ## 什么该存,什么不该存 diff --git a/docs/advanced/ii/custom-replay-command.md b/docs/advanced/ii/custom-replay-command.md index aac3fcc..d4a1ab1 100644 --- a/docs/advanced/ii/custom-replay-command.md +++ b/docs/advanced/ii/custom-replay-command.md @@ -1,8 +1,8 @@ # 自定义录像指令 -高级 I 的[录像一致性](../replay-consistency.md)讲的是「已有行为该不该记」,回答方式是给方法加 `@shouldReplay`。还有一种情况它覆盖不到:行为本身不在引擎里,录像里没有对应的指令码,回放时根本无从执行。这时需要新增一条录像指令。 +高级 I 的[录像一致性](../i/replay-consistency.md)讲的是「已有行为该不该记」,回答方式是给方法加 `@shouldReplay`。还有一种情况它覆盖不到:行为本身不在引擎里,录像里没有对应的指令码,回放时根本无从执行。这时需要新增一条录像指令。 -本篇讲一条自定义指令从指令码、指令对象、注册到记录的完整路径,并用技能给出完整示例。主动步与被动步的配合会先用选择框说明。新增指令码会修改 `packages-user` 下的引擎内容,升级时要预期合并成本;如果只是给已有行为补标记,回到[录像一致性](../replay-consistency.md)即可,不必新增指令。 +本篇讲一条自定义指令从指令码、指令对象、注册到记录的完整路径,并用技能给出完整示例。主动步与被动步的配合会先用选择框说明。新增指令码会修改 `packages-user` 下的引擎内容,升级时要预期合并成本;如果只是给已有行为补标记,回到[录像一致性](../i/replay-consistency.md)即可,不必新增指令。 ## 什么时候需要自定义指令 diff --git a/docs/advanced/ii/index.md b/docs/advanced/ii/index.md index a46bf12..6b658ea 100644 --- a/docs/advanced/ii/index.md +++ b/docs/advanced/ii/index.md @@ -32,12 +32,12 @@ 高级 II 假设你已经读完高级 I,尤其是下面四篇: -- [声明式 vs 命令式接口](../declarative-vs-imperative.md):决定你是在声明规则,还是写一次性脚本; -- [插件系统](../plugin-system.md):新增系统优先放在插件里,而不是直接改内置; -- [可存档内容](../saveable-content.md):新增状态要能进入存档,并在读档后恢复; -- [录像一致性](../replay-consistency.md):会改变游戏进程的方法要考虑是否登记录像。 +- [声明式 vs 命令式接口](../i/declarative-vs-imperative.md):决定你是在声明规则,还是写一次性脚本; +- [插件系统](../i/plugin-system.md):新增系统优先放在插件里,而不是直接改内置; +- [可存档内容](../i/saveable-content.md):新增状态要能进入存档,并在读档后恢复; +- [录像一致性](../i/replay-consistency.md):会改变游戏进程的方法要考虑是否登记录像。 -同层的 [data-common 的 Store](../data-stores.md) 与 [钩子深入](../hooks-in-depth.md) 也会被反复引用:前者决定你从哪里读内容定义,后者决定你在什么时机插入行为。 +同层的 [data-common 的 Store](../i/data-stores.md) 与 [钩子深入](../i/hooks-in-depth.md) 也会被反复引用:前者决定你从哪里读内容定义,后者决定你在什么时机插入行为。 中级教程里的相关篇目同样重要,按主题列在下面: @@ -125,4 +125,4 @@ - [新增怪物属性](./adding-enemy-attribute.md) - [怪物上下文与战斗深入](./enemy-context-and-combat.md) -返回[高级 I 导览](../index.md)。 +返回[高级 I 导览](../i/index.md)。 diff --git a/docs/advanced/ii/static-vs-dynamic-tiles.md b/docs/advanced/ii/static-vs-dynamic-tiles.md index fa98d68..26a321e 100644 --- a/docs/advanced/ii/static-vs-dynamic-tiles.md +++ b/docs/advanced/ii/static-vs-dynamic-tiles.md @@ -146,7 +146,7 @@ async function shiftRight( ### 移动器 mover -`transferToDynamic` 得到的动态图块已经带有自己的 `mover`,之后的位置变更都由它发起;`step` 只是 mover 上的单步便捷入口。`mover` 的接口、速度与路径配置、`onEnd` 等待等细节属于高级 I 的内容,见[高级 I · 移动 Mover](../mover.md),本篇不重复。 +`transferToDynamic` 得到的动态图块已经带有自己的 `mover`,之后的位置变更都由它发起;`step` 只是 mover 上的单步便捷入口。`mover` 的接口、速度与路径配置、`onEnd` 等待等细节属于高级 I 的内容,见[高级 I · 移动 Mover](../i/mover.md),本篇不重复。 ### moveBlock 简写 diff --git a/docs/advanced/index.md b/docs/advanced/index.md index 4121460..92a33d5 100644 --- a/docs/advanced/index.md +++ b/docs/advanced/index.md @@ -1,106 +1,41 @@ -# 高级 I 导览 +# 高级教程总导览 -高级教程写给已经能独立使用中级接口、现在想按自己的规则扩展数据端的人。到了这一层,问题不再是「这个接口怎么调」,而是「这个需求该放在哪一层、该由谁在什么时机执行」。弄错这一层,效果可能当下能跑,却会在存档、读档或录像回放时对不上。 +高级教程写给有高度定制需求、并且愿意理解引擎设计的作者。到了这一层,问题不再是「这个接口怎么调」,而是「这个需求该放在哪一层、该由谁在什么时机执行」。弄错这一层,效果可能当下能跑,却会在存档、读档或录像回放时对不上。 -这一页说明高级教程分几层、高级 I 包含哪些篇目、先读什么、以及一条贯穿全层的取舍原则:新增系统优先用插件,少改内置。 +本层按难度分成 I / II / III 三层;分层是难度标签,不是领域分类,具体主题可能在层间调整。这一页说明三层的定位与主题、怎么选阅读路径、需要哪些前置,以及本层与中级教程的分工。 -## 高级教程是给谁的 - -高级教程面向有高度定制需求的作者: - -- 你要新增一套有自己状态、生命周期和存档内容的系统,而不只是改一个数值; -- 你要理解扩展机制本身,知道声明式接口、插件、存档与录像之间怎么配合; -- 你愿意读一点源码,但不需要成为引擎的编写者。 - -它不要求你改造引擎内部。高级教程的目标是同时覆盖数据端与渲染端;渲染端尚未完工,当前只写数据端内容,渲染端部分只用占位标记指出归属。所以高级 I 的例子都落在数据端:你写的内容、你注册的对象、你声明的规则。 - -## 高级教程的三层 - -高级教程按难度与主题分成三层。层与顶层的中级教程不是同一套划分:中级讲「怎么用」,高级讲「怎么扩展」。 +## 三层结构 | 层级 | 定位 | 主要主题 | | --------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| 高级 I · 扩展基础 | 以插件系统为核心,后续内容都是它的拓展 | 声明式接口、插件系统、可存档内容、录像一致性、data-common 的 Store、钩子深入、移动 Mover、FaceHandler 系列 | -| [高级 II · 深入定制](./ii/) | 深入定制 | 范围、图块、装备、录像指令、寻路、勇士与怪物属性、战斗上下文 | -| 高级 III · 原理与架构 | 原理与架构 | 双端分离约束、数据端 L0–L3、编辑器识别协议、录像深层原理、性能与调试 | +| [高级 I · 扩展基础](./i/) | 较低门槛,核心是插件系统与基本扩展机制 | 声明式接口、插件系统、可存档内容、录像一致性、data-common 的 Store、钩子深入、移动 Mover、FaceHandler 系列 | +| [高级 II · 深入定制](./ii/) | 中等门槛,需要理解并修改引擎的特定系统 | 范围对象、图块、装备、录像指令、寻路、勇士与怪物属性、战斗上下文 | +| 高级 III · 原理与架构 | 最高门槛,系统讲解设计原理与架构 | 双端分离约束、数据端 L0–L3、编辑器识别协议、录像深层原理、性能与调试 | -高级 II 已经可阅读,入口见[高级 II 导览](./ii/);本层收录范围、图块、装备、录像指令、寻路、属性与战斗上下文。高级 III 仍用 `高级 III · <专题>` 这样的占位标记指出归属,暂不提供页面入口。 +高级 I 是入口层,插件系统是这一层的核心,后面的可存档内容、录像一致性、data-common 的 Store 与钩子深入都建立在它提供的扩展位置上。高级 II 按主题深入单个系统,进入前应当先读完高级 I 的相关篇目。高级 III 讲的是支撑前面两层的原理与架构,等该层页面就绪后开放入口。 -## 高级 I 包含什么 +## 怎么选阅读路径 -高级 I 一共九篇,包含本导览与八篇正文。 +- 先 I 后 II、III 按需:默认路径是从高级 I 顺着读,把插件、存档、录像与钩子这四块打通;遇到需要改造某个具体系统时再进高级 II,想弄清背后设计时再进高级 III。这条路径适合第一次系统扩展数据端的作者。 +- 按目标跳读:已经有明确需求时,可以直接按主题跳到对应层。例如要新增一套带存档的系统,从高级 I 的插件系统与可存档内容读起;要新增录像指令或自定义寻路,直接进高级 II;要判断某个约束为什么存在,再查高级 III。跳读时如果对某个接口的日常用法不熟,先回中级教程补齐。 -| 序 | 篇目 | 它能让你做什么 | -| --- | -------------------- | ------------------------------------------------------------ | -| 1 | 高级 I 导览 | 看清分层、阅读路径与取舍原则,决定先读哪篇 | -| 2 | 声明式 vs 命令式接口 | 分清两种思路,知道持续生效的规则与一次性动作各该写在哪 | -| 3 | 插件系统 | 独立写出插件,分清数据端与渲染端的依赖和生命周期 | -| 4 | 可存档内容 | 让自定义状态正确进入存档,并在读档后恢复到存档时的样子 | -| 5 | 录像一致性 | 判断数据端哪些方法该登记、哪些路径不该记,以及检查能保证什么 | -| 6 | data-common 的 Store | 知道五个 Store 各存什么、能做什么,以及为什么运行时只读 | -| 7 | 钩子深入 | 把钩子限定到一个对象上,建议一个对象一个钩子实例 | -| 8 | 移动 Mover | 能排出一次移动、等它走完,并在需要时自己实现移动器 | -| 9 | FaceHandler 系列 | 能选对朝向处理器、算出目标格与反方向,并把朝向映射到图块数字 | +## 前置 -推荐阅读顺序:声明式 vs 命令式 → 插件系统 → 可存档内容 → 录像一致性 → data-common 的 Store → 钩子深入。移动 Mover 与 FaceHandler 系列偏参考性质,等要写移动或朝向逻辑时再读;它们各自独立,不依赖前面的顺序。 +高级教程假设你已经读完中级教程,尤其是下面三篇: -插件系统是这一层的核心,可存档内容、录像一致性、data-common 的 Store 与钩子深入都建立在它提供的扩展位置上。也可以按需求跳读,但建议至少先读前两篇:它们决定你是在「声明一条规则」,还是在「写一段一次性脚本」。 +- [数据端与渲染端](../intermediate/data-vs-render.md):理解「算」与「画」的分工,以及为什么录像验证只跑数据端; +- [代码基础](../intermediate/code-basics.md):类、接口、模块与类型的基本写法; +- [数据插件](../intermediate/data-plugins.md):插件是什么、怎么注册、生命周期大致如何。 -## 阅读路径与前置 +中级教程里的数据端总览、钩子、录像指令等篇目也会在具体页面里被直接引用;遇到不熟悉的接口时,回到对应中级篇目即可,本层不重复解释。 -高级 I 假设你已经读完中级教程,尤其是数据端与渲染端、代码基础、数据插件这三篇;数据端总览、钩子、录像指令也会在具体页面里被直接引用。前置篇目的链接放在本页末尾。 +## 与中级教程的分工 -按目标不同,可以走两条路径: +中级教程讲「怎么用」:调用哪个接口、参数怎么填、常见的改法是什么。高级教程讲「怎么扩展」:新增的系统该放在哪一层、由谁在什么时机执行、怎样与存档和录像保持一致。同一件事在两层的深度不同,读高级时默认你已经会使用相关接口,因此这里只讲扩展点、取舍与边界,不重新罗列基础用法。 -- 做扩展系统:声明式 → 插件 → 可存档内容 → 录像一致性。适合新增任务、商店、计数器这类有自己状态与生命周期的功能。读完能独立装配一个插件,并让它的数据跟着存档走。 -- 改内容或机制:声明式 → data-common 的 Store → 钩子深入,按需接上移动 Mover 或 FaceHandler 系列。适合在现有机制上加行为、按接口读取内容定义。读完能分清哪些只需要读、哪些由数据端登记,也能把钩子限定到目标对象上;要改移动或朝向规则时,知道该换哪个扩展点。 +## 边界与注意 -两条路径都建议先读声明式篇。它决定你后面是在「加一条规则」,还是「写一段一次性脚本」;这个判断错了,插件、存档与录像都会跟着别扭。 - -## 优先用插件,少改内置 - -新增系统优先通过插件系统实现;只有不得不修改内置系统时,才去改内部。理由很现实:版本迁移时 `src` 基本不会改,而内置包(`packages` / `packages-user`)有可能会改。直接改内置,升级时就要跟着差异走,维护成本更高;把改动留在自己的 `src` 里,升级时要对照的地方少得多。 - -| 需求类型 | 优先做法 | 什么情况下才改内置 | -| -------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- | -| 新增一套有自己状态、生命周期与存档内容的系统 | 写成插件,在插件里装配对象 | 现有插件已覆盖大部分能力,只差一个接口或默认值时 | -| 给已有系统补一点行为 | 改内置类的方法体,或加一条分支 | 需要替换整套策略、或改动会破坏内置行为时,才独立成类 | -| 读取内容定义(图块、道具、地图、怪物、事件) | 通过 `state.*Store` 的接口读,不新建 Store | 现有 Store 装不下,且确实需要新的系统级数据表时 | -| 给会改状态的方法补录像标记 | 在内置方法上加一行装饰器 | 需要新的录像入口或自定义录像行为时(见[自定义录像指令](./ii/custom-replay-command.md)) | - -判断标准不是改动多少行,而是改动落在谁的代码里:能由插件或内容定义完成的,就留在 `src`;必须动内置包时,尽量把差异收敛在一个方法或一条分支上,方便升级时对照。 - -## 本层不覆盖什么 - -- 渲染端内容:渲染端尚未完工,高级教程当前只收录数据端内容,渲染端专题待完工后补上。 -- 高级 II 的深入定制内容:范围、图块、装备、录像指令、寻路、勇士与怪物属性、战斗上下文,见[高级 II 导览](./ii/)。 -- 高级 III 的原理与架构:双端分离约束、数据端 L0–L3、编辑器识别协议、录像深层原理、性能与调试。 -- API 参考:接口签名与类型清单不属于教程正文。 - -## 读完这一层应该能做什么 - -- 能用自己的话解释声明式与命令式的区别,并说出一个「仍然该用命令式」的场景。 -- 能拿到一个需求,判断它该做成插件,还是按现有接口读取即可。 -- 能说出一个可存档对象必须满足的三个约束:只存重建必需的数据、`saveState` 返回独立对象(引擎不替你深拷贝)、`loadState` 先清空再重建。 -- 能说明为什么建议一个对象一个钩子实例,并在回调拿到多个对象时用对象引用筛出目标。 -- 能排出一次移动、等到它结束,并在需要时实现自己的移动器或勇士顶层实现。 -- 能选对朝向处理器、算出目标格与反方向,并把朝向映射到图块数字。 - -## 继续阅读 - -高级 I 正文: - -- [声明式 vs 命令式接口](./declarative-vs-imperative.md) -- [插件系统](./plugin-system.md) -- [可存档内容](./saveable-content.md) -- [录像一致性](./replay-consistency.md) -- [data-common 的 Store](./data-stores.md) -- [钩子深入](./hooks-in-depth.md) -- [移动 Mover](./mover.md) -- [FaceHandler 系列](./face-handler.md) - -前置回顾(中级教程): - -- [数据端与渲染端](../intermediate/data-vs-render.md) -- [代码基础](../intermediate/code-basics.md) -- [数据插件](../intermediate/data-plugins.md) +- 渲染端内容:渲染端尚未完工,本层当前只收录数据端内容;渲染端专题只用占位标记指出归属,待完工后补上。 +- 编辑器内容:涉及编辑器与内容配置的部分只保留占位,不在本层展开。 +- 不重复中级已讲内容:接口的日常用法、参数清单与常见改法属于中级教程;本层只在需要说明扩展点时引用,不整段重写。 +- 高级 III 入口:该层页面尚未创建,本页与侧边栏都不提供链接;页面齐备后统一开放入口。