# 魔塔样板开发说明 ## 项目结构 - `public`: mota-js 样板所在目录 - `packages`: 核心引擎代码 monorepo - `packages-user`: 用户代码 monorepo - `src`: 游戏入口代码 依赖关系为单向:`src` → `packages-user` → `packages`。`packages` 与 `packages-user` 均可独立打包为库模式,`src` 为游戏的入口代码。 ## 开发环境 - `node.js ^20.0.0 || >=22.0.0` - `pnpm >= 10.0.0` - 任意支持 `ESNext` 特性的浏览器 **建议使用 `vscode`,搭配 `prettier` `eslint` 插件。** ## 开发说明 1. 将项目拉取到本地。 2. 运行 `pnpm i` 安装所有依赖(如提示运行 `pnpm approve-builds`,请允许全部)。 3. 运行 `pnpm dev` 进入开发环境。 ## 构建说明 | 命令 | 说明 | | --------------------- | ------------------------------------------------------- | | `pnpm build:packages` | 以库模式构建 `packages` 下的所有内容 | | `pnpm build:game` | 构建为可直接部署的游戏包 | | `pnpm build:lib` | 以库模式构建 `packages` 与 `packages-user` 下的所有内容 | | `pnpm type` | 对仓库执行类型检查 | | `pnpm check:circular` | 对仓库执行循环引用检查 | ## 术语统一 - 方法:一般指挂载到接口 `interface` 或类 `class` 上的函数。 - 函数:一般指在文件顶层定义的函数,有时也会指方法,需要根据语境判断。 - 接口:有时指 `interface`,有时也会指方法、成员等内容,需要根据语境判断。 - 成员/属性:一般指接口 `interface`、类 `class`、对象 `object` 上的字段。 ## 开发原则 ### 模块原则 - **无副作用**:所有模块只包含函数、类、常量的声明,不允许出现导出的变量声明或顶层代码执行,允许但不建议编写类的静态块。 - **模块初始化**:如需初始化,编写一个 `createXxx` 函数,在 `index.ts` 中整合后逐级向上传递,直至顶层模块统一执行。注意,当前设计理念下,**不应该**有场景会需要这种函数。 - **不转发导出**:不允许一个文件导出不属于当前 monorepo 或当前文件夹的内容。 - **无循环引用**:不允许出现循环引用。若遇到不得不循环引用的情况,应首先反思接口设计是否存在问题。 ### 命名规则 | 命名对象 | 规范 | | ---------------------------------------------- | ------------------------------------------ | | 变量、成员、一般常量、方法、函数 | 小驼峰 | | 类、接口、类型别名、命名空间、泛型、枚举、组件 | 大驼峰 | | 不变常量 | 全大写,单词间下划线分隔(如 `MAX_COUNT`) | | 专有名词缩写(如 `HTTP`、`URI`) | 全大写 | | 需被 `implements` 的接口 | 大写 `I` 开头 | | HTML/CSS 中的 `id`、`class` 等 | 连字符命名法 | | 代码文件名 | 小驼峰 | | Markdown 文档文件名 | 连字符命名法 | 不使用下划线命名法。 ### 注释规范 - 公共方法、接口必须在**源头处**(多数情况下为 `interface`)添加 `jsDoc` 注释;其他常用成员、方法、类型也必须添加注释(含义极为明确或极少使用的可例外,但建议全部添加)。 - 继承或 `implements` 而来的 API(方法、成员等),若注释说明无需变更,则**不应重复添加** `jsDoc` 注释。 - 不对构造器添加注释。若构造器使用了属性声明语法(`constructor(public prop: T)`)且成员需要说明,可仅对该成员添加参数注释,不写构造器描述。**在这种情况下**建议避免在构造器中使用属性声明语法,将成员单独声明并在构造器中赋值。此条建议**并非**要求不使用构造器的属性声明语法,而是仅在这一情况下不建议使用,常规情况下推荐使用此语法来缩短代码长度并提高可读性。 - 长文件可使用 `#region` / `#endregion` 分段以支持折叠。 - TODO 使用 `// TODO:` 或 `// todo:` 格式。 - 单行注释的 `//` 与注释内容之间留一个空格;不允许出现非 jsDoc 的多行注释,如需多行注释,使用多个单行注释代替。 - 注释合理换行:考虑中文字符较宽,建议每 40–60 个字符在标点符号后换行,不要频繁换行,每行长度应差不多,不允许在句中换行;参数注释换行后保持对齐。 - 单行注释结尾不加句号;较长的多行注释结尾可加句号。 - 一般不建议给接口(`inteface` 本身)、类型别名或类本身写注释(不好看),特殊情况除外。 - 对于 jsDoc 注释,方法注释必须使用换行风格;对于成员 jsDoc 注释,除非注释较长需要换行,否则使用不换行的风格。 ### 类型规范 - 不允许出现非必要的 `any` 类型。 - 所有类的成员必须显式声明类型。 - 无法避免类型错误时,使用 `// @ts-expect-error` 标记并说明原因。 - 未使用的变量或方法以下划线开头命名。 - 合理使用 `readonly`、`protected`、`private` 关键字。 - 可选参数过多时(大于两个),考虑改用对象参数。 - 尽量避免 `as` 类型断言,除非必要。 - 函数类型单独开一个 `type` 类型别名,除非函数类型本身较短(小于 20 字符,且不会因为此函数类型导致换行) ### 其他要求 - 不使用字符串作为键或特殊标识符(如枚举值、事件名、状态名等),应使用枚举代替。仅当明确表示字符串本身(如字符串类型的 id 别名、文件路径等)时方可使用字符串字面量。 - 严格遵循 `eslint` 配置,不允许出现 eslint 报错。 - 尽量不使用 `?.` 运算符,仅推荐在以下两种场景中使用: - 副作用函数调用,如 `this.obj?.func()` 或 `this.obj.func?.()` - 对象 Required 化,如 `{ value: obj?.value ?? 0 }` - 只进行必要的非空判断,非必要时直接使用非空断言 `!`。 - 除非参数要求传入函数等情况,不建议在函数内定义局部函数。 - 语句尽量不换行,除非必要,尤其注意三元运算符与 `private readonly` 类成员。 - 任何时候都不应该写 `getter` 和 `setter`。 ## 双端分离 样板将渲染端与数据端彻底分离: - **数据端**:可在 `node` 环境中单独运行,可直接用于录像验证,不负责任何渲染逻辑。 - **渲染端**:仅负责向数据端发送消息,不负责任何逻辑运算。 数据端允许调用渲染端代码,但必须使用全局接口 `Mota.r(() => {})` 包裹。除非必要,否则不建议在数据端调用渲染端代码。 ### 渲染端 渲染端目前已基本制作完成,分为两层: | 包 | 层级 | 说明 | | ---------------------- | ------ | ------------------------------------ | | `@user/client-base` | 系统层 | 负责渲染端核心系统 | | `@user/client-modules` | 实现层 | 依赖系统层实现客户端的渲染与用户交互 | ### 数据端 数据端目前正在从旧引擎进行彻底性重构,分为三层: **Layer 0 — 公共层**:包含公共接口、工具函数等内容,不依赖任何外部游戏逻辑,可被任意高层直接引用。包含统一接口 `IDataCommon`。 **Layer 1 — 数据层**:包含所有会影响游戏存档与流程的数据内容,如地图、怪物、玩家属性等。本层通过统一接口 `IStateBase` 对外暴露数据访问能力,各类数据模块均以此接口为核心组织。 **Layer 2 — 执行层**:直接引用 Layer 1,负责产生影响游戏进程的动作,如玩家控制、战斗计算等。本层内容不会进入存档,仅通过修改 Layer 1 的数据来影响游戏状态,并通过统一接口 `ICoreState` 对外暴露执行能力。 | 包 | 层级 | 说明 | | ------------------- | ------- | ------------------------------------------------------------------------------- | | `@user/data-common | Layer 0 | 公共层,定义 `IDataCommon` 及公共无依赖接口 | | `@user/data-base` | Layer 1 | 数据层,定义 `IStateBase` 及可存档游戏数据(地图、怪物、玩家属性等) | | `@user/data-system` | Layer 2 | 执行层,定义 `ICoreState`,依赖数据层实现玩家控制、战斗计算等影响游戏进程的动作 | | `@user/data-state` | — | 数据端的顶层模块,指导 Layer 2 的执行行为,不直接参与执行 |