mirror of
https://github.com/motajs/template.git
synced 2026-10-21 23:00:29 +08:00
docs(10-18): 收口高级 I 互链与构建
This commit is contained in:
parent
9644e2de0f
commit
5a6c3891b5
@ -282,3 +282,5 @@ console.log(raw, state.tileStore.getType(num));
|
||||
- `EnemyStore` 不在 `IDataCommon` 上,不要假设主对象能直接取到。
|
||||
- Store 注册没有逐对象模板,只有「在加载期集中登记」的机制骨架。
|
||||
- 不懂代码时优先在已有位置补条目,不要急着写新类或扩展主对象。
|
||||
|
||||
返回[高级 I 导览](./index.md)。
|
||||
|
||||
@ -108,7 +108,7 @@ hero.hp = hero.hp + 50;
|
||||
|
||||
<strong>引擎在哪个时机执行</strong>:存档与读档时。你只需要在初始化阶段注册一次,例如 `state.addSaveableContent('@my/xxx', content)`,之后引擎会在存档、读档时按注册表逐个调用。
|
||||
|
||||
<strong>为什么这比命令式更稳</strong>:存与读写在同一个接口里,不会出现「存的时候漏了一个字段、读的时候又忘了恢复」。注册一次之后,压缩级别、调用顺序、跨对象引用都由存档系统统一管理。存档的完整约束会在 `高级教程 I · 存档一致性` 中展开。
|
||||
<strong>为什么这比命令式更稳</strong>:存与读写在同一个接口里,不会出现「存的时候漏了一个字段、读的时候又忘了恢复」。注册一次之后,压缩级别、调用顺序、跨对象引用都由存档系统统一管理。存档的完整约束见[可存档内容](./saveable-content.md)。
|
||||
|
||||
### 钩子
|
||||
|
||||
@ -190,12 +190,10 @@ calculate(handler) {
|
||||
- <strong>只在该时刻生效、且不需要回放的脚本</strong>:如果一段脚本的效果本来就只应该发生一次,写成命令式没有问题。
|
||||
- <strong>需要立即得到返回值再决定下一步的流程</strong>:比如先判断能否使用道具,再决定是否扣除数量。
|
||||
|
||||
但要记住一条边界:<strong>这些入口动作必须计入录像</strong>。玩家按下的操作进入录像,回放时重新走一遍命令式接口,派生结果再由声明式规则重算。录像一致性的具体要求见 `高级教程 I · 录像一致性`。
|
||||
但要记住一条边界:<strong>这些入口动作必须计入录像</strong>。玩家按下的操作进入录像,回放时重新走一遍命令式接口,派生结果再由声明式规则重算。录像一致性的具体要求见[录像一致性](./replay-consistency.md)。
|
||||
|
||||
可以用一句话概括:<strong>一次性动作用命令式,持续生效的规则用声明式</strong>。
|
||||
|
||||
<!-- TODO(advanced-I): 录像一致性 -->
|
||||
|
||||
## 模板:声明式扩展的通用骨架
|
||||
|
||||
下面给的是机制本身的通用骨架,不对应任何一种具体效果。具体效果只用来帮助理解,不要把它当成模板照抄。
|
||||
@ -255,12 +253,10 @@ state.someSystem.useXxx(new XxxHandler(state));
|
||||
|
||||
- 你要替换整套策略,例如整条伤害公式都换掉;
|
||||
- 你要新增一组可独立复用的扩展,例如一个新的光环转换器;
|
||||
- 你要新增一个系统。此时优先通过插件实现,而不是直接改内置包,见 `高级教程 I · 插件系统`。
|
||||
- 你要新增一个系统。此时优先通过插件实现,而不是直接改内置包,见[插件系统](./plugin-system.md)。
|
||||
|
||||
<strong>如果你不怎么懂代码,建议直接改内置,而不是自己写新的类</strong>。自己写类要同时处理接口签名、注册时机与上下文刷新,改动面比在内置方法里加一个分支大得多,出错也更难排查。
|
||||
|
||||
<!-- TODO(advanced-I): 插件系统 -->
|
||||
|
||||
## 常见误区
|
||||
|
||||
- <strong>把声明式当成不用写逻辑</strong>。声明式只是把「什么时候执行」交给引擎,「怎么算」仍然要你写清楚。
|
||||
@ -306,3 +302,5 @@ if (system && info) {
|
||||
- 持续生效的规则用声明式;玩家直接产生的一次性动作用命令式,但要计入录像。
|
||||
- 计算类扩展必须无副作用;状态修改放到引擎规定的时机里。
|
||||
- 改内置优先;确实需要独立扩展时再自己写类,新增系统优先考虑插件。
|
||||
|
||||
返回[高级 I 导览](./index.md)。
|
||||
|
||||
@ -433,3 +433,5 @@ map.addHook(new TargetLayerResizeHooks(state, targetLayer));
|
||||
- 不要用 alias 判断身份;对象引用才唯一。
|
||||
- `addHook` 返回控制器,`removeHook` 要传原实例,控制器可 `unload`;`awake` 在加入已加载列表前调用,`destroy` 在移除时调用。
|
||||
- `addHook` 不去重,`forEachHook` 顺序不保证,异步钩子用 `Promise.all` 并发等待。
|
||||
|
||||
返回[高级 I 导览](./index.md)。
|
||||
|
||||
@ -293,12 +293,10 @@ export class MyPlugin implements IDataPluginDefine {
|
||||
|
||||
1. <strong>类名与 id</strong>:`MyPlugin` 与 `my-plugin` 换成你的名字,`id` 全局唯一。
|
||||
2. <strong>dependencies</strong>:填依赖插件的 id 字符串;没有就保持空数组,不要填插件对象。
|
||||
3. <strong>awake 里的装配</strong>:换成你自己的对象。需要进存档的对象必须实现 `ISaveableContent`(`saveState` / `loadState`),细节见 `高级教程 I · 可存档内容`。
|
||||
3. <strong>awake 里的装配</strong>:换成你自己的对象。需要进存档的对象必须实现 `ISaveableContent`(`saveState` / `loadState`),细节见[可存档内容](./saveable-content.md)。
|
||||
4. <strong>存档 id</strong>:`@plugin/my-plugin` 换成你自己的存档 id,与插件 id 分开命名。
|
||||
5. <strong>initialize / afterLoad</strong>:用不到就留空方法;用到时按需加逻辑。
|
||||
|
||||
<!-- TODO(advanced-I): 可存档内容 -->
|
||||
|
||||
<strong>注意</strong>:`awake` 里的异步工作不会被等待;`initialize` 与 `afterLoad` 里启动的异步任务必须 `await`,否则阶段会提前结束。注册时只改 `list.ts` 的数组,不要另外找地方调用插件方法。
|
||||
|
||||
## 示例:读一个现有插件
|
||||
@ -449,3 +447,5 @@ console.log(holder?.status === PluginStatus.Loaded, holder?.plugin);
|
||||
- 三个阶段都按依赖顺序走;循环依赖、缺失依赖会连同下游一起被阻断。
|
||||
- 插件 id 是对外契约,不要随意改名或复用;它与可存档 id 是两套命名。
|
||||
- 优先改内置;需要独立成组的功能时再写插件。
|
||||
|
||||
返回[高级 I 导览](./index.md)。
|
||||
|
||||
@ -262,3 +262,5 @@ endReplaySafetyCollection();
|
||||
- 收集区间目前只有测试在开启;需要时手动 `beginReplaySafetyCollection` / `endReplaySafetyCollection`。
|
||||
- 读档期间录像被临时禁用,执行器不在保护范围内。
|
||||
- 不懂代码时优先改内置加一行标记,不要急着写新类。
|
||||
|
||||
返回[高级 I 导览](./index.md)。
|
||||
|
||||
@ -173,7 +173,7 @@ export interface ISaveableExecutor<T> {
|
||||
|
||||
关键在时机:<strong>执行器在该对象的 `loadState` 返回后立即运行</strong>,不是等所有对象都读完之后统一运行。此时它前面的对象已经恢复完毕,后面的对象还没开始。这个顺序直接决定了跨对象引用该怎么解析。
|
||||
|
||||
还有一处与录像有关的细节:读档开始时,主对象会先清掉残留的录像禁用标记,再逐个恢复对象。每个对象的 `loadState` 都被 `replaySystem.disable()` 与 `revert()` 包裹,读档本身不会被记进录像;但执行器<strong>不在包裹范围内</strong>,因为它可能确实要做需要记录的行为。在执行器里操作会影响成绩的状态时要谨慎,录像一致性的完整规则见 `高级教程 I · 录像一致性`。
|
||||
还有一处与录像有关的细节:读档开始时,主对象会先清掉残留的录像禁用标记,再逐个恢复对象。每个对象的 `loadState` 都被 `replaySystem.disable()` 与 `revert()` 包裹,读档本身不会被记进录像;但执行器<strong>不在包裹范围内</strong>,因为它可能确实要做需要记录的行为。在执行器里操作会影响成绩的状态时要谨慎,录像一致性的完整规则见[录像一致性](./replay-consistency.md)。
|
||||
|
||||
### afterLoad 的两种含义
|
||||
|
||||
@ -416,3 +416,5 @@ console.log(content);
|
||||
- 插件 `afterLoad` 只在初次加载后运行一次,不能替代读档执行器。
|
||||
- 跨对象引用存 id 或键,读档时按顺序或延迟解析,不要存活对象引用。
|
||||
- 不懂代码时优先改内置的 `saveState` / `loadState`,不要急着写新类。
|
||||
|
||||
返回[高级 I 导览](./index.md)。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user