mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-24 16:13:24 +08:00
Compare commits
2 Commits
e282d3f111
...
89567924e8
Author | SHA1 | Date | |
---|---|---|---|
89567924e8 | |||
5e1ea25d6d |
@ -5,12 +5,17 @@ export default defineConfig({
|
||||
title: 'HTML5 魔塔样板 V2.B',
|
||||
description: 'HTML5 魔塔样板 V2.B 帮助文档',
|
||||
base: '/_docs/',
|
||||
markdown: {
|
||||
math: true
|
||||
},
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
outline: [2, 3],
|
||||
nav: [
|
||||
{ text: '主页', link: '/' },
|
||||
{ text: '指南', link: '/guide/diff' },
|
||||
{ text: 'API', link: '/api/' }
|
||||
{ text: 'API', link: '/api/' },
|
||||
{ text: '错误代码', link: '/logger/' }
|
||||
],
|
||||
sidebar: {
|
||||
'/guide/': [
|
||||
@ -18,7 +23,12 @@ export default defineConfig({
|
||||
text: '深度指南',
|
||||
items: [
|
||||
{ text: '差异说明', link: '/guide/diff' },
|
||||
{ text: '系统说明', link: '/guide/system' }
|
||||
{ text: '系统说明', link: '/guide/system' },
|
||||
{ text: 'UI 编写', link: '/guide/ui' },
|
||||
{ text: 'UI 优化', link: '/guide/ui-perf' },
|
||||
{ text: 'UI 系统', link: '/guide/ui-system' },
|
||||
{ text: 'UI 元素', link: '/guide/ui-elements' },
|
||||
{ text: 'UI 常见问题', link: '/guide/ui-faq' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -6,7 +6,7 @@ lang: zh-CN
|
||||
|
||||
本文档暂时只会对新样板新增内容进行说明,其余请查看[旧样板文档](https://h5mota.com/games/template/_docs/#/)。
|
||||
|
||||
本指南建立在你已经大致了解 js 的基础语法的基础上。如果还不了解可以尝试对指南内容进行模仿,或者查看[人类塔解析](https://h5mota.com/bbs/thread/?tid=1018&p=1)
|
||||
本指南建立在你已经大致了解 js 的基础语法的基础上。如果还不了解 js 语法可以尝试对指南内容进行模仿,或者查看[人类塔解析](https://h5mota.com/bbs/thread/?tid=1018&p=1)
|
||||
|
||||
如果你有能力直接使用源码版样板进行创作,也可以直接 fork 或 clone 2.B 样板[存储库](https://github.com/unanmed/HumanBreak/tree/template-v2.B)。2.B 样板使用了 vite 作为了构建工具,同时使用了 ts 等作为了开发语言。
|
||||
|
||||
@ -22,7 +22,7 @@ lang: zh-CN
|
||||
- 使用全新的 UI 编写方式,速度快,效率高
|
||||
- 模块化,可以使用 ES6 模块化语法
|
||||
- 移除插件系统,可以自定义代码目录结构,更加自由
|
||||
- 优化渲染端(client 端)与数据端(data 端)的通讯,渲染段现在可以直接引用数据端,不过数据端还不能直接引用渲染端
|
||||
- 优化渲染端(client 端)与数据端(data 端)的通讯,渲染端现在可以直接引用数据端,不过数据端还不能直接引用渲染端
|
||||
|
||||
## 差异内容
|
||||
|
||||
|
BIN
docs/guide/img/image.png
Normal file
BIN
docs/guide/img/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
3
docs/guide/img/mermaid-diagram-2025-03-12-210212.svg
Normal file
3
docs/guide/img/mermaid-diagram-2025-03-12-210212.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 35 KiB |
@ -26,7 +26,7 @@ lang: zh-CN
|
||||
- [@motajs/system](../api/motajs-system)
|
||||
- [@motajs/system-action](../api/motajs-system-action)
|
||||
- [@motajs/system-ui](../api/motajs-system-ui)
|
||||
- [@motajs/types](../api/types)
|
||||
- [@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)
|
||||
@ -97,13 +97,11 @@ hook.on('afterBattle', enemy => {
|
||||
|
||||
1. 加载渲染端入口
|
||||
2. 加载数据端入口
|
||||
3. 并行初始化数据端,写入 `Mota` 全局变量
|
||||
4. 初始化完毕后执行 `loading.emit('dataRegistered')` 钩子
|
||||
5. 并行初始化渲染端
|
||||
6. 初始化完毕后执行 `loading.emit('clientRegistered')` 钩子
|
||||
7. 二者都初始化完毕后执行 `loading.emit('registered')` 钩子
|
||||
8. 执行数据端各个模块的初始化函数
|
||||
9. 执行渲染段各个模块的初始化函数
|
||||
3. 并行初始化数据端与渲染端,在数据端写入 `Mota` 全局变量
|
||||
4. 数据端初始化完毕后执行 `loading.emit('dataRegistered')` 钩子,渲染端初始化完毕后执行 `loading.emit('clientRegistered')` 钩子
|
||||
5. 二者都初始化完毕后执行 `loading.emit('registered')` 钩子
|
||||
6. 执行数据端各个模块的初始化函数
|
||||
7. 执行渲染端各个模块的初始化函数
|
||||
|
||||
4. 如果是录像验证中:
|
||||
|
||||
@ -121,6 +119,10 @@ hook.on('afterBattle', enemy => {
|
||||
11. 资源加载完毕后执行 `loading.emit('loaded')` 钩子
|
||||
12. 进入标题界面
|
||||
|
||||
使用流程图表示如下:
|
||||
|
||||

|
||||
|
||||
## 函数重写
|
||||
|
||||
在 2.B 模式下,如果想改 `libs` 的内容,如果直接在里面改会很麻烦,而且两端通讯也不方便,因此我们建议在 `package-user` 中对函数重写,这样的话就可以使用模块化语法,更加方便。同时,2.B 也提供了函数重写接口,他在 `@motajs/legacy-common` 模块中,我们可以这么使用它:
|
||||
@ -146,7 +148,7 @@ export function patchMyFunctions() {
|
||||
}
|
||||
```
|
||||
|
||||
然后,我们找到 `client-modules` 文件夹下的 `index.ts` 文件,然后在 `create` 函数中调用 `patchMyFunctions`,这样我们的函数重写就完成了。
|
||||
然后,我们找到 `client-modules` 文件夹下的 `index.ts` 文件,然后在 `create` 函数中引入并调用 `patchMyFunctions`,这样我们的函数重写就完成了。**注意**,如果两个重写冲突,会在控制台弹出警告,并使用最后一次重写的内容。
|
||||
|
||||
::: warning
|
||||
**注意**,在渲染端重写的函数在录像验证中将无效,因为录像验证不会执行任何渲染端内容!
|
||||
@ -155,3 +157,32 @@ export function patchMyFunctions() {
|
||||
## 目录结构
|
||||
|
||||
我们建议每个文件夹中都有一个 `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)
|
||||
|
754
docs/guide/ui-elements.md
Normal file
754
docs/guide/ui-elements.md
Normal file
@ -0,0 +1,754 @@
|
||||
# UI 元素
|
||||
|
||||
本节将会讲解 UI 系统中常用的渲染元素以及基础使用。
|
||||
|
||||
## 通用属性
|
||||
|
||||
UI 元素包含很多通用属性,我们先来介绍这些属性,它们可以用在任何渲染元素和 UI 组件中。
|
||||
|
||||
### 定位属性
|
||||
|
||||
元素包含若干定位属性,其中最常用的是 `loc` 属性,我们也推荐全部使用这个属性来修改元素定位。其类型声明如下:
|
||||
|
||||
```ts
|
||||
type ElementLocator = [
|
||||
x?: number,
|
||||
y?: number,
|
||||
w?: number,
|
||||
h?: number,
|
||||
ax?: number,
|
||||
ay?: number
|
||||
];
|
||||
```
|
||||
|
||||
这些属性两两组成一组(`x, y` 一组,`w, h` 一组,`ax, ay` 一组),每组可选填,也就是说 `x` 和 `y` 要么都填,要么都不填,以此类推。
|
||||
|
||||
- `x` `y`: 元素的位置,描述了在没有旋转时元素的锚点位置,例如 `[32, 32]` 就表示这个元素锚点在 `32, 32` 的位置,默认锚点在元素左上角,也就表示元素左上角在 `32, 32`。
|
||||
- `w` `h`: 元素的长宽,描述了在没有缩放时元素的矩形长宽,默认是没有放缩的。
|
||||
- `ax` `ay`: 元素的锚点位置,描述了元素参考点的位置,所有位置变换等将以此点作为参考点。0 表示元素最左侧或最上侧,1 表示最右侧或最下侧,可以填不在 0-1 范围内的值,例如 `[-1, 1]` 表示锚点横坐标在元素左侧一个元素宽度的位置,纵坐标在元素下边缘的位置。
|
||||
|
||||

|
||||
|
||||
示例如下:
|
||||
|
||||
```tsx
|
||||
// 元素相对于 32, 32 位置居中(锚点在元素正中间),宽高为 64
|
||||
<sprite loc={[32, 32, 64, 64, 0.5, 0.5]} />
|
||||
```
|
||||
|
||||
除了 `loc` 属性之外,还可以通过设置 `anc` 属性来修改锚点位置,示例如下:
|
||||
|
||||
```tsx
|
||||
// 设置锚点,效果为靠右对齐,上下居中对齐
|
||||
<sprite anc={[1, 0.5]} />
|
||||
```
|
||||
|
||||
你还可以手动指定 `x` `y` `width` `height` `anchorX` `anchorY` 属性,但是这种方式比较啰嗦,并不建议使用:
|
||||
|
||||
```tsx
|
||||
<sprite x={32} y={32} width={64} height={64} anchorX={0.5} anchorY={0.5} />
|
||||
```
|
||||
|
||||
最后说明一下元素的 `type` 属性,此属性描述了元素的定位模式,默认为 `static` 常规定位,此定位模式下元素位置会按照上述内容更改,而在 `absolute` 模式下,不论怎么修改定位属性,它都会保持在左上角的位置,可能会在一些特殊场景下使用(极度不建议使用此属性,很可能在 2.B.1 版本就会将其删除)。
|
||||
|
||||
### 纵深属性
|
||||
|
||||
可以通过 `zIndex` 属性来调整一个元素的纵深。纵深描述了元素之间的重叠关系,纵深高的会处在纵深低的元素上方,同时也会阻碍交互事件向纵深低的元素传播。必要的时候,需要通过设置纵深属性来调整层级关系。未设置时,后面的元素会处在前面的元素之上。
|
||||
|
||||
```tsx
|
||||
// 这个元素会处在上层
|
||||
<sprite zIndex={10} />
|
||||
// 这个元素会处在下层
|
||||
<sprite zIndex={5} />
|
||||
```
|
||||
|
||||
### 效果属性
|
||||
|
||||
效果属性包含 `filter` `composite` 及 `alpha` 三个属性。
|
||||
|
||||
`filter` 表示此元素的滤镜,参考 [CanvasRenderingContext2D.filter](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter),可以填写内置函数或 svg 滤镜。默认不包含任何滤镜。示例如下:
|
||||
|
||||
```tsx
|
||||
// 亮度变为 150%,对比度变为 120%
|
||||
<sprite filter="brightness(150%) contrast(120%)" />
|
||||
```
|
||||
|
||||
`composite` 属性描述了当前元素与在此之前渲染的元素之间的混合模式,参考 [CanvasRenderingContext2D.globalCompositeOperation](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation),可以填写 26 个值。默认使用简单 `alpha` 混合,即 `source-over`。例如:
|
||||
|
||||
```tsx
|
||||
// 使用加算方式叠加,两个颜色的 RGB 值分别相加得到最终结果
|
||||
<sprite composite="lighter" />
|
||||
```
|
||||
|
||||
`alpha` 属性描述了此元素的不透明度,1 表示完全不透明,0 表示完全透明。在叠加时,所有颜色都会乘以此不透明度后叠加。默认值是完全不透明,即 1。但需要注意的是,虽然 1 表示完全不透明,但是如果画布内容本身包含透明内容(例如一个半透明矩形),即使是 1 也会表现为透明,因为叠加时会乘以 1,不透明度不变。示例如下:
|
||||
|
||||
```tsx
|
||||
// 一个半透明元素
|
||||
<sprite alpha={0.5} />
|
||||
```
|
||||
|
||||
### 缓存属性
|
||||
|
||||
可以通过 `cache` 和 `nocache` 属性来指定这个元素的缓存行为,其中 `nocache` 表示禁用此元素的缓存机制,优先级最高,设置后必然不使用缓存。`cache` 表示启用此元素的缓存行为,常用于一些默认不启用缓存的元素,优先级低于 `cache`。这两个元素都不能动态设置,也就是说不能使用响应式来修改其值。示例如下:
|
||||
|
||||
```tsx
|
||||
// 内部渲染内容比较简单,不需要启用缓存
|
||||
<container nocache>
|
||||
<text />
|
||||
</container>
|
||||
// 路径较为复杂,因此启用 g-path 的缓存行为
|
||||
<g-path path={veryComplexPath} cache />
|
||||
```
|
||||
|
||||
### 元素溢出行为
|
||||
|
||||
溢出行为是指,当其子元素超出父元素的大小时,执行的行为。例如,假如父元素大小为 `200 * 200`,里面有一个子元素,大小为 `100 * 100`,位于 `(150, 50)` 的位置,这时候子元素的一部分就会超出父元素的范围。
|
||||
|
||||
在本渲染系统中,所有元素的默认溢出行为是裁剪,即不会显示任何溢出内容,注意调整容器的宽高。在 `nocache` 模式下,由于不受到缓存的约束,溢出内容依然会显示,不过不建议利用此特性来编写 UI,因为这种行为可能会在后续的更新中修改。
|
||||
|
||||
### 隐藏元素
|
||||
|
||||
可以使用 `hidden` 属性来隐藏元素:
|
||||
|
||||
```tsx
|
||||
const hidden = ref(false);
|
||||
// 一般使用一个响应式变量来控制隐藏行为,因为设置成常量没有任何意义
|
||||
<sprite hidden={hidden.value} />;
|
||||
```
|
||||
|
||||
### 交互属性
|
||||
|
||||
交互属性包括 `cursor` 和 `noevent`。前者描述了鼠标覆盖在当前元素上时的指针样式,参考 [CSS: cursor](https://developer.mozilla.org/zh-CN/docs/Web/CSS/cursor)。示例如下:
|
||||
|
||||
```tsx
|
||||
// 鼠标放置在该元素上时使用小手样式
|
||||
<sprite cursor="pointer" />
|
||||
```
|
||||
|
||||
`noevent` 表明当前元素将不会触发任何事件,事件将会下穿至纵深更低的元素。示例如下:
|
||||
|
||||
```tsx
|
||||
// 设置为 noevent 模式
|
||||
<sprite zIndex={100} noevent />
|
||||
// 这样的话这个 onClick 就可以正常触发了
|
||||
<sprite zIndex={10} onClick={click} />
|
||||
```
|
||||
|
||||
### 高清与抗锯齿
|
||||
|
||||
包含 `hd` `anti` `noanti` 三个属性,`hd` 表示是否启用高清,大部分元素是默认启用的,除了几个像素风为主的元素(地图渲染等);`anti` 表示手动启用画布的抗锯齿行为,一般用于默认不启用抗锯齿的元素;`noanti` 表示手动关闭元素的抗锯齿行为,优先级高于 `anti`,一般用于像素风图片展示、图标显示等,同时也有助于提高渲染性能。
|
||||
|
||||
```tsx
|
||||
// 关闭高清
|
||||
<sprite hd={false} />
|
||||
// 关闭抗锯齿
|
||||
<sprite noanti />
|
||||
// 启用地图渲染的抗锯齿
|
||||
<layer anti />
|
||||
```
|
||||
|
||||
### 元素变换属性
|
||||
|
||||
可以通过调整 `transform` 属性来修改元素的线性变换,包括平移、旋转、缩放。如果是简易的变换,可以使用 `rotate` `scale` 属性来修改旋转、缩放,使用 `loc` 来修改位置:
|
||||
|
||||
```tsx
|
||||
// 旋转 90 度,横向放缩为 1.5 倍,纵向不放缩
|
||||
<sprite rotate={Math.PI / 2} scale={[1.5, 1]} loc={[32, 32]} />
|
||||
```
|
||||
|
||||
我们没有设置锚点属性,那么需要注意旋转后,`loc` 属性所标明的位置将不再是左上角的那个点,因为旋转后,原本在左上角的点将会变成右上角的点。旋转时,顺时针为正,逆时针为负。
|
||||
|
||||
使用上面这种方式时,没有办法指定变换的顺序。一般情况下,变换的顺序将会影响结果,例如先旋转,再放缩,和先放缩,再旋转,其结果是不同的。下面我们来讲解一下图形学中的 2D 矩阵变换的基本概念,以及如何使用 `transform` 属性。
|
||||
|
||||
### Transform 矩阵变换
|
||||
|
||||
大部分情况下用不到此属性,此属性理解难度较大,如果不是必须使用此属性,可以不看此小节。以下矩阵变换内容由 `DeepSeek R1` 模型生成,并稍作修改。
|
||||
|
||||
#### 为什么需要变换矩阵?
|
||||
|
||||
在 2D 图形学中,变换矩阵(3x3)可以统一表示以下基本变换操作:
|
||||
|
||||
- 平移(Translation)
|
||||
- 旋转(Rotation)
|
||||
- 缩放(Scale)
|
||||
- 错切(Skew)
|
||||
|
||||
通过矩阵乘法可以将多个变换组合为单个矩阵运算,其通用数学表示为(列主序):
|
||||
|
||||
$Transform=\begin{bmatrix} a & b & 0 \\ c & d & 0 \\ e & f & 1 \end{bmatrix}$
|
||||
|
||||
其中:
|
||||
|
||||
- `a,d` 控制缩放和旋转
|
||||
- `b,c` 控制错切
|
||||
- `e,f` 控制平移
|
||||
|
||||
#### 变换组合原理
|
||||
|
||||
矩阵乘法具有结合性但不具有交换性,变换顺序会影响最终效果,矩阵按从右到左的顺序应用变换:
|
||||
|
||||
最终矩阵 = 平移矩阵 × 旋转矩阵 × 缩放矩阵 × 原始坐标
|
||||
|
||||
#### Transform 类核心功能
|
||||
|
||||
首先创建其实例:
|
||||
|
||||
```ts
|
||||
import { Transform } from '@motajs/render';
|
||||
|
||||
const trans = new Transform();
|
||||
```
|
||||
|
||||
之后可以链式调用来修改矩阵:
|
||||
|
||||
```ts
|
||||
// 链式调用示例
|
||||
trans
|
||||
.setTranslate(100, 50)
|
||||
.rotate(Math.PI / 4)
|
||||
.scale(2, 1.5);
|
||||
```
|
||||
|
||||
方法对比
|
||||
|
||||
| 方法类型 | 特点 | 函数 |
|
||||
| -------- | -------------------------- | ---------------------------------------------------- |
|
||||
| 叠加变换 | 在现有变换基础上叠加新变换 | `translate` `rotate` `scale` `transform` |
|
||||
| 直接设置 | 覆盖当前变换参数 | `setTranslate` `setRotate` `setScale` `setTransform` |
|
||||
|
||||
#### 关键方法详解
|
||||
|
||||
平移变换:
|
||||
|
||||
```ts
|
||||
// 相对移动(叠加)
|
||||
trans.translate(20, -10);
|
||||
// 绝对定位(覆盖)
|
||||
trans.setTranslate(200, 150);
|
||||
```
|
||||
|
||||
旋转变换:
|
||||
|
||||
```ts
|
||||
// 叠加旋转 45 度
|
||||
trans.rotate(Math.PI / 4);
|
||||
// 设置绝对旋转角度
|
||||
trans.setRotate(Math.PI / 2);
|
||||
```
|
||||
|
||||
缩放变换:
|
||||
|
||||
```ts
|
||||
// X 轴放大 2 倍,Y 轴不变(叠加)
|
||||
trans.scale(2, 1);
|
||||
// 设置绝对缩放比例
|
||||
trans.setScale(0.5, 0.8);
|
||||
```
|
||||
|
||||
#### 高级功能
|
||||
|
||||
矩阵操作:
|
||||
|
||||
```ts
|
||||
// 手动设置变换矩阵
|
||||
trans.setTransform(
|
||||
1,
|
||||
0, // 缩放部分
|
||||
0,
|
||||
1, // 旋转部分
|
||||
100,
|
||||
50 // 平移部分
|
||||
);
|
||||
// 矩阵相乘(组合变换)
|
||||
const combined = trans.multiply(otherTransform);
|
||||
```
|
||||
|
||||
坐标变换:
|
||||
|
||||
```ts
|
||||
// 将局部坐标转换为世界坐标,即计算一个坐标经过此变换矩阵计算后的位置
|
||||
const worldPos = trans.transformed(10, 20);
|
||||
// 将世界坐标转换回局部坐标,即计算一个坐标经过此变换矩阵逆转换后的位置
|
||||
const localPos = trans.untransformed(150, 80);
|
||||
```
|
||||
|
||||
#### 性能优化技巧
|
||||
|
||||
使用自动更新机制:
|
||||
|
||||
```ts
|
||||
import { ITransformUpdatable } from '@motajs/render';
|
||||
|
||||
// 绑定可更新对象
|
||||
class MyElement implements ITransformUpdatable {
|
||||
updateTransform() {
|
||||
console.log('变换已更新!');
|
||||
}
|
||||
}
|
||||
|
||||
const element = new MyElement();
|
||||
trans.bind(element); // 变换修改时自动触发 updateTransform
|
||||
```
|
||||
|
||||
#### 最佳实践
|
||||
|
||||
推荐变换执行顺序:
|
||||
|
||||
1. 缩放(Scale)
|
||||
2. 旋转(Rotate)
|
||||
3. 平移(Translate)
|
||||
|
||||
```ts
|
||||
// 正确顺序示例
|
||||
trans
|
||||
.setScale(2)
|
||||
.rotate(Math.PI / 3)
|
||||
.setTranslate(100, 50);
|
||||
```
|
||||
|
||||
组合复杂变换:
|
||||
|
||||
```ts
|
||||
// 创建子变换
|
||||
const childTrans = trans
|
||||
.clone() // 从 trans 复制一个相同的变换出来,以防止修改原变换
|
||||
.rotate(-Math.PI / 6)
|
||||
.translate(30, 0);
|
||||
|
||||
// 应用组合变换
|
||||
const finalTrans = trans.multiply(childTrans);
|
||||
```
|
||||
|
||||
#### 应用到元素
|
||||
|
||||
赋值给 `transform` 属性即可
|
||||
|
||||
```tsx
|
||||
<sprite transform={finalTrans} />
|
||||
```
|
||||
|
||||
#### 常见问题排查
|
||||
|
||||
1. 变换不生效?
|
||||
|
||||
- 验证绑定的对象是否实现 `updateTransform`
|
||||
- 检查有没有把 `trans` 对象赋值给元素的 `transform` 属性
|
||||
|
||||
2. 性能问题
|
||||
|
||||
- 避免高频调用 `setTransform`
|
||||
- 优先使用叠加方法代替矩阵直接操作
|
||||
- 利用 `clone()` 复用已有变换
|
||||
|
||||
## `sprite` 标签
|
||||
|
||||
`sprite` 标签是一个允许你自定义渲染内容的标签,它新增了一个属性 `render` 属性,允许你传入一个函数来执行自定义渲染。函数定义如下:
|
||||
|
||||
```ts
|
||||
type RenderFn = (canvas: MotaOffscreenCanvas2D, transform: Transform) => void;
|
||||
```
|
||||
|
||||
- `canvas`: 要渲染至的画布,一般直接将内容渲染至这个画布上
|
||||
- `transform`: 当前元素的变换矩阵,相对于父元素,不常用
|
||||
|
||||
多数情况下,我们只会使用到第一个参数,`MotaOffscreenCanvas2D` 接口请参考 [API 文档](../api/motajs-render-core)。下面是一个典型案例:
|
||||
|
||||
```tsx
|
||||
const render = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const { ctx, width, height } = canvas; // 获取画布上下文以及长宽
|
||||
ctx.fillStyle = '#d84'; // 设置填充样式
|
||||
ctx.fillRect(0, 0, width, height); // 绘制一个矩形
|
||||
};
|
||||
```
|
||||
|
||||
`sprite` 元素的应用场景并不算多,因为样板内置的各种元素已经足够丰富,此元素一般只会在一些特殊情况下或性能敏感情况下使用。
|
||||
|
||||
## `container` 标签
|
||||
|
||||
`container` 表示一个容器,它可以将一系列元素作为子元素并渲染。它并没有新增任何属性。如果你想渲染子元素,请务必使用此元素包裹,除此之外的大部分元素是不能渲染子元素的。
|
||||
|
||||
## `container-custom` 标签
|
||||
|
||||
`container-custom` 是另一种容器,它允许你自定义对子元素的渲染方案,对于特殊场景下有一定的作用,例如样板自带的 `Scroll` 组件就使用此标签实现。它新增了一个 `render` 参数,定义如下:
|
||||
|
||||
```ts
|
||||
type CustomContainerRenderFn = (
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
children: RenderItem[],
|
||||
transform: Transform
|
||||
) => void;
|
||||
```
|
||||
|
||||
- `canvas`: 要渲染至的画布,一般直接将内容渲染至这个画布上
|
||||
- `children`: 要渲染的子元素,按 `zIndex` 升序排列
|
||||
- `transform`: 当前元素的变换矩阵,相对于父元素,不常用
|
||||
|
||||
典型案例如下:
|
||||
|
||||
```tsx
|
||||
const render = (
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
children: RenderItem[],
|
||||
transform: Transform
|
||||
) => {
|
||||
// 顺序遍历子元素,保证纵深关系正确
|
||||
children.forEach(v => {
|
||||
if (v.hidden) return; // 如果元素隐藏,则不渲染
|
||||
// 调用子元素的渲染函数,传入 canvas 和 transform 作为参数
|
||||
v.renderContent(canvas, transform);
|
||||
});
|
||||
};
|
||||
|
||||
<container-custom render={render} />;
|
||||
```
|
||||
|
||||
## `text` 标签
|
||||
|
||||
`text` 标签用于显示文字,它会自动计算文字的宽高并设置为元素宽高,因此不要手动指定宽高,否则可能会引起位置错误。它新增了这些属性:
|
||||
|
||||
```ts
|
||||
interface TextProps extends BaseProps {
|
||||
/** 要渲染的文字 */
|
||||
text?: string;
|
||||
/** 文字的填充样式 */
|
||||
fillStyle?: CanvasStyle;
|
||||
/** 文字的描边样式 */
|
||||
strokeStyle?: CanvasStyle;
|
||||
/** 文字的字体 */
|
||||
font?: Font;
|
||||
/** 文字的描边粗细 */
|
||||
strokeWidth?: number;
|
||||
}
|
||||
```
|
||||
|
||||
典型案例如下:
|
||||
|
||||
```tsx
|
||||
import { Font } from '@motajs/render';
|
||||
|
||||
<text
|
||||
// 文字内容
|
||||
text="这是一段文字"
|
||||
// 文字定位,不要填写宽高,如果需要填写锚点,可以使用 anc 属性或宽高填 void 0
|
||||
loc={[32, 32]}
|
||||
// 填充样式,纯白色
|
||||
fillStyle="#fff"
|
||||
// 描边样式,红色
|
||||
strokeStyle="#d54"
|
||||
// 字体,使用大小为 24px 的默认字体
|
||||
font={new Font(24)}
|
||||
// 描边宽度 3px,默认为 2px
|
||||
strokeWidth={3}
|
||||
/>;
|
||||
```
|
||||
|
||||
## `image` 标签
|
||||
|
||||
`image` 标签允许你显示一张图片,包含一个 `image` 属性,传入图片对象(注意不是注册图片名称)。用例如下:
|
||||
|
||||
```tsx
|
||||
// 获取注册的图片
|
||||
const img = core.material.images.images['myImage.png'];
|
||||
// 显示图片
|
||||
<image image={img} />;
|
||||
```
|
||||
|
||||
## `icon` 标签
|
||||
|
||||
`icon` 标签用于显示一个图标,可以包含动画帧。它有如下参数:
|
||||
|
||||
```ts
|
||||
export interface IconProps extends BaseProps {
|
||||
/** 图标 id 或数字 */
|
||||
icon: AllNumbers | AllIds;
|
||||
/** 显示图标的第几帧 */
|
||||
frame?: number;
|
||||
/** 是否开启动画,开启后 frame 参数无效 */
|
||||
animate?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
使用案例如下:
|
||||
|
||||
```tsx
|
||||
// 显示绿史莱姆,开启动画
|
||||
<icon icon="greenSlime" animate />
|
||||
```
|
||||
|
||||
## `winskin` 标签
|
||||
|
||||
`winskin` 标签允许你显示一个 rpg maker 风格的背景图(window skin),它有如下参数:
|
||||
|
||||
```ts
|
||||
export interface WinskinProps extends BaseProps {
|
||||
/** winskin 的图片 id */
|
||||
image: ImageIds;
|
||||
/** 边框大小 */
|
||||
borderSize?: number;
|
||||
}
|
||||
```
|
||||
|
||||
其中边框大小默认为 32,表示上边框和下边框加起来共 32 像素,即四周边框 16 像素宽。用例如下:
|
||||
|
||||
```tsx
|
||||
// 使用 winskin.png 作为图片,四周边框宽度为 24 像素
|
||||
<winskin image="winskin.png" borderSize={48} />
|
||||
```
|
||||
|
||||
## 图形标签
|
||||
|
||||
本小节讲解图形相关的标签,以下内容由 `DeepSeek R1` 模型生成并稍作修改。
|
||||
|
||||
### 通用属性说明
|
||||
|
||||
所有图形元素均支持以下核心属性:
|
||||
|
||||
| 属性分类 | 关键参数 | 说明 |
|
||||
| -------------- | ------------------------- | -------------------------------------------------------- |
|
||||
| **填充与描边** | `fill` `stroke` | 控制是否填充/描边(不同元素默认值不同) |
|
||||
| **样式控制** | `fillStyle` `strokeStyle` | 填充和描边样式,支持颜色/渐变等(如 `'#f00'`) |
|
||||
| **线型设置** | `lineWidth` `lineDash` | 线宽、虚线模式(如 `[5, 3]` 表示 5 像素实线+3 像素间隙) |
|
||||
| **高级控制** | `fillRule` `actionStroke` | 填充规则(非零/奇偶)、是否仅在描边区域响应交互 |
|
||||
|
||||
### 矩形 `<g-rect>`
|
||||
|
||||
矩形的定位直接使用 `loc` 即可,示例如下:
|
||||
|
||||
```tsx
|
||||
// 基础矩形,矩形默认仅填充模式,因此如果需要描边的话需要手动指定 fill 和 stroke 参数
|
||||
// 注意如果仅指定 stroke 参数的话,会变为仅描边形式
|
||||
<g-rect loc={[100, 100, 200, 150]} fill stroke fillStyle="#f0f" lineWidth={2} />
|
||||
```
|
||||
|
||||
### 圆形和扇形 `<g-circle>`
|
||||
|
||||
参数如下:
|
||||
|
||||
```ts
|
||||
interface CirclesProps {
|
||||
radius: number; // 半径
|
||||
start?: number; // 起始弧度(默认0)
|
||||
end?: number; // 结束弧度(默认2π)
|
||||
/**
|
||||
* 圆属性参数,可以填 `[圆心 x 坐标,圆心 y 坐标,半径,起始角度,终止角度]`,是 x, y, radius, start, end 的简写,
|
||||
* 其中半径可选,后两项要么都填,要么都不填
|
||||
*/
|
||||
circle?: CircleParams;
|
||||
}
|
||||
```
|
||||
|
||||
示例如下:
|
||||
|
||||
```tsx
|
||||
// 完整圆形
|
||||
<g-circle circle={[300, 200, 10]} fillStyle="skyblue" />
|
||||
// 扇形(60度到180度)
|
||||
<g-circle circle={[400, 300, 40, Math.PI/3, Math.PI]} />
|
||||
```
|
||||
|
||||
### 直线 `<g-line>`
|
||||
|
||||
核心参数:
|
||||
|
||||
```ts
|
||||
interface LineProps {
|
||||
x1: number; // 起点X
|
||||
y1: number; // 起点Y
|
||||
x2: number; // 终点X
|
||||
y2: number; // 终点Y
|
||||
/** 直线属性简写参数,可以填 `[x1, y1, x2, y2]`,都是必填 */
|
||||
line: [number, number, number, number];
|
||||
}
|
||||
```
|
||||
|
||||
示例如下:
|
||||
|
||||
```tsx
|
||||
// 普通直线
|
||||
<g-line
|
||||
line={[50, 50, 200, 150]}
|
||||
strokeStyle="red"
|
||||
lineDash={[10, 5]} // 虚线样式
|
||||
/>
|
||||
|
||||
// 带箭头的参考线
|
||||
<g-line
|
||||
// 不使用简写形式
|
||||
x1={300} y1={80}
|
||||
x2={450} y2={220}
|
||||
lineCap="round" // 端点圆形
|
||||
lineWidth={4}
|
||||
/>
|
||||
```
|
||||
|
||||
### 三次贝塞尔曲线 `<g-bezier>`
|
||||
|
||||
核心参数:
|
||||
|
||||
```ts
|
||||
interface BezierProps {
|
||||
sx: number; // 起点X
|
||||
sy: number; // 起点Y
|
||||
cp1x: number; // 控制点1X
|
||||
cp1y: number; // 控制点1Y
|
||||
cp2x: number; // 控制点2X(三次贝塞尔)
|
||||
cp2y: number; // 控制点2Y
|
||||
ex: number; // 终点X
|
||||
ey: number; // 终点Y
|
||||
/** 三次贝塞尔曲线参数简写,可以填 `[sx, sy, cp1x, cp1y, cp2x, cp2y, ex, ey]`,都是必填 */
|
||||
curve: BezierParams;
|
||||
}
|
||||
```
|
||||
|
||||
示例如下:
|
||||
|
||||
```tsx
|
||||
// 三次贝塞尔曲线
|
||||
<g-bezier
|
||||
curve={[100, 100, 150, 50, 250, 200, 300, 100]}
|
||||
strokeStyle="purple"
|
||||
lineWidth={3}
|
||||
/>;
|
||||
|
||||
// 动态路径
|
||||
const path = computed(() => [
|
||||
startX.value,
|
||||
startY.value,
|
||||
control1X.value,
|
||||
control1Y.value,
|
||||
control2X.value,
|
||||
control2Y.value,
|
||||
endX.value,
|
||||
endY.value
|
||||
]);
|
||||
<g-bezier curve={path.value} />;
|
||||
```
|
||||
|
||||
### 二次贝塞尔曲线 `<g-quad>`
|
||||
|
||||
核心参数:
|
||||
|
||||
```ts
|
||||
interface BezierProps {
|
||||
sx: number; // 起点X
|
||||
sy: number; // 起点Y
|
||||
cpx: number; // 控制点X
|
||||
cpy: number; // 控制点Y
|
||||
ex: number; // 终点X
|
||||
ey: number; // 终点Y
|
||||
/** 二次贝塞尔曲线参数,可以填 `[sx, sy, cpx, cpy, ex, ey]`,都是必填 */
|
||||
curve: QuadParams;
|
||||
}
|
||||
```
|
||||
|
||||
示例如下:
|
||||
|
||||
```tsx
|
||||
// 二次贝塞尔曲线
|
||||
<g-bezier
|
||||
curve={[100, 100, 250, 200, 300, 100]}
|
||||
strokeStyle="purple"
|
||||
lineWidth={3}
|
||||
/>;
|
||||
|
||||
// 动态路径
|
||||
const path = computed(() => [
|
||||
startX.value,
|
||||
startY.value,
|
||||
controlX.value,
|
||||
controlY.value,
|
||||
endX.value,
|
||||
endY.value
|
||||
]);
|
||||
<g-bezier curve={path.value} />;
|
||||
```
|
||||
|
||||
### 圆角矩形 `<g-rectr>`
|
||||
|
||||
圆角矩形的核心参数与 CSS 的 border-radius 类似,如下:
|
||||
|
||||
```ts
|
||||
interface RectRProps extends GraphicPropsBase {
|
||||
/**
|
||||
* 圆形圆角参数,可以填 `[r1, r2, r3, r4]`,后三项可选。填写不同数量下的表现:
|
||||
* - 1个:每个角都是 `r1` 半径的圆
|
||||
* - 2个:左上和右下是 `r1` 半径的圆,右上和左下是 `r2` 半径的圆
|
||||
* - 3个:左上是 `r1` 半径的圆,右上和左下是 `r2` 半径的圆,右下是 `r3` 半径的圆
|
||||
* - 4个:左上、右上、左下、右下 分别是 `r1, r2, r3, r4` 半径的圆
|
||||
*/
|
||||
circle?: RectRCircleParams;
|
||||
/**
|
||||
* 椭圆圆角参数,可以填 `[rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4]`,
|
||||
* 两两一组,后三组可选,填写不同数量下的表现:
|
||||
* - 1组:每个角都是 `[rx1, ry1]` 半径的椭圆
|
||||
* - 2组:左上和右下是 `[rx1, ry1]` 半径的椭圆,右上和左下是 `[rx2, ry2]` 半径的椭圆
|
||||
* - 3组:左上是 `[rx1, ry1]` 半径的椭圆,右上和左下是 `[rx2, ey2]` 半径的椭圆,右下是 `[rx3, ry3]` 半径的椭圆
|
||||
* - 4组:左上、右上、左下、右下 分别是 `[rx1, ry1], [rx2, ry2], [rx3, ry3], [rx4, ry4]` 半径的椭圆
|
||||
*/
|
||||
ellipse?: RectREllipseParams;
|
||||
}
|
||||
```
|
||||
|
||||
示例如下:
|
||||
|
||||
```tsx
|
||||
// 四角圆角半径都为 10 的圆角矩形
|
||||
<g-rectr loc={[0, 0, 200, 200]} circle={[10]} />
|
||||
// 每个角都是横向半径为 10,纵向半径为 5 的椭圆
|
||||
<g-rectr loc={[0, 0, 200, 200]} ellipse={[10, 5]} />
|
||||
// 左上和右下是半径为 10 的圆角,左下和右上是半径为 25 的圆角
|
||||
<g-rectr loc={[0, 0, 200, 200]} circle={[10, 25]} />
|
||||
```
|
||||
|
||||
### 自定义路径 `<g-path>`
|
||||
|
||||
核心参数:
|
||||
|
||||
```ts
|
||||
interface PathProps {
|
||||
path?: Path2D; // 自定义路径对象
|
||||
}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```tsx
|
||||
// 创建五角星
|
||||
const starPath = new Path2D();
|
||||
// ...路径绘制逻辑
|
||||
<g-path
|
||||
path={starPath}
|
||||
fill
|
||||
stroke
|
||||
fillStyle="orange"
|
||||
strokeStyle="#c00"
|
||||
lineWidth={2}
|
||||
/>;
|
||||
```
|
||||
|
||||
### 最佳实践建议
|
||||
|
||||
1. 交互增强:
|
||||
|
||||
```tsx
|
||||
<g-rect
|
||||
fill
|
||||
stroke
|
||||
actionStroke // 仅在描边区域响应点击
|
||||
onClick={handleSelect}
|
||||
/>
|
||||
```
|
||||
|
||||
2. 样式复用:
|
||||
|
||||
```tsx
|
||||
// 创建样式对象
|
||||
const themeStyle = {
|
||||
fillStyle: '#2c3e50',
|
||||
strokeStyle: '#ecf0f1',
|
||||
lineWidth: 2
|
||||
};
|
||||
|
||||
<g-rect fill {...themeStyle} />
|
||||
<g-circle stroke {...themeStyle} />
|
||||
```
|
61
docs/guide/ui-faq.md
Normal file
61
docs/guide/ui-faq.md
Normal file
@ -0,0 +1,61 @@
|
||||
# UI 常见问题
|
||||
|
||||
## 为什么我的 UI 不显示?
|
||||
|
||||
检查 UI 的纵深(zIndex)是否符合预期,有没有被其他元素遮挡;检查当前元素是否处于 `hidden` 状态。可以在控制台输入 `logTagTree()` 来输出当前的渲染树 `xml` 标签结构,会包含一些重要信息。
|
||||
|
||||
第二种可能性是你的元素处在了父元素范围之外,导致被裁剪掉。注意,`transform` 属性是对元素本身的变换,这也会导致元素本身的矩形范围发生变化,如果你的元素设置了缩放、旋转等,需要考虑此属性对位置的影响。
|
||||
|
||||
## 为什么我的元素 onClick 事件没办法触发?
|
||||
|
||||
可能你的元素被纵深更高的元素覆盖,导致事件无法传播至你的元素,考虑在纵深更高的元素上添加 `noevent` 标识来禁用它的事件传播。注意,一个纯透明的元素也可能会覆盖你的元素,仔细查看你的渲染树结构。
|
||||
|
||||
第二种可能性是其子元素拦截了冒泡或是其父元素拦截了捕获,检查 `e.stopPropagation` 调用情况。
|
||||
|
||||
## 我的数据更新后,为什么渲染内容没有更新?
|
||||
|
||||
你可能在使用 `sprite` 元素,然后在渲染函数里面调用了外部数据,这样的话当外部数据更新时,你的 `sprite` 元素并不会自动更新,需要手动更新。手动更新参考代码:
|
||||
|
||||
```tsx
|
||||
import { Sprite } from '@motajs/render';
|
||||
|
||||
const mySprite = ref<Sprite>();
|
||||
// 数据更新时,同时更新 sprite 元素
|
||||
watch(data, () => mySprite.value?.update());
|
||||
|
||||
// 将 mySprite 传入 ref 参数,这样当挂载完毕后就会将 mySprite.value 设置为该元素
|
||||
<sprite ref={mySprite} render={render} />;
|
||||
```
|
||||
|
||||
除此之外还可能你的数据不是响应式数据,确保你的数据经过了 `reactive` 或 `ref` 包裹。
|
||||
|
||||
## 我的 UI 很卡
|
||||
|
||||
可能使用了平铺式布局,建议使用 `Scroll` 组件或者 `Page` 组件来对平铺内容分割,从而提高渲染效率。可以参考对应的 [API 文档](../api/user-client-modules/Scroll)。
|
||||
|
||||
## 玩着玩着突然黑屏了一下,然后画面就不显示了
|
||||
|
||||
你应该遇到了内存泄漏问题,当一个元素被卸载后,它应该会被销毁,但是如果没有被预期销毁,那么会导致内存泄漏,最终导致爆显存,就会导致画面黑屏一下,然后内容就会不显示。样板本身已经针对这个问题进行了处理,一般情况下不会出现问题,出现这个问题时大概率是你自己的组件或 UI 有问题。可能原因有很多,例如你声明了一个列表,当组件挂载时将元素放入列表,但是当组件卸载时,你却没有将元素移除,这时候就会导致这个元素无法正确被垃圾回收,从而引起内存泄漏。
|
||||
|
||||
关于这个问题的最佳实践:
|
||||
|
||||
- 如果你手动存储了一些元素,确保在卸载时将它们删除
|
||||
- 在删除它们的同时,调用它们的 `destroy` 方法,来确保可以被垃圾回收
|
||||
- 在控制台输入 `Mota.require('@motajs/render').MotaOffscreenCanvas2D.list` 来查看当前还有哪些画布正在使用,游玩一段时间后再次输入,检查数量是否增长,如果增长,说明发生了内存泄漏
|
||||
- 确保组件卸载时已经清空了定时器等内容
|
||||
- 如果需要每帧执行函数,请使用 `onTick` 接口,而非其他方法
|
||||
|
||||
如果你直接使用 `MotaOffscreenCanvas2D` 接口,请确保:
|
||||
|
||||
- 在使用前调用了 `activate` 方法
|
||||
- 在使用后调用了 `deactivate` 方法
|
||||
- 如果不需要再修改画布属性,只需要绘制,请调用 `freeze` 方法
|
||||
- 如果之后不再使用该画布,请调用 `destroy` 方法
|
||||
|
||||
## 为什么我的滤镜不显示?
|
||||
|
||||
很遗憾,截止目前(2.B 发布日期),IOS 依然没有支持 `CanvasRenderingContext2D` 上的 `filter` 方法,所有滤镜属性在 IOS 上将不会显示。不过,我们提供了 `Shader` 元素,它使用 `WebGL2` 接口,允许你制作自己的滤镜,如果滤镜是必要的,请考虑使用此元素,但是需要一定的图形学基础,可以在造塔群询问我或造塔辅助 AI。
|
||||
|
||||
## 不同设备的显示内容会不一样吗?
|
||||
|
||||
从理论上来讲,除了上面那个问题提到的滤镜,其他的所有内容的渲染结果应该完全一致,如果出现了不一致的情况,请上报样板 bug。
|
142
docs/guide/ui-perf.md
Normal file
142
docs/guide/ui-perf.md
Normal file
@ -0,0 +1,142 @@
|
||||
---
|
||||
lang: zh-CN
|
||||
---
|
||||
|
||||
# UI 优化指南
|
||||
|
||||
多数情况下,我们编写的简单 UI 并不需要特别的性能优化,渲染系统的懒更新及缓存机制已经可以有很优秀的性能表现,不过我们还是可能会遇到一些需要特殊优化的场景,本节将会讲述如何优化 UI 的性能表现,优化建议包括:避免元素平铺;使用 `Scroll` 或 `Page` 组件优化平铺性能;避免元素自我更新;使用 `cache` 和 `nocache` 标识;特殊场景禁用抗锯齿和高清;在合适场景下隐藏一些元素等。
|
||||
|
||||
## 避免元素平铺
|
||||
|
||||
在不使用 `Scroll` 组件时,我们需要尽量避免元素平铺,因为这会导致更新时渲染次数上升,从而引起性能下降。我们建议善用树形结构的缓存特性,将可以作为一个整体的元素使用一个容器 `container` 包裹起来,减少更新时的渲染次数,尤其是对于那些不常更新的元素来说,更应该使用容器包裹。不过我们也不建议嵌套过深,这可能导致浪费在递归渲染上的时间过长,渲染效率变低。
|
||||
|
||||
画布渲染树的深度遍历特性使得:
|
||||
|
||||
- 每个独立容器的更新会触发子树的重新渲染
|
||||
- 容器层级过深会增加递归调用栈开销
|
||||
- 合理分组可将高频/低频更新元素隔离
|
||||
|
||||
下面是代码示例:
|
||||
|
||||
```tsx
|
||||
// ❌ 差的写法,全部平铺在一个容器里
|
||||
<container>
|
||||
<text text="1" />
|
||||
{/* 中间省略 998 个元素 */}
|
||||
<text text="1000" />
|
||||
</container>
|
||||
|
||||
// ✅ 好的写法
|
||||
<container>
|
||||
{/* 把不常更新的单独放到一个容器里面 */}
|
||||
<container>
|
||||
<text text="1" />
|
||||
{/* 中间省略 988 个元素 */}
|
||||
<text text="990" />
|
||||
</container>
|
||||
{/* 把常更新的单独放到一个容器里面 */}
|
||||
<container>
|
||||
<text text="991" />
|
||||
{/* 中间省略 8 个元素 */}
|
||||
<text text="1000" />
|
||||
</container>
|
||||
</container>
|
||||
```
|
||||
|
||||
## 使用 `Scroll` 或 `Page` 组件优化平铺性能
|
||||
|
||||
在一些特殊情况下,我们不得不使用平铺布局,例如上一节提到的怪物手册,或是展示一个列表等,这时候必须平铺元素。这时候我们可以使用 `Scroll` 组件或 `Page` 组件来优化性能表现。`Scroll` 组件中,只有在画面内的元素会被渲染,而画面外的不会被渲染,这会大大提高渲染效率;`Page` 组件允许你把列表拆分成多个部分,然后把内容放在不同页中,从而提高渲染性能。极端情况下,`Page` 组件的渲染效率要明显高于 `Scroll` 组件,但是滚动条对于交互更友好,我们推荐在简单场景下使用 `Scroll` 组件,而对于复杂场景,换为 `Page` 组件。两个组件的使用方式可以参考 [API 文档](../api/motajs-render-elements/)。
|
||||
|
||||
我们建议:
|
||||
|
||||
1. **优先使用 Scroll**:
|
||||
- 元素数量 < 500
|
||||
- 需要流畅滚动交互
|
||||
- 元素高度不固定
|
||||
2. **切换至 Page**:
|
||||
- 元素数量 > 1000
|
||||
- 需要支持快速跳转
|
||||
- 存在复杂子组件(如嵌套动画)
|
||||
|
||||
下面是代码示例:
|
||||
|
||||
```tsx
|
||||
// ❌ 差的写法,全部平铺在一个容器里
|
||||
<container>
|
||||
<text text="1" />
|
||||
{/* 中间省略 998 个元素 */}
|
||||
<text text="1000" />
|
||||
</container>
|
||||
|
||||
// ✅ 好的写法,使用 Scroll 组件优化
|
||||
<Scroll>
|
||||
<text text="1" />
|
||||
{/* 中间省略 998 个元素 */}
|
||||
<text text="1000" />
|
||||
</Scroll>
|
||||
|
||||
// ✅ 好的写法,使用 Page 组件优化
|
||||
<Page>
|
||||
{(page: number) => {
|
||||
return list.slice(page * 10, (page + 1) * 10).map(v => <text text={v.toString()} />)
|
||||
}}
|
||||
</Page>
|
||||
```
|
||||
|
||||
## 避免元素自我更新
|
||||
|
||||
元素自我更新是指,在元素的渲染函数内,触发了元素的冒泡更新,这会导致更新无限循环,而且难以察觉。为了解决难以察觉的问题,我们使用了一种方式来专门探测这种情况。常见的触发元素自我更新的场景就是使用 `sprite` 元素,例如:
|
||||
|
||||
```tsx
|
||||
const element = ref<Sprite>();
|
||||
const render = () => {
|
||||
element.value?.update();
|
||||
};
|
||||
|
||||
<sprite render={render} ref={element} />;
|
||||
```
|
||||
|
||||
在上面这段渲染代码中,`sprite` 元素的渲染函数又再次触发了自我更新,这会导致更新无限循环。在开发环境下,这种情况会在控制台抛出警告:`Unexpected recursive call of Sprite.update?uid in render function. Please ensure you have to do this, if you do, ignore this warn.`,这会告诉你是哪个类型的元素触发了循环更新,以及对应元素的 `uid`,从而帮助你寻找问题所在。不过,样板还是留出了一个口子,如果你必须使用循环更新,那么你可以忽略此条警告,在网站上游玩时这条警告将不会被触发,游戏会正常运行。
|
||||
|
||||
## 使用 `cache` 和 `nocache` 标识
|
||||
|
||||
`cache` 和 `nocache` 表示可以让你更加精确地控制渲染树的缓存行为,从而更好地优化渲染性能。默认情况下,这些元素是会被缓存的:`container` `container-custom` `template` `sprite` `image` `icon` `layer` `layer-group` `animation`,对于这些元素,你可以使用 `nocache` 标识来禁用它们的缓存,对于其本身或其子元素的渲染较为简单的场景,禁用缓存后渲染效率可能会更高。其他元素默认是禁用缓存的,如果你的渲染内容比较复杂,例如 `g-path` 元素的路径很复杂,可以使用 `cache` 表示来启用缓存,从而提高渲染效率。示例代码如下:
|
||||
|
||||
```tsx
|
||||
const render = (canvas: MotaOffscreenCanvas2D) => {
|
||||
canvas.ctx.fillRect(0, 0, 200, 200);
|
||||
};
|
||||
// ❌ 差的写法,一个简单的矩形绘制,但是 sprite 默认启用缓存,可能会拉低渲染效率
|
||||
<sprite render={render} />;
|
||||
|
||||
// ✅ 好的写法,使用 nocache 标识禁用 sprite 的缓存机制
|
||||
<sprite render={render} nocache />;
|
||||
```
|
||||
|
||||
## 特殊场景禁用抗锯齿和高清
|
||||
|
||||
默认情况下,大部分元素都是默认启用高清即抗锯齿的(`layer` 和 `layer-group` `icon` 不启用),这可能会导致一些不必要的计算出现,从而拉低渲染性能。对于一些需要保持像素风的内容,我们建议关闭抗锯齿和高清画布。代码示例如下:
|
||||
|
||||
```tsx
|
||||
// ❌ 差的写法,像素风图片使用默认设置,启用了抗锯齿和高清
|
||||
<image image="pixel.png" />
|
||||
// ✅ 好的写法,关闭了默认的抗锯齿和高清
|
||||
<image image="pixel.png" noanti hd={false} />
|
||||
```
|
||||
|
||||
## 在合适场景下隐藏一些元素
|
||||
|
||||
如果一个元素在某些场景下需要隐藏,另一些场景下需要显示,我们建议使用 `hidden` 属性来设置,而不是通过把它移动到画面外、调成透明颜色、使用 `if` 或三元表达式判断等方式。示例代码如下:
|
||||
|
||||
```tsx
|
||||
// ❌ 差的写法,使用条件表达式切换元素显示与否
|
||||
{
|
||||
!hidden.value && <sprite />;
|
||||
}
|
||||
// ✅ 好的写法,使用 hidden 属性
|
||||
<sprite hidden={hidden.value} />;
|
||||
```
|
||||
|
||||
## 后续计划
|
||||
|
||||
我们后续计划推出渲染树调试工具,届时可以更加细致方便地查看渲染树的渲染情况以及性能问题。
|
@ -0,0 +1,243 @@
|
||||
# UI 系统
|
||||
|
||||
本节将会讲解 2.B 的渲染树与 UI 系统的工作原理,以及一些常用 API。
|
||||
|
||||
## 创建一个自己的 UI 管理器
|
||||
|
||||
样板提供 `UIController` 类,允许你在自己的一个 UI 中创建自己的 UI 管理器,例如在样板中,游戏画面本身包含一个 UI 管理器,分为了封面、加载界面、游戏界面三种,其中游戏界面里面还有一个游戏 UI 管理器,我们常用的就是最后一个游戏 UI 管理器。
|
||||
|
||||
### 创建 UIController 实例
|
||||
|
||||
我们从 `@motajs/system-ui` 引入 `UIController` 类,然后对其实例化:
|
||||
|
||||
```ts
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
|
||||
// 传入一个字符串来表示这个控制器的 id
|
||||
export const myController = new UIController('my-controller');
|
||||
```
|
||||
|
||||
### 获取 UI 控制器
|
||||
|
||||
可以通过 id 来获取到这个控制器,或者直接引入对应文件中的控制器:
|
||||
|
||||
```ts
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
import { myController } from './myController';
|
||||
|
||||
const myController = UIController.get('my-controller');
|
||||
```
|
||||
|
||||
### 添加到渲染树
|
||||
|
||||
接下来,可以直接调用 `myController.render` 方法来添加到你自己的 UI 中:
|
||||
|
||||
```tsx
|
||||
<container>{myController.render()}</container>
|
||||
```
|
||||
|
||||
## UI 显示模式
|
||||
|
||||
### 内置显示模式
|
||||
|
||||
UI 管理器内置了两种显示模式,只显示最后一个以及显示所有。其中前者常用于级联式 UI,例如 `设置 -> 系统设置 -> 快捷键设置`,这时候只会显示最后一个 UI,前面的 UI 不会显示。后者常用于展示信息类的 UI,例如在地图上展示怪物信息等。我们可以通过下面这两个方法来设置 UI 显示模式,立即生效,但不推荐频繁切换,建议一个控制器只使用**一种**显示模式:
|
||||
|
||||
```ts
|
||||
// 设置为只显示最后一个
|
||||
myController.lastOnly();
|
||||
// 设置为显示所有
|
||||
myController.showAll();
|
||||
```
|
||||
|
||||
### 栈模式
|
||||
|
||||
对于级联式 UI,我们希望在关闭一个 UI 时,在其之后的 UI 也能关闭,例如对于上面提到的 `设置 -> 系统设置 -> 快捷键设置` 级联 UI,当我们关闭设置界面时,我们会希望系统设置和快捷键设置也一并关闭,而不是需要手动关闭。这时候,栈模式就可以做到这一点,启用栈模式时,关闭一个 UI 后,在其之后的 UI 也会全部关闭。我们依然可以使用上面两个方法来设置是否启用栈模式:
|
||||
|
||||
```ts
|
||||
// 设置为显示最后一个,启用栈模式,不过 lastOnly 默认启用栈模式,因此参数可不填
|
||||
myController.lastOnly(true);
|
||||
// 设置为显示最后一个,不启用栈模式
|
||||
myController.lastOnly(false);
|
||||
```
|
||||
|
||||
### 自定义显示模式
|
||||
|
||||
::: info
|
||||
这一小节内容不重要,没有特殊需求的可以不看。
|
||||
:::
|
||||
|
||||
样板内置的两个显示模式以及栈模式已经能够满足绝大多数情况,不过可能还会有一些非常特殊的情况满足不了,这时候我们可以使用 `showCustom` 方法来自定义一个显示模式。这个方法要求传入一个参数,参数需要是 `IUICustomConfig` 对象,对象要求实现 `open` `close` `hide` `show` `update` 五个方法,我们来介绍一下如何做出一个自定义显示模式。
|
||||
|
||||
方法说明如下:
|
||||
|
||||
- `open` 方法会在一个 UI 打开时调用,例如默认的 `lastOnly` 模式其实就是在打开 UI 时将 UI 添加至栈末尾,然后隐藏在其之前的所有 UI
|
||||
- `close` 方法会在一个 UI 关闭时调用,例如默认的 `lastOnly` 模式就会在这个时候把在传入 UI 之后的所有 UI 一并关闭
|
||||
- `hide` 方法会在一个 UI 隐藏时调用,默认的 `lastOnly` 模式会在这个时候把 UI 隐藏显示
|
||||
- `show` 方法会在一个 UI 显示时调用,默认的 `lastOnly` 模式会在这个时候把 UI 启用显示
|
||||
- `update` 方法会在切换显示模式时调用,默认的 `lastOnly` 模式会在这个时候把最后一个 UI 显示,之前的隐藏
|
||||
|
||||
那么,假如我们要做一个反向 `lastOnly`,即只显示第一个,添加 UI 时添加至队列开头,我们可以这么写:
|
||||
|
||||
```ts
|
||||
import { IUICustomConfig, IUIInstance } from '@motajs/system-ui';
|
||||
|
||||
const myCustomMode: IUICustomConfig = {
|
||||
open(ins: IUIInstance, stack: IUIInstance[]) {
|
||||
stack.forEach(v => v.hide()); // 隐藏当前所有 UI
|
||||
stack.unshift(ins); // 将要打开的 UI 添加至队列开头
|
||||
ins.show(); // 显示要打开的 UI
|
||||
},
|
||||
close(ins: IUIInstance, stack: IUIInstance[], index: number) {
|
||||
stack.splice(0, index + 1); // 关闭传入 UI 及其之前的所有内容
|
||||
stack[0]?.show(); // 显示第一个 UI
|
||||
},
|
||||
hide(ins: IUIInstance, stack: IUIInstance[], index: number) {
|
||||
ins.hide(); // 直接隐藏
|
||||
},
|
||||
show(ins: IUIInstance, stack: IUIInstance[], index: number) {
|
||||
ins.show(); // 直接显示
|
||||
},
|
||||
update(stack: IUIInstance[]) {
|
||||
stack.forEach(v => v.hide()); // 先隐藏所有 UI
|
||||
stack[0]?.show(); // 然后显示第一个 UI
|
||||
}
|
||||
};
|
||||
|
||||
myController.showCustom(myCustomMode); // 应用自己的显示模式
|
||||
```
|
||||
|
||||
## 设置 UI 背景
|
||||
|
||||
我们可以为 UI 设置背景组件,背景组件在 UI 打开时常亮。我们推荐使用此方法来为 UI 设置背景,因为它可以搭配 `keep` 防抖动来使用,避免出现 UI 闪烁的问题。现在,我们使用样板内置的 `Background` 背景组件作为例子,来展示如何设置背景:
|
||||
|
||||
```ts
|
||||
import { Background } from '@user/client-modules';
|
||||
|
||||
// 传入背景组件作为背景,然后设置参数,使用 winskin.png 作为背景
|
||||
myController.setBackground(Background, { winskin: 'winskin.png' });
|
||||
```
|
||||
|
||||
默认情况下,当我们打开 UI 时,背景组件将会自动展示,不过我们也可以手动控制背景组件是否显示,它的优先级高于系统优先级:
|
||||
|
||||
```ts
|
||||
myController.hideBackground(); // 隐藏背景组件,即使有 UI 已经打开,也不会显示背景
|
||||
myController.showBackground(); // 显示背景组件,在 UI 已经打开的情况下展示,没有 UI 打开时不显示
|
||||
```
|
||||
|
||||
## 背景维持防抖动
|
||||
|
||||
有时候,我们需要关闭当前 UI 然后立刻打开下一个 UI,例如使用一个道具时可能会打开一个新的页面,这时候会先关闭道具背包界面,再打开道具的页面,这时候可能会出现短暂的“背景丢失”,这是因为 UI 的挂载需要时间,在极短的时间内如果没有挂载上,那么就会在屏幕上什么都不显示,上面设置的背景 UI 也不会显示,会引起一次闪烁,观感很差。为了解决这个问题,我们提供了背景维持防抖动的功能,使用 `keep` 方法来实现:
|
||||
|
||||
```ts
|
||||
const keep = myController.keep();
|
||||
```
|
||||
|
||||
调用此方法后,在下一次 UI 全部关闭时,背景会暂时维持,直到有 UI 打开,也就是说它会维持一次 UI 背景不会关闭,下一次就失效了。这样的话,如果我们去使用一个打开页面的道具,就不会出现闪烁的问题了。不过,假如我们使用了一个没有打开页面的道具,会有什么表现?答案是背景一直显示着,用户就什么也干不了了,这显然不是我们希望的,因此 `keep` 函数的返回值提供了一些能力来让你关闭背景,它们包括:
|
||||
|
||||
```ts
|
||||
// 推荐方法,使用 safelyUnload 安全地卸载背景,这样如果有 UI 已经打开,不会将其关闭
|
||||
keep.safelyUnload();
|
||||
// 不推荐方法,调用后立刻关闭所有 UI,不常用
|
||||
keep.unload();
|
||||
```
|
||||
|
||||
## 打开与关闭 UI
|
||||
|
||||
在 UI 编写章节已经提到了打开和关闭 UI 使用 `open` 和 `close` 方法,现在我们更细致地讲解一下如何打开与关闭 UI。打开 UI 使用 `open` 方法,定义如下:
|
||||
|
||||
```ts
|
||||
function open<T extends UIComponent>(
|
||||
ui: IGameUI<T>,
|
||||
props: UIProps<T>,
|
||||
alwaysShow?: boolean
|
||||
): IUIInstance;
|
||||
```
|
||||
|
||||
其中第一个参数表示要打开的 UI,第二个表示传给 UI 的参数,第三个表示 UI 是否永远保持显示状态(除非被关闭),不受到显示模式的影响。同种 UI 可以打开多个,也可以在不同的控制器上同时打开多个相同的 UI。例如,如果我们想在主 UI 控制器中添加一个常量的返回游戏按钮,就可以这么写:
|
||||
|
||||
```ts
|
||||
// BackToGame 是自定义 UI,第三个参数传 true 来保证它一直显示在画面上
|
||||
myController.open(BackToGame, {}, true);
|
||||
```
|
||||
|
||||
关闭 UI 使用 `close` 方法,传入 UI 实例,即 `open` 方法的返回值,没有其他参数。例如:
|
||||
|
||||
```ts
|
||||
const MyUI = defineComponent(props => {
|
||||
// 所有通过 UI 控制器打开的,同时按照 UI 模板填写了 props 的 UI 都包含 controller 和 instance 属性
|
||||
props.controller.close(props.instance);
|
||||
}, myUIProps);
|
||||
```
|
||||
|
||||
除此之外,还提供了一个关闭所有 UI 的:
|
||||
|
||||
```ts
|
||||
function closeAll(ui?: IGameUI): void;
|
||||
```
|
||||
|
||||
其中参数表示要关闭的 UI 类型,不填时表示关闭所有 UI,填写时表示关闭所有指定类型的 UI。例如我想关闭所有 `EnemyInfo` UI,可以这么写:
|
||||
|
||||
```ts
|
||||
// EnemyInfo 是自定义 UI
|
||||
myController.closeAll(EnemyInfo);
|
||||
```
|
||||
|
||||
## 渲染系统的树结构
|
||||
|
||||
接下来我们来讲解一下渲染系统的一些工作原理。下面的部分由 `DeepSeek R1` 模型生成并稍作修改。
|
||||
|
||||
### 结构原理
|
||||
|
||||
想象一棵倒着生长的树:
|
||||
|
||||
- 根节点:相当于画布本身,是所有元素的起点
|
||||
- 枝干节点:类似文件夹,可以包含其他元素
|
||||
- 叶子节点:实际显示的内容,如图片、文字等
|
||||
|
||||
### 运作特点
|
||||
|
||||
- 层级管理:子元素永远在父元素的"内部"显示
|
||||
- 自动排序:像叠扑克牌一样,后添加的元素默认盖在之前元素上方,不过也可以通过参数来调整顺序
|
||||
- 智能裁剪:父元素就像相框,超出范围的内容自动隐藏
|
||||
|
||||
## 渲染系统的事件系统
|
||||
|
||||
### 事件传递三阶段
|
||||
|
||||
1. 收件扫描(捕获阶段):从根部开始层层扫描,寻找可能接收事件的元素,类似快递分拣中心扫描包裹目的地
|
||||
2. 精准投递(目标阶段):找到实际触发事件的元素进行处理,就像快递员将包裹送到收件人手中
|
||||
3. 回执确认(冒泡阶段):处理结果沿着原路返回汇报,如同收件人签收后系统更新物流状态
|
||||
|
||||
将事件分为三个阶段,是为了让交互更加符合直觉,你也不想点击内层按钮的时候外层按钮也被触发吧)
|
||||
|
||||
### 特殊处理机制
|
||||
|
||||
- 紧急拦截:任何环节都可以标记"无需继续传递"
|
||||
- 批量处理:多个事件自动合并减少处理次数
|
||||
- 智能过滤:自动忽略不可见区域的事件
|
||||
|
||||
## 冒泡更新
|
||||
|
||||
### 工作原理
|
||||
|
||||
当某个元素发生变化时:自动通知直系父元素,父元素检查自身是否需要调整,继续向上传递直到根部,最终统一计算所有需要改变的位置,并在下一帧执行更新。
|
||||
|
||||
### 设计优势
|
||||
|
||||
- 精准定位:只更新受影响的部分画面
|
||||
- 避免重复:多个子元素变化只需一次整体计算
|
||||
- 顺序保障:始终从最深层开始逐层处理
|
||||
|
||||
## 懒更新机制
|
||||
|
||||
### 工作模式
|
||||
|
||||
1. 收集阶段:记录所有需要改变的内容(如颜色变化、文字修改)
|
||||
2. 等待时机:一般是等待到下一帧
|
||||
3. 批量处理:一次性完成所有修改
|
||||
|
||||
### 实际效益
|
||||
|
||||
- 性能优化:减少像频繁开关灯的资源浪费
|
||||
- 流畅保障:避免连续小改动导致的画面闪烁
|
||||
- 智能调度:优先处理用户可见区域的变化
|
850
docs/guide/ui.md
850
docs/guide/ui.md
@ -0,0 +1,850 @@
|
||||
---
|
||||
lang: zh-CN
|
||||
---
|
||||
|
||||
# UI 编写
|
||||
|
||||
本文将介绍如何在 2.B 样板中编写 UI,以及如何优化 UI 性能。
|
||||
|
||||
## 创建 UI 文件
|
||||
|
||||
首先,我们打开 `packages-user/client-modules/render` 文件夹,这里是目前样板的 UI 目录(之后可能会修改),我们可以看到 `components` `legacy` `ui` 三个文件夹,其中 `component` 是组件文件夹,也就是所有 UI 都可能用到的组件,例如滚动条、分页、图标等,这些东西不会单独组成一个 UI,但是可以方便 UI 开发。`legacy` 文件夹是将要删除或重构的内容,不建议使用里面的内容。`ui` 就是 UI 文件夹,这里面存放了所有的 UI,我们在这里创建一个文件 `myUI.tsx`。
|
||||
|
||||
## 编写 UI 模板
|
||||
|
||||
下面,我们需要编写 UI 模板,以怪物手册为例,模板如下,直接复制粘贴即可:
|
||||
|
||||
```tsx
|
||||
import { defineComponent } from 'vue';
|
||||
import { GameUI, UIComponentProps } from '@motajs/system-ui';
|
||||
import { SetupComponentOptions } from '../components';
|
||||
|
||||
export interface MyBookProps extends UIComponentProps {}
|
||||
|
||||
const myBookProps = {
|
||||
props: ['controller', 'instance']
|
||||
} satisfies SetupComponentOptions<MyBookProps>;
|
||||
|
||||
export const MyBook = defineComponent<MyBookProps>(props => {
|
||||
return () => <container></container>;
|
||||
}, myBookProps);
|
||||
|
||||
export const MyBookUI = new GameUI('my-book', MyBook);
|
||||
```
|
||||
|
||||
然后打开 `index.ts`,增加如下代码:
|
||||
|
||||
```ts
|
||||
export * from './myUI';
|
||||
```
|
||||
|
||||
## 添加一些内容
|
||||
|
||||
新的 UI 使用 tsx 编写,即 `TypeScript JSX`,可以直接在 ts 文件中编写 XML,非常适合编写 UI。例如,我们想要把 UI 的位置设为水平竖直居中,位置在 240, 240,长宽为 480, 480,并显示一个文字,可以这么写:
|
||||
|
||||
```tsx
|
||||
// ... 其他内容
|
||||
// loc 参数表示这个元素的位置,六个数分别表示:
|
||||
// 横纵坐标;长宽;水平竖直锚点,0.5 表示居中,1 表示靠右或靠下对齐,可以填不在 0-1 范围的数
|
||||
// 每两项组成一组,这两项要么都填,要么都不填,例如长宽可以都不填,横纵坐标可以都不填
|
||||
// 不填时会使用默认值,或是组件内部计算出的值
|
||||
return () => (
|
||||
<container loc={[240, 240, 480, 480, 0.5, 0.5]}>
|
||||
{/* 文字元素会自动计算长宽,因此不能手动指定 */}
|
||||
<text text="这是一段文字" loc={[240, 240, void 0, void 0, 0.5, 0.5]} />
|
||||
</container>
|
||||
);
|
||||
```
|
||||
|
||||
## 显示 UI
|
||||
|
||||
我们编写完 UI 之后,这个 UI 并不会自己显示,需要手动打开。我们找到 `ui/main.tsx`,在 `MainScene` 这个根组件中添加一句话:
|
||||
|
||||
```ts
|
||||
// 在这添加引入
|
||||
import { MyBookUI } from './ui';
|
||||
// ... 其他内容
|
||||
const MainScene = defineComponent(() => {
|
||||
// ... 其他内容
|
||||
// 在这添加一句话,打开 UI,第二个参数为传入 UI 的参数,后面会有讲解
|
||||
// 纵深设为 100 以保证可以显示出来,纵深越大,元素越靠上,会覆盖纵深低的元素
|
||||
mainUIController.open(MyBookUI, { zIndex: 100 });
|
||||
return () => (
|
||||
// ... 其他内容
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
这样的话,我们就会在页面上显示一个新的 UI 了!不过这个 UI 会是常亮的 UI,没办法关闭,我们需要更精细的控制。我们可以在内部使用 `props.controller` 来获取到 UI 控制器实例,使用 `props.instance` 获取到当前 UI 实例,从而控制当前 UI 的状态:
|
||||
|
||||
```tsx
|
||||
export const MyBook = defineComponent<MyBookProps>(props => {
|
||||
// 例如,我们可以让它在打开 10 秒钟后关闭:
|
||||
setTimeout(() => props.controller.close(props.instance), 10000);
|
||||
return () => (
|
||||
// ... UI 内容
|
||||
);
|
||||
}, myBookProps);
|
||||
```
|
||||
|
||||
除此之外,我们还可以在任意渲染端模块中引入 `ui/controller` 来获取到根组件的 UI 控制器,注意跨文件夹引入时需要引入 `@user/client-modules`。例如,我们可以在其他文件中控制这个 UI 的开启与关闭:
|
||||
|
||||
```ts
|
||||
import { mainUIController, MyBookUI } from './ui';
|
||||
import { IUIInstance } from '@motajs/system-ui';
|
||||
|
||||
let myBookInstance: IUIInstance;
|
||||
export function openMyBook() {
|
||||
// 使用一个变量来记录打开的 UI 实例
|
||||
myBookInstance = mainUIController.open(MyBookUI, {});
|
||||
}
|
||||
|
||||
export function closeMyBook() {
|
||||
// 传入 UI 实例,将会关闭此 UI 及其之后的 UI
|
||||
mainUIController.close(myBookInstance);
|
||||
}
|
||||
```
|
||||
|
||||
也可以使用 `Mota.require` 引入:
|
||||
|
||||
```ts
|
||||
const { mainUIController } = Mota.require('@user/client-modules');
|
||||
```
|
||||
|
||||
也可以通过 `UIController` 的接口获取其实例:
|
||||
|
||||
```ts
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
|
||||
const mainUIController = UIController.getController('main-ui');
|
||||
```
|
||||
|
||||
更多的 UI 控制功能可以参考后续文档以及相关的 [UI 系统指南](./ui-system.md) 或 [API 文档](../api/motajs-system-ui/UIController)。
|
||||
|
||||
## 添加更多内容
|
||||
|
||||
既然我们要编写一个简易怪物手册,那么仅靠上面这些内容当然不够,我们需要更多的元素和组件才行,下面我们来介绍一些常用的元素及组件。
|
||||
|
||||
### 图标
|
||||
|
||||
既然是怪物手册,那么图标必然不能少,图标是 `<icon>` 元素,需要传入 `icon` 参数,例如:
|
||||
|
||||
```tsx
|
||||
return () => (
|
||||
<container>
|
||||
{/* 显示绿史莱姆图标,位置在 (32, 32),循环播放动画 */}
|
||||
<icon icon="greenSlime" loc={[32, 32]} animate />
|
||||
</container>
|
||||
);
|
||||
```
|
||||
|
||||
### 字体
|
||||
|
||||
我们很多时候也会想要自定义字体,可以通过 `Font` 类来实现这个功能:
|
||||
|
||||
```tsx
|
||||
import { Font, FontWeight } from '@motajs/render';
|
||||
|
||||
// 创建一个字体,包含五个参数,第一个是字体名称,第二个是字体大小,第三个是字体大小的单位,一般是 'px'
|
||||
// 第四个是字体粗细,默认是 400,可以填 FontWeight.Bold,FontWeight.Light 或是数字,范围在 1-1000 之间
|
||||
// 第五个是是否斜体。每个参数都是可选,不填则使用默认字体的样式。
|
||||
const font = new Font('myFont', 24, 'px', FontWeight.Bold, false);
|
||||
// 可以将这个字体设置为默认字体,之后的所有没有指定的都会使用此字体
|
||||
Font.setDefaults(font);
|
||||
// 如果需要使用默认字体,有两种写法
|
||||
const font = new Font();
|
||||
const font = Font.defaults();
|
||||
|
||||
return () => (
|
||||
<container>
|
||||
<icon icon="greenSlime" loc={[32, 32]} animate />
|
||||
<text
|
||||
text="绿史莱姆"
|
||||
// 使用上面定义的字体
|
||||
font={font}
|
||||
// 靠左对齐,上下居中对齐
|
||||
loc={[64, 48, void 0, void 0, 0, 0.5]}
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
```
|
||||
|
||||
更多的字体使用方法可以参考 [API 文档](../api/motajs-render-style/Font)
|
||||
|
||||
### 圆角矩形
|
||||
|
||||
我们可以为怪物手册的一栏添加圆角矩形,写法如下:
|
||||
|
||||
```tsx
|
||||
return () => (
|
||||
<container>
|
||||
<g-rectr
|
||||
// 圆角矩形的位置
|
||||
loc={[16, 16, 480 - 32, 480 - 32]}
|
||||
// 圆角矩形为仅描边
|
||||
stroke
|
||||
// 圆角半径,可以设置四个,具体参考圆角矩形的文档
|
||||
circle={[8]}
|
||||
// 描边样式,这里设为了金色
|
||||
strokeStyle="gold"
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
```
|
||||
|
||||
### 线段
|
||||
|
||||
我们也可以添加线段,作为怪物列表之间的分割线:
|
||||
|
||||
```tsx
|
||||
return () => (
|
||||
<container>
|
||||
<g-line
|
||||
// 线段的起始位置和终止位置,不需要指定 loc 属性
|
||||
line={[16, 80, 480 - 16, 80]}
|
||||
// 线的端点为圆形
|
||||
lineCap="round"
|
||||
// 线宽为 1
|
||||
lineWidth={1}
|
||||
// 虚线样式,5 个像素为实,5 个像素为虚
|
||||
lineDash={[5, 5]}
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
```
|
||||
|
||||
### winskin 背景
|
||||
|
||||
我们可以为手册添加一个 winskin 背景,可以使用 `Background` 组件:
|
||||
|
||||
```tsx
|
||||
// 从 components 文件夹中引入这个组件
|
||||
import { Background } from '../components';
|
||||
|
||||
return () => (
|
||||
<container loc={[240, 240, 480, 480, 0.5, 0.5]}>
|
||||
<Background
|
||||
// 位置是相对于父元素的,因此从 (0, 0) 开始
|
||||
loc={[0, 0, 480, 480]}
|
||||
// 设置 winskin 的图片名称
|
||||
winskin="winskin.png"
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
```
|
||||
|
||||
### 滚动条
|
||||
|
||||
怪物多的话一页肯定显示不完,因此我们可以添加一个滚动条 `Scroll` 组件,用法如下:
|
||||
|
||||
```tsx
|
||||
// 从 components 文件夹中引入这个组件
|
||||
import { Scroll } from '../components';
|
||||
|
||||
return () => (
|
||||
// 使用滚动条组件替换 container 元素
|
||||
<Scroll loc={[240, 240, 480, 480, 0.5, 0.5]}> // [!code ++]
|
||||
<Background
|
||||
// 位置是相对于父元素的,因此从 (0, 0) 开始
|
||||
loc={[0, 0, 480, 480]}
|
||||
// 设置 winskin 的图片名称
|
||||
winskin="winskin.png"
|
||||
/>
|
||||
{/* 其他内容 */}
|
||||
</Srcoll> // [!code ++]
|
||||
);
|
||||
```
|
||||
|
||||
在使用滚动条时,建议使用平铺式布局,将每个独立的内容平铺显示,而不是整体包裹为一个 `container`,这有助于提高性能表现。
|
||||
|
||||
### 循环
|
||||
|
||||
编写怪物手册的话,我们就必须用到循环,因为我们需要遍历当前怪物列表,然后每个怪物生成一个 `container`,在这个 `container` 里面显示内容。tsx 为我们提供了嵌入表达式的功能,因此我们可以通过 `map` 方法来遍历怪物列表,然后返回一个元素,组成元素数组,实现循环遍历的功能。示例如下:
|
||||
|
||||
```tsx
|
||||
export const MyBook = defineComponent<MyBookProps>(props => {
|
||||
// 获取怪物列表,enemys 为 CurrenEnemy 数组,可以查看 package-user/data-fallback/src/battle.ts
|
||||
const enemys = core.getCurrentEnemys();
|
||||
// 工具函数,居中,靠右,靠左对齐文字
|
||||
const central = (x: number, y: number) => [x, y, void 0, void 0, 0.5, 0.5];
|
||||
const right = (x: number, y: number) => [x, y, void 0, void 0, 1, 0.5];
|
||||
const left = (x: number, y: number) => [x, y, void 0, void 0, 0, 0.5];
|
||||
|
||||
return () => (
|
||||
<Scroll>
|
||||
{/* 写一个 map 循环,将一个容器元素返回,就可以显示了 */}
|
||||
{enemys.map((v, i) => {
|
||||
return (
|
||||
<container loc={[0, 80 * i, 480, 80]}>
|
||||
{/* 怪物图标与怪物名称 */}
|
||||
<icon icon={v.enemy.id} loc={[32, 16, 32, 32]} />
|
||||
<text text={v.enemy.enemy.name} loc={central(48, 64)} />
|
||||
{/* 显示怪物的属性 */}
|
||||
<text text="生命" loc={right(96, 20)} />
|
||||
<text text={v.enemy.info.hp} loc={left(108, 20)} />
|
||||
{/* 其他的属性,例如攻击,防御等 */}
|
||||
</container>
|
||||
);
|
||||
})}
|
||||
</Scroll>
|
||||
);
|
||||
}, myBookProps);
|
||||
```
|
||||
|
||||
### 条件判断
|
||||
|
||||
可以在表达式中使用三元表达式或者立即执行函数来实现条件判断:
|
||||
|
||||
```tsx
|
||||
return () => (
|
||||
<Scroll>
|
||||
{enemys.length === 0 ? (
|
||||
// 无怪物时,显示没有剩余怪物
|
||||
<text text="没有剩余怪物" loc={central(240. 240)} font={new Font('Verdana', 48)} /> // [!code ++]
|
||||
) : (
|
||||
enemys.map(v => {
|
||||
// 有怪物时
|
||||
})
|
||||
)}
|
||||
</Scroll>
|
||||
);
|
||||
```
|
||||
|
||||
## 响应式
|
||||
|
||||
使用新的 UI 系统时,最大的优势就是响应式了,它可以让 UI 在数据发生变动时自动更改显示内容,而不需要手动重绘。本 UI 系统完全兼容 `vue` 的响应式系统,非常方便。
|
||||
|
||||
### 基础用法
|
||||
|
||||
例如,我想要给我的怪物手册添加一个楼层 id 的参数,首先我们先定义这个参数:
|
||||
|
||||
```tsx
|
||||
import { computed } from 'vue';
|
||||
|
||||
export interface MyBookProps extends UIComponentProps {
|
||||
// 定义 floorId 参数
|
||||
floorId: FloorIds;
|
||||
}
|
||||
|
||||
const myBookProps = {
|
||||
// 这里也要修改
|
||||
props: ['controller', 'instance', 'floorId']
|
||||
} satisfies SetupComponentOptions<MyBookProps>;
|
||||
```
|
||||
|
||||
然后我们需要在这个参数发生变动时修改怪物列表,可以这么写:
|
||||
|
||||
```tsx
|
||||
export const MyBook = defineComponent<MyBookProps>(props => {
|
||||
// 使用 computed,这样的话就会自动追踪到 props.floorId 参数,更新怪物列表,并更新显示内容
|
||||
const enemys = computed(() => core.getCurrentEnemys(props.floorId)); // [!code ++]
|
||||
|
||||
return () => (
|
||||
<Scroll>
|
||||
{/* 需要使用 enemys.value 属性,不能直接使用 enemys.length */}
|
||||
{enemys.value.length === 0 ? ( // [!code ++]
|
||||
<text text="没有剩余怪物" loc={central(240. 240)} font={new Font('Verdana', 48)} />
|
||||
) : (
|
||||
// 同上,需要 value 属性
|
||||
enemys.value.map(v => {}) // [!code ++]
|
||||
)}
|
||||
</Scroll>
|
||||
);
|
||||
}, myBookProps);
|
||||
```
|
||||
|
||||
### 什么样的变量能使用响应式
|
||||
|
||||
其实,我们用一般的方式编写的变量或常量都是不能使用响应式的,例如这些都不行:
|
||||
|
||||
```ts
|
||||
let num = 10;
|
||||
let str = '123';
|
||||
|
||||
const num2 = computed(() => num * 2);
|
||||
const str2 = computed(() => parseInt(str));
|
||||
```
|
||||
|
||||
这么写的话,是没有响应式效果的,这是因为 `num` 和 `str` 并不是响应式变量,不能追踪到。对于 `string` `number` `boolean` 这些字面量类型的变量,我们需要使用 `ref` 函数包裹才可以:
|
||||
|
||||
```tsx
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 使用 ref 函数包裹
|
||||
const num = ref(10);
|
||||
// 使用 num.value 属性调用
|
||||
const num2 = computed(() => num.value * 2);
|
||||
// 使用 num.value 修改值
|
||||
num.value = 20;
|
||||
|
||||
// 这样的话就有响应式效果了
|
||||
<text text={num2.value.toString()} />;
|
||||
```
|
||||
|
||||
对于对象类型来说,需要使用 `reactive` 函数包裹,这个函数会把对象变成深层响应式,任何一级发生更改都会触发响应式更新,例如:
|
||||
|
||||
```tsx
|
||||
const obj = reactive({ obj1: { num: 10 } });
|
||||
|
||||
// 这个就不需要使用 value 属性了,只有 ref 函数包裹的需要
|
||||
obj.obj1.num = 20;
|
||||
|
||||
// 直接调用即可,当值更改时内容也会自动更新
|
||||
<text text={obj.obj1.num.toString()} />;
|
||||
```
|
||||
|
||||
数组也可以使用 `reactive` 方法来实现响应式:
|
||||
|
||||
```tsx
|
||||
// 传入一个泛型来指定这个变量的类型,这里使用数字数组作为示例
|
||||
const array = reactive<number[]>([]);
|
||||
|
||||
// 可以使用数组自身的方法添加或修改元素
|
||||
array.push(100);
|
||||
|
||||
<container>
|
||||
{/* 直接对数组遍历,数组修改后这段内容也会自动更新 */}
|
||||
{array.map(v => (
|
||||
<text text={v.toString()} />
|
||||
))}
|
||||
</container>;
|
||||
```
|
||||
|
||||
如果对象比较大,只想让第一层变为响应式,深层的不变,可以使用 `shallowReactive` 或 `shallowRef`,或使用 `markRaw` 手动标记不需要响应式的部分:
|
||||
|
||||
```ts
|
||||
// 这样的话,当 obj1.obj1.num 修改时,就不会触发响应式,而 obj1.obj1 修改时会触发
|
||||
const obj1 = shallowReactive({ obj1: { num: 10 } });
|
||||
// 使用 shallowRef,也可以变成浅层响应式
|
||||
const obj2 = shallowRef({ obj1: { num: 10 } });
|
||||
// 或者手动标记为不需要响应式
|
||||
const obj3 = reactive({ obj1: markRaw({ num: 10 }) });
|
||||
```
|
||||
|
||||
响应式不仅可以用在 `computed` 或者是渲染元素中,还可以使用 `watch` 监听。不过该方法有一定的限制,那就是尽量不要在组件顶层之外使用。下面是一些例子:
|
||||
|
||||
::: code-group
|
||||
|
||||
```ts [ref]
|
||||
const num1 = ref(10);
|
||||
const num2 = ref(20);
|
||||
|
||||
watch(num1, (newValue, oldValue) => {
|
||||
// 当 num1 的值发生变化时,在控制台输出新值和旧值
|
||||
console.log(newValue, oldValue);
|
||||
|
||||
// 这里就不是组件顶层,不要使用 watch。如果需要条件判断的话,可以在监听函数内部判断,而不是外部
|
||||
watch(num2, () => {});
|
||||
});
|
||||
```
|
||||
|
||||
```ts [reactive]
|
||||
const obj = reactive({
|
||||
num: 10,
|
||||
obj1: {
|
||||
num2: 20
|
||||
}
|
||||
});
|
||||
|
||||
// 监听 obj.num
|
||||
watch(
|
||||
() => obj.num,
|
||||
(newValue, oldValue) => {
|
||||
console.log(newValue, oldValue);
|
||||
}
|
||||
);
|
||||
// 监听 obj 整体
|
||||
watch(obj, () => {
|
||||
console.log(obj.num);
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: info
|
||||
传入组件的 `props` 参数也是响应式的,可以通过 `watch` 监听,或使用 `computed` 追踪。
|
||||
:::
|
||||
|
||||
关于更多 `vue` 响应式的知识,可以查看 [Vue 官方文档](https://cn.vuejs.org/)
|
||||
|
||||
## 鼠标与触摸交互事件
|
||||
|
||||
### 监听鼠标或触摸
|
||||
|
||||
通过上面这些内容,我们已经可以搭出来一个完整的怪物手册页面了,不过现在这个页面是死的,还没办法交互,我们需要让它有办法交互,允许用户点击和按键操作。UI 系统提供了丰富方便的接口来实现交互动作的监听,例如监听点击可以使用 `onClick`:
|
||||
|
||||
```tsx
|
||||
const click = () => {
|
||||
console.log('clicked!');
|
||||
};
|
||||
|
||||
// 直接将函数传入 onClick 属性即可
|
||||
<container onClick={click}>{/* 渲染内容 */}</container>;
|
||||
```
|
||||
|
||||
可以使用 `cursor` 属性来指定鼠标移动到该元素上时的指针样式,如下例所示,鼠标移动到这个容器上时就会变成小手的形状:
|
||||
|
||||
```tsx
|
||||
<container cursor="pointer" />
|
||||
```
|
||||
|
||||
鼠标与触摸事件的触发包括两个阶段,从根节点捕获,然后一路传递到最下层,然后从最下层冒泡,然后一路再传递回根节点,一般情况下我们使用冒泡阶段的监听即可,也就是 `onXxx`,例如 `onClick` 等,不过如果我们需要监听捕获阶段的事件,也可以使用 `onXxxCapture` 的方法来监听:
|
||||
|
||||
```tsx
|
||||
const clickCapture = () => {
|
||||
console.log('click capture.');
|
||||
};
|
||||
const click = () => {
|
||||
console.log('click bubble.');
|
||||
};
|
||||
|
||||
<container onClick={click} onClickCapture={clickCapture} />;
|
||||
```
|
||||
|
||||
当点击这个容器时,就会先触发 `clickCapture` 事件,再触发 `click` 事件。
|
||||
|
||||
### 监听事件的类型
|
||||
|
||||
鼠标和触摸交互包含如下类型:
|
||||
|
||||
- `click`: 当按下与抬起都发生在这个元素上时触发,冒泡阶段
|
||||
- `clickCapture`: 同上,捕获阶段
|
||||
- `down`: 当在这个元素上按下时触发,冒泡阶段
|
||||
- `downCapture`: 同上,捕获阶段
|
||||
- `up`: 当在这个元素上抬起时触发,冒泡阶段
|
||||
- `upCapture`: 同上,捕获阶段
|
||||
- `move`: 当在这个元素上移动时触发,冒泡阶段
|
||||
- `moveCapture`: 同上,捕获阶段
|
||||
- `enter`: 当进入这个元素时触发,顺序不固定,没有捕获阶段与冒泡阶段的分类
|
||||
- `leave`: 当离开这个元素时触发,顺序不固定,没有捕获阶段与冒泡阶段的分类
|
||||
- `wheel`: 当在这个元素上滚轮时触发,冒泡阶段
|
||||
- `wheelCapture`: 同上,捕获阶段
|
||||
|
||||
触发顺序如下,滚轮单独列出,不在下述顺序中:
|
||||
|
||||
1. `downCapture`,按下捕获
|
||||
2. `down`: 按下冒泡
|
||||
3. `moveCapture`: 移动捕获
|
||||
4. `move`: 移动冒泡
|
||||
5. `leave`: 离开元素
|
||||
6. `enter`: 进入元素
|
||||
7. `upCapture`: 抬起捕获
|
||||
8. `up`: 抬起冒泡
|
||||
9. `clickCapture`: 点击捕获
|
||||
10. `click`: 点击冒泡
|
||||
|
||||
### 阻止事件传播
|
||||
|
||||
有时候我们需要阻止交互事件的继续传播,例如按钮套按钮时,我们不希望点击内部按钮时也触发外部按钮,这时候我们需要在内部按钮中阻止冒泡的继续传播。每个交互事件都可以接受一个参数,调用这个参数的 `stopPropagation` 方法即可阻止冒泡或捕获的继续传播:
|
||||
|
||||
```tsx
|
||||
import { IActionEvent } from '@motajs/render';
|
||||
|
||||
const click1 = (e: IActionEvent) => {
|
||||
// 调用以阻止冒泡的继续传播
|
||||
e.stopPropagation();
|
||||
console.log('click1');
|
||||
};
|
||||
const click2 = () => {
|
||||
console.log('click2');
|
||||
};
|
||||
|
||||
<container onClick={click2}>
|
||||
<container onClick={click1}></container>
|
||||
</container>;
|
||||
```
|
||||
|
||||
在上面这个例子中,当我们点击内层的容器时,只会触发 `click1`,而不会触发 `click2`,只有当我们点击外层容器时,才会触发 `click2`,这样就成功避免了内外两个按钮同时触发的场景。
|
||||
|
||||
### 事件对象的属性
|
||||
|
||||
事件包含很多属性,它们定义如下,其中 `IActionEventBase` 是 `enter` `leave` 的事件对象,`IActionEvent` 是按下、抬起、移动、点击的事件对象,`IWheelEvent` 是滚轮的事件对象。
|
||||
|
||||
::: code-group
|
||||
|
||||
```ts [IActionEventBase]
|
||||
interface IActionEventBase {
|
||||
/** 当前事件是监听的哪个元素 */
|
||||
target: RenderItem;
|
||||
/** 是触摸操作还是鼠标操作 */
|
||||
touch: boolean;
|
||||
/**
|
||||
* 触发的按键种类,会出现在点击、按下、抬起三个事件中,而其他的如移动等该值只会是 {@link MouseType.None},
|
||||
* 电脑端可以有左键、中键、右键等,手机只会触发左键,每一项的值参考 {@link MouseType}
|
||||
*/
|
||||
type: MouseType;
|
||||
/**
|
||||
* 当前按下了哪些按键。该值是一个数字,可以通过位运算判断是否按下了某个按键。
|
||||
* 例如通过 `buttons & MouseType.Left` 来判断是否按下了左键。
|
||||
* 注意在鼠标抬起或鼠标点击事件中,并不会包含触发的那个按键
|
||||
*/
|
||||
buttons: number;
|
||||
/** 触发时是否按下了 alt 键 */
|
||||
altKey: boolean;
|
||||
/** 触发时是否按下了 shift 键 */
|
||||
shiftKey: boolean;
|
||||
/** 触发时是否按下了 ctrl 键 */
|
||||
ctrlKey: boolean;
|
||||
/** 触发时是否按下了 Windows(Windows) / Command(Mac) 键 */
|
||||
metaKey: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
```ts [IActionEvent]
|
||||
export interface IActionEvent extends IActionEventBase {
|
||||
/** 这次操作的标识符,在按下、移动、抬起阶段中保持不变 */
|
||||
identifier: number;
|
||||
/** 相对于触发元素左上角的横坐标 */
|
||||
offsetX: number;
|
||||
/** 相对于触发元素左上角的纵坐标 */
|
||||
offsetY: number;
|
||||
/** 相对于整个画布左上角的横坐标 */
|
||||
absoluteX: number;
|
||||
/** 相对于整个画布左上角的纵坐标 */
|
||||
absoluteY: number;
|
||||
|
||||
/**
|
||||
* 调用后将停止事件的继续传播。
|
||||
* 在捕获阶段,将会阻止捕获的进一步进行,在冒泡阶段,将会阻止冒泡的进一步进行。
|
||||
* 如果当前元素有很多监听器,该方法并不会阻止其他监听器的执行。
|
||||
*/
|
||||
stopPropagation(): void;
|
||||
}
|
||||
```
|
||||
|
||||
```ts [IWheelEvent]
|
||||
export interface IWheelEvent extends IActionEvent {
|
||||
/** 滚轮事件的鼠标横向滚动量 */
|
||||
wheelX: number;
|
||||
/** 滚轮事件的鼠标纵向滚动量 */
|
||||
wheelY: number;
|
||||
/** 滚轮事件的鼠标垂直屏幕的滚动量 */
|
||||
wheelZ: number;
|
||||
/** 滚轮事件的滚轮类型,表示了对应值的单位 */
|
||||
wheelType: WheelType;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
需要特别说明的是 `identifier` 属性,这个属性在移动端的表现没有异议,但是在电脑端,我们完全可以按下鼠标左键后,再按下鼠标右键,再按下鼠标侧键,抬起鼠标右键,抬起鼠标左键,再抬起鼠标侧键,这种情况下,我们必须单独定义 `identifier` 应该指代的是哪个。它遵循如下原则:
|
||||
|
||||
1. 按下、抬起、点击**永远**保持为同一个 `identifier`
|
||||
2. 移动过程中,使用最后一个按下的按键的 `identifier` 作为移动事件的 `identifier`
|
||||
3. 如果移动过程中,最后一个按下的按键抬起,那么依然会维持**原先的** `identifer`,**不会**回退至上一个按下的按键
|
||||
|
||||
除此之外,滚轮事件中的 `identifier` 永远为 -1。
|
||||
|
||||
## 监听按键操作
|
||||
|
||||
### 注册按键命令
|
||||
|
||||
首先,我们应该注册一个按键命令,我们从 `@motajs/system-action` 中引入 `gameKey` 常量,在模块顶层注册一个按键命令:
|
||||
|
||||
```ts
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
|
||||
gameKey
|
||||
// 将后面注册的内容形成一个组,在修改快捷键时比较直观
|
||||
// 命名建议为 @ui_[UI 名称]
|
||||
.group('@ui_mybook', '示例怪物手册')
|
||||
.register({
|
||||
// 命名时,建议使用 @ui_[UI 名称]_[按键名称] 的格式
|
||||
id: '@ui_mybook_moveUp',
|
||||
// 在自定义快捷键界面显示的名称
|
||||
name: '上移一个怪物',
|
||||
// 默认按键
|
||||
defaults: KeyCode.ArrowUp
|
||||
})
|
||||
// 可以继续注册其他的,这里不再演示
|
||||
.register({});
|
||||
```
|
||||
|
||||
### 实现按键操作
|
||||
|
||||
然后,我们需要从 `@motajs/render` 中引入 `useKey` 函数,然后在组件顶层这么使用:
|
||||
|
||||
```tsx
|
||||
import { useKey } from '@motajs/render';
|
||||
|
||||
export const MyBook = defineComponent<MyBookProps>(props => {
|
||||
// 第一个参数是按键实例,第二个参数是按键作用域,一般用不到
|
||||
const [key, scope] = useKey();
|
||||
|
||||
return () => <container />;
|
||||
});
|
||||
```
|
||||
|
||||
最后,实现按键操作,使用 `key.realize` 方法:
|
||||
|
||||
```tsx
|
||||
import { clamp } from 'lodash-es';
|
||||
|
||||
export const MyBook = defineComponent<MyBookProps>(props => {
|
||||
const selected = ref(0); // [!code ++]
|
||||
const [key, scope] = useKey();
|
||||
|
||||
// 实现按键操作,让选中的怪物索引减一 // [!code ++]
|
||||
key.realize('@ui_mybook_moveUp', () => {
|
||||
// clamp 函数是 lodash 库中的函数,可以将值限定在指定范围内 // [!code ++]
|
||||
selected.value = clamp(0, enemys.value.length - 1, selected.value - 1); // [!code ++]
|
||||
});
|
||||
|
||||
return () => <container />;
|
||||
});
|
||||
```
|
||||
|
||||
## 绘制选择框与动画
|
||||
|
||||
### 定义选择框动画
|
||||
|
||||
下面我们来把选择框加上,当按下方向键时,选择框会移动,当按下确定键时,会打开这个怪物的详细信息。首先,我们使用一个描边格式的 `g-rectr` 圆角矩形元素作为选择框:
|
||||
|
||||
```tsx
|
||||
<Scroll>
|
||||
<g-rectr loc={[16, 16, 480 - 32, 480 - 32]} stroke strokeStyle="gold" />
|
||||
</Scroll>
|
||||
```
|
||||
|
||||
接下来,我们需要让它能够移动,当用户按下按键时,选择框会平滑移动到目标位置。这时候,我们可以使用动画接口 `transitioned` 来实现平滑移动。我们需要先用它定义一个动画对象:
|
||||
|
||||
```ts
|
||||
// 这个函数在用户代码里面,直接引入
|
||||
import { transitioned } from '../use';
|
||||
// 从高级动画库中引入双曲速率曲线,该曲线视角效果相对较好
|
||||
import { hyper } from 'mutate-animate';
|
||||
|
||||
// 创建一个纵坐标动画对象,初始值为 0(第一个参数),动画时长 150ms(第二个参数)
|
||||
// 曲线为 慢-快-慢 的双曲正弦曲线(第三个参数)
|
||||
const rectY = transitioned(0, 150, hyper('sin', 'in-out'));
|
||||
```
|
||||
|
||||
然后,我们需要通过 `computed` 方法来动态生成圆角矩形的位置:
|
||||
|
||||
```ts
|
||||
const rectLoc = computed(() => [
|
||||
16,
|
||||
// 使用 rectY.ref.value 获取到动画对象的响应式变量
|
||||
rectY.ref.value,
|
||||
480 - 32,
|
||||
480 - 32
|
||||
]);
|
||||
```
|
||||
|
||||
最后,我们把圆角矩形的 `loc` 属性设为 `computed` 值:
|
||||
|
||||
```tsx
|
||||
<Scroll>
|
||||
<g-rectr loc={rectLoc.value} stroke strokeStyle="gold" />
|
||||
</Scroll>
|
||||
```
|
||||
|
||||
### 执行动画
|
||||
|
||||
接下来,我们需要监听当前选中怪物,然后根据当前怪物来设置元素位置,使用 `watch` 监听 `selected` 变量:
|
||||
|
||||
```ts
|
||||
watch(selected, value => {
|
||||
// 使用 set 方法来动画至目标值
|
||||
rectY.set(16 + value * 80);
|
||||
});
|
||||
```
|
||||
|
||||
除此之外,我们还可以添加当鼠标移动至怪物元素上时,选择框也移动至目标,我们需要监听 `onEnter` 事件:
|
||||
|
||||
```tsx
|
||||
const onEnter = (index: number) => {
|
||||
// 前面已经监听过 selected 了,这里直接设置即可,不需要再调用 rectY.set
|
||||
// 不过调用了也不会有什么影响,动画会智能处理这种情况
|
||||
selected.value = index;
|
||||
};
|
||||
|
||||
<Scroll>
|
||||
{/* 把圆角矩形的纵深调大,防止被怪物容器遮挡 */}
|
||||
<g-rectr loc={rectLoc.value} stroke strokeStyle="gold" zIndex={10} />
|
||||
{enemys.map((v, i) => {
|
||||
// 元素内容不再展示。监听时,需要传入一个函数,因此需要使用匿名箭头函数包裹,
|
||||
// 添加 void 关键字是为了防止返回值泄漏,不过在这里并不是必要,因为 onEnter 没有返回值
|
||||
return <container onEnter={() => void onEnter(i)}></container>;
|
||||
})}
|
||||
</Scroll>;
|
||||
```
|
||||
|
||||
### 处理重叠
|
||||
|
||||
如果你去尝试着使用上面这个方法来实现动画,并给每个怪物添加了一个点击事件,你会发现你可能无法触发选中怪物的点击事件,这是因为 `g-rectr` 的纵深 `zIndex` 较高,交互事件会传播至此元素,而不会传播至下层元素,于是就不会触发点击事件。样板自然也考虑到了这种情况,我们只需要给圆角矩形添加一个 `noevent` 标识,即可让交互事件不会受到此元素的影响,不过相应地,这个元素上的交互事件也将会无法触发。示例如下:
|
||||
|
||||
```tsx
|
||||
<Scroll>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
stroke
|
||||
strokeStyle="gold"
|
||||
zIndex={10}
|
||||
// 添加 noevent 标识,事件就不会传播至此元素
|
||||
noevent // [!code ++]
|
||||
/>
|
||||
{enemys.map((v, i) => {
|
||||
return <container onEnter={() => void onEnter(i)}></container>;
|
||||
})}
|
||||
</Scroll>
|
||||
```
|
||||
|
||||
## 调用 Scroll 组件接口
|
||||
|
||||
我们现在已经实现了按键操作,但是移动时并不能同时修改滚动条的位置,这会导致当前选中的怪物跑到画面之外,这时候我们需要自动滚动到目标位置,可以使用 `Scroll` 组件暴露出的接口来实现。我们使用 `ref` 属性来获取其接口:
|
||||
|
||||
```tsx
|
||||
import { ScrollExpose } from './components';
|
||||
|
||||
const scrollExpose = ref<ScrollExpose>();
|
||||
|
||||
<Scroll ref={scrollExpose}></Scroll>;
|
||||
```
|
||||
|
||||
然后,我们可以调用其 `scrollTo` 方法来滚动至目标位置:
|
||||
|
||||
```tsx
|
||||
import { ScrollExpose } from './components';
|
||||
|
||||
const scrollExpose = ref<ScrollExpose>();
|
||||
|
||||
watch(selected, () => {
|
||||
// 滚动到选中怪物上下居中的位置,组件内部会自动处理滚动条边缘,因此不需要担心为负值
|
||||
scrollExpose.value.scrollTo(selected.value * 80 - 240);
|
||||
});
|
||||
|
||||
<Scroll ref={scrollExpose}></Scroll>;
|
||||
```
|
||||
|
||||
## 修改 UI 参数
|
||||
|
||||
在打开 UI 时,我们可以传入参数,默认情况下,可以传入所有的 `BaseProps`,也就是所有元素通用属性,以及自己定义的 UI 参数。`BaseProps` 内容较多,可以参考 [API 文档](../api/motajs-render-vue/RenderItem.md)。除此之外,我们还为这个自定义怪物手册添加了 `floorId` 参数,它也可以在打开 UI 时传入。如果需要打开的 UI 参数具有响应式,例如可以动态修改楼层 id,可以使用 `reactive` 方法。示例如下:
|
||||
|
||||
```ts
|
||||
import { MyBookProps, MyBookUI } from './myUI';
|
||||
|
||||
const props = reactive<MyBookProps>({
|
||||
floorId: 'MT0',
|
||||
zIndex: 100
|
||||
});
|
||||
|
||||
mainUIController.open(MyBookUI, props);
|
||||
```
|
||||
|
||||
我们可以监听状态栏更新来实时更新参数:
|
||||
|
||||
```ts
|
||||
import { hook } from '@user/data-base';
|
||||
|
||||
// 监听状态栏更新事件
|
||||
hook.on('updateStatusBar', () => {
|
||||
// 状态栏更新时,修改怪物手册的楼层为当前楼层 id
|
||||
props.floorId = core.status.floorId,
|
||||
});
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过以上的学习,你已经可以做出一个自己的怪物手册了!试着做一下吧!
|
@ -69,6 +69,7 @@
|
||||
"globals": "^15.14.0",
|
||||
"less": "^4.2.0",
|
||||
"madge": "^8.0.0",
|
||||
"markdown-it-mathjax3": "^4.3.2",
|
||||
"postcss-preset-env": "^9.6.0",
|
||||
"rollup": "^3.29.4",
|
||||
"terser": "^5.31.6",
|
||||
|
@ -1,8 +1,18 @@
|
||||
{
|
||||
"name": "@user/client-modules",
|
||||
"dependencies": {
|
||||
"@motajs/client-base": "workspace:*",
|
||||
"@motajs/common": "workspace:*",
|
||||
"@motajs/render": "workspace:*",
|
||||
"@motajs/render-core": "workspace:*",
|
||||
"@motajs/legacy-common": "workspace:*",
|
||||
"@motajs/legacy-ui": "workspace:*",
|
||||
"@user/legacy-plugin-client": "workspace:*"
|
||||
"@motajs/types": "workspace:*",
|
||||
"@motajs/system-action": "workspace:*",
|
||||
"@motajs/system-ui": "workspace:*",
|
||||
"@user/data-base": "workspace:*",
|
||||
"@user/data-state": "workspace:*",
|
||||
"@user/legacy-plugin-client": "workspace:*",
|
||||
"@user/legacy-plugin-data": "workspace:*"
|
||||
}
|
||||
}
|
@ -581,3 +581,4 @@ export function waitbox<T>(
|
||||
}
|
||||
|
||||
export const WaitBoxUI = new GameUI('wait-box', WaitBox);
|
||||
export const BackgroundUI = new GameUI('background', Background);
|
||||
|
3
packages-user/client-modules/src/render/ui/controller.ts
Normal file
3
packages-user/client-modules/src/render/ui/controller.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
|
||||
export const mainUIController = new UIController('main-ui');
|
@ -1,2 +1,3 @@
|
||||
export * from './controller';
|
||||
export * from './main';
|
||||
export * from './statusBar';
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import { WeatherController } from '../../weather';
|
||||
import { defineComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { Textbox, Tip } from '../components';
|
||||
import { GameUI, UIController } from '@motajs/system-ui';
|
||||
import { GameUI } from '@motajs/system-ui';
|
||||
import {
|
||||
MAIN_HEIGHT,
|
||||
MAIN_WIDTH,
|
||||
@ -38,6 +38,7 @@ import { LayerGroupFilter } from '../legacy/gameCanvas';
|
||||
import { LayerGroupHalo } from '../legacy/halo';
|
||||
import { FloorChange } from '../legacy/fallback';
|
||||
import { PopText } from '../legacy/pop';
|
||||
import { mainUIController } from './controller';
|
||||
|
||||
const MainScene = defineComponent(() => {
|
||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
@ -235,4 +236,3 @@ const MainScene = defineComponent(() => {
|
||||
});
|
||||
|
||||
export const mainSceneUI = new GameUI('main-scene', MainScene);
|
||||
export const mainUIController = new UIController('main-ui');
|
||||
|
@ -92,7 +92,7 @@
|
||||
"58": "Fail to set ellipse round rect, since length of 'ellipse' property should only be 2, 4, 6 or 8. delivered: $1",
|
||||
"59": "Unknown icon '$1' in parsing text content.",
|
||||
"60": "Repeated Tip id: '$1'.",
|
||||
"61": "Unexpected recursive call of $1.update in render function. Please ensure you have to do this, if you do, ignore this warn.",
|
||||
"61": "Unexpected recursive call of $1.update?$2 in render function. Please ensure you have to do this, if you do, ignore this warn.",
|
||||
"62": "Recursive fallback fonts in '$1'.",
|
||||
"63": "Uncaught promise error in waiting box component. Error reason: $1",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||
|
@ -612,7 +612,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
update(item: RenderItem<any> = this): void {
|
||||
if (import.meta.env.DEV) {
|
||||
if (this.forbidUpdate) {
|
||||
logger.warn(61, this.constructor.name);
|
||||
logger.warn(61, this.constructor.name, this.uid.toString());
|
||||
}
|
||||
}
|
||||
if (this._parent) {
|
||||
|
@ -43,29 +43,31 @@ export class Transform {
|
||||
/**
|
||||
* 修改缩放,叠加关系
|
||||
*/
|
||||
scale(x: number, y: number = x) {
|
||||
scale(x: number, y: number = x): this {
|
||||
mat3.scale(this.mat, this.mat, [x, y]);
|
||||
this.scaleX *= x;
|
||||
this.scaleY *= y;
|
||||
this.modified = true;
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动,叠加关系
|
||||
*/
|
||||
translate(x: number, y: number) {
|
||||
translate(x: number, y: number): this {
|
||||
mat3.translate(this.mat, this.mat, [x, y]);
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.modified = true;
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转,叠加关系
|
||||
*/
|
||||
rotate(rad: number) {
|
||||
rotate(rad: number): this {
|
||||
mat3.rotate(this.mat, this.mat, rad);
|
||||
this.rad += rad;
|
||||
if (this.rad >= Math.PI * 2) {
|
||||
@ -74,38 +76,42 @@ export class Transform {
|
||||
}
|
||||
this.modified = true;
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缩放,非叠加关系
|
||||
*/
|
||||
setScale(x: number, y: number = x) {
|
||||
setScale(x: number, y: number = x): this {
|
||||
mat3.scale(this.mat, this.mat, [x / this.scaleX, y / this.scaleY]);
|
||||
this.scaleX = x;
|
||||
this.scaleY = y;
|
||||
this.modified = true;
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置位置,非叠加关系
|
||||
*/
|
||||
setTranslate(x: number, y: number) {
|
||||
setTranslate(x: number, y: number): this {
|
||||
mat3.translate(this.mat, this.mat, [x - this.x, y - this.y]);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.modified = true;
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置旋转,非叠加关系
|
||||
*/
|
||||
setRotate(rad: number) {
|
||||
setRotate(rad: number): this {
|
||||
mat3.rotate(this.mat, this.mat, rad - this.rad);
|
||||
this.rad = rad;
|
||||
this.modified = true;
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,7 +130,7 @@ export class Transform {
|
||||
d: number,
|
||||
e: number,
|
||||
f: number
|
||||
) {
|
||||
): this {
|
||||
mat3.multiply(
|
||||
this.mat,
|
||||
this.mat,
|
||||
@ -132,6 +138,7 @@ export class Transform {
|
||||
);
|
||||
this.calAttributes();
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,10 +157,11 @@ export class Transform {
|
||||
d: number,
|
||||
e: number,
|
||||
f: number
|
||||
) {
|
||||
): this {
|
||||
mat3.set(this.mat, a, b, 0, c, d, 0, e, f, 1);
|
||||
this.calAttributes();
|
||||
this.bindedObject?.updateTransform?.();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,9 +236,9 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
|
||||
/** 图标id */
|
||||
icon: AllNumbers = 0;
|
||||
/** 帧数 */
|
||||
frame?: number = 0;
|
||||
frame: number = 0;
|
||||
/** 是否启用动画 */
|
||||
animate?: boolean = false;
|
||||
animate: boolean = false;
|
||||
/** 图标的渲染信息 */
|
||||
private renderable?: RenderableData | AutotileRenderable;
|
||||
|
||||
@ -262,7 +262,7 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
|
||||
const ch = this.height;
|
||||
const frame = this.animate
|
||||
? RenderItem.animatedFrame % renderable.frame
|
||||
: 0;
|
||||
: this.frame;
|
||||
|
||||
if (!this.animate) {
|
||||
if (renderable.autotile) {
|
||||
@ -324,7 +324,7 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
|
||||
* 更新动画帧
|
||||
*/
|
||||
updateFrameAnimate(): void {
|
||||
this.update(this);
|
||||
if (this.animate) this.update(this);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
|
@ -88,12 +88,14 @@ export interface BaseProps {
|
||||
}
|
||||
|
||||
export interface SpriteProps extends BaseProps {
|
||||
/** 自定义的渲染函数 */
|
||||
render?: RenderFunction;
|
||||
}
|
||||
|
||||
export interface ContainerProps extends BaseProps {}
|
||||
|
||||
export interface ConatinerCustomProps extends ContainerProps {
|
||||
/** 自定义容器渲染函数 */
|
||||
render?: CustomContainerRenderFn;
|
||||
}
|
||||
|
||||
@ -102,14 +104,20 @@ export interface GL2Props extends BaseProps {}
|
||||
export interface ShaderProps extends BaseProps {}
|
||||
|
||||
export interface TextProps extends BaseProps {
|
||||
/** 要渲染的文字 */
|
||||
text?: string;
|
||||
/** 文字的填充样式 */
|
||||
fillStyle?: CanvasStyle;
|
||||
/** 文字的描边样式 */
|
||||
strokeStyle?: CanvasStyle;
|
||||
/** 文字的字体 */
|
||||
font?: Font;
|
||||
/** 文字的描边粗细 */
|
||||
strokeWidth?: number;
|
||||
}
|
||||
|
||||
export interface ImageProps extends BaseProps {
|
||||
/** 图片对象 */
|
||||
image: CanvasImageSource;
|
||||
}
|
||||
|
||||
@ -173,7 +181,7 @@ export interface CirclesProps extends GraphicPropsBase {
|
||||
start?: number;
|
||||
end?: number;
|
||||
/**
|
||||
* 圆属性参数,可以填 `[半径,起始角度,终止角度]`,是 radius, start, end 的简写,
|
||||
* 圆属性参数,可以填 `[圆心 x 坐标,圆心 y 坐标,半径,起始角度,终止角度]`,是 x, y, radius, start, end 的简写,
|
||||
* 其中半径可选,后两项要么都填,要么都不填
|
||||
*/
|
||||
circle?: CircleParams;
|
||||
@ -185,7 +193,7 @@ export interface EllipseProps extends GraphicPropsBase {
|
||||
start?: number;
|
||||
end?: number;
|
||||
/**
|
||||
* 椭圆属性参数,可以填 `[x半径,y半径,起始角度,终止角度]`,是 radiusX, radiusY, start, end 的简写,
|
||||
* 椭圆属性参数,可以填 `[圆心 x 坐标,圆心 y 坐标,x半径,y半径,起始角度,终止角度]`,是 x, y, radiusX, radiusY, start, end 的简写,
|
||||
* 其中前两项和后两项要么都填,要么都不填
|
||||
*/
|
||||
ellipse?: EllipseParams;
|
||||
@ -249,12 +257,17 @@ export interface RectRProps extends GraphicPropsBase {
|
||||
}
|
||||
|
||||
export interface IconProps extends BaseProps {
|
||||
icon: AllNumbers;
|
||||
/** 图标 id 或数字 */
|
||||
icon: AllNumbers | AllIds;
|
||||
/** 显示图标的第几帧 */
|
||||
frame?: number;
|
||||
/** 是否开启动画,开启后 frame 参数无效 */
|
||||
animate?: boolean;
|
||||
}
|
||||
|
||||
export interface WinskinProps extends BaseProps {
|
||||
/** winskin 的图片 id */
|
||||
image: ImageIds;
|
||||
/** 边框大小 */
|
||||
borderSize?: number;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export class UIController
|
||||
|
||||
/** 当前是否显示 UI */
|
||||
get active() {
|
||||
return this.showBack.value;
|
||||
return this.sysShowBack.value;
|
||||
}
|
||||
|
||||
/** 自定义显示模式下的配置信息 */
|
||||
@ -123,9 +123,6 @@ export class UIController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染这个 UI
|
||||
*/
|
||||
render(): VNode {
|
||||
return h(UIContainer, { controller: this });
|
||||
}
|
||||
@ -134,8 +131,9 @@ export class UIController
|
||||
* 设置背景 UI
|
||||
* @param back 这个 UI 控制器的背景 UI
|
||||
*/
|
||||
setBackground(back: IGameUI) {
|
||||
setBackground<T extends UIComponent>(back: IGameUI<T>, vBind: UIProps<T>) {
|
||||
this.background = back;
|
||||
this.backIns.value = new UIInstance(back, vBind, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,18 +1,8 @@
|
||||
import { Props } from '@motajs/render';
|
||||
import { IGameUI, IUIInstance, UIComponent, UIProps } from './shared';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { markRaw, mergeProps } from 'vue';
|
||||
|
||||
interface UIInstanceEvent {
|
||||
hide: [];
|
||||
show: [];
|
||||
close: [];
|
||||
}
|
||||
|
||||
export class UIInstance<C extends UIComponent>
|
||||
extends EventEmitter<UIInstanceEvent>
|
||||
implements IUIInstance<C>
|
||||
{
|
||||
export class UIInstance<C extends UIComponent> implements IUIInstance<C> {
|
||||
private static counter: number = 0;
|
||||
|
||||
readonly key: number = UIInstance.counter++;
|
||||
@ -24,7 +14,6 @@ export class UIInstance<C extends UIComponent>
|
||||
public vBind: UIProps<C>,
|
||||
public readonly alwaysShow: boolean = false
|
||||
) {
|
||||
super();
|
||||
this.ui = markRaw(ui);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { Props } from '@motajs/render';
|
||||
import { DefineComponent, DefineSetupFnComponent, Ref, ShallowRef } from 'vue';
|
||||
import {
|
||||
DefineComponent,
|
||||
DefineSetupFnComponent,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
VNode
|
||||
} from 'vue';
|
||||
|
||||
export type UIComponent = DefineSetupFnComponent<any> | DefineComponent;
|
||||
|
||||
@ -52,6 +58,11 @@ export interface IUIMountable {
|
||||
*/
|
||||
show(ins: IUIInstance<UIComponent>): void;
|
||||
|
||||
/**
|
||||
* 渲染这个 UI,可以直接嵌入渲染树中
|
||||
*/
|
||||
render(): VNode;
|
||||
|
||||
/**
|
||||
* 隐藏背景 UI
|
||||
*/
|
||||
|
338
pnpm-lock.yaml
338
pnpm-lock.yaml
@ -153,6 +153,9 @@ importers:
|
||||
madge:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0(typescript@5.5.4)
|
||||
markdown-it-mathjax3:
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.2(encoding@0.1.13)
|
||||
postcss-preset-env:
|
||||
specifier: ^9.6.0
|
||||
version: 9.6.0(postcss@8.5.3)
|
||||
@ -182,7 +185,7 @@ importers:
|
||||
version: 4.4.0(@types/node@18.19.44)(rollup@3.29.4)(typescript@5.5.4)(vite@4.5.9(@types/node@18.19.44)(less@4.2.0)(terser@5.31.6))
|
||||
vitepress:
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0(@algolia/client-search@5.18.0)(@types/node@18.19.44)(async-validator@4.2.5)(axios@1.7.4)(less@4.2.0)(postcss@8.5.3)(search-insights@2.17.3)(terser@5.31.6)(typescript@5.5.4)
|
||||
version: 1.5.0(@algolia/client-search@5.18.0)(@types/node@18.19.44)(async-validator@4.2.5)(axios@1.7.4)(less@4.2.0)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.3)(search-insights@2.17.3)(terser@5.31.6)(typescript@5.5.4)
|
||||
vue-tsc:
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(typescript@5.5.4)
|
||||
@ -190,7 +193,47 @@ importers:
|
||||
specifier: ^8.18.0
|
||||
version: 8.18.0
|
||||
|
||||
packages-user/client-modules: {}
|
||||
packages-user/client-modules:
|
||||
dependencies:
|
||||
'@motajs/client-base':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/client-base
|
||||
'@motajs/common':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common
|
||||
'@motajs/legacy-common':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/legacy-common
|
||||
'@motajs/legacy-ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/legacy-ui
|
||||
'@motajs/render':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/render
|
||||
'@motajs/render-core':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/render-core
|
||||
'@motajs/system-action':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/system-action
|
||||
'@motajs/system-ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/system-ui
|
||||
'@motajs/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
'@user/data-base':
|
||||
specifier: workspace:*
|
||||
version: link:../data-base
|
||||
'@user/data-state':
|
||||
specifier: workspace:*
|
||||
version: link:../data-state
|
||||
'@user/legacy-plugin-client':
|
||||
specifier: workspace:*
|
||||
version: link:../legacy-plugin-client
|
||||
'@user/legacy-plugin-data':
|
||||
specifier: workspace:*
|
||||
version: link:../legacy-plugin-data
|
||||
|
||||
packages-user/data-base:
|
||||
dependencies:
|
||||
@ -290,6 +333,9 @@ importers:
|
||||
|
||||
packages-user/entry-data:
|
||||
dependencies:
|
||||
'@motajs/legacy-common':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/legacy-common
|
||||
'@user/data-base':
|
||||
specifier: workspace:*
|
||||
version: link:../data-base
|
||||
@ -308,6 +354,9 @@ importers:
|
||||
|
||||
packages-user/legacy-plugin-client:
|
||||
dependencies:
|
||||
'@user/client-modules':
|
||||
specifier: workspace:*
|
||||
version: link:../client-modules
|
||||
'@user/data-state':
|
||||
specifier: workspace:*
|
||||
version: link:../data-state
|
||||
@ -320,6 +369,9 @@ importers:
|
||||
'@user/data-state':
|
||||
specifier: workspace:*
|
||||
version: link:../data-state
|
||||
'@user/data-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../data-utils
|
||||
|
||||
packages/client:
|
||||
dependencies:
|
||||
@ -2671,6 +2723,10 @@ packages:
|
||||
anon-tokyo@0.0.0-alpha.0:
|
||||
resolution: {integrity: sha512-4kq9NOB56RUC6YqZAkkuA2mLhfzdLa39RSi+dUOk6geL4rldWspBZP2XbKv3hhG8nf+HDL2LSOTb7opSbqY/gg==}
|
||||
|
||||
ansi-colors@4.1.3:
|
||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2888,6 +2944,13 @@ packages:
|
||||
resolution: {integrity: sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==}
|
||||
engines: {pnpm: '>=8'}
|
||||
|
||||
cheerio-select@1.6.0:
|
||||
resolution: {integrity: sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==}
|
||||
|
||||
cheerio@1.0.0-rc.10:
|
||||
resolution: {integrity: sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@ -2977,6 +3040,10 @@ packages:
|
||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
commander@9.2.0:
|
||||
resolution: {integrity: sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==}
|
||||
engines: {node: ^12.20.0 || >=14}
|
||||
|
||||
commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
|
||||
@ -3050,6 +3117,13 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
css-select@4.3.0:
|
||||
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
|
||||
|
||||
css-what@6.1.0:
|
||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
cssdb@8.1.0:
|
||||
resolution: {integrity: sha512-BQN57lfS4dYt2iL0LgyrlDbefZKEtUyrO8rbzrbGrqBk6OoyNTQLF+porY9DrpDBjLo4NEvj2IJttC7vf3x+Ew==}
|
||||
|
||||
@ -3187,6 +3261,23 @@ packages:
|
||||
dom-scroll-into-view@2.0.1:
|
||||
resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==}
|
||||
|
||||
dom-serializer@1.4.1:
|
||||
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
|
||||
domhandler@3.3.0:
|
||||
resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domhandler@4.3.1:
|
||||
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
@ -3218,6 +3309,9 @@ packages:
|
||||
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
entities@2.2.0:
|
||||
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
@ -3263,6 +3357,10 @@ packages:
|
||||
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
escape-goat@3.0.0:
|
||||
resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
escape-string-regexp@1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@ -3328,6 +3426,10 @@ packages:
|
||||
jiti:
|
||||
optional: true
|
||||
|
||||
esm@3.2.25:
|
||||
resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
espree@10.3.0:
|
||||
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@ -3642,6 +3744,12 @@ packages:
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
htmlparser2@5.0.1:
|
||||
resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==}
|
||||
|
||||
htmlparser2@6.1.0:
|
||||
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
|
||||
|
||||
http-cache-semantics@4.1.1:
|
||||
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
|
||||
|
||||
@ -3905,6 +4013,11 @@ packages:
|
||||
jszip@3.10.1:
|
||||
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||
|
||||
juice@8.1.0:
|
||||
resolution: {integrity: sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
@ -4028,9 +4141,18 @@ packages:
|
||||
mark.js@8.11.1:
|
||||
resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
|
||||
|
||||
markdown-it-mathjax3@4.3.2:
|
||||
resolution: {integrity: sha512-TX3GW5NjmupgFtMJGRauioMbbkGsOXAAt1DZ/rzzYmTHqzkO1rNAdiMD4NiruurToPApn2kYy76x02QN26qr2w==}
|
||||
|
||||
mathjax-full@3.2.2:
|
||||
resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==}
|
||||
|
||||
mdast-util-to-hast@13.2.0:
|
||||
resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
|
||||
|
||||
mensch@0.3.4:
|
||||
resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==}
|
||||
|
||||
meow@10.1.5:
|
||||
resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@ -4039,6 +4161,9 @@ packages:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
mhchemparser@4.2.1:
|
||||
resolution: {integrity: sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
|
||||
|
||||
@ -4071,6 +4196,11 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mime@2.6.0:
|
||||
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
hasBin: true
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
@ -4146,6 +4276,9 @@ packages:
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
mj-context-menu@0.6.1:
|
||||
resolution: {integrity: sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==}
|
||||
|
||||
mkdirp@0.5.6:
|
||||
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
|
||||
hasBin: true
|
||||
@ -4208,6 +4341,15 @@ packages:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-gyp@9.4.1:
|
||||
resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==}
|
||||
engines: {node: ^12.13 || ^14.13 || >=16}
|
||||
@ -4325,6 +4467,12 @@ packages:
|
||||
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
parse5-htmlparser2-tree-adapter@6.0.1:
|
||||
resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==}
|
||||
|
||||
parse5@6.0.1:
|
||||
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
|
||||
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
@ -4875,6 +5023,9 @@ packages:
|
||||
resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
slick@1.12.2:
|
||||
resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
@ -4920,6 +5071,10 @@ packages:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
speech-rule-engine@4.0.7:
|
||||
resolution: {integrity: sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==}
|
||||
hasBin: true
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
@ -5081,6 +5236,9 @@ packages:
|
||||
resolution: {integrity: sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
@ -5248,6 +5406,10 @@ packages:
|
||||
resolution: {integrity: sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==}
|
||||
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
|
||||
|
||||
valid-data-url@3.0.1:
|
||||
resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
|
||||
@ -5408,6 +5570,13 @@ packages:
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
|
||||
web-resource-inliner@6.0.1:
|
||||
resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
webpack-sources@3.2.3:
|
||||
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@ -5415,11 +5584,17 @@ packages:
|
||||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wicked-good-xpath@1.3.0:
|
||||
resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==}
|
||||
|
||||
wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
|
||||
@ -5454,6 +5629,10 @@ packages:
|
||||
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
xmldom-sre@0.1.31:
|
||||
resolution: {integrity: sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==}
|
||||
engines: {node: '>=0.1'}
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
@ -7814,6 +7993,8 @@ snapshots:
|
||||
dependencies:
|
||||
lodash-es: 4.17.21
|
||||
|
||||
ansi-colors@4.1.3: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.1.0: {}
|
||||
@ -8072,6 +8253,24 @@ snapshots:
|
||||
dependencies:
|
||||
'@kurkle/color': 0.3.2
|
||||
|
||||
cheerio-select@1.6.0:
|
||||
dependencies:
|
||||
css-select: 4.3.0
|
||||
css-what: 6.1.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
domutils: 2.8.0
|
||||
|
||||
cheerio@1.0.0-rc.10:
|
||||
dependencies:
|
||||
cheerio-select: 1.6.0
|
||||
dom-serializer: 1.4.1
|
||||
domhandler: 4.3.1
|
||||
htmlparser2: 6.1.0
|
||||
parse5: 6.0.1
|
||||
parse5-htmlparser2-tree-adapter: 6.0.1
|
||||
tslib: 2.6.3
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
@ -8146,6 +8345,8 @@ snapshots:
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
||||
commander@9.2.0: {}
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
compare-versions@6.1.1: {}
|
||||
@ -8221,6 +8422,16 @@ snapshots:
|
||||
dependencies:
|
||||
postcss: 8.5.3
|
||||
|
||||
css-select@4.3.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.1.0
|
||||
domhandler: 4.3.1
|
||||
domutils: 2.8.0
|
||||
nth-check: 2.1.1
|
||||
|
||||
css-what@6.1.0: {}
|
||||
|
||||
cssdb@8.1.0: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
@ -8349,6 +8560,28 @@ snapshots:
|
||||
|
||||
dom-scroll-into-view@2.0.1: {}
|
||||
|
||||
dom-serializer@1.4.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
entities: 2.2.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
|
||||
domhandler@3.3.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domhandler@4.3.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domutils@2.8.0:
|
||||
dependencies:
|
||||
dom-serializer: 1.4.1
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
duplexify@3.7.1:
|
||||
@ -8382,6 +8615,8 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.2.1
|
||||
|
||||
entities@2.2.0: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
@ -8483,6 +8718,8 @@ snapshots:
|
||||
|
||||
escalade@3.1.2: {}
|
||||
|
||||
escape-goat@3.0.0: {}
|
||||
|
||||
escape-string-regexp@1.0.5: {}
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
@ -8576,6 +8813,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
esm@3.2.25: {}
|
||||
|
||||
espree@10.3.0:
|
||||
dependencies:
|
||||
acorn: 8.14.0
|
||||
@ -8923,6 +9162,20 @@ snapshots:
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
htmlparser2@5.0.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 3.3.0
|
||||
domutils: 2.8.0
|
||||
entities: 2.2.0
|
||||
|
||||
htmlparser2@6.1.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
domutils: 2.8.0
|
||||
entities: 2.2.0
|
||||
|
||||
http-cache-semantics@4.1.1: {}
|
||||
|
||||
http-proxy-agent@5.0.0:
|
||||
@ -9137,6 +9390,16 @@ snapshots:
|
||||
readable-stream: 2.3.8
|
||||
setimmediate: 1.0.5
|
||||
|
||||
juice@8.1.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
cheerio: 1.0.0-rc.10
|
||||
commander: 6.2.1
|
||||
mensch: 0.3.4
|
||||
slick: 1.12.2
|
||||
web-resource-inliner: 6.0.1(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
@ -9284,6 +9547,20 @@ snapshots:
|
||||
|
||||
mark.js@8.11.1: {}
|
||||
|
||||
markdown-it-mathjax3@4.3.2(encoding@0.1.13):
|
||||
dependencies:
|
||||
juice: 8.1.0(encoding@0.1.13)
|
||||
mathjax-full: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
mathjax-full@3.2.2:
|
||||
dependencies:
|
||||
esm: 3.2.25
|
||||
mhchemparser: 4.2.1
|
||||
mj-context-menu: 0.6.1
|
||||
speech-rule-engine: 4.0.7
|
||||
|
||||
mdast-util-to-hast@13.2.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@ -9296,6 +9573,8 @@ snapshots:
|
||||
unist-util-visit: 5.0.0
|
||||
vfile: 6.0.3
|
||||
|
||||
mensch@0.3.4: {}
|
||||
|
||||
meow@10.1.5:
|
||||
dependencies:
|
||||
'@types/minimist': 1.2.5
|
||||
@ -9313,6 +9592,8 @@ snapshots:
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
mhchemparser@4.2.1: {}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
dependencies:
|
||||
micromark-util-symbol: 2.0.1
|
||||
@ -9344,6 +9625,8 @@ snapshots:
|
||||
mime@1.6.0:
|
||||
optional: true
|
||||
|
||||
mime@2.6.0: {}
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
min-indent@1.0.1: {}
|
||||
@ -9417,6 +9700,8 @@ snapshots:
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
mj-context-menu@0.6.1: {}
|
||||
|
||||
mkdirp@0.5.6:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
@ -9468,6 +9753,12 @@ snapshots:
|
||||
|
||||
negotiator@0.6.3: {}
|
||||
|
||||
node-fetch@2.7.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
optionalDependencies:
|
||||
encoding: 0.1.13
|
||||
|
||||
node-gyp@9.4.1:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
@ -9616,6 +9907,12 @@ snapshots:
|
||||
|
||||
parse-node-version@1.0.1: {}
|
||||
|
||||
parse5-htmlparser2-tree-adapter@6.0.1:
|
||||
dependencies:
|
||||
parse5: 6.0.1
|
||||
|
||||
parse5@6.0.1: {}
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
path-dirname@1.0.2: {}
|
||||
@ -10234,6 +10531,8 @@ snapshots:
|
||||
|
||||
slash@2.0.0: {}
|
||||
|
||||
slick@1.12.2: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
smob@1.5.0: {}
|
||||
@ -10278,6 +10577,12 @@ snapshots:
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
speech-rule-engine@4.0.7:
|
||||
dependencies:
|
||||
commander: 9.2.0
|
||||
wicked-good-xpath: 1.3.0
|
||||
xmldom-sre: 0.1.31
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
sprintf-js@1.1.3: {}
|
||||
@ -10446,6 +10751,8 @@ snapshots:
|
||||
dependencies:
|
||||
through2: 2.0.5
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
trim-newlines@4.1.1: {}
|
||||
@ -10613,6 +10920,8 @@ snapshots:
|
||||
|
||||
uuid@2.0.3: {}
|
||||
|
||||
valid-data-url@3.0.1: {}
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
dependencies:
|
||||
spdx-correct: 3.2.0
|
||||
@ -10716,7 +11025,7 @@ snapshots:
|
||||
less: 4.2.0
|
||||
terser: 5.31.6
|
||||
|
||||
vitepress@1.5.0(@algolia/client-search@5.18.0)(@types/node@18.19.44)(async-validator@4.2.5)(axios@1.7.4)(less@4.2.0)(postcss@8.5.3)(search-insights@2.17.3)(terser@5.31.6)(typescript@5.5.4):
|
||||
vitepress@1.5.0(@algolia/client-search@5.18.0)(@types/node@18.19.44)(async-validator@4.2.5)(axios@1.7.4)(less@4.2.0)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.3)(search-insights@2.17.3)(terser@5.31.6)(typescript@5.5.4):
|
||||
dependencies:
|
||||
'@docsearch/css': 3.8.2
|
||||
'@docsearch/js': 3.8.2(@algolia/client-search@5.18.0)(search-insights@2.17.3)
|
||||
@ -10737,6 +11046,7 @@ snapshots:
|
||||
vite: 5.4.14(@types/node@18.19.44)(less@4.2.0)(terser@5.31.6)
|
||||
vue: 3.5.13(typescript@5.5.4)
|
||||
optionalDependencies:
|
||||
markdown-it-mathjax3: 4.3.2(encoding@0.1.13)
|
||||
postcss: 8.5.3
|
||||
transitivePeerDependencies:
|
||||
- '@algolia/client-search'
|
||||
@ -10817,14 +11127,34 @@ snapshots:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
web-resource-inliner@6.0.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
ansi-colors: 4.1.3
|
||||
escape-goat: 3.0.0
|
||||
htmlparser2: 5.0.1
|
||||
mime: 2.6.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
valid-data-url: 3.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webpack-sources@3.2.3: {}
|
||||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wicked-good-xpath@1.3.0: {}
|
||||
|
||||
wide-align@1.1.5:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
@ -10849,6 +11179,8 @@ snapshots:
|
||||
|
||||
xml-name-validator@4.0.0: {}
|
||||
|
||||
xmldom-sre@0.1.31: {}
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
Loading…
Reference in New Issue
Block a user