HumanBreak/docs/guide/system.md
2025-03-13 19:38:39 +08:00

189 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
lang: zh-CN
---
# 系统说明
本文将介绍 2.B 的系统都做了哪些更改
## 模块化
2.B 样板现在已经迁移至了 monorepo将代码模块化共分为 20 余个模块,每个模块的具体内容可以参考 API 文档,模块列表如下:
- [@motajs/client](../api/motajs-client)
- [@motajs/client-base](../api/motajs-client-base)
- [@motajs/common](../api/motajs-common)
- [@motajs/legacy-client](../api/motajs-legacy-client)
- [@motajs/legacy-common](../api/motajs-legacy-common)
- [@motajs/legacy-data](../api/motajs-legacy-data)
- [@motajs/legacy-system](../api/motajs-legacy-system)
- [@motajs/legacy-ui](../api/motajs-legacy-ui)
- [@motajs/render](../api/motajs-render)
- [@motajs/render-core](../api/motajs-render-core)
- [@motajs/render-elements](../api/motajs-render-elements)
- [@motajs/render-style](../api/motajs-render-style)
- [@motajs/render-vue](../api/motajs-render-vue)
- [@motajs/system](../api/motajs-system)
- [@motajs/system-action](../api/motajs-system-action)
- [@motajs/system-ui](../api/motajs-system-ui)
- [@motajs/types](../api/motajs-types)
- [@user/client-modules](../api/user-client-modules)
- [@user/data-base](../api/user-data-base)
- [@user/data-fallback](../api/user-data-fallback)
- [@user/data-state](../api/user-data-state)
- [@user/data-utils](../api/user-data-utils)
- [@user/entry-client](../api/user-entry-client)
- [@user/entry-data](../api/user-entry-data)
- [@user/legacy-plugin-client](../api/user-legacy-plugin-client)
- [@user/legacy-plugin-data](../api/user-legacy-plugin-data)
## Mota 全局变量
与 2.A 不同2.B 对 `Mota` 全局变量做了简化,不再拥有 `Mota.Plugin` `Mota.Package` `Mota.requireAll` 属性与方法,它们全部整合至了 `Mota.require` 方法中,同时该方法的用法与 2.A 也不同,在 2.A 中,我们往往使用 `Mota.require('var', 'xxx')` 的方式调用,繁琐且不直观。在 2.B 中,我们可以直接填入模块名称,就可以获取到其内容了,例如:
```ts
const { hook, loading } = Mota.require('@user/data-base'); // 获取 hook 与 loading
const { Font } = Mota.require('@motajs/render'); // 获取 Font 字体类
```
我们只需要填写一个参数,而不需要填写两个参数了,更加直观,而且与 ES6 模块语法类似,便于转换。
多数情况下,我们是不需要使用 `Mota` 全局变量的。不过,还是有一些特殊情况需要使用该全局变量才可以,这些情况包括:
- 在数据端调用渲染端接口,数据端需要跑录像验证,因此不能直接引入渲染端接口,需要通过此全局变量才可以。
-`libs` `functions.js` 中调用接口,这两个地方暂时还没有模块化,因此无法直接引入,需要通过此全局变量调用。
## 渲染端与数据端通信
一般情况下,渲染端**可以**直接引入数据端的内容,例如你可以在 `@user/client-modules` 里面直接引入 `@user/data-state` 的接口,这是没有问题的。不过,由于数据端需要在服务器上跑录像验证,因此**不能**直接引入渲染端的内容,否则会导致验证报错。如果需要在数据端引用渲染端接口,我们需要这么做:
```ts
// @user/data-state 中的某文件
const num = 100;
Mota.r(() => {
// 使用 r 方法包裹,这样这个函数就会在渲染端运行,可以有返回值,但是在录像验证中只会是 undefined
const { Font } = Mota.require('@motajs/render');
const font = new Font('Verdana', 18);
// 函数内也可以调用外部变量,例如这里就调用了外部的 num 变量,但是极度不推荐在渲染端修改数据端的内容
// 否则很可能导致录像不能运行,这里这个例子就会导致录像运行出错,因为录像验证时并不会执行这段代码,
// 勇士的血量也就不会变大,于是就出错了。
core.status.hero.hp += num;
});
```
除此之外,我们还可以使用钩子来进行数据通信。示例如下:
```ts
// 渲染端和数据端都可以使用这个方式引入
import { hook } from '@user/data-base';
// 也可以通过 Mota.require 方法引入
const { hook } = Mota.require('@user/data-base');
// 监听战后函数,每次与怪物战斗后,都会执行这个函数
// 每个钩子的参数定义可以参考 package-user/data-base/src/game.ts GameEvent 接口
hook.on('afterBattle', enemy => {
console.log('与怪物战斗:', enemy.id);
});
```
## 加载流程
与 2.A 相比,加载流程也不太一样,下面是 2.B 的加载流程:
1. 加载 `index.html`
2. 加载 2.x 样板的第三方库
3. 如果是游戏中,加载 `src/main.ts`
1. 加载渲染端入口
2. 加载数据端入口
3. 并行初始化数据端与渲染端,在数据端写入 `Mota` 全局变量
4. 数据端初始化完毕后执行 `loading.emit('dataRegistered')` 钩子,渲染端初始化完毕后执行 `loading.emit('clientRegistered')` 钩子
5. 二者都初始化完毕后执行 `loading.emit('registered')` 钩子
6. 执行数据端各个模块的初始化函数
7. 执行渲染端各个模块的初始化函数
4. 如果是录像验证中:
1. 加载数据端入口
2. 初始化数据端,写入 `Mota` 全局变量
3. 初始化完毕后执行 `loading.emit('dataRegistered')``loading.emit('registered')` 钩子
4. 执行数据端各个模块的初始化函数
5. 执行 `main.js` 初始化
6. 加载全塔属性
7. 加载 `core.js` 及其他 `libs` 中的脚本
8. 加载完毕后执行 `loading.emit('coreInit')` 钩子
9. 开始资源加载
10. 自动元件加载完毕后执行 `loading.emit('autotileLoaded')` 钩子
11. 资源加载完毕后执行 `loading.emit('loaded')` 钩子
12. 进入标题界面
使用流程图表示如下:
![加载流程图](./img/mermaid-diagram-2025-03-12-210212.svg)
## 函数重写
在 2.B 模式下,如果想改 `libs` 的内容,如果直接在里面改会很麻烦,而且两端通讯也不方便,因此我们建议在 `package-user` 中对函数重写这样的话就可以使用模块化语法更加方便。同时2.B 也提供了函数重写接口,他在 `@motajs/legacy-common` 模块中,我们可以这么使用它:
```ts
// 新建一个 ts 文件,例如叫做 override.ts放在 client-modules 文件夹下
import { Patch, PatchClass } from '@motajs/legacy-common';
// 新建函数,这个操作是必要的,我们不能直接在顶层使用这个接口
export function patchMyFunctions() {
// 创建 Patch 实例,参数表示这个 Patch 示例要重写哪个文件中的函数
// 如果需要复写两个文件,那么就需要创建两个实例
const patch = new Patch(PatchClass.Control);
// 使用 add 函数来重写,第一个参数会有自动补全
// 如果要重写的函数以下划线开头,可能会有报错
// 这时候需要去 types/declaration 中对应的文件中添加声明
patch.add('getFlag', (name, defaultValue) => {
// 重写 getFlag如果变量是数字那么 +100 后返回
const value = core.status.?hero.?flags[name] ?? defaultValue;
return typeof value === 'number' ? value + 100 : value;
});
}
```
然后,我们找到 `client-modules` 文件夹下的 `index.ts` 文件,然后在 `create` 函数中引入并调用 `patchMyFunctions`,这样我们的函数重写就完成了。**注意**,如果两个重写冲突,会在控制台弹出警告,并使用最后一次重写的内容。
::: warning
**注意**,在渲染端重写的函数在录像验证中将无效,因为录像验证不会执行任何渲染端内容!
:::
## 目录结构
我们建议每个文件夹中都有一个 `index.ts` 文件,将本文件夹中的其他文件经由此文件导出,这样方便管理,同时结构清晰。可以参考 `packages-user/client-modules` 文件夹中是如何做的。
## ES6 模块化语法
我们推荐使用 ES6 模块化语法来编写代码,这会大大提高开发效率。下面来简单说明一下模块化语法的用法,首先是引入其他模块:
```ts
import { Patch } from '@motajs/legacy-common'; // 从样板库中引入接口
// 引入本地文件,注意不要填写后缀名,只可以在同一个 packages-user 子文件夹下使用
// 不可以跨文件夹使用,例如 packages-user/client-modules 就不能直接引用 packages-user/data-base 文件夹
// 需要使用 import { ... } from '@user/data-base'
import { patchMyFunctions } from './override';
```
然后是从当前模块导出内容:
```ts
// 导出函数
export function myFunc() { ... }
// 导出变量/常量
export const num = 100;
// 导出类
export class MyClass { ... }
// 从另一个模块中导出全部内容,即将另一个模块的内容转发为当前模块
export * from './xxx';
```
更多模块化语法内容请查看[这个文档](https://h5mota.com/bbs/thread/?tid=1018&p=3#p33)
与 TypeScript 相关语法请查看[这个文档](https://h5mota.com/bbs/thread/?tid=1018&p=3#p41)