diff --git a/docs/intermediate/code-basics.md b/docs/intermediate/code-basics.md new file mode 100644 index 0000000..a2bbd6e --- /dev/null +++ b/docs/intermediate/code-basics.md @@ -0,0 +1,148 @@ +# 代码基础 + +即使你只懂一点代码,也能读懂并编写本引擎的大部分内容。本篇用尽量简短的方式,补齐写中级教程所需的最小语法与类型知识。如果你已有 JavaScript / TypeScript 基础,可以快速跳过对应小节。 + +## 一、简明语法 + +### 变量 + +```ts +// let 声明之后可以重新赋值的变量 +let count = 0; + +// const 声明之后不再重新赋值的变量 +const name = 'hero'; + +count = count + 1; +``` + +### 函数 + +```ts +// 函数接收参数,返回结果 +function add(a: number, b: number): number { + return a + b; +} + +// 箭头函数是函数的简写形式 +const double = (n: number) => n * 2; +``` + +### 分支与循环 + +```ts +if (count > 0) { + // 条件成立时执行 +} else { + // 条件不成立时执行 +} + +for (const item of items) { + // 逐个处理 items 中的每一项 +} +``` + +### 对象与数组 + +```ts +// 对象:一组有名字的字段 +const hero = { hp: 100, atk: 10 }; + +// 数组:一串有序的值 +const skills = ['a', 'b', 'c']; + +hero.atk = hero.atk + 5; +``` + +## 二、模块与引入 + +一个文件就是一个模块。用 `export` 把内容暴露出去,用 `import` 从别的模块引入。在本引擎里,文件顶部的 `import` 必须紧挨在一起,中间不夹注释或空行。 + +```ts +// math.ts +export function add(a: number, b: number): number { + return a + b; +} + +export const PI = 3.14; +``` + +```ts +// main.ts +import { add, PI } from './math'; + +add(1, PI); +``` + +也可以整体引入: + +```ts +import * as math from './math'; + +math.add(1, 2); +``` + +## 三、类型入门 + +TypeScript 在 JavaScript 之上增加了类型,用来描述「这份数据长什么样」。类型会在编写与编译阶段被检查,帮助你在运行之前就发现错误。 + +### 基础类型 + +```ts +let n: number = 1; +let s: string = 'text'; +let b: boolean = true; +``` + +### 接口 + +当一份对象有固定的形状时,用 `interface` 描述它。声明好接口之后,就可以把一个符合该形状的对象标注为这个类型。 + +```ts +interface Hero { + hp: number; + atk: number; +} + +const hero: Hero = { hp: 100, atk: 10 }; +``` + +### 泛型:类型的「函数」 + +普通函数接收**值**、返回**值**;泛型则接收**类型**、返回**类型**,可以把它理解成「类型的函数」。尖括号里的 `T` 是一个类型参数,调用时才确定具体是什么类型。 + +```ts +// T 是类型参数,取什么类型由调用时传入的值决定 +function first(list: T[]): T { + return list[0]; +} + +const a = first([1, 2, 3]); // T 被推断为 number,a 的类型是 number +const b = first(['x', 'y']); // T 被推断为 string,b 的类型是 string +``` + +### 接口与实现:一种约定 + +本引擎大量使用 `interface` 描述接口,再让类去 `implements` 它,以此保证类实现了接口要求的所有内容。 + +```ts +interface Runner { + run(): void; +} + +class FastRunner implements Runner { + run(): void { + // 具体实现 + } +} +``` + +这是一种**建议但非强制**的约定:遵循它能让代码结构更清晰、类型更可靠;不遵循也不会导致程序出错。 + +## 进一步学习 + +本篇只覆盖了编写本引擎所需的最小语法。如果你想系统学习 JavaScript / TypeScript,下面这份教程更适合本引擎的作者: + +[JavaScript / TypeScript 教程](https://h5mota.com/bbs/thread/?tid=1018&p=3#p41) + +写代码时该把文件放到哪里,见 [src 导览](./src-tour.md)。 diff --git a/docs/intermediate/hooks.md b/docs/intermediate/hooks.md new file mode 100644 index 0000000..5e57ae9 --- /dev/null +++ b/docs/intermediate/hooks.md @@ -0,0 +1,103 @@ +# 钩子 + +钩子(hook)是本引擎提供的「事件订阅」机制。当游戏里发生某个关键动作时,主对象会通知所有挂在它上面的钩子;你在钩子里写下想做的事,就能在正确的时机介入游戏流程。 + +## 钩子是什么 + +数据端的主对象(如勇士、地图、战斗流程等)在发生关键动作时,会依次调用自己身上每个钩子的对应方法。你只需要写一个钩子对象,把它注册到关心的主对象上,就能在这些动作发生时收到通知。 + +一个钩子对象就是若干个方法的集合,方法名对应「什么时候触发」。例如想在勇士属性变化时做点什么,就实现 `onUpdateAttribute`: + +```ts +const hook = { + onUpdateAttribute(name, value) { + // 当勇士的某个属性变化时执行 + } +}; +``` + +## 常用钩子族一览 + +「钩子族」是同一类钩子方法的集合(一个 `interface`)。常用的几族如下,方法名以源码为准: + +| 钩子族 | 所属主对象 | 主要方法 | +| --- | --- | --- | +| `IDataCommonHooks` | 数据端公共对象 | `onTip` | +| `IHeroAttributeHooks` | 勇士属性 | `onUpdateAttribute` | +| `IHeroLocationHooks` | 勇士位置 | `onSetPos`、`onSetFloor` | +| `IHeroStateHooks` | 勇士状态 | `onBeforeChangeFloor`、`onAfterChangeFloor` | +| `IEquipmentStateHooks` | 装备 | `onChangeModifier` | +| `IHeroFollowersControllerHooks` | 勇士跟随者 | `onAddFollower`、`onRemoveFollower`、`onGatherFollowers` | +| `IObjectMoverHooks` | 移动器 | `onMoveStart`、`onMoveEnd`、`onStepStart`、`onStepEnd`、`onSetPos`、`onSetFaceDir`、`onSetMoveDir` | +| `ICombatFlowHooks` | 战斗流程 | `onBeforeCombat`、`onAfterCombat` | +| `IGameEventExecutorHooks` | 事件执行器 | `onBeforeExecuteEvent`、`onAfterExecuteEvent` | +| `IPathfindingSystemHooks` | 寻路系统 | `onBeforeTeleportTo`、`onAfterTeleportTo` | +| `IMapLayerHooks` | 地图图层 | `onResize`、`onUpdateArea`、`onUpdateBlock`、`onOpenDoor`、`onCloseDoor`、`onCreateDynamic`、`onDeleteDynamic`、`onUpdateDynamicPosition` | +| `IGameMapHooks` | 地图 | `onChangeBackground`、`onUpdateLayer`、`onResizeLayer` | +| `IReplaySystemHooks` | 录像系统 | `onCreateSandbox`、`onRecordCommand` | +| `IReplaySandboxHooks` | 录像沙盒 | `onStep`、`onSpeedSet`、`onStartReplay`、`onStopReplay`、`onPauseReplay`、`onResumeReplay` | + +## 需求 → 钩子对照表 + +下表把常见需求和对应的钩子方法列在一起,照着找即可: + +| 需求 | 对应钩子 | +| --- | --- | +| 勇士属性变化时做点事 | `IHeroAttributeHooks.onUpdateAttribute` | +| 勇士被设置坐标或切换楼层 | `IHeroLocationHooks.onSetPos` / `onSetFloor` | +| 勇士移动开始、结束或每走一步 | `IObjectMoverHooks.onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` | +| 切换楼层前后做过渡处理 | `IHeroStateHooks.onBeforeChangeFloor` / `onAfterChangeFloor` | +| 战斗前或战斗后介入 | `ICombatFlowHooks.onBeforeCombat` / `onAfterCombat` | +| 事件执行前后介入 | `IGameEventExecutorHooks.onBeforeExecuteEvent` / `onAfterExecuteEvent` | +| 地图图块被修改、区域被更新 | `IMapLayerHooks.onUpdateBlock` / `onUpdateArea` | +| 开门或关门时做点什么 | `IMapLayerHooks.onOpenDoor` / `onCloseDoor` | +| 动态图块被创建或删除 | `IMapLayerHooks.onCreateDynamic` / `onDeleteDynamic` | +| 装备的修饰器发生变化 | `IEquipmentStateHooks.onChangeModifier` | +| 瞬移寻路前后介入 | `IPathfindingSystemHooks.onBeforeTeleportTo` / `onAfterTeleportTo` | +| 录像播放到某一步 | `IReplaySandboxHooks.onStep` | +| 录像记录新指令 | `IReplaySystemHooks.onRecordCommand` | +| 通知渲染端进行一次提示 | `IDataCommonHooks.onTip` | + +若某类需求暂未在表中找到,可以在对应主对象的 `IHookable` 接口处查看它支持的全部钩子方法(例如 `hero-attribute`、`enemy-status-hooks` 等篇会进一步说明相关钩子的用法)。 + +## 注册与移除钩子 + +用 `addHook` 把钩子对象注册到主对象上,它会返回一个控制器: + +```ts +const controller = state.hero.attribute.addHook({ + onUpdateAttribute(name, value) { + // 属性变化时的处理 + } +}); +``` + +需要移除时,有两种方式:把**原来的钩子对象**传给 `removeHook`,或调用 `addHook` 返回的控制器的 `unload` 方法。 + +```ts +// 方式一:传回原来的钩子对象 +state.hero.attribute.removeHook(hook); + +// 方式二:用注册时返回的控制器卸载 +controller.unload(); +``` + +数据端的内置钩子在 `src/data/hook/initialize.ts` 中统一注册(涵盖战斗、事件、勇士、加载、寻路等),它是了解内置钩子如何挂载的入口。 + +## 异步钩子 + +部分钩子返回 `Promise`,是**异步钩子**,会在对应的动画或流程真正结束后才兑现;等待它们完成可以保证时序正确。例如: + +- `IObjectMoverHooks` 的 `onMoveStart`、`onMoveEnd`、`onStepStart`、`onStepEnd`; +- `ICombatFlowHooks` 的 `onBeforeCombat`、`onAfterCombat`; +- `IHeroStateHooks` 的 `onBeforeChangeFloor`、`onAfterChangeFloor`; +- `IMapLayerHooks` 的 `onOpenDoor`、`onCloseDoor`、`onDeleteDynamic`; +- `IReplaySandboxHooks` 的 `onStep`。 + +其余钩子均为同步钩子,触发时立即执行。 + +## 一个提醒:钩子可能被非本对象触发 + +钩子由所属主对象触发,但同一个主对象可能对应多个游戏对象(例如场上有多只怪物)。因此钩子被触发时,不一定是针对你关心的那一只;当场上存在多个同类主对象时,需要在回调里根据传入的参数判断是否为你的目标对象。一般情况下不会串。 + +钩子建立在数据端的主对象之上,两端分工见 [数据端与渲染端](./data-vs-render.md);编写钩子所需的语法见 [代码基础](./code-basics.md)。