diff --git a/docs/.vitepress/api.ts b/docs/.vitepress/api.ts index 2faba7e..2dc2380 100644 --- a/docs/.vitepress/api.ts +++ b/docs/.vitepress/api.ts @@ -1,5 +1,6 @@ import { basename, join, resolve } from 'node:path'; import { DefaultTheme } from 'vitepress'; +import { existsSync } from 'node:fs'; import { readdir, stat, writeFile } from 'node:fs/promises'; const apiDir = resolve('./docs/api'); @@ -11,6 +12,10 @@ const weight: Record = { }; export async function generateSidebar(): Promise { + if (!existsSync(apiDir)) { + return; + } + const sidebar: DefaultTheme.SidebarItem[] = [ { text: '目录', link: '/api/' } ]; diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 5d101e9..2950712 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,39 +1,6 @@ -import { defineConfig, Plugin } from 'vitepress'; +import { defineConfig } from 'vitepress'; import { MermaidMarkdown, MermaidPlugin } from 'vitepress-plugin-mermaid'; -import api from './apiSidebar'; import { join } from 'path'; -import { generateSidebar } from './api'; - -function listenSidebar(): Plugin { - return { - name: 'sidebar-listen', - configureServer(server) { - server.watcher - .on('add', filePath => { - console.log(`📄 文件新增: ${filePath}`); - generateSidebar(); - }) - .on('unlink', filePath => { - console.log(`❌ 文件删除: ${filePath}`); - generateSidebar(); - }) - .on('addDir', dirPath => { - console.log(`📁 目录新增: ${dirPath}`); - generateSidebar(); - }) - .on('unlinkDir', dirPath => { - console.log(`📁 目录删除: ${dirPath}`); - generateSidebar(); - }) - .on('raw', (event, path, _) => { - if (event === 'rename') { - console.log(`🔄 文件或文件夹重命名: ${path}`); - generateSidebar(); - } - }); - } - }; -} // https://vitepress.dev/reference/site-config export default defineConfig({ @@ -50,110 +17,8 @@ export default defineConfig({ themeConfig: { // https://vitepress.dev/reference/default-theme-config outline: [2, 3], - nav: [ - { text: '主页', link: '/' }, - { text: '指南', link: '/guide/quick-start' }, - { text: 'API', link: '/api/' }, - { text: '错误代码', link: '/logger/' } - ], - sidebar: { - '/guide/': [ - { - text: '深度指南', - items: [ - { text: '快速开始', link: '/guide/quick-start' }, - - { text: '差异说明', link: '/guide/diff' }, - { text: '系统说明', link: '/guide/system' }, - { text: '代码编写', link: '/guide/coding' }, - { text: '音频系统', link: '/guide/audio' }, - { - text: 'UI 系统', - collapsed: false, - items: [ - { text: '快速浏览', link: '/guide/ui/' }, - { text: '编写 UI', link: '/guide/ui/ui' }, - { text: 'UI 元素', link: '/guide/ui/elements' }, - { - text: '组件使用指南', - link: '/guide/ui/component' - }, - { text: '优化性能', link: '/guide/ui/perf' }, - { text: 'UI 系统', link: '/guide/ui/system' }, - { text: '常见问题', link: '/guide/ui/faq' }, - { text: '未来规划', link: '/guide/ui/future' } - ] - }, - { - text: '常见需求指南', - collapsed: false, - items: [ - { - text: '快速浏览', - link: '/guide/implements/index' - }, - { - text: '怪物特殊属性', - link: '/guide/implements/special' - }, - { - text: '修改状态栏', - link: '/guide/implements/status-bar' - }, - { - text: '新增 UI', - link: '/guide/implements/new-ui' - }, - { - text: '主动技能', - link: '/guide/implements/skill' - }, - { - text: '新增按键', - link: '/guide/implements/hotkey' - }, - { - text: '动画效果', - link: '/guide/implements/animate' - }, - { - text: '选择框与确认框', - link: '/guide/implements/choice' - } - ] - } - ] - } - ], - '/logger/': [ - { - text: '错误代码一览', - items: [ - { - text: '错误代码', - collapsed: false, - items: [ - { text: '1-50', link: '/logger/error/error1' } - ] - }, - { - text: '警告代码', - collapsed: false, - items: [ - { text: '1-50', link: '/logger/warn/warn1' }, - { text: '51-100', link: '/logger/warn/warn2' } - ] - } - ] - } - ], - '/api/': [ - { - text: 'API 列表', - items: api - } - ] - }, + nav: [], + sidebar: {}, socialLinks: [ { icon: 'github', link: 'https://github.com/unanmed/HumanBreak' } ], @@ -189,7 +54,7 @@ export default defineConfig({ }, vite: { // @ts-expect-error 类型错误 - plugins: [MermaidPlugin(), listenSidebar()], + plugins: [MermaidPlugin()], optimizeDeps: { include: ['mermaid'] }, diff --git a/docs/api/index.md b/docs/api/index.md deleted file mode 100644 index c509d5a..0000000 --- a/docs/api/index.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -lang: zh-CN ---- - -# API 列表 - -- [@motajs/client](./motajs-client/index.md) 渲染层代码 -- [@motajs/client-base](./motajs-client-base/index.md) 渲染层底层代码 -- [@motajs/common](./motajs-common/index.md) 渲染层和数据层通用代码 -- [@motajs/legacy-client](./motajs-legacy-client/index.md) 遗留渲染层代码 -- [@motajs/legacy-common](./motajs-legacy-common/index.md) 遗留通用代码 -- [@motajs/legacy-system](./motajs-legacy-system/index.md) 遗留渲染层系统代码 -- [@motajs/legacy-ui](./motajs-legacy-ui/index.md) 遗留 UI 相关代码 -- [@motajs/render](./motajs-render/index.md) 渲染系统代码 -- [@motajs/render-core](./motajs-render-core/index.md) 渲染系统核心代码 -- [@motajs/render-elements](./motajs-render-elements/index.md) 渲染系统内置元素代码 -- [@motajs/render-style](./motajs-render-style/index.md) 渲染系统样式代码 -- [@motajs/render-vue](./motajs-render-vue/index.md) 渲染系统 vue 支持代码 -- [@motajs/system](./motajs-system/index.md) 渲染层系统代码 -- [@motajs/system-action](./motajs-system-action/index.md) 渲染层交互系统代码 -- [@motajs/system-ui](./motajs-system-ui/index.md) 渲染层 UI 系统代码 -- [@motajs/types](./motajs-types/index.md) 渲染层类型代码 -- [@user/client-modules](./user-client-modules/index.md) 用户渲染层主要代码 -- [@user/data-base](./user-data-base/index.md) 用户数据层底层代码 -- [@user/data-fallback](./user-data-fallback/index.md) 用户数据层向后兼容代码 -- [@user/data-state](./user-data-state/index.md) 用户数据层状态代码 -- [@user/data-utils](./user-data-utils/index.md) 用户数据层工具代码 -- [@user/entry-client](./user-entry-client/index.md) 用户渲染层入口 -- [@user/entry-data](./user-entry-data/index.md) 用户数据层入口 -- [@user/legacy-plugin-client](./user-legacy-plugin-client/index.md) 用户遗留渲染层代码 -- [@user/legacy-plugin-data](./user-legacy-plugin-data/index.md) 用户遗留数据层代码 diff --git a/docs/api/motajs-client-base/KeyCode.md b/docs/api/motajs-client-base/KeyCode.md deleted file mode 100644 index b73bbd2..0000000 --- a/docs/api/motajs-client-base/KeyCode.md +++ /dev/null @@ -1,3 +0,0 @@ -# KeyCode - -参考 `monaco-editor` 文档 [KeyCode](https://microsoft.github.io/monaco-editor/docs.html#enums/KeyCode.html) diff --git a/docs/api/motajs-client-base/index.md b/docs/api/motajs-client-base/index.md deleted file mode 100644 index 163dbb2..0000000 --- a/docs/api/motajs-client-base/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# @motajs/client-base - -目录: - -- [KeyCode](./KeyCode.md) diff --git a/docs/api/motajs-client/index.md b/docs/api/motajs-client/index.md deleted file mode 100644 index 3ea7788..0000000 --- a/docs/api/motajs-client/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# @motajs/client - -`@motajs/client` 包含多个模块: - -- [`@motajs/client-base`](../motajs-client-base/) - -示例: - -```ts -import { KeyCode } from '@motajs/client'; - -// 等价于 -const { KeyCode } = Mota.require('@motajs/client'); -``` diff --git a/docs/api/motajs-common/Logger.md b/docs/api/motajs-common/Logger.md deleted file mode 100644 index 4d5bc70..0000000 --- a/docs/api/motajs-common/Logger.md +++ /dev/null @@ -1,212 +0,0 @@ -# Logger - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| --------- | ------------- | ------ | ---------------------------------------------------------------- | -| `enabled` | `boolean` | `true` | 控制日志输出是否启用。设为 `false` 可临时关闭日志输出。 | -| `level` | `LogLevel` | - | 日志级别,决定输出的最低日志等级(通过构造函数传入,不可修改)。 | -| `info` | `ILoggerInfo` | - | 包含错误和警告信息的配置对象(通过构造函数传入,不可修改)。 | - -## 方法说明 - -### `constructor` - -```ts -function constructor(level: LogLevel, info: ILoggerInfo): Logger; -``` - -#### 描述 - -构造一个 `Logger` 实例。 - -#### 参数 - -- `level`: 日志对象输出等级。 -- `info`: 日志内容。 - -### `error` - -```ts -function error(code: number, ...params: string[]): void; -``` - -#### 描述 - -记录一个错误信息。 - -#### 参数 - -- `code`: 错误代码,对应 `info.error` 中的键值。 -- `...params`: 替换错误信息中的占位符(如 $1, $2)的参数。 - -#### 行为 - -- 如果未找到对应 `code` 的错误信息,会触发 `error(16)` 表示代码未定义。 -- 根据日志级别 `level` 决定是否输出到控制台 - -### `warn` - -```ts -function warn(code: number, ...params: string[]): void; -``` - -#### 描述 - -记录一个警告信息。 - -#### 参数 - -- `code`: 警告代码,对应 `info.warn` 中的键值。 -- `...params`: 替换警告信息中的占位符的参数。 - -#### 行为 - -- 如果未找到对应 `code` 的警告信息,会触发 `error(16)`。 -- 仅在 `level <= LogLevel.WARNING` 时输出。 - -### `log` - -```ts -function log(text: string): void; -``` - -#### 描述 - -记录一条普通日志。 - -#### 参数 - -`text`: 日志文本内容。 - -#### 行为 - -- 仅在 `level <= LogLevel.LOG` 时输出到控制台。 - -### `catch` - -```ts -function catch(fn: () => T): LoggerCatchReturns; -``` - -#### 描述 - -捕获函数执行期间产生的日志信息,并抑制日志输出。 - -#### 参数 - -- `fn`: 需要执行的函数。 - -#### 返回值 - -- `ret`: 函数 `fn` 的返回值。 -- `info`: 捕获的日志信息数组。 - -#### 行为 - -- 执行期间会临时禁用日志输出,执行完成后恢复原有状态。 - -### `disable` - -```ts -function disable(): void; -``` - -#### 描述 - -禁用日志输出(设置 `enabled = false`)。 - -### `enable` - -```ts -function enable(): void; -``` - -#### 描述 - -启用日志输出(设置 `enabled = true`)。 - -## 接口说明 - -### `LoggerCatchInfo` - -#### 结构 - -```ts -interface LoggerCatchInfo { - /** 错误/警告代码(仅 error/warn 方法存在) */ - code?: number; - /** 日志等级 */ - level: LogLevel; - /** 解析后的完整信息 */ - message: string; -} -``` - -### `LoggerCatchReturns` - -#### 结构 - -```ts -interface LoggerCatchReturns { - /** 被捕获函数的返回值 */ - ret: T; - /** 捕获的日志信息列表 */ - info: LoggerCatchInfo[]; -} -``` - -## 使用示例 - -- 初始化 Logger - -```ts -import { LogLevel, Logger } from './logger'; - -const logInfo = { - error: { - 404: 'Page $1 not found.', - 500: 'Internal server error: $1' - }, - warn: { - 101: 'Deprecated API: $1' - } -}; - -const logger = new Logger(LogLevel.WARNING, logInfo); -``` - -- 记录错误 - -```ts -logger.error(404, 'home'); -// 控制台输出: [ERROR Code 404] Page home not found. -``` - -- 记录警告 - -```ts -logger.warn(101, '/old-api'); -// 控制台输出: [WARNING Code 101] Deprecated API: /old-api -``` - -- 捕获日志 - -```ts -const result = logger.catch(() => { - logger.error(500, 'database timeout'); - return { success: false }; -}); - -console.log(result.info[0].message); // "Internal server error: database timeout" -``` - -- 禁用日志 - -```ts -logger.disable(); -logger.log('This will not be printed'); // 无输出 -logger.enable(); -``` diff --git a/docs/api/motajs-common/functions.md b/docs/api/motajs-common/functions.md deleted file mode 100644 index f34cf50..0000000 --- a/docs/api/motajs-common/functions.md +++ /dev/null @@ -1,24 +0,0 @@ -# @motajs/common 函数 - -## `sleep` - -```ts -function sleep(time: number): Promise; -``` - -#### 描述 - -创建一个等待指定时长的异步。 - -#### 参数 - -- `time`: 等待时长 - -#### 使用示例 - -```ts -async function myFunc() { - await sleep(1000); - // 这后面的内容会在 1 秒之后执行 -} -``` diff --git a/docs/api/motajs-common/index.md b/docs/api/motajs-common/index.md deleted file mode 100644 index dd45248..0000000 --- a/docs/api/motajs-common/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# @motajs/common - -目录: - -- [函数](./functions.md) - ---- - -类目录: - -- [Logger](./Logger.md) diff --git a/docs/api/motajs-legacy-client/index.md b/docs/api/motajs-legacy-client/index.md deleted file mode 100644 index aa9f43e..0000000 --- a/docs/api/motajs-legacy-client/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/legacy-client - -目录: diff --git a/docs/api/motajs-legacy-common/Patch.md b/docs/api/motajs-legacy-common/Patch.md deleted file mode 100644 index 214153d..0000000 --- a/docs/api/motajs-legacy-common/Patch.md +++ /dev/null @@ -1,120 +0,0 @@ -# Patch API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 类描述 - -`Patch` 类用于对旧版接口的函数实现进行动态重写,支持按模块类别批量修改目标类的原型方法。需配合 `PatchClass` 枚举指定要修改的模块类型。 - ---- - -## 泛型说明 - -- `T extends PatchClass`: 表示要修改的模块类别(如 `PatchClass.Actions` 对应动作模块) - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ------------ | ---- | ---------------------------- | -| `patchClass` | `T` | 只读,当前补丁关联的模块类别 | - ---- - -## 构造方法 - -```typescript -function constructor(patchClass: T): Patch; -``` - -- **参数** - - `patchClass`: 指定要修改的模块类别(从 `PatchClass` 枚举中选择) - -**示例** - -```typescript -// 创建针对控制模块的补丁 -const patch = new Patch(PatchClass.Control); -``` - ---- - -## 方法说明 - -### `add` - -```typescript -function add( - key: K, - patch: PatchList[T][K] -): void; -``` - -为目标模块添加函数补丁。 - -- **参数** - - `key`: 要修改的函数名(需为目标模块原型存在的函数) - - `patch`: 新的函数实现 - -**示例** - -```typescript -// 重写控制模块的 setFlag 方法 -control.add('setFlag', function (this: Control, key, value) { - console.log('执行重写后的 setFlag 代码'); - if (typeof value === 'number') { - // 数字额外增加 100 点 - core.status.hero.flags[key] = value + 100; - } else { - core.status.hero.flags[key] = value; - } -}); -``` - ---- - -### `Patch.patchAll` - -```typescript -function patchAll(): void; -``` - -**静态方法**:应用所有未执行的补丁修改。一般不需要自己调用,游戏启动阶段已经包含了此方法的调用。 - ---- - -### `Patch.patch` - -```typescript -function patch(patch: Patch): void; -``` - -**静态方法**:立即应用指定补丁实例的修改。一般不需要自己调用,游戏启动阶段已经包含了此方法的调用。 - -- **参数** - - `patch`: 要应用的补丁实例 - ---- - -## 总使用示例 - -```typescript -import { Patch, PatchClass } from '@motajs/legacy-common'; - -// 新建函数,这个操作是必要的,我们不能直接在顶层使用这个接口 -export function patchMyFunctions() { - // 创建 Patch 实例,参数表示这个 Patch 示例要重写哪个文件中的函数 - // 如果需要复写两个文件,那么就需要创建两个实例 - const patch = new Patch(PatchClass.Control); - - // 使用 add 函数来重写,第一个参数会有自动补全 - // 如果要重写的函数以下划线开头,可能会有报错 - // 这时候需要去 types/declaration 中对应的文件中添加声明 - patch.add('getFlag', (name, defaultValue) => { - // 重写 getFlag,如果变量是数字,那么 +100 后返回 - const value = core.status?.hero?.flags[name] ?? defaultValue; - return typeof value === 'number' ? value + 100 : value; - }); -} -``` diff --git a/docs/api/motajs-legacy-common/index.md b/docs/api/motajs-legacy-common/index.md deleted file mode 100644 index 2b112e3..0000000 --- a/docs/api/motajs-legacy-common/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/legacy-common - -目录: diff --git a/docs/api/motajs-legacy-system/index.md b/docs/api/motajs-legacy-system/index.md deleted file mode 100644 index 914499d..0000000 --- a/docs/api/motajs-legacy-system/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/legacy-system - -目录: diff --git a/docs/api/motajs-legacy-ui/index.md b/docs/api/motajs-legacy-ui/index.md deleted file mode 100644 index e1375f5..0000000 --- a/docs/api/motajs-legacy-ui/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/legacy-ui - -目录: diff --git a/docs/api/motajs-render-core/Container.md b/docs/api/motajs-render-core/Container.md deleted file mode 100644 index 25bed56..0000000 --- a/docs/api/motajs-render-core/Container.md +++ /dev/null @@ -1,121 +0,0 @@ -# Container 类 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - Container --> RenderItem --> EventEmitter - - click RenderItem "./RenderItem" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| ---------------- | -------------- | ------ | ------------------------------ | -| `sortedChildren` | `RenderItem[]` | `[]` | 按 `zIndex` 排序后的子元素列表 | - ---- - -## 构造方法 - -### `constructor` - -**参数** - -- `type`: 渲染模式(`absolute` 绝对定位 / `static` 跟随摄像机) -- `cache`: 是否启用渲染缓存 -- `fall`: 是否启用变换矩阵下穿机制 - -**示例** - -```typescript -const container = new Container('static'); -``` - ---- - -## 方法说明 - -### `appendChild` - -```typescript -function appendChild(...children: RenderItem[]): void; -``` - -**描述** -添加子元素并触发重新排序。 -**示例** - -```typescript -const child = new RenderItem('static'); -container.appendChild(child); // 添加子元素 -``` - ---- - -### `removeChild` - -```typescript -function removeChild(...child: RenderItem[]): void; -``` - -**描述** -移除指定子元素并触发重新排序。 -**示例** - -```typescript -container.removeChild(child); // 移除子元素 -``` - ---- - -### `requestSort` - -```typescript -function requestSort(): void; -``` - -**描述** -标记需要重新排序子元素(在下一帧前自动执行)。 - ---- - -### `forEachChild` - -```typescript -function forEachChild(fn: (ele: RenderItem) => void): void; -``` - -**描述** -遍历元素的每一个子元素(DFS 遍历),并对每一个元素执行函数。 - ---- - -## 总使用示例 - -```typescript -// 创建基础容器 -const baseContainer = new Container('absolute'); -baseContainer.size(800, 600); - -// 添加子元素 -const sprite1 = new Sprite('static'); -sprite1.pos(100, 100).setZIndex(2); -baseContainer.appendChild(sprite1); - -const sprite2 = new Sprite('static'); -sprite2.pos(200, 200).setZIndex(1); -baseContainer.appendChild(sprite2); - -// 将容器添加到根元素 -rootElement.appendChild(baseContainer); - -// 动态修改子元素层级 -sprite1.setZIndex(0); // 自动触发重新排序 -``` diff --git a/docs/api/motajs-render-core/ContainerCustom.md b/docs/api/motajs-render-core/ContainerCustom.md deleted file mode 100644 index f269f73..0000000 --- a/docs/api/motajs-render-core/ContainerCustom.md +++ /dev/null @@ -1,71 +0,0 @@ -# ContainerCustom 类 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - ContainerCustom --> Container --> RenderItem --> EventEmitter - - click Container "./Container" - click RenderItem "./RenderItem" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| ---------- | ------------------------- | ----------- | ---------------------- | -| `renderFn` | `CustomContainerRenderFn` | `undefined` | 自定义渲染函数(可选) | - ---- - -## 构造方法 - -继承自 `Container`,参数与父类一致。 - ---- - -## 方法说明 - -### `setRenderFn` - -```typescript -function setRenderFn(render?: CustomContainerRenderFn): void; -``` - -**描述** -设置自定义渲染函数,覆盖默认的子元素渲染逻辑。 -**参数** - -- `render`: 接收画布、子元素列表和变换矩阵的回调函数 - -**示例** - -```typescript -customContainer.setRenderFn((canvas, children, transform) => { - children.forEach(child => { - child.renderContent(canvas, transform); - }); -}); -``` - ---- - -## 总使用示例 - -```ts -// 创建自定义容器 -const customContainer = new ContainerCustom('static'); -customContainer.setRenderFn((canvas, children) => { - // 倒序渲染子元素 - children.reverse().forEach(child => { - child.renderContent(canvas, Transform.identity); - }); -}); -``` diff --git a/docs/api/motajs-render-core/Event.md b/docs/api/motajs-render-core/Event.md deleted file mode 100644 index bfe4894..0000000 --- a/docs/api/motajs-render-core/Event.md +++ /dev/null @@ -1,201 +0,0 @@ -# Event 模块 API 文档 - -以下内容由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 枚举说明 - -### MouseType - -| 值 | 说明 | -| --------- | ---------------- | -| `None` | 没有按键按下 | -| `Left` | 左键 | -| `Middle` | 中键(按下滚轮) | -| `Right` | 右键 | -| `Back` | 侧键后退 | -| `Forward` | 侧键前进 | - -### WheelType - -| 值 | 说明 | -| ------- | -------------------------- | -| `None` | 无单位 | -| `Pixel` | 以像素为单位 | -| `Line` | 以行为单位(约 1rem) | -| `Page` | 以页为单位(一个屏幕高度) | - -### ActionType - -| 值 | 说明 | -| ------- | -------------------------------- | -| `Click` | 点击事件(按下与抬起在同一元素) | -| `Down` | 鼠标或手指按下事件 | -| `Move` | 鼠标或手指移动事件 | -| `Up` | 鼠标或手指抬起事件 | -| `Enter` | 进入元素时触发 | -| `Leave` | 离开元素时触发 | -| `Wheel` | 滚轮事件 | - -### EventProgress - -| 值 | 说明 | -| --------- | -------- | -| `Capture` | 捕获阶段 | -| `Bubble` | 冒泡阶段 | - ---- - -## 接口说明 - -### IActionEventBase - -| 属性名 | 类型 | 说明 | -| ---------- | ------------ | ----------------------------------------------------------------- | -| `target` | `RenderItem` | 触发事件的元素 | -| `touch` | `boolean` | 是否为触摸操作(`true` 表示触摸,`false` 表示鼠标) | -| `type` | `MouseType` | 触发事件的按键类型(参考 `MouseType`) | -| `buttons` | `number` | 当前按下的按键(通过位运算判断,例如 `buttons & MouseType.Left`) | -| `altKey` | `boolean` | 是否按下 `Alt` 键 | -| `shiftKey` | `boolean` | 是否按下 `Shift` 键 | -| `ctrlKey` | `boolean` | 是否按下 `Ctrl` 键 | -| `metaKey` | `boolean` | 是否按下 `Windows/Command` 键 | - ---- - -### IActionEvent - -#### 继承关系 - -```mermaid -graph LR - IActionEvent --> IActionEventBase -``` - -| 属性名 | 类型 | 说明 | -| ------------ | -------- | -------------------------------------------- | -| `identifier` | `number` | 操作的唯一标识符(在按下、移动、抬起中一致) | -| `offsetX` | `number` | 相对于元素左上角的横坐标 | -| `offsetY` | `number` | 相对于元素左上角的纵坐标 | -| `absoluteX` | `number` | 相对于整个画布左上角的横坐标 | -| `absoluteY` | `number` | 相对于整个画布左上角的纵坐标 | - -#### 方法说明 - -##### `stopPropagation` - -```typescript -function stopPropagation(): void; -``` - -**描述** -停止事件的传播(捕获或冒泡阶段)。 - -**示例** - -```typescript -item.on('click', ev => { - ev.stopPropagation(); // 阻止事件继续传播 -}); -``` - ---- - -### IWheelEvent - -```mermaid -graph LR - IWheelEvent --> IActionEvent --> IActionEventBase -``` - -| 属性名 | 类型 | 说明 | -| ----------- | ----------- | -------------------- | -| `wheelX` | `number` | 横向滚动量 | -| `wheelY` | `number` | 纵向滚动量 | -| `wheelZ` | `number` | 垂直屏幕方向的滚动量 | -| `wheelType` | `WheelType` | 滚动量的单位类型 | - ---- - -### ERenderItemActionEvent - -描述了所有的交互事件类型。 - -| 事件名 | 参数类型 | 说明 | -| -------------- | ---------------------------- | ------------------ | -| `clickCapture` | `Readonly` | 点击事件的捕获阶段 | -| `click` | `Readonly` | 点击事件的冒泡阶段 | -| `downCapture` | `Readonly` | 按下事件的捕获阶段 | -| `down` | `Readonly` | 按下事件的冒泡阶段 | -| `moveCapture` | `Readonly` | 移动事件的捕获阶段 | -| `move` | `Readonly` | 移动事件的冒泡阶段 | -| `upCapture` | `Readonly` | 抬起事件的捕获阶段 | -| `up` | `Readonly` | 抬起事件的冒泡阶段 | -| `enter` | `Readonly` | 进入元素事件 | -| `leave` | `Readonly` | 离开元素事件 | -| `wheelCapture` | `Readonly` | 滚轮事件的捕获阶段 | -| `wheel` | `Readonly` | 滚轮事件的冒泡阶段 | - ---- - -### ActionEventMap - -| 键(ActionType) | 值类型 | 说明 | -| ---------------- | -------------- | ------------ | -| `Click` | `IActionEvent` | 点击事件 | -| `Down` | `IActionEvent` | 按下事件 | -| `Enter` | `IActionEvent` | 进入元素事件 | -| `Leave` | `IActionEvent` | 离开元素事件 | -| `Move` | `IActionEvent` | 移动事件 | -| `Up` | `IActionEvent` | 抬起事件 | -| `Wheel` | `IWheelEvent` | 滚轮事件 | - ---- - -## 总使用示例 - -::: code-group - -```typescript -// 创建渲染元素(以 Sprite 为例) -const item = new Sprite(); - -// 监听点击事件(冒泡阶段) -item.on('click', ev => { - console.log('点击坐标:', ev.offsetX, ev.offsetY); - ev.stopPropagation(); // 阻止冒泡 -}); - -// 监听滚轮事件(捕获阶段) -item.on('wheelCapture', ev => { - console.log('滚轮滚动量:', ev.wheelY, '单位:', WheelType[ev.wheelType]); -}); - -// 监听进入元素事件 -item.on('enter', ev => { - console.log('进入元素,触发按键:', MouseType[ev.type]); -}); -``` - -```tsx -// 监听点击事件(冒泡阶段) -const click = (ev: IActionEvent) => { - console.log('点击坐标:', ev.offsetX, ev.offsetY); - ev.stopPropagation(); // 阻止冒泡 -}; - -// 监听滚轮事件(捕获阶段) -const wheel = (ev: IWheelEvent) => { - console.log('滚轮滚动量:', ev.wheelY, '单位:', WheelType[ev.wheelType]); -}; - -// 监听进入元素事件 -const enter = (ev: IActionEventBase) => { - console.log('进入元素,触发按键:', MouseType[ev.type]); -}; - -; -``` - -::: diff --git a/docs/api/motajs-render-core/GL2.md b/docs/api/motajs-render-core/GL2.md deleted file mode 100644 index a8964a3..0000000 --- a/docs/api/motajs-render-core/GL2.md +++ /dev/null @@ -1,151 +0,0 @@ -# GL2 类 API 文档 - -**需丰富** - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 继承关系 - -```mermaid -graph LR - GL2 --> RenderItem --> EventEmitter - - click RenderItem "./RenderItem" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| ------------------------------- | ------------------------ | ------------------ | --------------------------------------------- | -| `support` | `boolean`(静态) | 检测 WebGL2 支持性 | 标识当前环境是否支持 WebGL2 | -| `canvas` | `HTMLCanvasElement` | - | 绑定的 WebGL2 画布元素 | -| `gl` | `WebGL2RenderingContext` | - | WebGL2 渲染上下文 | -| `UNIFORM_1f` ~ `UNIFORM_4uiv` | `UniformType` 枚举 | 对应枚举值 | WebGL uniform 类型常量(共 25 种) | -| `U_MATRIX_2x2` ~ `U_MATRIX_4x4` | `UniformMatrix` 枚举 | 对应枚举值 | 矩阵类型 uniform 常量(9 种) | -| `ATTRIB_1f` ~ `ATTRIB_I4uiv` | `AttribType` 枚举 | 对应枚举值 | 顶点属性类型常量(12 种) | -| `MAX_TEXTURE_COUNT` | `number` | `0` | 最大纹理支持数量(实际值由 WebGL 上下文决定) | - ---- - -## 构造方法 - -### `constructor` - -**参数** - -- `type`: 渲染模式(`absolute` 绝对定位 / `static` 跟随摄像机) - -**行为** - -- 初始化 WebGL2 上下文 -- 自动检测 WebGL2 支持性(通过静态属性 `support`) -- 设置默认渲染模式 - ---- - -## 方法说明 - -### `createProgram` - -```typescript -function createProgram( - Program: ProgramConstructor, - vs?: string, - fs?: string -): T; -``` - -**描述** -创建 WebGL 着色器程序 -**参数** - -- `Program`: 着色器程序类(需继承 `GL2Program`) -- `vs`: 自定义顶点着色器代码(可选) -- `fs`: 自定义片元着色器代码(可选) - **示例** - -```typescript -class MyProgram extends GL2Program {} -const program = gl2.createProgram(MyProgram); -``` - ---- - -### `useProgram` - -```typescript -function useProgram(program: GL2Program): void; -``` - -**描述** -切换当前使用的着色器程序 -**示例** - -```typescript -gl2.useProgram(shaderProgram); -``` - ---- - -### `framebuffer` - -```typescript -function framebuffer( - name: string, - texture: IShaderTexture2D, - clear?: boolean -): void; -``` - -**描述** -将渲染结果输出到帧缓冲纹理 -**参数** - -- `name`: 帧缓冲名称 -- `texture`: 目标纹理对象 -- `clear`: 是否清空画布 - ---- - -### `drawScene`(抽象方法) - -```typescript -function drawScene( - canvas: MotaOffscreenCanvas2D, - gl: WebGL2RenderingContext, - program: GL2Program, - transform: Transform -): void; -``` - -**描述** -抽象渲染方法,子类必须实现具体绘制逻辑 - ---- - -## 静态方法说明 - -### `GL2.support` - -```typescript -static readonly support: boolean; -``` - -**描述** -静态只读属性,检测 WebGL2 支持性 -**示例** - -```typescript -if (GL2.support) { - // 初始化 WebGL2 功能 -} -``` - ---- - -## 总使用示例 - -暂时没有。 diff --git a/docs/api/motajs-render-core/GL2Program.md b/docs/api/motajs-render-core/GL2Program.md deleted file mode 100644 index 742140d..0000000 --- a/docs/api/motajs-render-core/GL2Program.md +++ /dev/null @@ -1,188 +0,0 @@ -# GL2Program 类 API 文档 - -**需丰富** - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - GL2Program --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 说明 | -| -------------- | ------------------------ | ------------------------------------------ | -| `gl` | `WebGL2RenderingContext` | WebGL2 渲染上下文 | -| `element` | `GL2` | 关联的 GL2 渲染元素 | -| `program` | `WebGLProgram \| null` | WebGL 着色器程序对象 | -| `renderMode` | `RenderMode` | 当前渲染模式(默认 `RenderMode.Elements`) | -| `usingIndices` | `IShaderIndices \| null` | 当前使用的顶点索引数组 | - ---- - -## 方法说明 - -### `defineUniform` - -```typescript -function defineUniform( - uniform: string, - type: T -): IShaderUniform | null; -``` - -**描述** -定义 Uniform 变量 -**参数** - -- `uniform`: Uniform 变量名 -- `type`: Uniform 类型(如 `GL2.UNIFORM_2f`) - **返回值** -- 操作对象(可设置值)或 `null`(定义失败) - ---- - -### `defineTexture` - -```typescript -function defineTexture( - name: string, - index: number, - w?: number, - h?: number -): IShaderTexture2D | null; -``` - -**描述** -定义纹理对象 -**参数** - -- `name`: 纹理名称 -- `index`: 纹理索引(建议不超过 8) -- `w`: 纹理宽度(可选) -- `h`: 纹理高度(可选) - **示例** - -```typescript -const tex = program.defineTexture('diffuse', 0, 512, 512); -``` - ---- - -### `paramElements` - -```typescript -function paramElements( - mode: GLenum, - count: number, - type: GLenum, - offset: number -): void; -``` - -**描述** -设置元素模式渲染参数 -**参数** - -- `mode`: 渲染模式(如 `gl.TRIANGLES`) -- `count`: 元素数量 -- `type`: 数据类型(如 `gl.UNSIGNED_SHORT`) -- `offset`: 数据偏移量 - ---- - -### `requestCompile` - -```typescript -function requestCompile(force?: boolean): boolean; -``` - -**描述** -请求编译着色器 -**参数** - -- `force`: 是否强制重新编译 - **返回值** -- `true` 表示编译成功 - ---- - -### `vs` - -```typescript -function vs(vs: string): void; -``` - -**描述** -设置顶点着色器代码 -**示例** - -```typescript -program.vs(` - ${GL2_PREFIX.VERTEX} - in vec4 aPosition; - void main() { - gl_Position = aPosition; - } -`); -``` - ---- - -## 事件说明 - -| 事件名 | 触发时机 | 参数类型 | -| -------- | ---------------- | -------- | -| `load` | 程序被加载使用时 | `[]` | -| `unload` | 程序被卸载时 | `[]` | - ---- - -## 总使用示例 - -```typescript -// 创建着色器程序 -const program = gl2.createProgram(GL2Program); - -// 定义着色器 -program.vs(` - ${GL2_PREFIX.VERTEX} - uniform mat4 uProjection; - in vec4 aPosition; - void main() { - gl_Position = uProjection * aPosition; - } -`); - -program.fs(` - ${GL2_PREFIX.FRAGMENT} - out vec4 fragColor; - uniform vec3 uColor; - void main() { - fragColor = vec4(uColor, 1.0); - } -`); - -// 定义 Uniform 和纹理 -const colorUniform = program.defineUniform('uColor', UniformType.Uniform3f); -const diffuseTex = program.defineTexture('diffuse', 0, 512, 512); - -// 设置渲染参数 -program.paramElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); - -// 编译并应用 -if (program.requestCompile()) { - gl2.useProgram(program); - colorUniform?.set(1.0, 0.5, 0.2); - diffuseTex?.set(imageElement); -} -``` diff --git a/docs/api/motajs-render-core/MotaOffscreenCanvas2D.md b/docs/api/motajs-render-core/MotaOffscreenCanvas2D.md deleted file mode 100644 index c56d1d6..0000000 --- a/docs/api/motajs-render-core/MotaOffscreenCanvas2D.md +++ /dev/null @@ -1,311 +0,0 @@ -# MotaOffscreenCanvas2D 类 API 文档 - -以下内容由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - MotaOffscreenCanvas2D --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| ---------------- | -------------------------- | -------- | ------------------------------------------- | -| `canvas` | `HTMLCanvasElement` | - | 关联的 HTML 画布元素 | -| `ctx` | `CanvasRenderingContext2D` | - | 画布的 2D 渲染上下文 | -| `width` | `number` | 自动计算 | 画布的逻辑宽度(不包含缩放比例) | -| `height` | `number` | 自动计算 | 画布的逻辑高度(不包含缩放比例) | -| `autoScale` | `boolean` | `false` | 是否自动跟随 `core.domStyle.scale` 进行缩放 | -| `highResolution` | `boolean` | `true` | 是否启用高清画布(根据设备像素比例缩放) | -| `antiAliasing` | `boolean` | `true` | 是否启用抗锯齿 | -| `scale` | `number` | `1` | 当前画布的缩放比例 | -| `symbol` | `number` | `0` | 更新标识符,值变化表示画布被被动清空或调整 | -| `freezed` | `boolean`(只读) | `false` | 当前画布是否被冻结(冻结后不可修改属性) | -| `active` | `boolean`(只读) | `true` | 当前画布是否处于激活状态 | - ---- - -## 构造方法 - -### `constructor` - -```ts -function constructor( - alpha: boolean = true, - canvas?: HTMLCanvasElement -): MotaOffscreenCanvas2D; -``` - -**描述** -创建一个新的离屏画布。 -**参数** - -- `alpha`: 是否启用透明度通道(默认为 `true`)。 -- `canvas`: 可指定现有画布,未提供时自动创建新画布。 - **注意** -- 在自定义渲染元素中,建议使用 `RenderItem.requireCanvas` 而非直接调用此构造函数。之后如果不使用,再使用 `RenderItem.deleteCanvas` 删除。 - ---- - -## 方法说明 - -### `size` - -```ts -function size(width: number, height: number): void; -``` - -**描述** -设置画布的尺寸。 -**参数** - -- `width`: 逻辑宽度(最小为 1)。 -- `height`: 逻辑高度(最小为 1)。 - **行为** -- 自动计算缩放比例(考虑 `highResolution` 和 `autoScale`)。 -- 调整画布物理尺寸和样式尺寸。 - -**示例** - -```typescript -const canvas = new MotaOffscreenCanvas2D(); -canvas.size(800, 600); // 设置画布尺寸为 800x600(逻辑尺寸) -``` - ---- - -### `withGameScale` - -```ts -function withGameScale(auto: boolean): void; -``` - -**描述** -设置画布是否跟随 `core.domStyle.scale` 自动缩放。 -**参数** - -- `auto`: 是否启用自动缩放。 - -**示例** - -```typescript -canvas.withGameScale(true); // 启用自动缩放 -``` - ---- - -### `setHD` - -```ts -function setHD(hd: boolean): void; -``` - -**描述** -设置是否为高清画布(基于设备像素比例)。 -**参数** - -- `hd`: 是否启用高清模式。 - -**示例** - -```typescript -canvas.setHD(false); // 关闭高清模式 -``` - ---- - -### `setAntiAliasing` - -```ts -function setAntiAliasing(anti: boolean): void; -``` - -**描述** -设置抗锯齿功能。 -**参数** - -- `anti`: 是否启用抗锯齿。 - -**示例** - -```typescript -canvas.setAntiAliasing(false); // 关闭抗锯齿 -``` - ---- - -### `clear` - -```ts -function clear(): void; -``` - -**描述** -清空画布内容。 -**注意** - -- 冻结状态下调用此方法会触发警告。 - -**示例** - -```typescript -canvas.clear(); // 清空画布 -``` - ---- - -### `delete` - -```ts -function delete(): void -``` - -**描述** -删除画布,释放资源并解除 DOM 绑定。 - -**示例** - -```typescript -canvas.delete(); // 删除画布 -``` - ---- - -### `freeze` - -```ts -function freeze(): void; -``` - -**描述** -冻结画布,禁止修改属性,并从全局列表中移除。 - -**示例** - -```typescript -canvas.freeze(); // 冻结画布 -``` - ---- - -### `activate` - -```ts -function activate(): void; -``` - -**描述** -激活画布,使其跟随游戏缩放调整尺寸。 - -**示例** - -```typescript -canvas.activate(); // 激活画布 -``` - ---- - -### `deactivate` - -```ts -function deactivate(): void; -``` - -**描述** -停用画布,不再自动调整尺寸,可能被垃圾回收。 - -**示例** - -```typescript -canvas.deactivate(); // 停用画布 -``` - ---- - -## 静态方法说明 - -### `MotaOffscreenCanvas2D.clone` - -```ts -function clone(canvas: MotaOffscreenCanvas2D): MotaOffscreenCanvas2D; -``` - -**描述** -复制一个画布对象,结果画布将被冻结。 -**返回值** - -- 复制的画布对象(不可修改属性,但可绘制)。 - -**示例** - -```typescript -const cloned = MotaOffscreenCanvas2D.clone(sourceCanvas); // 复制画布 -``` - ---- - -### `MotaOffscreenCanvas2D.refreshAll` - -```ts -function refreshAll(force: boolean = false): void; -``` - -**描述** -刷新所有已注册画布的尺寸(仅在窗口大小变化时自动调用)。 -**参数** - -- `force`: 是否强制刷新所有画布(默认仅刷新启用 `autoScale` 的画布)。 - ---- - -## 事件类型 - -### `resize` - -**触发时机** -当画布被动调整尺寸时触发(例如窗口大小变化或 `core.domStyle.scale` 变化)。 - -**监听示例** - -```typescript -canvas.on('resize', () => { - console.log('画布尺寸已调整'); -}); -``` - ---- - -## 使用示例 - -```typescript -// 创建画布 -const canvas = new MotaOffscreenCanvas2D(); - -// 配置属性 -canvas.size(800, 600); -canvas.withGameScale(true); -canvas.setHD(true); - -// 监听调整事件 -canvas.on('resize', () => { - console.log('画布尺寸已更新'); -}); - -// 绘制内容 -canvas.ctx.fillStyle = 'red'; -canvas.ctx.fillRect(0, 0, canvas.width, canvas.height); - -// 冻结画布 -canvas.freeze(); - -// 复制画布 -const cloned = MotaOffscreenCanvas2D.clone(canvas); -``` diff --git a/docs/api/motajs-render-core/MotaRenderer.md b/docs/api/motajs-render-core/MotaRenderer.md deleted file mode 100644 index aa0940f..0000000 --- a/docs/api/motajs-render-core/MotaRenderer.md +++ /dev/null @@ -1,174 +0,0 @@ -# MotaRenderer 类 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - MotaRenderer --> Container --> RenderItem --> EventEmitter - - click Container "./Container" - click RenderItem "./RenderItem" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| -------- | ------------------------- | ----------- | ------------------------------- | -| `isRoot` | `boolean` | `true` | 标识为渲染树根节点 | -| `target` | `MotaOffscreenCanvas2D` | - | 绑定的目标画布 | -| `idMap` | `Map` | `new Map()` | ID 到渲染元素的映射表(受保护) | - ---- - -## 构造方法 - -### `constructor` - -**参数** - -- `id`: 目标 canvas 元素的 DOM ID(默认为 `render-main`) - -**行为** - -- 自动绑定指定 ID 的 canvas 元素 -- 初始化渲染循环和事件监听 -- 设置默认锚点为中心点(0.5, 0.5) - -**示例** - -```typescript -// 创建主渲染器 -const renderer = new MotaRenderer(); - -// 创建带自定义 ID 的渲染器 -const customRenderer = new MotaRenderer('game-canvas'); -``` - ---- - -## 方法说明 - -### `getElementById` - -```typescript -function getElementById(id: string): RenderItem | null; -``` - -**描述** -通过 ID 获取渲染树中的元素。 -**示例** - -```typescript -const hero = renderer.getElementById('player'); -``` - ---- - -### `refresh` - -```typescript -function refresh(): void; -``` - -**描述** -强制刷新渲染内容(清空画布并重新渲染所有元素)。 - ---- - -### `toTagTree` - -```typescript -function toTagTree(space?: number): string; -``` - -**描述** -(调试用)将渲染树输出为 XML 格式字符串。 -**参数** - -- `space`: 缩进空格数 - **示例** - -```typescript -console.log(renderer.toTagTree()); -/* 输出示例: - - - - - -*/ -``` - ---- - -### `destroy` - -```typescript -function destroy(): void; -``` - -**描述** -销毁渲染器,释放所有资源并解除事件监听。 - ---- - -## 静态方法说明 - -### `MotaRenderer.get` - -```typescript -function get(id: string): MotaRenderer | undefined; -``` - -**描述** -通过 ID 获取已注册的渲染器实例。 -**示例** - -```typescript -const mainRenderer = MotaRenderer.get('render-main'); -``` - ---- - -## 总使用示例 - -```typescript -// 初始化渲染器 -const renderer = new MotaRenderer(); - -// 创建游戏元素 -const player = new Sprite(); -player.size(32, 32); -player.setRenderFn(canvas => { - canvas.ctx.fillStyle = 'blue'; - canvas.ctx.fillRect(0, 0, 32, 32); -}); - -// 添加交互逻辑 -player.on('click', ev => { - console.log('玩家被点击', ev.offsetX, ev.offsetY); -}); - -// 构建场景 -const scene = new Container('absolute'); -scene.appendChild(player); -renderer.appendChild(scene); - -// 动态查找元素 -setTimeout(() => { - const found = renderer.getElementById('player'); - found?.pos(100, 100); -}, 1000); - -// 销毁渲染器(退出时调用) -window.addEventListener('beforeunload', () => { - renderer.destroy(); -}); -``` diff --git a/docs/api/motajs-render-core/RenderAdapter.md b/docs/api/motajs-render-core/RenderAdapter.md deleted file mode 100644 index d9146f2..0000000 --- a/docs/api/motajs-render-core/RenderAdapter.md +++ /dev/null @@ -1,230 +0,0 @@ -# RenderAdapter API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - RenderAdapter --> 无继承关系 -``` - -_RenderAdapter 为独立类,无父类或子类。_ - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ------------------ | --------------------------------- | ------------------------------ | -| `items` | `Set` | 所有元素的集合 | -| `id` | `string` | 适配器的唯一标识符 | -| `adapters`(静态) | `Map>` | 全局存储所有已创建的适配器实例 | - ---- - -## 构造方法 - -### `constructor` - -```typescript -function constructor(id: string): RenderAdapter; -``` - -创建一个适配器实例并自动注册到全局 `adapters` 集合中。 -**示例:** - -```typescript -const adapter = new RenderAdapter('ui-elements'); -``` - ---- - -## 方法说明 - -### `add` - -```typescript -function add(item: T): void; -``` - -向集合中添加一个元素。 -**示例:** - -```typescript -adapter.add(document.getElementById('box')); -``` - -### `remove` - -```typescript -function remove(item: T): void; -``` - -从集合中移除一个元素。 -**示例:** - -```typescript -adapter.remove(document.getElementById('box')); -``` - -### `receiveGlobal` - -```typescript -function receiveGlobal( - id: string, - fn: (...params: any[]) => Promise -): void; -``` - -注册全局异步函数(不与具体元素绑定)。 -**示例:** - -```typescript -adapter.receiveGlobal('refresh', async () => { - await fetchData(); -}); -``` - -### `receive` - -```typescript -function receive( - id: string, - fn: (item: T, ...params: any[]) => Promise -): void; -``` - -注册元素的异步执行函数。 -**示例:** - -```typescript -adapter.receive('fadeOut', async (element: HTMLElement) => { - element.style.opacity = '0'; - await new Promise(resolve => setTimeout(resolve, 1000)); -}); -``` - -### `receiveSync` - -```typescript -function receiveSync(id: string, fn: (item: T, ...params: any[]) => any): void; -``` - -注册元素的同步执行函数。 -**示例:** - -```typescript -adapter.receiveSync('highlight', (element: HTMLElement) => { - element.style.backgroundColor = 'yellow'; -}); -``` - -### `all` - -```typescript -function all(fn: string, ...params: any[]): Promise; -``` - -对所有元素执行异步函数,返回 `Promise.all` 结果。 -**示例:** - -```typescript -await adapter.all('fadeOut'); // 所有元素淡出 -``` - -### `any` - -```typescript -function any(fn: string, ...params: any[]): Promise; -``` - -对所有元素执行异步函数,返回 `Promise.any` 结果。 -**示例:** - -```typescript -await adapter.any('loadImage'); // 任一图片加载完成即继续 -``` - -### `sync` - -```typescript -function sync(fn: string, ...params: any[]): R[]; -``` - -对所有元素执行同步函数。 -**示例:** - -```typescript -adapter.sync('highlight'); // 所有元素高亮 -``` - -### `global` - -```typescript -function global(id: string, ...params: any[]): Promise; -``` - -调用全局异步函数。 -**示例:** - -```typescript -await adapter.global('refresh'); // 触发全局刷新 -``` - -### `destroy` - -```typescript -function destroy(): void; -``` - -销毁适配器实例并从全局 `adapters` 中移除。 -**示例:** - -```typescript -adapter.destroy(); -``` - ---- - -## 静态方法说明 - -### `RenderAdapter.get` - -```typescript -function get(id: string): RenderAdapter | undefined; -``` - -通过 ID 获取已注册的适配器实例。 -**示例:** - -```typescript -const adapter = RenderAdapter.get('ui-elements'); -``` - ---- - -## 总使用示例 - -```typescript -// 创建适配器 -const animationAdapter = new RenderAdapter('animations'); - -// 注册动画函数 -animationAdapter.receive('slideLeft', async (element: HTMLElement) => { - element.style.transform = 'translateX(-100px)'; - await new Promise(resolve => setTimeout(resolve, 500)); -}); - -// 添加元素 -const box = document.getElementById('box'); -animationAdapter.add(box); - -// 执行动画 -animationAdapter.all('slideLeft').then(() => { - console.log('所有元素滑动完成'); -}); - -// 销毁适配器 -animationAdapter.destroy(); -``` diff --git a/docs/api/motajs-render-core/RenderItem.md b/docs/api/motajs-render-core/RenderItem.md deleted file mode 100644 index 40ca605..0000000 --- a/docs/api/motajs-render-core/RenderItem.md +++ /dev/null @@ -1,477 +0,0 @@ -# RenderItem 类 API 文档 - -**需丰富** - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - RenderItem --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 接口说明 - -### IRenderUpdater - -```typescript -interface IRenderUpdater { - update(item?: RenderItem): void; -} -``` - -**描述** -定义元素更新能力的接口。`RenderItem` 通过 `update` 方法通知父元素需要重新渲染。 - ---- - -### IRenderAnchor - -```typescript -interface IRenderAnchor { - anchorX: number; - anchorY: number; - setAnchor(x: number, y: number): void; -} -``` - -**描述** -管理元素锚点的接口。锚点用于定位元素的渲染基准点(如中心点、左上角等)。 - ---- - -### IRenderConfig - -```typescript -interface IRenderConfig { - highResolution: boolean; - antiAliasing: boolean; - setHD(hd: boolean): void; - setAntiAliasing(anti: boolean): void; -} -``` - -**描述** -管理画布渲染配置的接口,控制高清模式和抗锯齿的开关。 - ---- - -### IRenderChildable - -```typescript -interface IRenderChildable { - children: Set; - appendChild(...child: RenderItem[]): void; - removeChild(...child: RenderItem[]): void; - requestSort(): void; -} -``` - -**描述** -管理子元素的接口。需在子类中实现具体逻辑(如 `Container` 元素)。 - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| ---------------- | -------------------------- | --------------- | ---------------------------------------------------- | -| `uid` | `number` | 自动递增 | 元素的唯一标识符 | -| `id` | `string` | `''` | 元素 ID(原则不可重复) | -| `zIndex` | `number` | `0` | 元素的纵深层级(决定遮挡关系) | -| `width` | `number` | `200` | 元素的逻辑宽度 | -| `height` | `number` | `200` | 元素的逻辑高度 | -| `anchorX` | `number` | `0` | 锚点横坐标(`0` 左端,`1` 右端) | -| `anchorY` | `number` | `0` | 锚点纵坐标(`0` 上端,`1` 下端) | -| `type` | `RenderItemPosition` | `'static'` | 渲染模式(`absolute` 绝对定位,`static` 跟随摄像机) | -| `highResolution` | `boolean` | `true` | 是否启用高清画布 | -| `antiAliasing` | `boolean` | `true` | 是否启用抗锯齿 | -| `hidden` | `boolean` | `false` | 元素是否隐藏 | -| `filter` | `string` | `'none'` | 元素的滤镜效果 | -| `composite` | `GlobalCompositeOperation` | `'source-over'` | 渲染混合模式 | -| `alpha` | `number` | `1` | 元素的不透明度(`0` 透明,`1` 不透明) | -| `cursor` | `string` | `'inherit'` | 鼠标悬停时的光标样式 | -| `noEvent` | `boolean` | `false` | 是否忽略交互事件 | -| `isRoot` | `boolean` | `false` | 是否为根元素(需实现 `IRenderTreeRoot` 接口) | -| `connected` | `boolean` | 自动计算 | 元素是否已连接到根元素 | - -**特殊属性说明** - -- `transform`: 元素的变换矩阵(`Transform` 实例),修改时会自动触发 `updateTransform`。 - ---- - -## 构造方法 - -### `constructor(type: RenderItemPosition, enableCache: boolean = true, transformFallThrough: boolean = false)` - -**参数** - -- `type`: 渲染模式(`absolute` 或 `static`) -- `enableCache`: 是否启用渲染缓存(默认启用) -- `transformFallThrough`: 是否启用变换矩阵下穿机制(默认关闭) - -**示例** - -```typescript -const item = new RenderItem('absolute'); -``` - ---- - -## 方法说明 - -### `size` - -```typescript -function size(width: number, height: number): void; -``` - -**描述** -设置元素的尺寸。 -**示例** - -```typescript -item.size(300, 200); // 设置宽度 300,高度 200 -``` - ---- - -### `pos` - -```typescript -function pos(x: number, y: number): void; -``` - -**描述** -设置元素的坐标(等效于修改 `transform` 的平移量)。 -**示例** - -```typescript -item.pos(100, 50); // 设置坐标为 (100, 50) -``` - ---- - -### `append` - -```typescript -function append(parent: RenderItem): void; -``` - -**描述** -将元素添加到指定父元素下。 -**示例** - -```typescript -const parent = new RenderItem('static'); -item.append(parent); // 将 item 添加为 parent 的子元素 -``` - ---- - -### `remove` - -```typescript -function remove(): boolean; -``` - -**描述** -从父元素中移除当前元素。 -**返回值** - -- `true` 表示移除成功,`false` 表示失败。 - **示例** - -```typescript -item.remove(); // 从父元素中移除 -``` - ---- - -### `hide` - -```typescript -function hide(): void; -``` - -**描述** -隐藏元素。 -**示例** - -```typescript -item.hide(); // 隐藏元素 -``` - ---- - -### `show` - -```typescript -function show(): void; -``` - -**描述** -显示元素。 -**示例** - -```typescript -item.show(); // 显示元素 -``` - ---- - -### `delegateTicker` - -```typescript -function delegateTicker(fn: TickerFn, time?: number, end?: () => void): number; -``` - -**描述** -委托动画帧函数,持续执行指定时间。 -**返回值** - -- 委托 ID,可用于移除。 - **示例** - -```typescript -const id = item.delegateTicker(() => { - console.log('每帧执行'); -}, 1000); // 持续 1 秒 -``` - ---- - -### `destroy` - -```typescript -function destroy(): void; -``` - -**描述** -销毁元素,释放资源。 -**示例** - -```typescript -item.destroy(); // 销毁元素 -``` - -### `getAbsolutePosition` - -```typescript -function getAbsolutePosition(x?: number, y?: number): [number, number]; -``` - -**描述** -获取元素在全局坐标系中的绝对坐标。 -**示例** - -```typescript -const [absX, absY] = item.getAbsolutePosition(); // 获取元素原点绝对坐标 -``` - ---- - -### `getBoundingRect` - -```typescript -function getBoundingRect(): DOMRectReadOnly; -``` - -**描述** -获取元素的包围矩形(相对于父元素坐标系)。 -**示例** - -```typescript -const rect = item.getBoundingRect(); -console.log(rect.width, rect.height); -``` - ---- - -### `setZIndex` - -```typescript -function setZIndex(zIndex: number): void; -``` - -**描述** -设置元素的纵深层级(`zIndex` 越大越靠前)。 -**示例** - -```typescript -item.setZIndex(5); // 置顶显示 -``` - ---- - -### `requestRenderFrame` - -```typescript -function requestRenderFrame(fn: () => void): void; -``` - -**描述** -在下一帧渲染时执行函数(适用于需要在渲染流程中更新的操作)。 -**示例** - -```typescript -item.requestRenderFrame(() => { - item.pos(item.x + 1, item.y); // 每帧右移 1 单位 -}); -``` - ---- - -### `setFilter` - -```typescript -function setFilter(filter: string): void; -``` - -**描述** -设置元素的 CSS 滤镜效果(如模糊、灰度等)。 -**示例** - -```typescript -item.setFilter('blur(5px)'); // 添加模糊效果 -``` - ---- - -## 受保护方法说明 - -### `render` - -```typescript -protected abstract render(canvas: MotaOffscreenCanvas2D, transform: Transform): void; -``` - -**描述** -抽象渲染方法,子类必须实现此方法以定义具体渲染逻辑。 -**示例** - -```typescript -class CustomItem extends RenderItem { - protected render(canvas: MotaOffscreenCanvas2D) { - canvas.ctx.fillStyle = 'red'; - canvas.ctx.fillRect(0, 0, this.width, this.height); - } -} -``` - ---- - -### `isActionInElement` - -```typescript -protected isActionInElement(x: number, y: number): boolean; -``` - -**描述** -判断坐标点是否在元素范围内(可覆盖实现自定义碰撞检测)。 -**默认行为** -检测坐标是否在 `[0, width] x [0, height]` 矩形内。 - ---- - -## 静态方法说明 - -### `RenderItem.ticker` - -**类型** - -```typescript -const ticker: Ticker; -``` - -**描述** -全局动画帧管理器,用于处理所有委托的动画帧函数。 - ---- - -## 事件说明 - -事件继承自 [ERenderItemActionEvent](./Event.md#erenderitemactionevent) - -| 事件名 | 参数类型 | 说明 | -| -------------- | ------------------------- | ------------------ | -| `beforeRender` | `Transform` | 渲染前触发 | -| `afterRender` | `Transform` | 渲染后触发 | -| `destroy` | `[]` | 元素销毁时触发 | -| `transform` | `[RenderItem, Transform]` | 变换矩阵更新时触发 | - ---- - -## 总使用示例 - -::: code-group - -```typescript [基础操作] -// 创建元素(以 Sprite 为例) -const item = new Sprite('static'); - -// 设置属性 -item.size(400, 300); -item.pos(100, 50); -item.setAnchor(0.5, 0.5); // 设置中心锚点 -item.setZIndex(2); - -// 监听渲染事件 -item.on('beforeRender', transform => { - console.log('即将渲染,变换矩阵:', transform); -}); - -// 添加动画效果 -const tickerId = item.delegateTicker(time => { - item.pos(Math.sin(time / 1000) * 100, 50); -}); - -// 销毁元素 -setTimeout(() => { - item.destroy(); -}, 5000); -``` - -```typescript [创建自定义元素] -// 创建自定义可交互元素 -class Button extends RenderItem { - constructor() { - super('static'); - this.size(100, 40); - this.on('click', ev => { - console.log('按钮被点击!坐标:', ev.offsetX, ev.offsetY); - }); - } - - protected render(canvas: MotaOffscreenCanvas2D) { - // 绘制圆角矩形按钮 - const ctx = canvas.ctx; - ctx.fillStyle = '#4CAF50'; - ctx.roundRect(0, 0, this.width, this.height, 8); - ctx.fill(); - } -} - -// 使用按钮 -const button = new Button(); -button.pos(200, 150); -button.append(parentElement); - -// 添加鼠标悬停效果 -button.on('enter', () => button.setFilter('brightness(1.2)')); -button.on('leave', () => button.setFilter('none')); -``` - -::: - -# RenderItem 类 API 文档(补充部分) - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- diff --git a/docs/api/motajs-render-core/Shader.md b/docs/api/motajs-render-core/Shader.md deleted file mode 100644 index b213a8b..0000000 --- a/docs/api/motajs-render-core/Shader.md +++ /dev/null @@ -1,12 +0,0 @@ -# Shader 类 API 文档 - -```mermaid -graph LR - Shader --> GL2 --> RenderItem --> EventEmitter - - click GL2 "./GL2" - click RenderItem "./RenderItem" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -用法同 [GL2](./GL2.md) diff --git a/docs/api/motajs-render-core/ShaderProgram.md b/docs/api/motajs-render-core/ShaderProgram.md deleted file mode 100644 index 30395a9..0000000 --- a/docs/api/motajs-render-core/ShaderProgram.md +++ /dev/null @@ -1,11 +0,0 @@ -# Shader 类 API 文档 - -```mermaid -graph LR - ShaderProgram --> GL2Program --> EventEmitter - - click GL2Program "./GL2Program" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -用法同 [GL2Program](./GL2Program.md) diff --git a/docs/api/motajs-render-core/Sprite.md b/docs/api/motajs-render-core/Sprite.md deleted file mode 100644 index 40f07b1..0000000 --- a/docs/api/motajs-render-core/Sprite.md +++ /dev/null @@ -1,121 +0,0 @@ -# Sprite 类 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - Sprite --> RenderItem --> EventEmitter - - click RenderItem "./RenderItem" - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| ---------- | ---------------- | ---------- | -------------------------------------- | -| `renderFn` | `RenderFunction` | `() => {}` | 自定义渲染函数,用于定义精灵的绘制逻辑 | - ---- - -## 构造方法 - -### `constructor` - -**参数** - -- `type`: 渲染模式(`absolute` 绝对定位 / `static` 跟随摄像机) -- `cache`: 是否启用渲染缓存(默认启用) -- `fall`: 是否启用变换矩阵下穿机制(默认关闭) - -**示例** - -```typescript -const sprite = new Sprite('static'); -``` - ---- - -## 方法说明 - -### `setRenderFn` - -```typescript -function setRenderFn(fn: RenderFunction): void; -``` - -**描述** -设置自定义渲染函数,用于定义精灵的具体绘制逻辑。 -**参数** - -- `fn`: 接收画布和变换矩阵的回调函数,格式为 `(canvas, transform) => void` - -**示例** - -```typescript -sprite.setRenderFn((canvas, transform) => { - // 绘制一个红色矩形 - canvas.ctx.fillStyle = 'red'; - canvas.ctx.fillRect(0, 0, sprite.width, sprite.height); -}); -``` - ---- - -## 总使用示例 - -```typescript -// 创建精灵实例 -const sprite = new Sprite('absolute'); -sprite.size(100, 100); // 设置尺寸 -sprite.pos(200, 150); // 设置坐标 - -// 定义渲染逻辑 -sprite.setRenderFn(canvas => { - const ctx = canvas.ctx; - // 绘制渐变圆形 - const gradient = ctx.createRadialGradient(50, 50, 0, 50, 50, 50); - gradient.addColorStop(0, 'yellow'); - gradient.addColorStop(1, 'orange'); - ctx.fillStyle = gradient; - ctx.beginPath(); - ctx.arc(50, 50, 50, 0, Math.PI * 2); - ctx.fill(); -}); - -// 添加到父容器 -container.appendChild(sprite); - -// 监听变换事件 -sprite.on('transform', (item, transform) => { - console.log('精灵变换矩阵更新:', transform); -}); -``` - ---- - -## 高级用法示例 - -```typescript -// 创建动态旋转精灵 -const rotatingSprite = new Sprite('static'); -rotatingSprite.size(80, 80); -rotatingSprite.setRenderFn((canvas, transform) => { - canvas.ctx.fillStyle = 'blue'; - canvas.ctx.fillRect(0, 0, 80, 80); -}); - -// 每帧旋转 -rotatingSprite.delegateTicker(time => { - rotatingSprite.transform.setRotate(time / 1000); -}); - -// 添加到场景 -rootContainer.appendChild(rotatingSprite); -``` diff --git a/docs/api/motajs-render-core/Transform.md b/docs/api/motajs-render-core/Transform.md deleted file mode 100644 index a9ff837..0000000 --- a/docs/api/motajs-render-core/Transform.md +++ /dev/null @@ -1,332 +0,0 @@ -# Transform 类 API 文档 - -以下内容由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 继承关系 - -```mermaid -graph LR - Transform --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 默认值 | 说明 | -| -------------- | --------------------- | ----------- | ----------------------------------------------------------- | -| `mat` | `mat3` | 单位矩阵 | 存储当前变换的 3x3 矩阵 | -| `x` | `number` | `0` | 水平平移量 | -| `y` | `number` | `0` | 垂直平移量 | -| `scaleX` | `number` | `1` | 水平缩放比例 | -| `scaleY` | `number` | `1` | 垂直缩放比例 | -| `rad` | `number` | `0` | 旋转弧度值(范围:`[0, 2π)`) | -| `modified` | `boolean`(私有) | `false` | 标识变换是否被修改过 | -| `bindedObject` | `ITransformUpdatable` | `undefined` | 绑定的对象(当变换更新时自动调用其 `updateTransform` 方法) | - ---- - -## 构造方法 - -### `constructor` - -创建一个新的变换实例,初始化为单位矩阵。 - -```typescript -const transform = new Transform(); -``` - ---- - -## 方法说明 - -### `bind` - -```typescript -function bind(obj?: ITransformUpdatable): void; -``` - -**描述** -绑定一个对象,当变换更新时自动调用其 `updateTransform` 方法(若存在)。 - -**示例** - -```typescript -const obj = { updateTransform: () => console.log('Transform updated!') }; -transform.bind(obj); -``` - ---- - -### `reset` - -```typescript -function reset(): void; -``` - -**描述** -重置所有参数到初始状态(单位矩阵)。 - -**示例** - -```typescript -transform.reset(); // 重置为 x=0, y=0, scaleX=1, scaleY=1, rad=0 -``` - ---- - -### `scale` - -```typescript -function scale(x: number, y: number = x): this; -``` - -**描述** -叠加缩放变换(相对于当前状态)。 - -**参数** - -- `x`: 水平缩放比例 -- `y`: 垂直缩放比例(默认同 `x`) - -**示例** - -```typescript -transform.scale(2); // 水平和垂直均放大2倍 -transform.scale(1.5, 0.5); // 水平放大1.5倍,垂直缩小到0.5倍 -``` - ---- - -### `translate` - -```typescript -function translate(x: number, y: number): this; -``` - -**描述** -叠加平移变换(相对于当前状态)。 - -**参数** - -- `x`: 水平平移量 -- `y`: 垂直平移量 - -**示例** - -```typescript -transform.translate(100, 50); // 向右平移100单位,向下平移50单位 -``` - ---- - -### `rotate` - -```typescript -function rotate(rad: number): this; -``` - -**描述** -叠加旋转变换(相对于当前状态)。 - -**参数** - -- `rad`: 旋转弧度值 - -**示例** - -```typescript -transform.rotate(Math.PI / 2); // 顺时针旋转90度 -``` - ---- - -### `setScale` - -```typescript -function setScale(x: number, y: number = x): this; -``` - -**描述** -直接设置缩放比例(非叠加,覆盖当前状态)。 - -**示例** - -```typescript -transform.setScale(3, 2); // 设置水平缩放3倍,垂直缩放2倍 -``` - ---- - -### `setTranslate` - -```typescript -function setTranslate(x: number, y: number): this; -``` - -**描述** -直接设置平移量(非叠加,覆盖当前状态)。 - -**示例** - -```typescript -transform.setTranslate(200, 100); // 直接定位到(200, 100) -``` - ---- - -### `setRotate` - -```typescript -function setRotate(rad: number): this; -``` - -**描述** -直接设置旋转角度(非叠加,覆盖当前状态)。 - -**示例** - -```typescript -transform.setRotate(Math.PI); // 设置旋转180度 -``` - ---- - -### `transformed` - -```typescript -function transformed(x: number, y: number): vec3; -``` - -**描述** -将坐标点 `(x, y)` 应用当前变换矩阵,返回变换后的坐标。 - -**返回值** - -- `vec3`: 变换后的三维坐标(`[x, y, 1]`) - -**示例** - -```typescript -const point = transform.transformed(10, 20); // 应用变换后的坐标 -``` - ---- - -### `untransformed` - -```typescript -function untransformed(x: number, y: number): vec3; -``` - -**描述** -将坐标点 `(x, y)` 逆向应用当前变换矩阵,返回原坐标。 - -**示例** - -```typescript -const origin = transform.untransformed(50, 30); // 逆向变换后的坐标 -``` - ---- - -### `clone` - -```typescript -function clone(): Transform; -``` - -**描述** -复制当前变换实例。 - -**示例** - -```typescript -const cloned = transform.clone(); // 生成一个完全相同的副本 -``` - ---- - -## 静态方法说明 - -### `Transform.transformed` - -```typescript -function transformed(transform: Transform, x: number, y: number): vec3; -``` - -**描述** -静态方法,直接通过变换矩阵计算坐标点 `(x, y)` 的变换结果。 - -**示例** - -```typescript -const result = Transform.transformed(transform, 5, 5); -``` - ---- - -### `Transform.untransformed` - -```typescript -function untransformed(transform: Transform, x: number, y: number): vec3; -``` - -**描述** -静态方法,直接通过变换矩阵逆向计算坐标点 `(x, y)` 的原位置。 - -**示例** - -```typescript -const origin = Transform.untransformed(transform, 100, 50); -``` - ---- - -## 接口说明 - -### `ITransformUpdatable` - -```typescript -interface ITransformUpdatable { - updateTransform?(): void; -} -``` - -**描述** -可绑定对象的接口,当变换更新时触发 `updateTransform` 方法(可选)。 - ---- - -## 总使用示例 - -```typescript -// 创建变换实例 -const transform = new Transform(); - -// 应用变换 -transform - .translate(50, 30) - .scale(2) - .rotate(Math.PI / 4); - -// 绑定对象 -const obj = { - updateTransform: () => console.log('Transform updated!') -}; -transform.bind(obj); - -// 坐标转换 -const transformedPoint = transform.transformed(10, 10); -console.log('Transformed point:', transformedPoint); - -// 复制变换 -const cloned = transform.clone(); - -// 静态方法使用 -const staticResult = Transform.transformed(cloned, 5, 5); -``` diff --git a/docs/api/motajs-render-core/functions.md b/docs/api/motajs-render-core/functions.md deleted file mode 100644 index a428eaf..0000000 --- a/docs/api/motajs-render-core/functions.md +++ /dev/null @@ -1,119 +0,0 @@ -# 函数 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 函数说明 - -### `isWebGLSupported` - -**功能** -检测浏览器是否支持 WebGL 1.0 -**返回值** - -- `true`: 支持 -- `false`: 不支持 - -**示例** - -```typescript -if (isWebGLSupported()) { - // 初始化 WebGL 1.0 功能 -} -``` - ---- - -### `isWebGL2Supported` - -**功能** -检测浏览器是否支持 WebGL 2.0 -**返回值** - -- `true`: 支持 -- `false`: 不支持 - ---- - -### `addTiming` - -**功能** -组合两个缓动函数为加法函数 -**数学表达** -`newTiming(p) = timing1(p) + timing2(p)` - -**参数** - -- `timing1`: 第一个缓动函数 -- `timing2`: 第二个缓动函数 - -**示例** - -```typescript -const linear = (p: number) => p; -const bounce = (p: number) => p * p; -const combined = addTiming(linear, bounce); // p + p² -``` - ---- - -### `multiplyTiming` - -**功能** -组合两个缓动函数为乘法函数 -**数学表达** -`newTiming(p) = timing1(p) * timing2(p)` - ---- - -### `isSetEqual` - -**功能** -判断两个集合是否相等(元素完全相同) -**实现逻辑** - -1. 直接引用相同 → `true` -2. 大小不同 → `false` -3. 检查 set1 是否是 set2 的子集 - ---- - -### `transformCanvas` - -**功能** -将变换矩阵应用到画布上下文 -**实现逻辑** - -```typescript -const mat = transform.mat; // 获取 3x3 矩阵 -const [a, b, , c, d, , e, f] = mat; // 分解为 2D 变换参数 -ctx.transform(a, b, c, d, e, f); // 应用变换 -``` - -**参数** - -- `canvas`: 目标画布对象 -- `transform`: 变换矩阵 - -**示例** - -```typescript -const transform = new Transform(); -transform.translate(100, 50); -transformCanvas(myCanvas, transform); // 应用平移变换 -``` - ---- - -## 工具函数关系图 - -```mermaid -graph LR - checkSupport --> isWebGLSupported - checkSupport --> isWebGL2Supported - addTiming --> 组合缓动函数 - multiplyTiming --> 组合缓动函数 - isSetEqual --> 集合操作 - transformCanvas --> 矩阵变换 -``` diff --git a/docs/api/motajs-render-core/index.md b/docs/api/motajs-render-core/index.md deleted file mode 100644 index 9ddbb44..0000000 --- a/docs/api/motajs-render-core/index.md +++ /dev/null @@ -1 +0,0 @@ -# @motajs/render-core diff --git a/docs/api/motajs-render-elements/BlockCacher.md b/docs/api/motajs-render-elements/BlockCacher.md deleted file mode 100644 index 1a05397..0000000 --- a/docs/api/motajs-render-elements/BlockCacher.md +++ /dev/null @@ -1,369 +0,0 @@ -# BlockCacher API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - BlockCacher --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -_继承自 `EventEmitter`,支持事件监听。_ - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ------------ | ---------------- | ------------------------------------------------------------------- | -| `width` | `number` | 区域总宽度(元素单位) | -| `height` | `number` | 区域总高度(元素单位) | -| `blockSize` | `number` | 单个分块的大小(元素单位) | -| `blockData` | `BlockData` | 分块计算结果(包含分块数量、最后一个块的尺寸等信息) | -| `cacheDepth` | `number` | 缓存深度(每个分块可存储多个缓存层) | -| `cache` | `Map` | 缓存存储结构,键为精确索引(`(x + y * blockWidth) * depth + deep`) | - ---- - -## 构造方法 - -### `constructor` - -```typescript -function constructor( - width: number, - height: number, - size: number, - depth?: number -): BlockCacher; -``` - -创建分块缓存管理器并自动计算初始分块。 -**示例:** - -```typescript -const cacher = new BlockCacher(800, 600, 64); // 800x600区域,64为分块大小 -``` - ---- - -## 方法说明 - -### `size` - -```typescript -function size(width: number, height: number): void; -``` - -重置区域尺寸并重新分块(触发 `split` 事件)。 -**示例:** - -```typescript -cacher.size(1024, 768); // 重置为1024x768区域 -``` - -### `setBlockSize` - -```typescript -function setBlockSize(size: number): void; -``` - -修改分块尺寸并重新分块(触发 `split` 事件)。 -**示例:** - -```typescript -cacher.setBlockSize(128); // 分块大小改为128 -``` - -### `setCacheDepth` - -```typescript -function setCacheDepth(depth: number): void; -``` - -调整缓存深度(最大 31),自动迁移旧缓存。 -**示例:** - -```typescript -cacher.setCacheDepth(3); // 每个分块支持3层缓存 -``` - -### `split` - -```typescript -function split(): void; -``` - -重新计算分块数据并触发 `'split'` 事件。 -**示例:** - -```typescript -cacher.split(); // 手动触发分块计算(一般无需调用) -``` - -### `clearCache` - -```typescript -function clearCache(index: number, deep: number): void; -``` - -清除指定分块索引的缓存(按二进制掩码清除深度)。 -**示例:** - -```typescript -cacher.clearCache(5, 0b101); // 清除分块5的第0层和第2层缓存 -``` - -### `clearCacheByIndex` - -```typescript -function clearCacheByIndex(index: number): void; -``` - -直接按精确索引清除单个缓存。 -**示例:** - -```typescript -cacher.clearCacheByIndex(42); // 清除精确索引42对应的缓存 -``` - -### `clearAllCache` - -```typescript -function clearAllCache(): void; -``` - -清空所有缓存并销毁关联资源。 -**示例:** - -```typescript -cacher.clearAllCache(); // 完全重置缓存 -``` - -### `getIndex` - -```typescript -function getIndex(x: number, y: number): number; -``` - -根据分块坐标获取分块索引(分块坐标 -> 分块索引)。 -**示例:** - -```typescript -const index = cacher.getIndex(2, 3); // 获取(2,3)分块的索引 -``` - -### `getIndexByLoc` - -```typescript -function getIndexByLoc(x: number, y: number): number; -``` - -根据元素坐标获取所属分块索引(元素坐标 -> 分块索引)。 -**示例:** - -```typescript -const index = cacher.getIndexByLoc(150, 200); // 元素坐标(150,200)所在分块索引 -``` - -### `getBlockXYByIndex` - -```typescript -function getBlockXYByIndex(index: number): [number, number]; -``` - -根据分块索引获取分块坐标(分块索引 -> 分块坐标)。 -**示例:** - -```typescript -const [x, y] = cacher.getBlockXYByIndex(5); // 分块5的坐标 -``` - -### `getBlockXY` - -```typescript -function getBlockXY(x: number, y: number): [number, number]; -``` - -获取一个元素位置所在的分块位置(即使它不在任何分块内)(元素索引 -> 分块坐标)。 -**示例:** - -```typescript -const [x, y] = cacher.getBlockXY(11, 24); // 指定位置所在分块位置 -``` - -### `getPreciseIndex` - -```typescript -function getPreciseIndex(x: number, y: number, deep: number): number; -``` - -根据分块坐标与 `deep` 获取一个分块的精确索引(分块坐标 -> 分块索引)。 -**示例:** - -```typescript -const index = cacher.getPreciseIndex(2, 1, 3); // 指定分块的索引 -``` - -### `getPreciseIndexByLoc` - -```typescript -function getPreciseIndexByLoc(x: number, y: number, deep: number): number; -``` - -根据元素坐标及 `deep` 获取元素所在块的精确索引(元素坐标 -> 分块索引)。 -**示例:** - -```typescript -const index = cacher.getPreciseIndexByLoc(22, 11, 3); // 指定元素所在分块的索引 -``` - -### `updateElementArea` - -```typescript -function updateElementArea( - x: number, - y: number, - width: number, - height: number, - deep: number = 2 ** 31 - 1 -): Set; -``` - -根据元素区域清除相关分块缓存(返回受影响的分块索引集合)(元素坐标->分块清空)。 -**示例:** - -```typescript -const blocks = cacher.updateElementArea(100, 100, 200, 200); // 清除200x200区域内的缓存 -``` - -### `updateArea` - -```typescript -function updateArea( - x: number, - y: number, - width: number, - height: number, - deep: number = 2 ** 31 - 1 -): Set; -``` - -更新指定分块区域内的缓存(注意坐标是分块坐标,而非元素坐标)(分块坐标->分块清空)。 -**示例:** - -```typescript -const blocks = cacher.updateArea(1, 1, 1, 1); // 清除指定分块区域内的缓存 -``` - -### `getIndexOf` - -```typescript -function getIndexOf( - x: number, - y: number, - width: number, - height: number -): Set; -``` - -传入分块坐标与范围,获取该区域内包含的所有分块索引(分块坐标->分块索引集合)。 -**示例:** - -```typescript -const blocks = cacher.getIndexOf(1, 1, 1, 1); // 清除指定分块区域内的缓存 -``` - -### `getIndexOfElement` - -```typescript -function getIndexOfElement( - x: number, - y: number, - width: number, - height: number -): Set; -``` - -传入元素坐标与范围,获取该区域内包含的所有分块索引(元素坐标->分块索引集合)。 -**示例:** - -```typescript -const blocks = cacher.getIndexOfElement(3, 5, 12, 23); // 清除指定元素区域内的缓存 -``` - -### `getRectOfIndex` - -```typescript -function getRectOfIndex(block: number): [number, number, number, number]; -``` - -获取分块索引对应的元素坐标范围(分块索引 -> 元素矩形坐标)。 -**示例:** - -```typescript -const [x1, y1, x2, y2] = cacher.getRectOfIndex(5); // 分块5的坐标范围 -``` - -### `getRectOfBlockXY` - -```typescript -function getRectOfBlockXY( - x: number, - y: number -): [number, number, number, number]; -``` - -根据分块坐标,获取这个分块所在区域的元素矩形范围(左上角横纵坐标及右下角横纵坐标)(分块坐标 -> 元素矩形坐标)。 -**示例:** - -```typescript -const [x1, y1, x2, y2] = cacher.getRectOfIndex(5); // 分块5的坐标范围 -``` - -### `destroy` - -```typescript -function destroy(): void; -``` - -摧毁这个块缓存。 - ---- - -## 事件说明 - -| 事件名 | 参数 | 描述 | -| ------- | ---- | ------------------ | -| `split` | 无 | 分块参数变更时触发 | - ---- - -## 总使用示例 - -```typescript -// 创建缓存管理器 -const cacheManager = new BlockCacher(1024, 768, 64); - -// 监听分块变化 -cacheManager.on('split', () => { - console.log('分块已重新计算'); -}); - -// 添加测试缓存项 -const blockIndex = cacheManager.getIndex(2, 3); -const preciseIndex = cacheManager.getPreciseIndex(2, 3, 0); -cacheManager.cache.set( - preciseIndex, - new CanvasCacheItem(new MotaOffscreenCanvas2D(), 1) -); - -// 清除特定区域缓存 -cacheManager.updateElementArea(150, 150, 100, 100); - -// 销毁管理器 -cacheManager.destroy(); -``` diff --git a/docs/api/motajs-render-elements/Camera.md b/docs/api/motajs-render-elements/Camera.md deleted file mode 100644 index 758d302..0000000 --- a/docs/api/motajs-render-elements/Camera.md +++ /dev/null @@ -1,386 +0,0 @@ -# Camera API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - Camera --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -_继承自 `EventEmitter`,支持事件监听。_ - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| --------------------- | ------------------- | ------------------------------------------------ | -| `readonly binded` | `RenderItem` | 当前绑定的渲染元素 | -| `transform` | `Transform` | 目标变换矩阵,默认与 `binded.transform` 共享引用 | -| `protected operation` | `CameraOperation[]` | 当前应用的变换操作列表(平移/旋转/缩放) | - ---- - -## 构造方法 - -### `Camera.for` - -```typescript - function for(item: RenderItem): Camera -``` - -获取或创建与渲染元素关联的摄像机实例。 -**示例:** - -```typescript -const item = new RenderItem(); -const camera = Camera.for(item); // 获取或创建摄像机 -``` - -### `constructor` - -```typescript -function constructor(item: RenderItem): Camera; -``` - -直接创建摄像机实例(不会自动注册到全局映射)。 -**注意:** 推荐优先使用 `Camera.for()` 方法。 - ---- - -## 方法说明 - -### `disable` - -```typescript -function disable(): void; -``` - -禁用摄像机变换效果。 -**示例:** - -```typescript -camera.disable(); // 暂停所有摄像机变换 -``` - -### `enable` - -```typescript -function enable(): void; -``` - -启用摄像机变换效果。 - -### `requestUpdate` - -```typescript -function requestUpdate(): void; -``` - -请求在下一帧强制更新变换矩阵。 - -### `removeOperation` - -```typescript -function removeOperation(operation: CameraOperation): void; -``` - -移除一个变换操作。 -**参数说明** - -- `operation`: 要移除的操作 - -**示例** - -```ts -const operation = camera.addTranslate(); -camera.removeOperation(operation); -``` - -### `clearOperation` - -```ts -function clearOperation(): void; -``` - -清空变换操作列表。 - -### `addTranslate` - -```typescript -function addTranslate(): ICameraTranslate; -``` - -添加平移操作并返回操作对象。 -**示例:** - -```typescript -const translateOp = camera.addTranslate(); -translateOp.x = 100; // 设置横向偏移 -camera.requestUpdate(); -``` - -### `addRotate` - -```typescript -function addRotate(): ICameraRotate; -``` - -添加旋转操作并返回操作对象。 -**示例:** - -```typescript -const rotateOp = camera.addRotate(); -rotateOp.angle = Math.PI / 2; // 设置90度旋转 -camera.requestUpdate(); -``` - -### `addScale` - -```typescript -function addScale(): ICameraScale; -``` - -添加缩放操作并返回操作对象。 -**示例:** - -```typescript -const scaleOp = camera.addScale(); -scaleOp.x = 2; // 横向放大2倍 -camera.requestUpdate(); -``` - -### `applyAnimation` - -```ts -function applyAnimation(time: number, update: () => void): void; -``` - -施加动画。 - -**参数说明** - -- `time`: 动画时长。 -- `update`: 每帧执行的更新函数。 - -### `applyTranslateAnimation` - -```typescript -function applyTranslateAnimation( - operation: ICameraTranslate, - animate: Animation, - time: number -): void; -``` - -为平移操作绑定动画。 -**参数说明:** - -- `animate`: 预定义的动画实例 -- `time`: 动画持续时间(毫秒) - -### `applyRotateAnimation` - -```typescript -function applyRotateAnimation( - operation: ICameraRotate, - animate: Animation, - time: number -): void; -``` - -为旋转操作绑定动画。 - -### `applyScaleAnimation` - -```typescript -function applyScaleAnimation( - operation: ICameraScale, - animate: Animation, - time: number -): void; -``` - -为缩放操作绑定动画。 - -### `applyTranslateTransition` - -```typescript -function applyTranslateTransition( - operation: ICameraTranslate, - animate: Transition, - time: number -): void; -``` - -为平移操作绑定渐变。 -**参数说明:** - -- `animate`: 预定义的渐变实例 -- `time`: 渐变持续时间(毫秒) - -### `applyRotateTransition` - -```typescript -function applyRotateTransition( - operation: ICameraRotate, - animate: Transition, - time: number -): void; -``` - -为旋转操作绑定渐变。 - -### `applyScaleTransition` - -```typescript -function applyScaleTransition( - operation: ICameraScale, - animate: Transition, - time: number -): void; -``` - -为缩放操作绑定渐变。 - -### `stopAllAnimates` - -```ts -function stopAllAnimates(): void; -``` - -停止所有动画。 - -### `destroy` - -```typescript -function destroy(): void; -``` - -销毁摄像机并释放所有资源。 -**示例:** - -```typescript -camera.destroy(); // 解除绑定并清理动画 -``` - ---- - -## 事件说明 - -| 事件名 | 参数 | 描述 | -| --------- | ---- | ------------------ | -| `destroy` | 无 | 摄像机被销毁时触发 | - ---- - -## 总使用示例 - -::: code-group - -```typescript [动画] -import { Animation, linear } from 'mutate-animate'; - -// 获取摄像机实例 -const item = new Sprite(); -const camera = Camera.for(item); - -// 添加平移和缩放操作 -const translate = camera.addTranslate(); -const scale = camera.addScale(); - -// 创建动画实例 -const anim = new Animation() - .mode(linear()) - .time(1000) - .move(100, 100) - .time(800) - .scale(1.5); - -// 绑定动画 -camera.applyTranslateAnimation(translate, anim, 1000); -camera.applyScaleAnimation(scale, anim, 800); - -// 启用摄像机 -camera.enable(); - -// 销毁(当不再需要时) -setTimeout(() => camera.destroy(), 2000); -``` - -```typescript [渐变] -import { Transition, hyper } from 'mutate-animate'; -// 获取摄像机实例 -const item = new Sprite(); -const camera = Camera.for(item); - -// 添加平移和缩放操作 -const translate = camera.addTranslate(); -const scale = camera.addScale(); - -// 创建渐变实例,使用双曲正弦速率曲线 -const tran = new Transition().mode(hyper('sin', 'out')).time(1000); - -// 初始化参数,这一步不会执行渐变 -tran.value.x = 0; -tran.value.y = 0; -tran.value.size = 0; - -// 对参数执行渐变,直接设置即可 -tran.value.x = 100; -tran.value.y = 200; -tran.time(800); // 设置渐变时长为 800 毫秒 -tran.value.size = 1.5; - -// 绑定动画 -camera.applyTranslateTransition(translate, tran, 1000); -camera.applyScaleTransition(scale, tran, 800); - -// 启用摄像机 -camera.enable(); - -// 销毁(当不再需要时) -setTimeout(() => camera.destroy(), 2000); -``` - -::: - ---- - -## 接口说明 - -### `ICameraTranslate` - -```typescript -interface ICameraTranslate { - readonly type: 'translate'; - readonly from: RenderItem; - x: number; // 横向偏移量 - y: number; // 纵向偏移量 -} -``` - -### `ICameraRotate` - -```typescript -interface ICameraRotate { - readonly type: 'rotate'; - readonly from: RenderItem; - angle: number; // 旋转弧度值 -} -``` - -### `ICameraScale` - -```typescript -interface ICameraScale { - readonly type: 'scale'; - readonly from: RenderItem; - x: number; // 横向缩放比 - y: number; // 纵向缩放比 -} -``` diff --git a/docs/api/motajs-render-elements/CameraAnimation.md b/docs/api/motajs-render-elements/CameraAnimation.md deleted file mode 100644 index 55434de..0000000 --- a/docs/api/motajs-render-elements/CameraAnimation.md +++ /dev/null @@ -1,201 +0,0 @@ -# CameraAnimation API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - CameraAnimation --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -_继承自 `EventEmitter`,支持事件监听。_ - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| -------- | -------- | ---------------- | -| `camera` | `Camera` | 关联的摄像机实例 | - ---- - -## 构造方法 - -### `constructor` - -```typescript -function constructor(camera: Camera): CameraAnimation; -``` - -创建摄像机动画管理器,需绑定到特定 `Camera` 实例。 -**示例:** - -```typescript -const camera = Camera.for(renderItem); -const animation = new CameraAnimation(camera); -``` - ---- - -## 方法说明 - -### `translate` - -```typescript -function translate( - operation: ICameraTranslate, - x: number, - y: number, - time: number, - start: number, - timing: TimingFn -): void; -``` - -为平移操作添加动画。 -**参数说明:** - -- `x`, `y`: 目标偏移量(格子坐标,自动乘以 `32`,之后可能改动) -- `time`: 动画持续时间(毫秒) -- `start`: 动画开始时间(相对于总动画开始的延迟) -- `timing`: 缓动函数(输入时间完成度,输出动画完成度) - -### `rotate` - -```typescript -function rotate( - operation: ICameraRotate, - angle: number, - time: number, - start: number, - timing: TimingFn -): void; -``` - -为旋转操作添加动画。 -**参数说明:** - -- `angle`: 目标旋转弧度(如 `Math.PI` 表示 `180` 度) -- 其余参考[`rotate`](#rotate) - -### `scale` - -```typescript -function scale( - operation: ICameraScale, - scale: number, - time: number, - start: number, - timing: TimingFn -): void; -``` - -为缩放操作添加动画。 -**参数说明:** - -- `scale`: 目标缩放倍率(如 `1.5` 表示放大 `1.5` 倍) -- 其余参考[`rotate`](#rotate) - -### `start` - -```typescript -function start(): void; -``` - -启动所有已添加的动画,按时间顺序执行。 -**注意:** 调用后动画将按 `start` 参数定义的顺序触发。 - -### `destroy` - -```typescript -function destroy(): void; -``` - -销毁动画管理器并释放所有资源(停止未完成的动画)。 - ---- - -## 事件说明 - -| 事件名 | 参数 | 描述 | -| --------- | -------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `animate` | `operation: CameraOperation`
`execution: CameraAnimationExecution`
`item: CameraAnimationData` | 当某个动画片段开始执行时触发 | - ---- - -## 总使用示例 - -```typescript -import { hyper, trigo } from 'mutate-animate'; - -// 创建渲染元素和摄像机 -const renderItem = new Sprite(); -const camera = Camera.for(renderItem); - -// 添加平移和旋转操作 -const translateOp = camera.addTranslate(); -const rotateOp = camera.addRotate(); - -// 创建动画管理器 -const animation = new CameraAnimation(camera); - -// 添加平移动画:1秒后开始,持续2秒,横向移动3格(3*32像素) -animation.translate( - translateOp, - 3, - 0, // x=3, y=0(自动乘32) - 2000, // 动画时长2秒 - 1000, // 延迟1秒开始 - hyper('sin', 'out') // 双曲正弦函数 -); - -// 添加旋转动画:立即开始,持续1.5秒,旋转180度 -animation.rotate( - rotateOp, - Math.PI, // 目标角度(弧度) - 1500, // 动画时长1.5秒 - 0, // 无延迟 - trigo('sin', 'out') // 正弦函数 -); - -// 启动动画 -animation.start(); - -// 监听动画事件 -animation.on('animate', (operation, execution, item) => { - console.log('动画片段开始:', item.type); -}); - -// 销毁(动画结束后) -setTimeout(() => { - animation.destroy(); - camera.destroy(); -}, 5000); -``` - ---- - -## 接口说明 - -### `CameraAnimationExecution` - -```typescript -interface { - data: CameraAnimationData[]; // 动画片段列表 - animation: Animation; // 关联的动画实例 -} -``` - -### `CameraAnimationData` - -```typescript -type CameraAnimationData = - | TranslateAnimation - | TranslateAsAnimation - | RotateAnimation - | ScaleAnimation; -``` diff --git a/docs/api/motajs-render-elements/index.md b/docs/api/motajs-render-elements/index.md deleted file mode 100644 index 899a74d..0000000 --- a/docs/api/motajs-render-elements/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/render-elements - -目录: diff --git a/docs/api/motajs-render-style/Font.md b/docs/api/motajs-render-style/Font.md deleted file mode 100644 index f5908b8..0000000 --- a/docs/api/motajs-render-style/Font.md +++ /dev/null @@ -1,186 +0,0 @@ -# Font API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - Font --> IFontConfig -``` - -_实现 `IFontConfig` 接口,表示字体配置。_ - ---- - -## 接口说明 - -### `IFontConfig` - -```typescript -interface IFontConfig { - family: string; // 字体名称(如 "Arial") - size: number; // 字号数值 - sizeUnit: string; // 字号单位(推荐 "px") - weight: number; // 字重(0-1000) - italic: boolean; // 是否斜体 -} -``` - -### `FontWeight` 枚举 - -```typescript -enum FontWeight { - Light = 300, // 细体 - Normal = 400, // 常规 - Bold = 700 // 粗体 -} -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ---------- | --------- | ------------------------- | -| `family` | `string` | 字体家族名称(只读) | -| `size` | `number` | 字号数值(只读) | -| `sizeUnit` | `string` | 字号单位(如 "px",只读) | -| `weight` | `number` | 字重数值(只读) | -| `italic` | `boolean` | 是否斜体(只读) | - ---- - -## 构造方法 - -### `constructor` - -```typescript -function constructor( - family?: string, - size?: number, - sizeUnit?: string, - weight?: number, - italic?: boolean -): Font; -``` - -创建字体实例,参数默认使用静态默认值。 -**示例:** - -```typescript -const font = new Font('Arial', 14, 'px', FontWeight.Bold, true); -``` - ---- - -## 方法说明 - -### `addFallback` - -```typescript -function addFallback(...fallback: Font[]): void; -``` - -添加后备字体(当主字体不可用时使用)。 -**注意:** 检测到递归添加时会触发 `logger.warn(62)` 警告。 -**示例:** - -```typescript -const mainFont = new Font('CustomFont'); -const fallback1 = new Font('Arial'); -const fallback2 = new Font('Helvetica'); -mainFont.addFallback(fallback1, fallback2); // 添加两个后备 -``` - -### `string` - -```typescript -function string(): string; -``` - -生成 CSS 字体字符串,包含后备字体。 -**示例:** - -```typescript -console.log(font.string()); // 示例输出:"italic 700 14px CustomFont, 400 16px Arial" -``` - ---- - -## 静态方法说明 - -### `Font.parse` - -```typescript -function parse(str: string): Font; -``` - -解析 CSS 字体字符串(支持多字体声明)。 -**示例:** - -```typescript -const parsed = Font.parse('italic 16px "Fira Sans", Arial'); -// 主字体:Fira Sans,后备:Arial -``` - -### `Font.setDefaults` - -```typescript -function setDefaults(font: Font): void; -``` - -全局设置默认字体参数。 -**示例:** - -```typescript -Font.setDefaults(new Font('Segoe UI', 14, 'px', 400, false)); -``` - -### `Font.clone` - -```typescript -function clone(font: Font, options: Partial): Font; -``` - -克隆字体并修改指定属性。 -**示例:** - -```typescript -const cloned = Font.clone(baseFont, { - size: 18, - weight: FontWeight.Bold -}); -``` - ---- - -## 总使用示例 - -```typescript -// 创建主字体 -const mainFont = new Font('CustomFont', 16, 'px', FontWeight.Normal); - -// 添加后备字体(注意避免循环引用) -const fallbackA = new Font('Arial'); -const fallbackB = new Font('Helvetica'); -fallbackA.addFallback(fallbackB); -// 错误示例(触发警告62): -// fallbackB.addFallback(fallbackA); - -mainFont.addFallback(fallbackA); - -// 生成 CSS 字符串 -console.log(mainFont.string()); -// 输出: "400 16px CustomFont, 400 16px Arial, 400 16px Helvetica" - -// 解析 CSS 字符串 -const parsed = Font.parse('italic 700 24px Fantasy, "Comic Sans"'); -parsed.addFallback(new Font('Verdana')); - -// 克隆并修改 -const boldFont = Font.clone(parsed, { - weight: FontWeight.Bold, - italic: false -}); -``` diff --git a/docs/api/motajs-render-style/index.md b/docs/api/motajs-render-style/index.md deleted file mode 100644 index 866d002..0000000 --- a/docs/api/motajs-render-style/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/render-style - -目录: diff --git a/docs/api/motajs-render-vue/BaseProps.md b/docs/api/motajs-render-vue/BaseProps.md deleted file mode 100644 index bdedaf6..0000000 --- a/docs/api/motajs-render-vue/BaseProps.md +++ /dev/null @@ -1,134 +0,0 @@ -# BaseProps 接口文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 接口定义 - -```typescript -interface BaseProps { - // 基础定位 - x?: number; // 横坐标(单位:像素) - y?: number; // 纵坐标(单位:像素) - anchorX?: number; // 横向锚点比例(0~1,默认 0) - anchorY?: number; // 纵向锚点比例(0~1,默认 0) - zIndex?: number; // 纵深层级(值越大越靠上) - - // 尺寸控制 - width?: number; // 元素宽度(单位:像素,默认 200) - height?: number; // 元素高度(单位:像素,默认 200) - - // 渲染控制 - filter?: string; // CSS滤镜(如 "blur(5px)") - hd?: boolean; // 启用高清画布(默认 true) - anti?: boolean; // 启用抗锯齿(默认 true) - noanti?: boolean; // 强制禁用抗锯齿(优先级高于 anti) - hidden?: boolean; // 隐藏元素(默认 false) - - // 变换与定位 - transform?: Transform; // 变换矩阵对象 - type?: RenderItemPosition; // 定位模式("static" | "absolute") - cache?: boolean; // 启用缓存优化(根据不同元素的特性有不同的值,多数情况下使用默认配置即可达到最优性能) - nocache?: boolean; // 强制禁用缓存(优先级高于 cache) - fall?: boolean; // 继承父元素变换矩阵(默认 false),不建议使用此参数,可能很快就会被删除 - - // 交互与样式 - id?: string; // 唯一标识符 - alpha?: number; // 不透明度(0~1,默认 1) - composite?: GlobalCompositeOperation; // 混合模式(如 "lighter") - cursor?: string; // 鼠标悬停样式(如 "pointer") - noevent?: boolean; // 禁用交互事件(默认 false) - - // 简写属性 - loc?: ElementLocator /* - [x, y, width?, height?, anchorX?, anchorY?] - 如果填写的话,两两一组要么都填要么都不填,也就是说元素数量需要是 2,4,6 个 - */; - anc?: ElementAnchor; // [anchorX, anchorY],如果填写的话,两项必填 - scale?: ElementScale; // [scaleX, scaleY],如果填写的话,两项必填 - rotate?: number; // 旋转弧度值(单位:弧度) -} -``` - ---- - -## 完整使用示例 - -```tsx -import { defineComponent } from 'vue'; -import { Transform } from '@motajs/render-core'; - -// 注意,以下属性均可选,按照自己需要填写即可,不需要的可以不用填,简单需求一般只需要修改定位 -// 而复杂需求可能需要填写更多的参数,但是基本不会出现所有参数都要填的场景 -// 编写 UI 的流程参考深度指南中的 UI 编写 -export const MyUI = defineComponent(() => { - return () => ( - - ); -}); -``` - ---- - -## 属性效果说明 - -| 属性组 | 关键效果 | -| -------------- | -------------------------------------------------------------------------- | -| **基础定位** | 元素将出现在 (100,200) 坐标,以中心点(锚点 0.5)为基准定位 | -| **尺寸控制** | 元素尺寸固定为 300x200 像素 | -| **渲染控制** | 应用阴影滤镜,高清画质,关闭抗锯齿实现像素风格 | -| **变换与定位** | 附加额外平移变换,使用绝对定位模式,禁用缓存优化 | -| **交互与样式** | 元素半透明,叠加混合模式,显示"move"光标,响应交互事件 | -| **简写属性** | 通过 loc 覆盖坐标和尺寸,anc 设置底部锚点,scale 实现拉伸/压缩,旋转 45 度 | - ---- - -## 注意事项 - -1. **优先级规则**: - - `noanti` > `anti`,`nocache` > `cache` - - 显式属性(如 `x`)与简写属性(如 `loc` 中的 `x`)相比,谁最后被设置,就用谁的 -2. **简写属性解析**: - ```ts - loc = [100, 200, 300, 200]; // → x=100, y=200, width=300, height=200 - anc = [0.5, 0]; // → anchorX=0.5, anchorY=0 - scale = [2]; // → scaleX=2, scaleY=2 - ``` -3. **其他注意事项** - - `transform.translate` 与 `x` `y` 和简写定位属性的 `x` `y` 完全等效,设置后者也会让 `transform` 的平移量改变 - - 如果不允许交互,那么光标也不会显示 - - 同 `zIndex` 下,后插入的元素会在上层,但是这也意味着如果是动态插入的元素(例如由于响应式更改而插入了一个新元素),会显示在后面代码的元素之上 -4. **常见问题** - - 参考 [指南](../../guide/ui/faq.md) diff --git a/docs/api/motajs-render-vue/GraphicBaseProps.md b/docs/api/motajs-render-vue/GraphicBaseProps.md deleted file mode 100644 index 600ebc8..0000000 --- a/docs/api/motajs-render-vue/GraphicBaseProps.md +++ /dev/null @@ -1,188 +0,0 @@ -# GraphicBaseProps API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 接口定义 - -```typescript -interface ILineProperty { - /** 线宽 */ - lineWidth: number; - /** 线的虚线设置 */ - lineDash?: number[]; - /** 虚线偏移量 */ - lineDashOffset?: number; - /** 线的连接样式 */ - lineJoin: CanvasLineJoin; - /** 线的顶端样式 */ - lineCap: CanvasLineCap; - /** 线的斜接限制,当连接为miter类型时可填,默认为10 */ - miterLimit: number; -} - -interface GraphicBaseProps extends BaseProps, Partial { - /** 是否填充(默认 false) */ - fill?: boolean; - /** 是否描边(默认 false) */ - stroke?: boolean; - /** 强制先描边后填充(优先级最高,默认 false) */ - strokeAndFill?: boolean; - /** 填充规则(默认 "evenodd") */ - fillRule?: CanvasFillRule; - /** 填充样式(颜色/渐变/图案) */ - fillStyle?: CanvasStyle; - /** 描边样式(颜色/渐变/图案) */ - strokeStyle?: CanvasStyle; - /** 交互时是否仅检测描边区域(默认 false) */ - actionStroke?: boolean; -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| ---------------- | ----------------------------------------------- | ----------- | -------------------------------------------------------------- | -| `fill` | `boolean` | `false` | 启用填充 | -| `stroke` | `boolean` | `false` | 启用描边 | -| `strokeAndFill` | `boolean` | `false` | 先描边后填充 | -| `fillRule` | `'nonzero'` \| `'evenodd'` | `'evenodd'` | 填充路径环绕原则 | -| `fillStyle` | `string` \| `CanvasGradient` \| `CanvasPattern` | - | 填充样式 | -| `strokeStyle` | `string` \| `CanvasGradient` \| `CanvasPattern` | - | 描边样式 | -| `actionStroke` | `boolean` | `false` | 设为 `true` 时,交互事件仅响应描边区域(需配合 `stroke` 使用) | -| `lineWidth` | `number` | `2` | 设置描边的线宽 | -| `lineDash` | `number[]` | `[]` | 设置虚线样式 | -| `lineDashOffset` | `number` | `0` | 虚线样式偏移量 | -| `lineJoin` | `string` | `bevel` | 线的连接方式 | -| `lineCap` | `string` | `butt` | 线的末端样式 | -| `miterLimit` | `number` | `10` | 当使用 `miter` 连接方式时,其最大斜接限制 | - ---- - -## 使用示例(以矩形为例) - -### 示例 1:仅填充模式 - -```tsx - -``` - -**效果**: - -- 200x150 矩形 -- 无描边效果 - ---- - -### 示例 2:仅描边模式 - -```tsx - -``` - -**交互特性**: - -- 4px 黑色半透明描边 -- 鼠标悬停在描边区域才会触发事件 - ---- - -### 示例 3:填充 + 描边(默认顺序) - -```tsx - -``` - -**渲染顺序**: - -1. 填充黄色背景 -2. 在填充层上绘制黑色描边 - ---- - -### 示例 4:先描边后填充 - -```tsx - -``` - -**渲染顺序**: - -1. 绘制紫色描边 -2. 在描边层上填充浅紫色 - **视觉效果**:描边被填充色覆盖一部分 - ---- - -## 最佳实践 - -### 交互增强技巧 - -```tsx -import { ref } from 'vue'; - -// 高亮描边交互反馈 -const hovered = ref(false); -// 使用 void 关键字屏蔽返回值,避免返回值泄漏 -const enter = () => void (hovered.value = true); -const leave = () => void (hovered.value = false); - -; -``` - ---- - -## 注意事项 - -1. **样式覆盖顺序**: - `strokeAndFill` 会强制按 **描边 → 填充** 顺序渲染,忽略 `fill` 和 `stroke` 的独立设置。 - -2. **路径闭合规则**: - `fillRule="evenodd"` 适用于以下场景: - - ```tsx - // 五角星镂空效果 - - ``` - -3. **性能问题**: - 多数情况下,图形的性能很好,不需要单独优化,但是如果你使用 `path` 标签,且内容复杂,建议添加 `cache` 属性来启用缓存,避免频繁的复杂图形绘制。 diff --git a/docs/api/motajs-render-vue/functions.md b/docs/api/motajs-render-vue/functions.md deleted file mode 100644 index e52dc1a..0000000 --- a/docs/api/motajs-render-vue/functions.md +++ /dev/null @@ -1,149 +0,0 @@ -# use.ts API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 函数说明 - -### `onTick` - -```typescript -function onTick(fn: (time: number) => void): void; -``` - -**功能**:注册每帧执行的回调(自动管理生命周期) -**推荐使用场景**:替代 `ticker` -**参数说明**: - -- `fn`: 接收当前时间戳的帧回调函数 - **示例**: - -```typescript -// Vue 组件中 -onTick(time => { - console.log('当前帧时间:', time); -}); -``` - ---- - -### `useAnimation` - -```typescript -function useAnimation(): [Animation]; -``` - -**功能**:创建动画实例(自动销毁资源) -**返回值**:包含动画实例的元组 -**推荐使用场景**:替代直接 `new Animation()` -**示例**: - -```typescript -const [anim] = useAnimation(); -anim.time(1000).move(100, 200); -``` - ---- - -### `useTransition` - -```typescript -function useTransition(): [Transition]; -``` - -**功能**:创建渐变实例(自动销毁资源) -**返回值**:包含渐变实例的元组 -**推荐使用场景**:替代直接 `new Transition()` -**示例**: - -```typescript -const [transition] = useTransition(); -transition.value.x = 10; -transition.time(500); -transition.value.x = 100; -``` - ---- - -### `useKey` - -```typescript -function useKey(noScope?: boolean): [Hotkey, symbol]; -``` - -**功能**:管理按键作用域(自动注销绑定) -**参数说明**: - -- `noScope`: 是否使用全局作用域(默认创建新作用域) - **返回值**:元组 [热键实例, 作用域标识] - **推荐使用场景**:替代直接操作全局热键实例 - **示例**: - -```typescript -const [hotkey, scope] = useKey(); -hotkey.realize('mykey_id', () => console.log('mykey_id emitted.')); -``` - ---- - -### `onEvent` - -```typescript -function onEvent< - T extends ERenderItemEvent, - K extends EventEmitter.EventNames ->( - item: RenderItem, - key: K, - listener: EventEmitter.EventListener -): void; -``` - -**功能**:自动管理事件监听生命周期 -**推荐使用场景**:替代直接 `item.on()` + 手动注销 -**示例**: - -```typescript -onEvent(sprite, 'click', event => { - console.log('元素被点击', event); -}); -``` - ---- - -## 总使用示例 - -```tsx -import { defineComponent } from 'vue'; -import { useAnimation, onTick, useKey } from '@motajs/render-vue'; - -export const MyComponent = defineComponent(() => { - // 动画控制 - const [anim] = useAnimation(); - anim.time(1000).rotate(Math.PI); - - // 帧循环 - onTick(time => { - console.log('当前游戏运行时间:', time); - }); - - // 按键控制 - const [hotkey, scope] = useKey(); - hotkey.realize('mykey_id', () => console.log('mykey_id emitted.')); - - return () => ; -}); -``` - ---- - -## 注意事项 - -1. **资源管理**:所有通过这些接口创建的资源(动画/渐变/事件)都会在组件卸载时自动销毁 -2. **内存安全**:使用原生接口可能导致内存泄漏,这些封装接口确保: - - 自动注销事件监听 - - 自动停止动画/渐变 - - 自动清理按键绑定 -3. **类型安全**:所有接口均包含完整的类型推断(如 `onEvent` 的事件类型检查) -4. **框架适配**:专为 Vue3 组合式 API 设计,不可用于其他框架环境 diff --git a/docs/api/motajs-render-vue/index.md b/docs/api/motajs-render-vue/index.md deleted file mode 100644 index 2cde0a7..0000000 --- a/docs/api/motajs-render-vue/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/render-vue - -目录: diff --git a/docs/api/motajs-render-vue/标签 container.md b/docs/api/motajs-render-vue/标签 container.md deleted file mode 100644 index 59ef047..0000000 --- a/docs/api/motajs-render-vue/标签 container.md +++ /dev/null @@ -1,139 +0,0 @@ -# ContainerProps API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - ContainerProps --> BaseProps - - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface ContainerProps extends BaseProps {} // 无新增属性,完全继承 BaseProps -``` - ---- - -## 核心能力 - -1. **嵌套结构**:支持多层容器嵌套,构建复杂 UI 层级 -2. **批量更新**:通过容器隔离高频/低频更新内容 -3. **虚拟化支持**:推荐结合 `Scroll`/`Page` 组件处理大数据量场景 - ---- - -## 完整示例集 - -### 示例 1:基础容器(静态内容优化) - -```tsx -import { ref } from 'vue'; -import { Font } from '@motajs/render-style'; - -const count = ref(0); -const boldFont = new Font('Verdana', 18, 'px', 700); - -// 静态容器(启用缓存) - - {/* 静态背景 */} - - - {/* 动态计数器 */} - - - -; -``` - -**优化策略**: - -- 背景层缓存,避免重复绘制 -- 计数器单独放置在独立容器,避免高频内容污染致使低频内容也需要高频更新 - ---- - -### 示例 2:条件渲染 + 循环渲染 - -```tsx -import { ref } from 'vue'; - -// 根据状态显示不同内容 -const tab = ref<'list' | 'detail'>('list'); - -const item = [{ id: 0, name: '第一个元素' }]; - -return () => ( - - {/* 选项卡导航 */} - - - - - - {/* 条件内容区 */} - {tab === 'list' ? ( - {/* 循环渲染 */} - - {items.map((item, i) => ( - - ))} - - ) : ( - - - - )} - -); -``` - ---- - -### 示例 3:动态布局嵌套 - -```tsx -// 自适应居中布局 - - {/* 主内容卡片 */} - - - - - {/* 控制栏 */} - - - - -``` - ---- - -## 性能优化指南 - -### 缓存策略对比 - -| 场景 | 配置 | 重绘频率 | 适用场景 | -| ---------------- | -------------- | -------- | --------------- | -| 不频繁更新的内容 | `cache=true` | 偶尔重绘 | 背景/图标等 | -| 高频更新内容 | `nocache=true` | 每帧重绘 | 动画/计数器 | -| 混合内容 | 分层容器 | 按需更新 | 带静态背景的 UI | - -### 大数据量处理方案 - -考虑使用[滚动条](../user-client-modules/组件%20Scroll.md)或[分页](../user-client-modules/组件%20Page.md)组件。 - ---- - -## 注意事项 - -1. **缓存失效条件**:当任意子元素及自身发生变化时,将会自动触发更新 -2. **嵌套层级**:推荐使用容器嵌套提高缓存能力,但是并建议不嵌套过多 -3. **子元素更新**:修改容器子元素时会触发整个容器的缓存重建 -4. **内存管理**:超大缓存容器(如 4096x4096)可能触发浏览器纹理限制 diff --git a/docs/api/motajs-render-vue/标签 custom-container.md b/docs/api/motajs-render-vue/标签 custom-container.md deleted file mode 100644 index 2b2375a..0000000 --- a/docs/api/motajs-render-vue/标签 custom-container.md +++ /dev/null @@ -1,88 +0,0 @@ -# ContainerCustomProps API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - ContainerCustomProps --> ContainerProps --> BaseProps - - click ContainerProps "./ContainerProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface ContainerCustomProps extends ContainerProps { - /** - * 自定义容器渲染函数 - * @param canvas - 离屏画布对象(已应用容器的变换矩阵) - * @param children - 所有子元素(未经过滤,按照 zIndex 从小到大排列) - * @param transform - 容器自身相对于父元素的变换矩阵 - */ - render?: ( - canvas: MotaOffscreenCanvas2D, - children: RenderItem[], - transform: Transform - ) => void; -} -``` - ---- - -## 核心能力 - -1. **虚拟化渲染**:通过自定义筛选逻辑实现仅渲染想要渲染的内容 -2. **渲染劫持**:完全接管子元素的绘制流程 - ---- - -## 完整示例集 - -### 示例 1:默认渲染模式(等同普通容器) - -```tsx -// 不传 render 参数时,自动渲染全部子元素 - - - - - -``` - ---- - -### 示例 2:部分渲染(仅显示可见区域) - -```tsx -const render = ( - canvas: MotaOffscreenCanvas2D, - children: RenderItem[], - transform: Transform -) => { - // 在 [0, 0, 200, 200] 之外的内容不渲染 - children.forEach(child => { - const rect = child.getBoundingRect(); - // 不在范围内的排除 - if ( - rect.right < 0 || - rect.bottom < 0 || - rect.left > 200 || - rect.top > 200 - ) - return; - child.renderContent(canvas, transform); - }); -}; - - - {/* 循环渲染 */} - {items.map((item, i) => ( - - ))} -; -``` diff --git a/docs/api/motajs-render-vue/标签 g-bezier.md b/docs/api/motajs-render-vue/标签 g-bezier.md deleted file mode 100644 index 19fbac7..0000000 --- a/docs/api/motajs-render-vue/标签 g-bezier.md +++ /dev/null @@ -1,202 +0,0 @@ -# g-bezier 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - BezierProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface BezierProps extends GraphicBaseProps { - sx?: number; // 起点X坐标 - sy?: number; // 起点Y坐标 - cp1x?: number; // 第一控制点X坐标 - cp1y?: number; // 第一控制点Y坐标 - cp2x?: number; // 第二控制点X坐标 - cp2y?: number; // 第二控制点Y坐标 - ex?: number; // 终点X坐标 - ey?: number; // 终点Y坐标 - curve?: BezierParams; // 简写属性 [sx, sy, cp1x, cp1y, cp2x, cp2y, ex, ey] -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| ------- | ------------------------------------------ | ------ | ------------------------------------- | -| `sx` | `number` | - | 曲线起点 X 坐标(单位:像素) | -| `sy` | `number` | - | 曲线起点 Y 坐标(单位:像素) | -| `cp1x` | `number` | - | 第一控制点 X 坐标(影响曲线起始方向) | -| `cp1y` | `number` | - | 第一控制点 Y 坐标 | -| `cp2x` | `number` | - | 第二控制点 X 坐标(影响曲线结束方向) | -| `cp2y` | `number` | - | 第二控制点 Y 坐标 | -| `ex` | `number` | - | 曲线终点 X 坐标 | -| `ey` | `number` | - | 曲线终点 Y 坐标 | -| `curve` | `[sx, sy, cp1x, cp1y, cp2x, cp2y, ex, ey]` | - | 简写属性:一次性定义全部坐标点 | - ---- - -## 完整示例集 - -### 示例 1:基础三次贝塞尔曲线 - -```tsx -// 曲线默认仅描边,不需要单独设置 stroke 属性 - -``` - ---- - -### 示例 2:虚线波浪线 - -```tsx - -``` - -**曲线形态**: - -- 起点 (50,200) → 第一控制点 (150,50) -- 第二控制点 (250,350) → 终点 (350,200) -- 形成"S"型波浪线 - ---- - -### 示例 3:动态流体效果 - -```tsx -import { ref } from 'vue'; - -const offset = ref(0); - -// 每帧更新控制点位置 -onTick(() => { - offset.value += 0.02; -}); - -; -``` - ---- - -## 控制点行为说明 - -### 控制点影响示意图 - -```typescript -/* - (cp1) - ▲ - | -(start)●━━╋━━━━━━━━━━━┓ - ┃ ┃ - ┃ (cp2) ┃ - ┃ ▼ ┃ - ┗━━━━━━━●━━(end) -*/ -``` - -- **第一控制点** (`cp1`):控制曲线起始方向的弯曲程度 -- **第二控制点** (`cp2`):控制曲线结束方向的弯曲程度 - -### 特殊形态案例 - -| 控制点布局 | 曲线形态描述 | -| ------------------------------------ | ----------------- | -| `cp1`靠近起点,`cp2`靠近终点 | 近似直线 | -| `cp1`与`cp2`对称分布 | 形成对称波浪 | -| `cp1`在起点正上方,`cp2`在终点正下方 | 创建垂直"S"型曲线 | - ---- - -## 高级用法示例 - -### 复杂路径组合 - -```tsx -// 组合多条贝塞尔曲线形成花瓣造型 - - {Array.from({ length: 5 }).map((_, i) => ( - - ))} - -``` - ---- - -## 注意事项 - -1. **闭合路径**: - 三次贝塞尔曲线默认不闭合,如需闭合需手动连接到起点: - -```tsx - -``` - -2. **控制点极值**: - 当控制点距离起点/终点过远时可能产生剧烈弯曲: - -```tsx -// 可能产生非预期锐角 - -``` diff --git a/docs/api/motajs-render-vue/标签 g-circle.md b/docs/api/motajs-render-vue/标签 g-circle.md deleted file mode 100644 index 412ec79..0000000 --- a/docs/api/motajs-render-vue/标签 g-circle.md +++ /dev/null @@ -1,182 +0,0 @@ -# g-circle 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - CircleProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface CircleProps extends GraphicBaseProps { - radius?: number; // 圆的半径 - start?: number; // 起始角度(单位:弧度) - end?: number; // 结束角度(单位:弧度) - circle?: CircleParams; // 简写属性 [x, y, radius, start?, end?],后两项要么都填,要么都不填 -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| -------- | ------------------------- | ------ | ---------------------------------------------------------- | -| `radius` | `number` | - | 圆的半径(单位:像素) | -| `start` | `number` | `0` | 起始角度(弧度),0 弧度 = 3 点钟方向(水平向右) | -| `end` | `number` | `2π` | 结束角度(弧度),默认完整圆 | -| `circle` | `[x, y, r, start?, end?]` | - | 简写属性:
`[圆心x, 圆心y, 半径, 起始角度?, 结束角度?]` | - ---- - -## 完整示例集 - -### 示例 1:基础圆形(填充) - -```tsx - -``` - ---- - -### 示例 2:描边圆形 - -```tsx - -``` - ---- - -### 示例 3:弓形(填充闭合) - -```tsx - -``` - -**角度说明**: - -- 绘制方向:**顺时针**(从 12 点走向 9 点) -- 自动闭合路径形成扇形 - ---- - -### 示例 4:圆弧(非闭合) - -```tsx - -``` - -**路径特征**: - -- 起始角度:45 度(右上对角线方向) -- 结束角度:270 度(12 点钟方向) -- 开环不闭合,形成月牙形弧线 - ---- - -## 角度系统详解 - -### 坐标系与方向 - -```typescript -// 0弧度基准点与绘制方向示意图 -/* - Math.PI / 2 (90°) - | - | -Math.PI (180°) ——+—— 0 (0°) - | - | - Math.PI * 3 / 2 (270°) -*/ -``` - -- **0 弧度基准**:3 点钟方向(与浏览器 Canvas API 完全一致) -- **绘制方向**:角度递增为顺时针方向(`start=0, end=Math.PI/2` 绘制右下四分之一圆) - -### 特殊角度对照表 - -| 弧度值 | 角度值 | 方向 | -| ------------- | ------ | ------ | -| `0` | 0° | 正右 → | -| `Math.PI/2` | 90° | 正上 ↑ | -| `Math.PI` | 180° | 正左 ← | -| `3*Math.PI/2` | 270° | 正下 ↓ | - ---- - -## 高级用法示例 - -### 动态进度环 - -```tsx -import { ref } from 'vue'; -import { onTick } from '@motajs/render-vue'; - -const progress = ref(0); - -// 每帧更新进度 -onTick(() => { - progress.value += 0.01; -}); - -; -``` - -**效果**: - -- 0%时从顶部开始 -- 进度条顺时针增长 -- 圆角端点消除锯齿 - ---- - -## 注意事项 - -1. **简写属性优先级**: - -```tsx -// 谁最后设置用谁的 - -``` diff --git a/docs/api/motajs-render-vue/标签 g-ellipse.md b/docs/api/motajs-render-vue/标签 g-ellipse.md deleted file mode 100644 index 877b73e..0000000 --- a/docs/api/motajs-render-vue/标签 g-ellipse.md +++ /dev/null @@ -1,175 +0,0 @@ -# g-ellipse 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - EllipseProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface EllipseProps extends GraphicBaseProps { - radiusX?: number; // 椭圆X轴半径 - radiusY?: number; // 椭圆Y轴半径 - start?: number; // 起始角度(单位:弧度) - end?: number; // 结束角度(单位:弧度) - ellipse?: EllipseParams; // 简写属性 [x, y, rx, ry, start?, end?] -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| --------- | ------------------------------ | ------ | ------------------------------------------------------------------ | -| `radiusX` | `number` | - | 椭圆 X 轴半径(单位:像素) | -| `radiusY` | `number` | - | 椭圆 Y 轴半径(单位:像素) | -| `start` | `number` | `0` | 起始角度(弧度),0 弧度 = 3 点钟方向 | -| `end` | `number` | `2π` | 结束角度(弧度),默认完整椭圆 | -| `ellipse` | `[x, y, rx, ry, start?, end?]` | - | 简写属性:
`[圆心x, 圆心y, X半径, Y半径, 起始角度?, 结束角度?]` | - ---- - -## 完整示例集 - -### 示例 1:基础椭圆(填充) - -```tsx - -``` - ---- - -### 示例 2:描边椭圆 - -```tsx - -``` - ---- - -### 示例 3:椭圆弓形(闭合) - -```tsx - -``` - -**角度说明**: - -- 绘制方向:**顺时针**(从 45 度到 225 度) -- 自动闭合路径形成扇形 -- 若路径自相交,`evenodd` 规则会生成镂空 - ---- - -### 示例 4:椭圆弧线(非闭合) - -```tsx - -``` - -**路径特征**: - -- 垂直方向椭圆弧(X 半径 > Y 半径) -- 起始角度:-90 度(12 点方向) -- 结束角度:90 度(6 点方向) -- 开环形成对称弧线 - ---- - -## 角度系统详解 - -### 坐标系与方向 - -```typescript -// 角度系统与圆形一致,但受半径比例影响: -/* - radiusY - ↑ - | - radiusX +---→ -*/ -``` - -- **0 弧度基准**:3 点钟方向(与浏览器 Canvas API 一致) -- **半径影响**:当 `radiusX ≠ radiusY` 时,相同角度对应的端点位置会拉伸 -- **绘制方向**:角度递增为顺时针方向 - -### 特殊角度效果 - -| 参数组合 | 效果描述 | -| ---------------------------------- | ------------------------ | -| `radiusX=radiusY` | 退化为标准圆形 | -| `start=0, end=Math.PI` | 右半椭圆(水平方向半圆) | -| `start=Math.PI/2, end=3*Math.PI/2` | 上半椭圆(垂直方向半圆) | - ---- - -## 高级用法示例 - -### 动态仪表盘 - -```tsx -import { ref } from 'vue'; - -const value = ref(0.3); - -; -``` - -**效果**: - -- 椭圆弧仪表盘,从左上方向右侧延伸 -- 进度值 `0.3` 时覆盖 30%路径 - ---- - -## 注意事项 - -1. **简写属性优先级**: - -```tsx -// 谁最后被设置用谁的 - -``` diff --git a/docs/api/motajs-render-vue/标签 g-line.md b/docs/api/motajs-render-vue/标签 g-line.md deleted file mode 100644 index b88bff2..0000000 --- a/docs/api/motajs-render-vue/标签 g-line.md +++ /dev/null @@ -1,125 +0,0 @@ -# g-line 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - LineProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface LineProps extends GraphicBaseProps { - x1?: number; // 起点X坐标 - y1?: number; // 起点Y坐标 - x2?: number; // 终点X坐标 - y2?: number; // 终点Y坐标 - line?: LineParams; // 简写属性 [x1, y1, x2, y2] -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| ---------- | ----------------------------------- | -------- | ---------------------------------------------- | -| `x1` | `number` | - | 线段起点 X 坐标(单位:像素) | -| `y1` | `number` | - | 线段起点 Y 坐标(单位:像素) | -| `x2` | `number` | - | 线段终点 X 坐标(单位:像素) | -| `y2` | `number` | - | 线段终点 Y 坐标(单位:像素) | -| `line` | `[x1, y1, x2, y2]` | - | 简写属性:一次性定义起点和终点坐标 | -| `lineDash` | `number[]` | - | 虚线模式(如 `[5, 3]` 表示 5px 实线+3px 间隙) | -| `lineCap` | `"butt"` \| `"round"` \| `"square"` | `"butt"` | 线段端点样式 | - ---- - -## 完整示例集 - -### 示例 1:基础实线 - -```tsx -// 线段默认就是仅描边,因此不需要单独设置 stroke 属性 - -``` - ---- - -### 示例 2:虚线线段 - -```tsx - -``` - -**效果说明**: - -- 水平红色虚线 -- 线段端点呈圆形 - ---- - -### 示例 3:动态线段(动画) - -```tsx -import { transitioned } from '@user/client-modules'; - -// 创建渐变 -const x2 = transitioned(100, 2000, linear()); -x2.set(400); // 终点横坐标从 100 变到 400 - -return () => ( - -); -``` - -**动态效果**: - -- 线段从 100px 位置向右延伸至 400px -- 2 秒完成动画 - ---- - -## 线段样式对照表 - -| 样式组合 | 效果图示 | -| --------------------- | ----------------- | -| `lineCap="butt"` | 平头端点:⎯ | -| `lineCap="round"` | 圆头端点:⭘―――――⭘ | -| `lineCap="square"` | 方头端点:▯―――――▯ | -| `lineDash=[20,5,5,5]` | 复杂虚线:━━⧀┄⧀┄ | - ---- - -## 注意事项 - -1. **坐标系差异**: - 线段坐标基于父容器坐标系,如需相对定位建议嵌套在`container`中: - -```tsx - - {/* 实际坐标为 (100,100)→(150,150) */} - - -``` diff --git a/docs/api/motajs-render-vue/标签 g-path.md b/docs/api/motajs-render-vue/标签 g-path.md deleted file mode 100644 index 30cb870..0000000 --- a/docs/api/motajs-render-vue/标签 g-path.md +++ /dev/null @@ -1,173 +0,0 @@ -# g-path 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - PathProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface PathProps extends GraphicBaseProps { - path?: Path2D; // 自定义路径对象 -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| -------------- | -------------------------- | ----------- | -------------------------------------------------- | -| `path` | `Path2D` | - | 自定义矢量路径(支持多路径、贝塞尔曲线等复杂形状) | -| `fillRule` | `"nonzero"` \| `"evenodd"` | `"evenodd"` | 填充规则(影响路径重叠区域的渲染) | -| `actionStroke` | `boolean` | `false` | 设为 `true` 时,交互事件仅检测描边区域 | - ---- - -## 完整示例集 - -### 示例 1:复杂星形路径 - -```tsx -// 创建五角星路径 -const starPath = new Path2D(); -for (let i = 0; i < 5; i++) { - const angle = (i * 2 * Math.PI) / 5 - Math.PI / 2; - const x = 100 + Math.cos(angle) * 50; - const y = 100 + Math.sin(angle) * 50; - if (i === 0) starPath.moveTo(x, y); - else starPath.lineTo(x, y); -} -starPath.closePath(); - -; -``` - -**效果说明**: - -- 使用`evenodd`规则自动产生星形镂空效果 -- 金色填充+橙色描边的五角星 - ---- - -### 示例 2:交互式描边检测 - -```tsx -import { ref } from 'vue'; -const [clicked, setClicked] = useState(false); - -const clicked = ref(false); - -// 创建对话气泡路径 -const bubblePath = new Path2D(); -bubblePath.moveTo(50, 20); -bubblePath.quadraticCurveTo(25, 0, 0, 20); -bubblePath.quadraticCurveTo(25, 40, 50, 20); -bubblePath.rect(0, 20, 200, 100); - -const click = () => void (clicked.value = !click.value); - -; -``` - -**交互特性**: - -- 点击描边区域切换填充颜色 -- 内部区域不响应点击事件 - ---- - -### 示例 3:组合路径(齿轮造型) - -```tsx -const gearPath = new Path2D(); -// 主体圆形 -gearPath.arc(100, 100, 80, 0, Math.PI * 2); -// 添加8个齿牙 -for (let i = 0; i < 8; i++) { - const angle = ((Math.PI * 2) / 8) * i; - gearPath.moveTo(100 + Math.cos(angle) * 90, 100 + Math.sin(angle) * 90); - gearPath.lineTo( - 100 + Math.cos(angle + Math.PI / 8) * 110, - 100 + Math.sin(angle + Math.PI / 8) * 110 - ); - gearPath.lineTo( - 100 + Math.cos(angle - Math.PI / 8) * 110, - 100 + Math.sin(angle - Math.PI / 8) * 110 - ); -} - -; -``` - ---- - -## 高级用法示例 - -### SVG 路径转换 - -```tsx -// 将SVG路径转换为Path2D -const svgPath = 'M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80'; -const path = new Path2D(svgPath); - -; -``` - ---- - -## 注意事项 - -1. **路径闭合**: - - ```tsx - // 必须显式闭合路径才能正确填充 - const path = new Path2D(); - path.moveTo(0, 0); - path.lineTo(100, 0); - path.lineTo(100, 100); - path.closePath(); // 关键闭合操作 - ``` diff --git a/docs/api/motajs-render-vue/标签 g-quad.md b/docs/api/motajs-render-vue/标签 g-quad.md deleted file mode 100644 index eff64ac..0000000 --- a/docs/api/motajs-render-vue/标签 g-quad.md +++ /dev/null @@ -1,119 +0,0 @@ -# g-quad 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - QuadraticProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface QuadraticProps extends GraphicBaseProps { - sx?: number; // 起点X坐标 - sy?: number; // 起点Y坐标 - cpx?: number; // 控制点X坐标 - cpy?: number; // 控制点Y坐标 - ex?: number; // 终点X坐标 - ey?: number; // 终点Y坐标 - curve?: QuadParams; // 简写属性 [sx, sy, cpx, cpy, ex, ey] -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| ------- | ---------------------------- | ------ | --------------------------------------- | -| `sx` | `number` | - | 曲线起点 X 坐标(单位:像素) | -| `sy` | `number` | - | 曲线起点 Y 坐标(单位:像素) | -| `cpx` | `number` | - | 控制点 X 坐标(决定曲线弯曲方向和程度) | -| `cpy` | `number` | - | 控制点 Y 坐标 | -| `ex` | `number` | - | 曲线终点 X 坐标 | -| `ey` | `number` | - | 曲线终点 Y 坐标 | -| `curve` | `[sx, sy, cpx, cpy, ex, ey]` | - | 简写属性:一次性定义全部坐标点 | - ---- - -## 完整示例集 - -### 示例 1:基础二次贝塞尔曲线 - -```tsx - -``` - ---- - -### 示例 2:虚线抛物线 - -```tsx - -``` - -**曲线形态**: - -- 起点 (50,400) → 控制点 (250,100) → 终点 (450,400) -- 形成对称的类似抛物线形状的曲线 - ---- - -## 控制点行为说明 - -### 控制点影响示意图 - -```typescript -/* - (cpx,cpy) - ● - / \ - / \ -(start) ●-----●(end) -*/ -``` - -- **单控制点**:二次贝塞尔曲线仅有一个控制点,同时影响曲线的起始和结束方向 -- **对称性**:控制点距离起点/终点的垂直距离越大,曲线弯曲越明显 - -### 特殊形态案例 - -| 控制点布局 | 曲线形态描述 | -| ------------------------ | ------------- | -| 控制点在起点终点连线中点 | 退化为直线 | -| 控制点在起点正上方 | 形成"U"型曲线 | -| 控制点在终点右侧 | 形成"C"型曲线 | - ---- - -## 注意事项 - -1. **控制点极限值**: - 当控制点与起点/终点距离过大时可能产生锐角: - -```tsx -// 可能产生非预期的尖角形态 - -``` diff --git a/docs/api/motajs-render-vue/标签 g-rect.md b/docs/api/motajs-render-vue/标签 g-rect.md deleted file mode 100644 index 86f4143..0000000 --- a/docs/api/motajs-render-vue/标签 g-rect.md +++ /dev/null @@ -1,91 +0,0 @@ -# g-rect 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - RectProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 使用示例 - -### 示例 1:仅填充模式 - -```tsx - -``` - -**效果**: - -- 200x150 矩形 -- 无描边效果 - ---- - -### 示例 2:仅描边模式 - -```tsx - -``` - -**交互特性**: - -- 4px 黑色半透明描边 -- 鼠标悬停在描边区域才会触发事件 - ---- - -### 示例 3:填充 + 描边(默认顺序) - -```tsx - -``` - -**渲染顺序**: - -1. 填充黄色背景 -2. 在填充层上绘制黑色描边 - ---- - -### 示例 4:强制先描边后填充 - -```tsx - -``` - -**渲染顺序**: - -1. 绘制紫色描边 -2. 在描边层上填充浅紫色 - **视觉效果**:描边被填充色覆盖一部分 diff --git a/docs/api/motajs-render-vue/标签 g-rectr.md b/docs/api/motajs-render-vue/标签 g-rectr.md deleted file mode 100644 index f245ef3..0000000 --- a/docs/api/motajs-render-vue/标签 g-rectr.md +++ /dev/null @@ -1,167 +0,0 @@ -# g-rectr 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - RectRProps --> GraphicBaseProps --> BaseProps - - click GraphicBaseProps "./GraphicBaseProps" - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface RectRProps extends GraphicBaseProps { - /** - * 圆形圆角参数 [左上, 右上, 右下, 左下] - * - 1个值:全角相同 - * - 2个值:左上+右下 / 右上+左下 - * - 3个值:左上 / 右上+左下 / 右下 - * - 4个值:分别设置四个角 左上、右上、左下、右下 - */ - circle?: RectRCircleParams; - - /** - * 椭圆圆角参数 [x半径, y半径, ...] - * - 1组:全角相同 - * - 2组:左上+右下 / 右上+左下 - * - 3组:左上 / 右上+左下 / 右下 - * - 4组:分别设置四个角 左上、右上、左下、右下 - */ - ellipse?: RectREllipseParams; -} -``` - ---- - -## 核心行为规则 - -- **参数限制**:若圆角值超过 `width/height` 的 50% 或为负数,将自动修正: - - 负值 → 修正为 0 - - 超过 50% → 修正为 50% -- **参数优先级**:`ellipse` 优先级高于 `circle` - ---- - -## 完整示例集 - -### 示例 1:统一圆形圆角 - -```tsx - -``` - ---- - -### 示例 2:差异圆形圆角 - -```tsx - -``` - ---- - -### 示例 3:椭圆圆角 - -```tsx - -``` - ---- - -### 示例 4:混合椭圆圆角 - -```tsx - -``` - ---- - -## 参数对照表 - -### 圆形圆角(circle)参数规则 - -| 参数数量 | 生效规则 | -| -------- | -------------------------------------------- | -| 1 | 全角相同:`[20] → [20,20,20,20]` | -| 2 | 对角对称:`[10,30] → [10,30,30,10]` | -| 3 | 左上/对角/右下:`[10,20,30] → [10,20,30,20]` | -| 4 | 独立设置四个角:`[10,20,30,40]` | - -### 椭圆圆角(ellipse)参数规则 - -| 参数组数 | 生效规则 | -| -------- | ---------------------------------------------- | -| 1 | 全角相同:`[15,20] → 四角均为15x20` | -| 2 | 对角对称:`[10,5,20,10] → 左上+右下/右上+左下` | -| 3 | 左上/对角/右下:`[10,20,15,5,20,10]` | -| 4 | 独立设置四个角:`[10,20,15,25,20,30,5,10]` | - ---- - -## 错误处理示例 - -```tsx -// 危险参数示例 - -``` - -**实际生效值**: - -- 负值修正:`-10 → 0` -- 超限修正:`200 → min(200, 50/2=25) → 25px` -- 最终参数:`[0,25] → [0,25,25,0]` - ---- - -## 最佳实践建议 - -1. **响应式圆角**: - -```tsx -// 圆角随尺寸变化 - -``` diff --git a/docs/api/motajs-render-vue/标签 icon.md b/docs/api/motajs-render-vue/标签 icon.md deleted file mode 100644 index f70b128..0000000 --- a/docs/api/motajs-render-vue/标签 icon.md +++ /dev/null @@ -1,121 +0,0 @@ -# icon 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - IconProps --> BaseProps - - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface IconProps extends BaseProps { - icon: AllNumbers | AllIds; // 图标ID或数字标识 - frame?: number; // 显示指定帧(从0开始计数) - animate?: boolean; // 是否启用帧动画循环(默认false) -} -``` - ---- - -## 核心属性说明 - -| 属性 | 类型 | 默认值 | 说明 | -| --------- | -------------------- | -------- | ------------------------------------------------------ | -| `icon` | `number` \| `string` | **必填** | 图标资源标识符(对应素材库中的图块 ID 或预设数字编码) | -| `frame` | `number` | `0` | 指定显示的帧序号(当`animate=false`时生效) | -| `animate` | `boolean` | `false` | 启用自动播放帧动画(优先级高于`frame`参数) | - ---- - -## 完整示例集 - -### 示例 1:静态显示指定帧 - -```tsx -// 显示图块ID为"greenSlime"的第3帧(索引从0开始) - -``` - ---- - -### 示例 2:动态帧动画 - -```tsx -// 自动播放4帧循环动画 - -``` - -**动画行为**: - -- 播放图标自带的 4 帧动画序列(0→1→2→3→0...) -- 动画速度由素材预设帧率决定 - ---- - -### 示例 3:交互控制动画 - -```tsx -import { ref } from 'vue'; - -const animating = ref(false); - -const click = () => void (animating.value = false); - -// 点击切换动画状态 -; -``` - -**交互逻辑**: - -- 初始状态显示第 1 帧(门关闭) -- 点击后播放开门动画 - ---- - -## 帧动画系统规则 - -### 帧索引定义 - -```typescript -// 图块素材帧结构示例 -/* - [0] [1] [2] [3] - +---+---+---+---+ - | | | | | // 4帧水平排列的图块素材 - +---+---+---+---+ -*/ -``` - -- **播放方向**:始终从 `0` 帧开始正向循环 -- **循环模式**:播放到最后一帧后回到第 `0` 帧 - -### 参数限制 - -| 场景 | 系统行为 | -| ----------------------------- | ------------------------------------- | -| `frame` 超过最大帧数 | 报错 | -| `frame` 为负数 | 报错 | -| `animate=true` 时修改 `frame` | `frame` 参数被忽略,始终从 0 开始播放 | diff --git a/docs/api/motajs-render-vue/标签 image.md b/docs/api/motajs-render-vue/标签 image.md deleted file mode 100644 index 5429cfd..0000000 --- a/docs/api/motajs-render-vue/标签 image.md +++ /dev/null @@ -1,103 +0,0 @@ -# image 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - ImageProps --> BaseProps - - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface ImageProps extends BaseProps { - /** 必填 - 图片对象(CanvasImageSource 类型) */ - image: CanvasImageSource; -} -``` - ---- - -## 核心功能 - -- **尺寸控制**:通过 `width/height` 或 `loc` 简写属性定义图片尺寸(默认 200x200) -- **像素风支持**:通过 `noanti=true` 禁用抗锯齿 - ---- - -## 使用示例 - -### 示例 1:基础图片显示 - -```tsx -const image = core.material.images.images['myimage.webp']; -// 显示 200x200 的默认尺寸图片 -; -``` - -**等效简写**: - -```tsx -const image = core.material.images.images['myimage.webp']; - -; -``` - ---- - -### 示例 2:自定义尺寸 - -```tsx -const image = core.material.images.images['myimage.webp']; -// 方式一:直接设置宽高 -; - -// 方式二:通过 loc 简写属性 -; -``` - ---- - -### 示例 3:像素风渲染(禁用抗锯齿) - -```tsx -const pixelImage = core.material.images.images['myimage.webp']; - -// 硬核像素风格配置 -; -``` - -**效果说明**: - -- 原始 32x32 像素图 → 放大为 64x64 像素 -- 每个像素块保持锐利边缘 - ---- - -## 属性配置表 - -| 属性 | 类型 | 默认值 | 说明 | -| ------- | ------------------- | -------- | ------------------------------------------- | -| `image` | `CanvasImageSource` | **必填** | 图片资源(ImageBitmap/HTMLImageElement 等) | diff --git a/docs/api/motajs-render-vue/标签 sprite.md b/docs/api/motajs-render-vue/标签 sprite.md deleted file mode 100644 index e73517d..0000000 --- a/docs/api/motajs-render-vue/标签 sprite.md +++ /dev/null @@ -1,148 +0,0 @@ -# sprite 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - SpriteProps --> BaseProps - - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface SpriteProps extends BaseProps { - /** - * 自定义渲染函数 - * @param canvas - 离屏画布对象 - * @param transform - 当前元素相对于父元素的变换矩阵 - */ - render?: (canvas: MotaOffscreenCanvas2D, transform: Transform) => void; -} -``` - ---- - -## 核心能力 - -通过 `render` 函数实现 **动态绘制**,可结合: - -- 基础定位/变换参数(继承 `BaseProps`) -- 动画系统(`useAnimation`) -- 帧回调(`onTick`) -- 自定义图形绘制(路径/滤镜/混合模式) - -**注意**,这个标签虽然非常基础,但是应该并不常用,因为很多内容都有对应的标签可以实现(例如线可以使用 `g-line` 标签等),因此如果你在考虑使用此标签,请确认你必须使用它,或是你的场景对性能非常敏感。 - ---- - -## 使用示例 - -以下的示例代码均应该在**组件内部**编写,哪里是组件内部请参考[指南](../../guide/ui/ui.md)。 - -### 示例 1:基础图形 - -```tsx -import { MotaOffscreenCanvas2D } from '@motajs/render-core'; - -// 绘制旋转了 45 度的彩色方块 -const render = (canvas: MotaOffscreenCanvas2D) => { - const ctx = canvas.ctx; - ctx.fillStyle = 'rgba(255,0,0,0.8)'; - ctx.strokeStyle = 'blue'; - ctx.lineWidth = 3; - ctx.beginPath(); - ctx.rect(0, 0, 200, 200); - ctx.fill(); - ctx.stroke(); -}; - -; -``` - -**效果**: - -- 100x100 红色方块,中心点位于 (200,200) -- 45 度旋转,蓝色描边 -- 80% 不透明度 - ---- - -### 示例 2:结合动画系统 - -```tsx -import { ref } from 'vue'; -import { useAnimation } from '@motajs/render-vue'; - -// 平移动画 + 动态缩放 -const [anim] = useAnimation(); -anim.time(2000).move(100, 0).scale(1.5); - -const loc = ref([0, 0]); -const scale = ref([1, 1]); - -onTick(() => { - loc.value = [anim.x, anim.y]; - scale.value = [anim.size, anim.size]; -}); - -const render = canvas => { - const ctx = canvas.ctx; - ctx.rect(0, 0, 200, 200); - ctx.fill(); -}; - -return () => ; -``` - -**效果**: - -- 矩形横向放大 1.5 倍,横向位置移动 100px -- 2 秒线性动画 - ---- - -### 示例 3:交互事件 + 滤镜 - -```tsx -import { ref } from 'vue'; - -// 悬浮模糊 -const filter = ref('none'); - -const enter = () => { - filter.value = 'blur(2px)'; -}; -const leave = () => { - filter.value = 'none'; -}; - -return () => ( - -); -``` - -**效果**: - -- 鼠标悬浮时添加模糊滤镜 -- 鼠标悬浮显示指针光标 - ---- - -## 注意事项 - -1. **坐标系**:`render` 函数内使用 **局部坐标系**,锚点变换已自动处理 -2. **循环更新**:避免在 `render` 中循环更新自身 diff --git a/docs/api/motajs-render-vue/标签 text.md b/docs/api/motajs-render-vue/标签 text.md deleted file mode 100644 index 8b4efd7..0000000 --- a/docs/api/motajs-render-vue/标签 text.md +++ /dev/null @@ -1,160 +0,0 @@ -# TextProps API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - TextProps --> BaseProps - - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface TextProps extends BaseProps { - text?: string; // 显示的文字内容 - fillStyle?: CanvasStyle; // 文字填充样式(颜色/渐变/图案) - strokeStyle?: CanvasStyle; // 文字描边样式 - font?: Font; // 字体配置对象 - strokeWidth?: number; // 描边宽度(单位:像素) -} -``` - ---- - -## 使用示例 - -### 1. 基本文本渲染 - -```tsx -import { Font } from '@motajs/render-style'; - -; -``` - -**效果**: - -- 在 (100,50) 位置显示蓝色 "Hello World" -- 使用 24px 粗体 Arial 字体 -- 无描边效果 - ---- - -### 2. 描边文字 + 填充组合 - -```tsx -import { Font } from '@motajs/render-style'; - -; -``` - -**效果**: - -- 金色文字带黑色半透明描边 -- 32px 加粗斜体 Verdana -- 2px 描边宽度 - ---- - -### 3. 动态更新文本 - -```tsx -import { ref } from 'vue'; -import { Font } from '@motajs/render-style'; - -// Vue3 组件示例 -const count = ref(0); - -onTick(() => { - count.value++; -}); - -return () => ( - -); -``` - ---- - -## 高级用法示例 - -### 文字路径动画 - -```tsx -import { ref } from 'vue'; -import { Font, onTick } from '@motajs/render'; - -const offset = ref(0); - -onTick(() => { - offset.value = (offset.value + 1) % 100; -}); - -; -``` - ---- - -### 文字阴影 + 滤镜 - -```tsx -import { Font } from '@motajs/render-style'; - -; -``` - ---- - -## 属性兼容性表 - -| 属性 | 是否继承 BaseProps | 动态更新支持 | 性能影响等级 | -| ------------- | ------------------ | ------------ | ------------ | -| `text` | 否 | ✔️ | 中 | -| `fillStyle` | 否 | ✔️ | 中 | -| `strokeStyle` | 否 | ✔️ | 中 | -| `font` | 否 | ✔️ | 高 | -| `strokeWidth` | 否 | ✔️ | 低 | - ---- - -## 注意事项 - -1. 如果需要显示多行文本,考虑使用 [TextContent](../user-client-modules/组件%20TextContent.md) -2. 考虑到浏览器兼容性,不建议在颜色中填写一些新标准的语法,例如 `rgb(0.3, 0.6, 0.8 / 0.6)` `#rgba` 等 diff --git a/docs/api/motajs-render-vue/标签 winskin.md b/docs/api/motajs-render-vue/标签 winskin.md deleted file mode 100644 index 7e65c53..0000000 --- a/docs/api/motajs-render-vue/标签 winskin.md +++ /dev/null @@ -1,117 +0,0 @@ -# winskin 标签 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - WinskinProps --> BaseProps - - click BaseProps "./BaseProps" -``` - ---- - -## 接口定义 - -```typescript -interface WinskinProps extends BaseProps { - /** - * 窗口皮肤图片资源ID - */ - image: ImageIds; - - /** - * 边框粗细 - * - 设置为 32 时表示原始大小(16 像素宽度),默认为 32 - * - 设为0时仅显示中心内容区 - */ - borderSize?: number; -} -``` - ---- - -## 核心功能演示 - -### 示例 1:基础窗口皮肤 - -```tsx -// 使用默认边框(borderSize=32) - - - - - -``` - ---- - -### 示例 2:不同边框粗细对比 - -```tsx -// 细边框(borderSize=16) -; - -// 粗边框(borderSize=32) -; - -// 无边框(borderSize=0) -; -``` - -**效果差异**: - -- 细边框:内容区域更大,适合信息密集场景 -- 粗边框:装饰性更强,适合标题窗口 -- 无边框:仅保留中心纹理,适合全屏背景 - ---- - -## 九宫格原理示意图 - -```typescript -/* - +-------------------+ - | 1 top 2 | ← borderSize - | | - | | -left | 4 center 3 | right - | | - | | - | 5 bottom 6 | - +-------------------+ -*/ -``` - ---- - -## 高级用法示例 - -### 动态边框动画 - -```tsx -import { hyper } from 'mutate-animate'; -import { transitioned } from '@user/client-modules'; -import { onTick } from '@motajs/render-vue'; - -const border = transitioned(16, 1000, hyper('sin', 'in-out')); -onTick(() => { - if (border.value === 16) border.set(32); - if (border.value === 32) border.set(16) -}) - - - - -; -``` diff --git a/docs/api/motajs-render/index.md b/docs/api/motajs-render/index.md deleted file mode 100644 index f4012da..0000000 --- a/docs/api/motajs-render/index.md +++ /dev/null @@ -1,16 +0,0 @@ -# @motajs/render - -此模块包含如下模块的内容,可以直接引用: - -- [@motajs/render-core](../motajs-render-core/) -- [@motajs/render-elements](../motajs-render-elements/) -- [@motajs/render-style](../motajs-render-style/) -- [@motajs/render-vue](../motajs-render-vue/) - -引入示例: - -```ts -import { Container } from '@motajs/render'; -// 二者等价,不需要单独使用一个量来接收 -import { Container } from '@motajs/render-core'; -``` diff --git a/docs/api/motajs-system-action/Hotkey.md b/docs/api/motajs-system-action/Hotkey.md deleted file mode 100644 index 64bbdb5..0000000 --- a/docs/api/motajs-system-action/Hotkey.md +++ /dev/null @@ -1,311 +0,0 @@ -# Hotkey API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - Hotkey --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -## 类描述 - -`Hotkey` 是按键系统的核心类,用于管理按键绑定、辅助键状态、按键分组及事件触发逻辑。继承自 `EventEmitter`,支持自定义事件监听。主要用于实现复杂的快捷键系统。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| -------------- | ---------------------------- | -------------------------------------------- | -| `id` | `string` | 控制器的唯一标识符 | -| `name` | `string` | 控制器的显示名称 | -| `data` | `Record` | 存储所有已注册的按键配置 | -| `keyMap` | `Map` | 按键代码到配置的映射(支持多键绑定同一操作) | -| `enabled` | `boolean` | 当前控制器是否启用(默认 `false`) | -| `list`(静态) | `Hotkey[]` | 静态属性,存储所有已创建的控制器实例 | - ---- - -## 构造方法 - -```typescript -function constructor(id: string, name: string): Hotkey; -``` - -- **参数** - - `id`: 控制器的唯一标识符 - - `name`: 控制器的显示名称 - -**示例** - -```typescript -const editorHotkey = new Hotkey('editor', '编辑器快捷键'); -``` - ---- - -## 方法说明 - -### `register` - -```typescript -function register(data: RegisterHotkeyData): this; -``` - -注册一个按键配置。 - -- **参数** - ```typescript - interface RegisterHotkeyData { - id: string; // 按键唯一标识(可含数字后缀,如 "copy_1") - name: string; // 显示名称(如 "复制") - defaults: KeyCode; // 默认按键代码 - ctrl?: boolean; // 是否默认需要 Ctrl 辅助键 - shift?: boolean; // 是否默认需要 Shift 辅助键 - alt?: boolean; // 是否默认需要 Alt 辅助键 - } - ``` - -**示例** - -```typescript -editorHotkey.register({ - id: 'copy', - name: '复制', - defaults: KeyCode.KeyC, - ctrl: true -}); -``` - ---- - -### `realize` - -```typescript -function realize(id: string, func: HotkeyFunc, config?: HotkeyEmitConfig): this; -``` - -为按键绑定触发逻辑。 - -- **参数** - - `id`: 目标按键 ID(无需后缀) - - `func`: 触发时执行的函数 - - `config`: 触发类型配置(节流/超时等) - -**示例** - -```typescript -editorHotkey.realize( - 'copy', - (id, key, ev) => { - console.log('执行复制操作'); - }, - { type: 'down-throttle', throttle: 500 } -); -``` - ---- - -### `group` - -```typescript -function group(id: string, name: string, keys?: RegisterHotkeyData[]): this; -``` - -创建按键分组,后续注册的按键自动加入该组。 - -- **参数** - - `id`: 分组唯一标识 - - `name`: 分组显示名称 - - `keys`: 可选,预注册的按键列表 - ---- - -### `set` - -```typescript -function set(id: string, key: KeyCode, assist: number, emit?: boolean): void; -``` - -动态修改按键绑定。 - -- **参数** - - `id`: 目标按键 ID - - `key`: 新按键代码 - - `assist`: 辅助键状态(二进制位:Ctrl=1<<0, Shift=1<<1, Alt=1<<2) - - `emit`: 是否触发 `set` 事件(默认 `true`) - ---- - -### `when` - -```typescript -function when(fn: () => boolean): this; -``` - -为当前作用域的按键绑定添加触发条件。 - -- **参数** - - `fn`: 条件函数,返回 `true` 时允许触发按键逻辑 - -**示例** - -```typescript -// 仅在游戏处于运行状态时允许触发 -controller.when(() => gameState === 'running'); -``` - ---- - -### `enable` - -```typescript -function enable(): void; -``` - -### `disable` - -```typescript -function disable(): void; -``` - -启用/禁用整个按键控制器(禁用后所有按键事件将被忽略)。 - -**示例** - -```typescript -// 暂停游戏时禁用按键 -controller.disable(); -``` - ---- - -### `use` - -```typescript -function use(symbol: symbol): void; -``` - -切换当前作用域,后续 `realize` 方法绑定的逻辑将关联到该作用域。 - -- **参数** - - `symbol`: 唯一作用域标识符 - ---- - -### `dispose` - -```typescript -function dispose(symbol?: symbol): void; -``` - -释放指定作用域及其绑定的所有按键逻辑。 - -- **参数** - - `symbol`(可选): 要释放的作用域(默认释放当前作用域) - -**示例** - -```typescript -const scope = Symbol(); -controller.use(scope); -// ...绑定操作... -controller.dispose(scope); // 释放该作用域 -``` - ---- - -### `emitKey` - -```typescript -function emitKey( - key: KeyCode, - assist: number, - type: KeyEventType, - ev: KeyboardEvent -): boolean; -``` - -手动触发按键事件(可用于模拟按键操作)。 - -- **参数** - - `key`: 按键代码 - - `assist`: 辅助键状态(二进制位:`Ctrl=1<<0`, `Shift=1<<1`, `Alt=1<<2`) - - `type`: 事件类型(`'up'` 或 `'down'`) - - `ev`: 原始键盘事件对象 -- **返回值** - `true` 表示事件被成功处理,`false` 表示无匹配逻辑 - -**示例** - -```typescript -// 模拟触发 Ctrl+S 保存操作 -controller.emitKey( - KeyCode.KeyS, - 1 << 0, // Ctrl 激活 - 'down', - new KeyboardEvent('keydown') -); -``` - ---- - -## 静态方法说明 - -### `Hotkey.get` - -```typescript -function get(id: string): Hotkey | undefined; -``` - -**静态方法**:根据 ID 获取控制器实例。 - ---- - -## 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| --------- | --------------------------------------------------- | ---------------- | -| `set` | `[id: string, key: KeyCode, assist: number]` | 按键绑定被修改时 | -| `emit` | `[key: KeyCode, assist: number, type: KeyEmitType]` | 按键被触发时 | -| `press` | `[key: KeyCode]` | 按键被按下时 | -| `release` | `[key: KeyCode]` | 按键被释放时 | - -**事件监听示例** - -```typescript -gameKey.on('emit', (key, assist) => { - console.log(`按键 ${KeyCode[key]} 触发,辅助键状态:${assist}`); -}); -``` - ---- - -## 总使用示例 - -::: code-group - -```typescript [注册] -import { gameKey } from '@motajs/system-action'; - -gameKey.register({ - id: 'jump', - name: '跳跃', - defaults: KeyCode.Space -}); -``` - -```typescript [实现] -import { useKey } from '@motajs/render-vue'; - -const [gameKey] = useKey(); - -// 绑定跳跃逻辑 -gameKey.realize('jump', () => { - player.jump(); -}); -``` - -::: diff --git a/docs/api/motajs-system-action/Keyboard.md b/docs/api/motajs-system-action/Keyboard.md deleted file mode 100644 index f3929fa..0000000 --- a/docs/api/motajs-system-action/Keyboard.md +++ /dev/null @@ -1,221 +0,0 @@ -# Keyboard API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - Keyboard --> EventEmitter -``` - -## 类描述 - -`Keyboard` 是虚拟键盘的核心类,用于管理动态按键布局、处理按键事件及辅助键状态。继承自 `EventEmitter`,支持自定义事件监听。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| -------------- | ---------------- | ----------------------------------------------------------- | -| `id` | `string` | 键盘的唯一标识符 | -| `keys` | `KeyboardItem[]` | 当前键盘包含的按键列表(响应式数组) | -| `assist` | `number` | 辅助键状态(二进制位表示:Ctrl=1<<0, Shift=1<<1, Alt=1<<2) | -| `fontSize` | `number` | 按键文本字体大小(默认 18) | -| `list`(静态) | `Keyboard[]` | 静态属性,存储所有已创建的键盘实例 | - -**KeyboardItem 结构**: - -```typescript -interface KeyboardItem { - key: KeyCode; // 按键代码 - text?: string; // 显示文本(可选) - x: number; // X 坐标 - y: number; // Y 坐标 - width: number; // 宽度 - height: number; // 高度 -} -``` - ---- - -## 构造方法 - -```typescript -function constructor(id: string): Keyboard; -``` - -- **参数** - - `id`: 键盘的唯一标识符 - -**示例** - -```typescript -const numpad = new Keyboard('numpad'); -``` - ---- - -## 方法说明 - -### `add` - -```typescript -function add(item: KeyboardItem): Keyboard; -``` - -### `remove` - -```typescript -function remove(item: KeyboardItem): Keyboard; -``` - -添加/移除按键,返回当前实例以便链式调用。 - -**示例** - -```typescript -// 添加数字键 1 -numpad.add({ - key: KeyCode.Digit1, - text: '1', - x: 0, - y: 0, - width: 60, - height: 60 -}); - -// 移除按键 -numpad.remove(existingKey); -``` - ---- - -### `extend` - -```typescript -function extend( - keyboard: Keyboard, - offsetX?: number, - offsetY?: number -): Keyboard; -``` - -继承其他键盘的按键布局并添加偏移量。 - -**示例** - -```typescript -const extendedKB = new Keyboard('extended'); -extendedKB.extend(numpad, 100, 0); // 向右偏移 100px -``` - ---- - -### `emitKey` - -```typescript -function emitKey(key: KeyboardItem, index: number): void; -``` - -模拟触发按键事件(自动处理辅助键状态)。 - ---- - -### `createScope` - -```typescript -function createScope(): symbol; -``` - -### `disposeScope` - -```typescript -function disposeScope(): void; -``` - -管理事件监听作用域: - -- `createScope`: 创建新作用域(返回唯一标识符) -- `disposeScope`: 释放当前作用域 - ---- - -### `withAssist` - -```typescript -function withAssist(assist: number): symbol; -``` - -创建预设辅助键状态的作用域(如 `Ctrl+Shift`)。 - ---- - -### `Keyboard.get` - -```typescript -function get(id: string): Keyboard | undefined; -``` - -**静态方法**:根据 ID 获取键盘实例。 - ---- - -## 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| -------------- | ------------------------- | ------------------ | -| `add` | `KeyboardItem` | 新增按键时 | -| `remove` | `KeyboardItem` | 移除按键时 | -| `extend` | `Keyboard` | 继承其他键盘布局时 | -| `emit` | `item, assist, index, ev` | 触发按键时 | -| `scopeCreate` | `symbol` | 创建作用域时 | -| `scopeDispose` | `symbol` | 释放作用域时 | - -**事件监听示例** - -```typescript -numpad.on('emit', (item, assist) => { - console.log(`按键 ${item.key} 触发,辅助键状态:${assist}`); -}); -``` - ---- - -## 总使用示例 - -```typescript -import { KeyCode } from '@motajs/client-base'; -import { Keyboard } from '@motajs/system-action'; - -// 创建数字键盘 -const numpad = new Keyboard('numpad'); - -// 添加基础按键 -numpad - .add({ key: KeyCode.Digit1, x: 0, y: 0, width: 60, height: 60 }) - .add({ key: KeyCode.Digit2, x: 60, y: 0, width: 60, height: 60 }); - -// 添加功能键(带辅助状态) -const ctrlScope = numpad.withAssist(1 << 0); // Ctrl 激活 -numpad.add({ - key: KeyCode.KeyC, - text: '复制', - x: 120, - y: 0, - width: 120, - height: 60 -}); - -// 监听复制键 -numpad.on('emit', item => { - if (item.key === KeyCode.KeyC) { - console.log('执行复制操作'); - } -}); - -// 触发按键 -numpad.emitKey(numpad.keys[0], 0); // 模拟按下数字 1 - -// 获取键盘实例 -const foundKB = Keyboard.get('numpad'); -``` diff --git a/docs/api/motajs-system-action/index.md b/docs/api/motajs-system-action/index.md deleted file mode 100644 index 970f853..0000000 --- a/docs/api/motajs-system-action/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/system-action - -目录: diff --git a/docs/api/motajs-system-ui/GameUI.md b/docs/api/motajs-system-ui/GameUI.md deleted file mode 100644 index c8dae21..0000000 --- a/docs/api/motajs-system-ui/GameUI.md +++ /dev/null @@ -1,97 +0,0 @@ -# GameUI API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - GameUI --> IGameUI -``` - -_实现 `IGameUI` 接口_ - -## 接口描述 - -`IGameUI` 是 UI 系统的核心接口,定义了 UI 实例的基础结构和静态操作方法。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| -------------- | ---------------------------------- | ------------------------------------------------------------------- | -| `name` | `string` | 只读,UI 的唯一标识名称 | -| `component` | `C extends UIComponent` | 只读,关联的 Vue 组件实例 | -| `list`(静态) | `Map>` | 静态属性,存储所有已注册的 UI 实例,键为 `name`,值为 `GameUI` 实例 | - ---- - -## 构造方法 - -### `consturctor` - -```typescript -function constructor(name: string, component: C): GameUI; -``` - -- **参数** - - `name`: UI 的唯一标识名称 - - `component`: 关联的 Vue 组件实例 - -**示例** - -```typescript -import { defineComponent } from 'vue'; - -export const MyComponent = defineComponent(...); - -// 创建 UI 实例并自动注册到静态列表 -export const MyUI = new GameUI('my-ui', MyComponent); -``` - ---- - -## 方法说明 - -### `GameUI.get` - -```typescript -function get(id: string): GameUI | null; -``` - -**静态方法**:根据 UI 名称从静态列表获取实例。 - -- **参数** - - `id`: UI 的唯一标识名称 -- **返回值** - 匹配的 `GameUI` 实例,未找到时返回 `null` - -**示例** - -```typescript -// 获取名为 "home" 的 UI 实例 -const ui = GameUI.get('my-ui'); -if (ui) { - console.log(ui.component); // 输出关联的 Vue 组件 -} -``` - ---- - -## 总使用示例 - -```tsx -import { defineComponent } from 'vue'; -import { GameUI } from '@motajs/system-ui'; - -// 定义组件 -export const MyCom = defineComponent(() => { - return () => ( - - - - ); -}); - -// 定义 UI 实例 -export const MyUI = new GameUI('my-ui', MyCom); -``` diff --git a/docs/api/motajs-system-ui/UIController.md b/docs/api/motajs-system-ui/UIController.md deleted file mode 100644 index 0d44e89..0000000 --- a/docs/api/motajs-system-ui/UIController.md +++ /dev/null @@ -1,284 +0,0 @@ -# UIController API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - UIController --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -## 类描述 - -`UIController` 是 UI 控制系统的核心类,负责管理 UI 实例的显示栈、背景控制以及多种显示模式。继承自 `EventEmitter`,支持事件监听。想要编写 UI 请参考[深度指南](../../guide/ui/ui.md)。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| --------------------- | ---------------------------- | --------------------------------------------------------------- | -| `stack` | `IUIInstance[]` (响应式数组) | 当前管理的 UI 实例栈 | -| `mode` | `UIMode` | UI 显示模式,默认为 `LastOnlyStack` | -| `background` | `IGameUI` | 背景 UI 的配置实例 | -| `backIns` | `ShallowRef` | 背景 UI 实例的响应式引用 | -| `showBack` | `ComputedRef` | 当前是否显示背景(用户设置与系统状态共同决定) | -| `active`(只读) | `boolean` | 系统是否显示背景 UI(等价于 `sysShowBack.value`) | -| `controllers`(静态) | `Map` | 静态属性,存储所有已创建的控制器实例,键为 `id`,值为控制器实例 | - ---- - -## 构造方法 - -### `constructor` - -```typescript -function constructor(id: string): UIController; -``` - -- **参数** - - `id`: 控制器的唯一标识符(若重复会触发警告日志) - -**示例** - -```typescript -const mainController = new UIController('main'); -``` - ---- - -## 方法说明 - -### `render` - -```typescript -function render(): VNode; -``` - -渲染 UI 容器组件(用于 Vue 挂载)。 - -**示例** - -```tsx -import { defineComponent } from 'vue'; -import { UIController } from '@motajs/system-ui'; - -export const myController = new UIController('my-controller'); - -export const MyCom = defineComponent(() => { - return () => {myController.render()}; -}); -``` - ---- - -### `setBackground` - -```typescript -function setBackground( - back: IGameUI, - vBind: UIProps -): void; -``` - -设置背景 UI 并初始化其实例。 - -- **参数** - - `back`: 背景 UI 配置实例 - - `vBind`: 传递给背景组件的 Props 对象 - -**示例** - -```typescript -import { BackgroundUI } from './background'; - -// 显示背景组件,并传入参数 theme='dark' -mainController.setBackground(BackgroundUI, { theme: 'dark' }); -``` - ---- - -### `hideBackground` - -```typescript -function hideBackground(): void; -``` - -隐藏背景 UI (不影响系统状态)。 - ---- - -### `showBackground` - -```typescript -function showBackground(): void; -``` - -显示背景 UI(不影响系统状态)。 - ---- - -### `keep` - -```typescript -function keep(): IKeepController; -``` - -维持背景显示(防闪烁),返回控制器对象: - -```typescript -interface IKeepController { - safelyUnload(): void; // 安全卸载(仅在栈为空时关闭) - unload(): void; // 强制立即卸载 -} -``` - -**示例** - -```typescript -const keeper = mainController.keep(); -// 执行某些操作后... -keeper.safelyUnload(); -``` - ---- - -### `open` - -```typescript -function open( - ui: IGameUI, - vBind: UIProps, - alwaysShow?: boolean -): IUIInstance; -``` - -打开一个新 UI 实例并加入栈中。 - -- **参数** - - `ui`: UI 配置实例 - - `vBind`: 组件 Props - - `alwaysShow`: 是否强制显示(默认 `false`) -- **返回值** - 打开的 UI 实例,可以用于关闭等操作 - -**示例** - -```typescript -import { MyUI } from './myUI'; - -const instance = mainController.open(MyUI, { param: 80 }); -``` - ---- - -### `close` - -```typescript -function close(ui: IUIInstance): void; -``` - -关闭指定 UI 实例(根据当前模式可能影响其他实例)。 - ---- - -### `closeAll` - -```typescript -function closeAll(ui?: IGameUI): void; -``` - -关闭所有或指定类型的所有 UI 实例。 - -- **参数** - - `ui`(可选): 指定要关闭的 UI 类型,不填时表示关闭所有 UI - ---- - -### `lastOnly` - -```typescript -function lastOnly(stack?: boolean): void; -``` - -切换显示模式:仅显示最后一个 UI(可设置为栈模式) - ---- - -### `showAll` - -```typescript -function showAll(stack?: boolean): void; -``` - -切换显示模式:显示所有非隐藏 UI(可设置为栈模式) - ---- - -### `showCustom` - -```typescript -function showCustom(config: IUICustomConfig): void; -``` - -切换显示模式:使用自定义模式(需实现 `IUICustomConfig`),参考[指南](../../guide/ui/system.md#自定义显示模式) - ---- - -### `UIController.getController` - -```typescript -function getController(id: string): UIController | null; -``` - -**静态方法**:根据 ID 获取控制器实例。 - -**示例** - -```typescript -const ctrl = UIController.getController('main'); -``` - ---- - -## 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| ------- | ------------------------------- | ------------------ | -| `open` | `ui: IGameUI, ins: IUIInstance` | 新 UI 实例被打开时 | -| `close` | `ins: IUIInstance` | UI 实例被关闭时 | - -**事件监听示例** - -```typescript -mainController.on('open', (ui, ins) => { - console.log(`Opened UI: ${ui.name}`); -}); -``` - ---- - -## 总使用示例 - -```typescript -import { BackgroundUI, DialogUI } from './myUI'; -import { mainController } from '@user/client-modules'; - -// 事件监听 -mainController.on('close', ins => { - console.log('UI closed:', ins.ui.name); -}); - -// 设置背景 -mainController.setBackground(BackgroundUI, { color: '#333' }); - -// 打开 UI -const dialogIns = mainController.open(DialogUI, { title: '提示' }); - -// 切换显示模式,仅显示最后一个,启用栈模式 -mainController.lastOnly(true); - -// 关闭 UI -mainController.close(dialogIns); -``` diff --git a/docs/api/motajs-system-ui/UIInstance.md b/docs/api/motajs-system-ui/UIInstance.md deleted file mode 100644 index 53c7890..0000000 --- a/docs/api/motajs-system-ui/UIInstance.md +++ /dev/null @@ -1,121 +0,0 @@ -# UIInstance API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - UIInstance --> IUIInstance -``` - -_实现 `IUIInstance` 接口_ - -## 类描述 - -`UIInstance` 表示通过 `GameUI` 模板创建的具体 UI 实例,用于管理单个 UI 实例的状态和数据绑定。实现了 `IUIInstance` 接口。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ------------ | ------------ | -------------------------------------------------------- | -| `key` | `number` | 只读,实例的唯一标识(用于 Vue 的 `key` 属性) | -| `ui` | `IGameUI` | 只读,关联的 UI 配置实例(即创建该实例的 `GameUI` 模板) | -| `vBind` | `UIProps` | 只读,传递给 UI 组件的响应式 Props 对象 | -| `hidden` | `boolean` | 当前实例是否处于隐藏状态 | -| `alwaysShow` | `boolean` | 是否强制保持显示(不受显示模式影响) | - ---- - -## 构造方法 - -```typescript -function constructor( - ui: IGameUI, - vBind: UIProps, - alwaysShow: boolean = false -): UIInstance; -``` - -- **参数** - - `ui`: 关联的 `GameUI` 配置实例 - - `vBind`: 初始化的组件 Props 对象 - - `alwaysShow`: 是否强制保持显示(默认 `false`) - -**注意事项**:一般不需要手动创建 `UIInstance` 实例,请使用 [`UIController.open`](./UIController.md#open) 打开 UI 并创建实例。 - ---- - -## 方法说明 - -### `setVBind` - -```typescript -function setVBind(data: Partial>, merge?: boolean): void; -``` - -更新组件的响应式 Props。 - -- **参数** - - `data`: 需要更新的数据(部分 Props) - - `merge`: 是否与现有数据合并(默认 `true`),若为 `false` 则完全覆盖 - -**示例** - -```typescript -// 合并更新音量值 -instance.setVBind({ volume: 60 }); - -// 覆盖所有 Props -instance.setVBind({ theme: 'dark' }, false); -``` - ---- - -### `hide` - -```typescript -function hide(): void; -``` - -控制实例的显示状态(直接操作 `hidden` 属性)。 - -**示例** - -```typescript -instance.hide(); // 隐藏 UI -setTimeout(() => instance.show(), 1000); // 1 秒后显示 -``` - ---- - -### `show` - -```typescript -function show(): void; -``` - -控制实例的显示状态(直接操作 `hidden` 属性)。 - -**示例** - -```typescript -instance.show(); // 隐藏 UI -setTimeout(() => instance.show(), 1000); // 1 秒后显示 -``` - ---- - -## 总使用示例 - -```typescript -import { myController, MyUI } from './myUI'; - -const myIns = myController.open(MyUI, { title: '警告' }); - -// 动态更新 props -myIns.setVBind({ title: '错误' }); - -// 设置显示状态 -myIns.show(); -``` diff --git a/docs/api/motajs-system-ui/index.md b/docs/api/motajs-system-ui/index.md deleted file mode 100644 index f84a151..0000000 --- a/docs/api/motajs-system-ui/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/system-ui - -目录: diff --git a/docs/api/motajs-system/index.md b/docs/api/motajs-system/index.md deleted file mode 100644 index 76a8633..0000000 --- a/docs/api/motajs-system/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# @motajs/system - -包含两个模块: - -- [`@motajs/system-action`](../motajs-system-action/index.md) -- [`@motajs/system-ui`](../motajs-system-ui/index.md) - -## 引入示例 - -```ts -import { gameKey, UIController } from '@motajs/system'; - -gameKey.register(...); -const myController = new UIController('my-controller'); -``` - -等效于: - -```ts -import { gameKey } from '@motajs/system-action'; -import { UIController } from '@motajs/system-ui'; - -gameKey.register(...); -const myController = new UIController('my-controller'); -``` diff --git a/docs/api/motajs-types/index.md b/docs/api/motajs-types/index.md deleted file mode 100644 index d3d5030..0000000 --- a/docs/api/motajs-types/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @motajs/types - -目录: diff --git a/docs/api/user-client-modules/AudioDecoder.md b/docs/api/user-client-modules/AudioDecoder.md deleted file mode 100644 index 249c8aa..0000000 --- a/docs/api/user-client-modules/AudioDecoder.md +++ /dev/null @@ -1,142 +0,0 @@ -# AudioDecoder API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -音频解码系统的核心抽象类,为不同音频格式提供统一的解码接口。主要处理浏览器原生不支持音频格式的解码任务(如 iOS 平台的 Ogg 格式)。 - ---- - -## 静态成员说明 - -### `decoderMap` - -```typescript -declare const decoderMap: Map AudioDecoder>; -``` - -解码器注册表,存储格式类型与解码器类的映射关系 - ---- - -## 静态方法说明 - -### `AudioDecoder.registerDecoder` - -```typescript -function registerDecoder( - type: AudioType, - decoder: new () => AudioDecoder -): void; -``` - -注册自定义解码器到全局解码器系统 - -| 参数 | 类型 | 说明 | -| ------- | ------------------------ | -------------- | -| type | `AudioType` | 音频格式类型 | -| decoder | `new () => AudioDecoder` | 解码器构造函数 | - ---- - -### `AudioDecoder.decodeAudioData` - -```typescript -function decodeAudioData( - data: Uint8Array, - player: AudioPlayer -): Promise; -``` - -核心解码入口方法,自动选择最佳解码方案 - -| 参数 | 类型 | 说明 | -| ------ | ------------- | ---------------- | -| data | `Uint8Array` | 原始音频字节数据 | -| player | `AudioPlayer` | 音频播放器实例 | - -**处理流程**: - -1. 通过文件头检测音频类型 -2. 优先使用浏览器原生解码能力 -3. 无原生支持时查找注册的自定义解码器 -4. 返回标准 `AudioBuffer` 格式数据 - ---- - -## 抽象方法说明 - -### `abstract create` - -```typescript -function create(): Promise; -``` - -初始化解码器实例(需分配 WASM 内存等资源) - ---- - -### `abstract destroy` - -```typescript -function destroy(): void; -``` - -销毁解码器实例(需释放资源) - ---- - -### `abstract decode` - -```typescript -function decode(data: Uint8Array): Promise; -``` - -流式解码方法(分块处理) - -| 参数 | 类型 | 说明 | -| ---- | ------------ | ------------ | -| data | `Uint8Array` | 音频数据分块 | - ---- - -### `abstract decodeAll` - -```typescript -function decodeAll(data: Uint8Array): Promise; -``` - -全量解码方法(单次处理完整文件) - ---- - -### `abstract flush` - -```typescript -function flush(): Promise; -``` - -冲刷解码器缓冲区,获取残留数据 - ---- - -## 数据结构 - -### IAudioDecodeData - -```typescript -interface IAudioDecodeData { - channelData: Float32Array[]; // 各声道 PCM 数据 - samplesDecoded: number; // 已解码采样数 - sampleRate: number; // 采样率 (Hz) - errors: IAudioDecodeError[]; // 解码错误集合 -} -``` - -## 内置解码器 - -- `VorbisDecoder`: 解码 ogg vorbis 音频。 -- `OpusDecoder`: 解码 ogg opus 音频。 diff --git a/docs/api/user-client-modules/AudioEffect.md b/docs/api/user-client-modules/AudioEffect.md deleted file mode 100644 index 0853131..0000000 --- a/docs/api/user-client-modules/AudioEffect.md +++ /dev/null @@ -1,207 +0,0 @@ -# AudioEffect API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -音频处理管道的核心抽象类,为构建音频效果链提供基础框架。所有效果器通过输入/输出节点串联,形成可定制的音频处理流水线。 - ---- - -## 核心架构 - -音频播放流程: - -```mermaid -graph LR - Source --> Effect1 - Effect1 --> Effect2[...] - Effect2 --> GainNode - GainNode --> Destination -``` - ---- - -## 抽象成员说明 - -| 成员 | 类型 | 说明 | -| -------- | ----------- | -------------------------- | -| `input` | `AudioNode` | 效果器输入节点(必须实现) | -| `output` | `AudioNode` | 效果器输出节点(必须实现) | - ---- - -## 核心方法说明 - -### `connect` - -```typescript -function connect(target: IAudioInput, output?: number, input?: number): void; -``` - -连接至下游音频节点 - -| 参数 | 类型 | 说明 | -| ------ | ------------- | -------------------------- | -| target | `IAudioInput` | 目标效果器/节点 | -| output | `number` | 当前效果器输出通道(可选) | -| input | `number` | 目标效果器输入通道(可选) | - ---- - -### `disconnect` - -```typescript -function disconnect( - target?: IAudioInput, - output?: number, - input?: number -): void; -``` - -断开与下游节点的连接 - ---- - -### `abstract start` - -```typescript -function start(): void; -``` - -效果器激活时调用(可用于初始化参数) - ---- - -### `abstract end` - -```typescript -function end(): void; -``` - -效果器停用时调用(可用于资源回收) - ---- - -## 自定义效果器示例 - -### 混响效果器实现 - -```typescript -export class ReverbEffect extends AudioEffect { - private convolver: ConvolverNode; - private dryGain: GainNode; - private wetGain: GainNode; - - constructor(ac: AudioContext) { - super(ac); - - // 创建节点网络 - this.dryGain = ac.createGain(); - this.wetGain = ac.createGain(); - this.convolver = ac.createConvolver(); - - // 定义输入输出 - this.input = this.dryGain; - this.output = this.ac.createGain(); - - // 构建处理链 - this.dryGain.connect(this.output); - this.dryGain.connect(this.convolver); - this.convolver.connect(this.wetGain); - this.wetGain.connect(this.output); - } - - /** 设置混响强度 */ - setMix(value: number) { - this.dryGain.gain.value = 1 - value; - this.wetGain.gain.value = value; - } - - /** 加载脉冲响应 */ - async loadImpulse(url: string) { - const response = await fetch(url); - const buffer = await this.ac.decodeAudioData( - await response.arrayBuffer() - ); - this.convolver.buffer = buffer; - } - - start() { - this.output.gain.value = 1; - } - - end() { - this.output.gain.value = 0; - } -} -``` - ---- - -## 内置效果器说明 - -### StereoEffect(立体声控制) - -```mermaid -graph LR - Input --> Panner[PannerNode] - Panner --> Output -``` - -- 控制声相/3D 空间定位 -- 支持设置声音方位和位置 - -### VolumeEffect(音量控制) - -```mermaid -graph LR - Input --> Gain[GainNode] - Gain --> Output -``` - -- 全局音量调节 -- 支持实时音量渐变 - -### ChannelVolumeEffect(多声道控制) - -```mermaid -graph LR - Input --> Splitter[ChannelSplitter] - Splitter --> Gain1 - Splitter --> Gain2 - Gain1 --> Merger - Gain2 --> Merger - Merger --> Output -``` - -- 6 声道独立音量控制 -- 支持环绕声场调节 - -### DelayEffect(延迟效果) - -```mermaid -graph LR - Input --> Delay[DelayNode] - Delay --> Output -``` - -- 基础延迟效果 -- 精确到采样级的延迟控制 - -### EchoEffect(回声效果) - -```mermaid -graph LR - Input --> Gain - Gain --> Delay - Delay --> Gain[反馈循环] - Gain --> Output -``` - -- 带反馈的延迟效果 -- 自动渐弱回声处理 - ---- diff --git a/docs/api/user-client-modules/AudioPlayer.md b/docs/api/user-client-modules/AudioPlayer.md deleted file mode 100644 index 0a5125b..0000000 --- a/docs/api/user-client-modules/AudioPlayer.md +++ /dev/null @@ -1,172 +0,0 @@ -# AudioPlayer API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -音频系统的核心控制器,负责管理音频上下文、路由系统、效果器工厂和全局音频参数。支持多音轨管理和 3D 音频空间化配置。 - -```mermaid -graph LR - AudioPlayer --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 核心架构 - -```mermaid -graph TD - Player[AudioPlayer] --> Sources[音频源工厂] - Player --> Effects[效果器工厂] - Player --> Routes[路由系统] - Player --> Listener[3D 听者配置] -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 说明 | -| ------------- | ------------------------- | ------------------------ | -| `ac` | `AudioContext` | Web Audio API 上下文实例 | -| `audioRoutes` | `Map` | 已注册的音频路由表 | -| `gain` | `GainNode` | 全局音量控制节点 | - ---- - -## 方法说明 - -此处暂时只列出方法的简易说明。方法理解难度不高,如果需要可以自行查看代码以及其相关注释来查看如何使用。 - -### 音频源工厂方法 - -| 方法名 | 返回值 | 说明 | -| ----------------------- | -------------------- | ----------------------------- | -| `createSource(Source)` | `AudioSource` | 创建自定义音频源 | -| `createStreamSource()` | `AudioStreamSource` | 创建流式音频源(直播/长音频) | -| `createElementSource()` | `AudioElementSource` | 基于 HTML5 Audio 元素的音源 | -| `createBufferSource()` | `AudioBufferSource` | 基于 AudioBuffer 的静态音源 | - -### 效果器工厂方法 - -| 方法名 | 返回值 | 说明 | -| ----------------------------- | --------------------- | ---------------------------- | -| `createEffect(Effect)` | `AudioEffect` | 创建自定义效果器 | -| `createVolumeEffect()` | `VolumeEffect` | 全局音量控制器 | -| `createStereoEffect()` | `StereoEffect` | 立体声场控制器 | -| `createChannelVolumeEffect()` | `ChannelVolumeEffect` | 多声道独立音量控制(6 声道) | -| `createDelayEffect()` | `DelayEffect` | 精确延迟效果器 | -| `createEchoEffect()` | `EchoEffect` | 回声效果器(带反馈循环) | - -### 路由管理方法 - -| 方法名 | 参数 | 说明 | -| --------------------- | -------------------- | -------------- | -| `createRoute(source)` | `AudioSource` | 创建新播放路由 | -| `addRoute(id, route)` | `string, AudioRoute` | 注册命名路由 | -| `getRoute(id)` | `string` | 获取已注册路由 | -| `removeRoute(id)` | `string` | 移除指定路由 | - -### 全局控制方法 - -| 方法名 | 参数 | 说明 | -| ------------------------------- | ------------------------ | -------------------- | -| `setVolume(volume)` | `number` (0.0-1.0) | 设置全局音量 | -| `getVolume()` | - | 获取当前全局音量 | -| `setListenerPosition(x,y,z)` | `number, number, number` | 设置听者 3D 空间坐标 | -| `setListenerOrientation(x,y,z)` | `number, number, number` | 设置听者朝向 | -| `setListenerUp(x,y,z)` | `number, number, number` | 设置听者头顶朝向 | - ---- - -## 使用示例 - -### 基础音乐播放 - -```typescript -import { audioPlayer } from '@user/client-modules'; - -// 创建音频源(以音频缓冲为例) -const bgmSource = audioPlayer.createBufferSource(); - -// 创建播放路由 -const bgmRoute = audioPlayer.createRoute(bgmSource); - -// 添加效果链 -bgmRoute.addEffect([ - audioPlayer.createStereoEffect(), - audioPlayer.createVolumeEffect() -]); - -// 播放控制 -audioPlayer.play('bgm'); -audioPlayer.pause('bgm'); -``` - -### 3D 环境音效 - -```typescript -import { audioPlayer } from '@user/client-modules'; - -// 配置3D听者 -audioPlayer.setListenerPosition(0, 0, 0); // 听者在原点 -audioPlayer.setListenerOrientation(0, 0, -1); // 面朝屏幕内 - -// 创建环境音源 -const ambientSource = audioPlayer.createBufferSource(); -await ambientSource.setBuffer(/* 这里填写音频缓冲 */); - -// 配置3D音效路由 -const ambientRoute = audioPlayer.createRoute(ambientSource); -const stereo = audioPlayer.createStereoEffect(); -stereo.setPosition(5, 2, -3); // 音源位于右前方高处 -ambientRoute.addEffect(stereo); - -// 循环播放 -ambientRoute.setLoop(true); -audioPlayer.addRoute('ambient', ambientRoute); -audioPlayer.play('ambient'); -``` - ---- - -## 生命周期管理 - -```mermaid -sequenceDiagram - participant User - participant AudioPlayer - participant AudioContext - - User->>AudioPlayer: new AudioPlayer() - AudioPlayer->>AudioContext: 创建音频上下文 - User->>AudioPlayer: createRoute() - AudioPlayer->>AudioRoute: 实例化路由 - User->>AudioPlayer: play() - AudioPlayer->>AudioContext: 启动音频时钟 - loop 播放周期 - AudioPlayer->>AudioRoute: 更新状态 - end - User->>AudioPlayer: stop() - AudioPlayer->>AudioRoute: 释放资源 - AudioPlayer->>AudioContext: 关闭上下文 -``` - ---- - -## 注意事项 - -1. **空间音频配置** - 3D 效果需统一坐标系: - - ```txt - (0,0,0) 屏幕中心 - X+ → 右 - Y+ ↑ 上 - Z+ ⊙ 朝向用户 - ``` diff --git a/docs/api/user-client-modules/AudioRoute.md b/docs/api/user-client-modules/AudioRoute.md deleted file mode 100644 index 681f00f..0000000 --- a/docs/api/user-client-modules/AudioRoute.md +++ /dev/null @@ -1,221 +0,0 @@ -# AudioRoute 音频播放路由 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -音频播放控制的核心类,负责管理音频源与效果器的连接关系,协调播放状态转换,并处理音频管线生命周期。 - -```mermaid -graph LR - AudioRoute --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 说明 | -| ------------- | ------------------ | ------------------------------------------------ | -| `output` | `AudioNode` | 最终输出节点(继承自 IAudioOutput) | -| `effectRoute` | `AudioEffect[]` | 效果器链数组(按顺序存储已连接的效果器实例) | -| `endTime` | `number` | 淡出过渡时长(单位:秒),默认 0 | -| `status` | `AudioStatus` | 当前播放状态(见下方枚举定义) | -| `duration` | `number` (getter) | 音频总时长(单位:秒) | -| `currentTime` | `number` (get/set) | 当前播放进度(单位:秒),设置时会触发 seek 操作 | - ---- - -### AudioStatus 枚举 - -```typescript -enum AudioStatus { - Playing, // 正在播放 - Pausing, // 淡出暂停过程中 - Paused, // 已暂停 - Stoping, // 淡出停止过程中 - Stoped // 已停止 -} -``` - ---- - -## 方法说明 - -### `setEndTime` - -```typescript -function setEndTime(time: number): void; -``` - -设置淡出过渡时长 - -| 参数 | 类型 | 说明 | -| ---- | -------- | ------------------------ | -| time | `number` | 淡出动画时长(单位:秒) | - ---- - -### `onStart` - -```typescript -function onStart(fn?: (route: AudioRoute) => void): void; -``` - -注册播放开始钩子函数 - -| 参数 | 类型 | 说明 | -| ---- | ----------------- | ------------------------ | -| `fn` | `(route) => void` | 播放开始时触发的回调函数 | - ---- - -### `onEnd` - -```typescript -function onEnd(fn?: (time: number, route: AudioRoute) => void): void; -``` - -注册播放结束钩子函数 - -| 参数 | 类型 | 说明 | -| ---- | --------------------------- | --------------------------------------------- | -| `fn` | `(duration, route) => void` | 淡出阶段开始时触发的回调,duration 为淡出时长 | - ---- - -### `play` - -```typescript -function play(when?: number = 0): Promise; -``` - -启动/恢复音频播放 - -| 参数 | 类型 | 说明 | -| ------ | -------- | -------------------------------------- | -| `when` | `number` | 基于 AudioContext 时间的启动时刻(秒) | - ---- - -### `pause` - -```typescript -function pause(): Promise; -``` - -触发暂停流程(执行淡出过渡) - ---- - -### `resume` - -```typescript -function resume(): void; -``` - -从暂停状态恢复播放(执行淡入过渡) - ---- - -### `stop` - -```typescript -function stop(): Promise; -``` - -完全停止播放并释放资源 - ---- - -### `addEffect` - -```typescript -function addEffect(effect: AudioEffect | AudioEffect[], index?: number): void; -``` - -添加效果器到处理链 - -| 参数 | 类型 | 说明 | -| -------- | ------------------ | ------------------------------ | -| `effect` | `AudioEffect`/数组 | 要添加的效果器实例 | -| `index` | `number` (可选) | 插入位置,负数表示从末尾倒计数 | - ---- - -### `removeEffect` - -```typescript -function removeEffect(effect: AudioEffect): void; -``` - -从处理链移除效果器 - -| 参数 | 类型 | 说明 | -| -------- | ------------- | ------------------ | -| `effect` | `AudioEffect` | 要移除的效果器实例 | - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| -------------- | ---- | ------------------ | -| `updateEffect` | - | 效果器链发生变更时 | -| `play` | - | 开始/恢复播放时 | -| `stop` | - | 完全停止播放后 | -| `pause` | - | 进入暂停状态后 | -| `resume` | - | 从暂停状态恢复时 | - ---- - -## 总使用示例 - -```typescript -import { audioPlayer } from '@user/client-modules'; - -// 创建音频播放器和路由 -const source = audioPlayer.createBufferSource(); -const route = audioPlayer.createRoute(audioSource); - -// 配置效果链 -const stereo = audioPlayer.createStereoEffect(); -const echo = audioPlayer.createEchoEffect(); -const volume = audioPlayer.createVolumeEffect(); - -route.addEffect([stereo, echo], 0); // 插入到链首 -route.addEffect(volume); // 音量控制放到链尾 - -// 播放暂停 -await route.play(); -await route.pause(); -route.resume(); // 继续操作不是异步,不需要 await -await route.stop(); -``` - ---- - -## 处理流程示意图 - -```mermaid -sequenceDiagram - participant User - participant AudioRoute - participant Effects - - User->>AudioRoute: play() - AudioRoute->>Effects: 启动所有效果器 - Effects-->>AudioRoute: 准备完成 - AudioRoute->>Source: 开始播放 - loop 播放中 - AudioRoute->>Effects: 实时音频处理 - end - User->>AudioRoute: pause() - AudioRoute->>Effects: 启动淡出过渡 - Effects-->>AudioRoute: 过渡完成 - AudioRoute->>Source: 暂停播放 -``` diff --git a/docs/api/user-client-modules/AudioSource.md b/docs/api/user-client-modules/AudioSource.md deleted file mode 100644 index e3c0e20..0000000 --- a/docs/api/user-client-modules/AudioSource.md +++ /dev/null @@ -1,184 +0,0 @@ -# AudioSource API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -音频系统的源头抽象类,定义了音频播放的核心控制接口。支持多种音频源类型,包括流媒体、HTML 音频元素和静态音频缓冲。 - -```mermaid -graph LR - AudioPlayer --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 抽象成员说明 - -| 成员 | 类型 | 说明 | -| ------------- | ----------- | ------------------------ | -| `output` | `AudioNode` | 音频输出节点(必须实现) | -| `duration` | `number` | 音频总时长(秒) | -| `currentTime` | `number` | 当前播放时间(秒) | -| `playing` | `boolean` | 播放状态标识 | - ---- - -## 核心方法说明 - -### `abstract play` - -```typescript -function play(when?: number): void; -``` - -启动音频播放时序 - -| 参数 | 类型 | 说明 | -| ---- | -------- | ----------------------------------------------- | -| when | `number` | 预定播放时间(基于 `AudioContext.currentTime`) | - ---- - -### `abstract stop` - -```typescript -function stop(): number; -``` - -停止播放并返回停止时刻 - ---- - -### `abstract connect` - -```typescript -function connect(target: IAudioInput): void; -``` - -连接至音频处理管线 - -| 参数 | 类型 | 说明 | -| ------ | ------------- | ------------------- | -| target | `IAudioInput` | 下游处理节点/效果器 | - ---- - -### `abstract setLoop` - -```typescript -function setLoop(loop: boolean): void; -``` - -设置循环播放模式 - ---- - -## 事件系统 - -| 事件名 | 参数 | 触发时机 | -| ------ | ---- | -------------- | -| `play` | - | 开始播放时 | -| `end` | - | 自然播放结束时 | - ---- - -## 自定义音频源示例 - -### 网络实时通话源 - -```typescript -class WebRTCAudioSource extends AudioSource { - private mediaStream: MediaStreamAudioSourceNode; - output: AudioNode; - - constructor(ac: AudioContext, stream: MediaStream) { - super(ac); - this.mediaStream = ac.createMediaStreamSource(stream); - this.output = this.mediaStream; - } - - get duration() { - return Infinity; - } // 实时流无固定时长 - get currentTime() { - return this.ac.currentTime; - } - - play() { - this.mediaStream.connect(this.output); - this.playing = true; - this.emit('play'); - } - - stop() { - this.mediaStream.disconnect(); - this.playing = false; - return this.ac.currentTime; - } - - connect(target: IAudioInput) { - this.output.connect(target.input); - } - - setLoop() {} // 实时流不支持循环 -} - -// 使用示例 -navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { - const source = new WebRTCAudioSource(audioContext, stream); - source.connect(effectsChain); - source.play(); -}); -``` - ---- - -## 内置实现说明 - -### AudioStreamSource(流媒体源) - -```mermaid -graph LR - Network[网络数据流] --> Buffer[缓冲区] - Buffer --> Decoder[音频解码器] - Decoder --> Output[实时音频节点] -``` - -- 支持渐进式加载 -- 动态缓冲管理 -- 适用于浏览器自身不支持的音频类型 - -### AudioElementSource(HTML 音频元素源) - -```mermaid -graph LR - AudioTag[audio 元素] -->|音频流| Output[媒体元素源节点] -``` - -- 基于 HTML5 Audio 元素 -- 支持跨域资源 -- 自动处理音频格式兼容 - -### AudioBufferSource(静态音频缓冲源) - -```mermaid -graph LR - File[音频文件] --> Decode[解码为 AudioBuffer] - Decode --> Output[缓冲源节点] -``` - -- 完整音频数据预加载 -- 精确播放控制 -- 支持内存音频播放 - ---- - -## 注意事项 - -1. **时间精度** - 所有时间参数均以 `AudioContext.currentTime` 为基准,精度可达 0.01 秒 diff --git a/docs/api/user-client-modules/BgmController.md b/docs/api/user-client-modules/BgmController.md deleted file mode 100644 index 0930c54..0000000 --- a/docs/api/user-client-modules/BgmController.md +++ /dev/null @@ -1,158 +0,0 @@ -# BgmController API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - BgmController --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -## 类描述 - -`BgmController` 是背景音乐系统的核心控制器,支持多 BGM 的加载、音量控制、渐变切换和播放状态管理。继承自 `EventEmitter`,提供完整的音频事件监听机制。 - ---- - -## 泛型说明 - -- `T extends string`: BGM 的唯一标识符类型(默认为项目预定义的 `BgmIds`) - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ---------------- | --------- | ----------------------------------------- | -| `prefix` | `string` | BGM 资源路径前缀(默认 `bgms.`) | -| `playingBgm` | `T` | 当前正在播放的 BGM ID | -| `enabled` | `boolean` | 是否启用音频控制(默认 true) | -| `transitionTime` | `number` | 音频切换渐变时长(单位:毫秒,默认 2000) | - ---- - -## 核心方法说明 - -### `setTransitionTime` - -```typescript -function setTransitionTime(time: number): void; -``` - -设置所有 BGM 的渐变切换时长。 - -- **参数** - - `time`: 渐变时长(毫秒) - ---- - -### `blockChange` - -```typescript -function blockChange(): void; -``` - -### `unblockChange` - -```typescript -function unblockChange(): void; -``` - -屏蔽/解除屏蔽 BGM 切换(用于特殊场景)。 - ---- - -### `setVolume` - -```typescript -function setVolume(volume: number): void; -``` - -### `getVolume` - -```typescript -function getVolume(): number; -``` - -控制全局音量(范围 0-1)。 - ---- - -### `setEnabled` - -```typescript -function setEnabled(enabled: boolean): void; -``` - -启用/禁用整个 BGM 系统(禁用时停止所有播放)。 - ---- - -### `addBgm` - -```typescript -function addBgm(id: T, url?: string): void; -``` - -加载并注册 BGM 资源。 - -- **参数** - - `id`: BGM 唯一标识 - - `url`: 自定义资源路径(默认 `project/bgms/${id}`) - ---- - -### `removeBgm` - -```typescript -function removeBgm(id: T): void; -``` - -移除已注册的 BGM 资源。 - ---- - -### 播放控制方法 - -```typescript -function play(id: T, when?: number): void; // 播放指定 BGM(带渐变) -function pause(): void; // 暂停当前 BGM(保留进度) -function resume(): void; // 继续播放当前 BGM -function stop(): void; // 停止当前 BGM(重置进度) -``` - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| -------- | ---- | ----------------- | -| `play` | `[]` | 开始播放新 BGM 时 | -| `pause` | `[]` | 暂停播放时 | -| `resume` | `[]` | 继续播放时 | -| `stop` | `[]` | 完全停止播放时 | - ---- - -## 总使用示例 - -```typescript -import { bgmController } from '@user/client-modules'; - -// 设置全局参数 -bgmCtrl.setTransitionTime(1500); -bgmCtrl.setVolume(0.8); - -// 播放控制 -bgmCtrl.play('battle.mp3'); // 播放战斗BGM -bgmCtrl.pause(); // 暂停(如打开菜单) -bgmCtrl.resume(); // 继续播放 -bgmCtrl.play('boss_battle.mp3'); // 切换至BOSS战BGM -bgmCtrl.stop(); // 完全停止(如战斗结束) - -// 事件监听 -bgmCtrl.on('play', () => { - console.log('BGM 开始播放:', bgmCtrl.playingBgm); -}); -``` diff --git a/docs/api/user-client-modules/HeroKeyMover.md b/docs/api/user-client-modules/HeroKeyMover.md deleted file mode 100644 index a5e5350..0000000 --- a/docs/api/user-client-modules/HeroKeyMover.md +++ /dev/null @@ -1,147 +0,0 @@ -# HeroKeyMover API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 类描述 - -`HeroKeyMover` 是勇士按键移动的核心控制器,负责将热键系统与勇士移动逻辑结合,实现基于键盘输入的连续移动控制。支持多方向优先级处理和移动中断机制。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| ------------ | ----------- | ---------------------------------------- | -| `hotkey` | `Hotkey` | 关联的热键控制器实例 | -| `mover` | `HeroMover` | 勇士移动逻辑执行器 | -| `scope` | `symbol` | 当前移动触发的作用域(默认使用主作用域) | -| `hotkeyData` | `MoveKey` | 移动方向与热键的映射配置 | - ---- - -## 构造方法 - -```typescript -function constructor( - hotkey: Hotkey, - mover: HeroMover, - config?: MoveKeyConfig -): HeroKeyMover; -``` - -- **参数** - - `hotkey`: 已配置的热键控制器实例 - - `mover`: 勇士移动逻辑实例 - - `config`: 自定义方向键映射配置(可选) - -**默认按键映射**: - -```typescript -const map = { - left: 'moveLeft', - right: 'moveRight', - up: 'moveUp', - down: 'moveDown' -}; -``` - ---- - -## 方法说明 - -### `setScope` - -```typescript -function setScope(scope: symbol): void; -``` - -设置当前移动控制的作用域(用于多场景隔离)。 - -- **参数** - - `scope`: 唯一作用域标识符 - ---- - -### `press` - -```typescript -function press(dir: Dir): void; -``` - -触发指定方向的移动按键按下状态。 - -- **参数** - - `dir`: 移动方向(`'left' | 'right' | 'up' | 'down'`) - ---- - -### `release` - -```typescript -function release(dir: Dir): void; -``` - -解除指定方向的移动按键按下状态。 - -- **参数** - - `dir`: 要释放的移动方向 - ---- - -### `tryStartMove` - -```typescript -function tryStartMove(): boolean; -``` - -尝试启动移动逻辑(自动根据当前方向键状态判断)。 - -- **返回值** - `true` 表示移动成功启动,`false` 表示条件不满足 - ---- - -### `endMove` - -```typescript -function endMove(): void; -``` - -立即终止当前移动过程。 - ---- - -### `destroy` - -```typescript -function destroy(): void; -``` - -销毁控制器实例(自动解除所有事件监听)。 - ---- - -## 总使用示例 - -```typescript -import { gameKey, mainScope } from '@motajs/system-action'; - -// 初始化移动控制器 -const keyMover = new HeroKeyMover( - gameKey, - heroMover, - { left: 'moveLeft', right: 'moveRight' } // 自定义部分按键映射 -); - -// 设置允许触发的作用域 -keyMover.setScope(mainScope); - -// 销毁控制器 -keyMover.destroy(); -``` - -## 移动优先级机制 - -1. **最后按下优先**:当同时按下多个方向键时,以后按下的方向为准 -2. **队列延续**:在移动过程中持续检测按键状态,自动延续移动队列 -3. **作用域隔离**:只有当前作用域匹配时才会响应按键事件 diff --git a/docs/api/user-client-modules/SoundPlayer.md b/docs/api/user-client-modules/SoundPlayer.md deleted file mode 100644 index 28f36da..0000000 --- a/docs/api/user-client-modules/SoundPlayer.md +++ /dev/null @@ -1,197 +0,0 @@ -# SoundPlayer API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -音效管理核心类,提供短音频的加载、播放和空间化控制功能。推荐通过全局单例 `soundPlayer` 使用。 - -```mermaid -graph LR - AudioPlayer --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 属性说明 - -| 属性名 | 类型 | 说明 | -| --------- | --------------------- | ---------------------- | -| `enabled` | `boolean` | 总开关状态(默认启用) | -| `buffer` | `Map` | 已加载音效缓冲存储池 | -| `playing` | `Set` | 当前活跃音效 ID 集合 | -| `gain` | `VolumeEffect` | 全局音量控制器 | - ---- - -## 方法说明 - -### 基础控制 - -#### setEnabled - -```typescript -function setEnabled(enabled: boolean): void; -``` - -启用/禁用音效系统(禁用时立即停止所有音效) - -| 参数 | 类型 | 说明 | -| ------- | --------- | ------------ | -| enabled | `boolean` | 是否启用音效 | - ---- - -#### setVolume / getVolume - -```typescript -function setVolume(volume: number): void; -function getVolume(): number; -``` - -全局音量控制(范围 0.0~1.0) - ---- - -### 资源管理 - -#### add - -```typescript -async function add(id: T, data: Uint8Array): Promise; -``` - -加载并缓存音效资源 - -| 参数 | 类型 | 说明 | -| ---- | ------------ | ---------------- | -| id | `T` | 音效唯一标识符 | -| data | `Uint8Array` | 原始音频字节数据 | - ---- - -### 播放控制 - -#### play - -```typescript -function play( - id: T, - position?: [number, number, number], - orientation?: [number, number, number] -): number; -``` - -播放指定音效(返回音效实例 ID) - -| 参数 | 类型 | 默认值 | 说明 | -| ----------- | ----------- | --------- | ------------------------- | -| id | `T` | - | 音效标识符 | -| position | `[x, y, z]` | `[0,0,0]` | 3D 空间坐标(右手坐标系) | -| orientation | `[x, y, z]` | `[1,0,0]` | 声音传播方向向量 | - -**坐标系说明**: - -```txt -(0,0,0) 听者位置 -X+ → 右 -Y+ ↑ 上 -Z+ ⊙ 朝向听者正前方 -``` - ---- - -#### stop - -```typescript -function stop(num: number): void; -``` - -停止指定音效实例 - -| 参数 | 类型 | 说明 | -| ---- | -------- | -------------------- | -| num | `number` | play() 返回的实例 ID | - ---- - -#### stopAllSounds - -```typescript -function stopAllSounds(): void; -``` - -立即停止所有正在播放的音效 - ---- - -## 使用示例 - -### 基础音效系统 - -```typescript -import { soundPlayer } from '@user/client-modules'; - -// 播放射击音效(右侧声场) -const shotId = soundPlayer.play('shoot', [2, 0, 0]); - -// 播放爆炸音效(左后方) -soundPlayer.play('explosion', [-3, 0, -2], [-1, 0, -1]); - -// 停止特定音效 -soundPlayer.stop(shotId); - -// 全局音量控制 -soundPlayer.setVolume(0.7); -``` - -### 3D 环境音效 - -```typescript -// 汽车引擎循环音效 -let engineSoundId = -1; - -function startEngine() { - engineSoundId = soundPlayer.play('engine', [0, 0, -5]); -} - -function updateCarPosition(x: number, z: number) { - const route = audioPlayer.getRoute(`sounds.${engineSoundId}`); - const stereo = route?.effectRoute[0] as StereoEffect; - stereo?.setPosition(x, 0, z); -} -``` - ---- - -## 生命周期管理 - -```mermaid -sequenceDiagram - participant User - participant SoundPlayer - participant AudioPlayer - - User->>SoundPlayer: add('explosion', data) - SoundPlayer->>AudioPlayer: decodeAudioData() - AudioPlayer-->>SoundPlayer: AudioBuffer - User->>SoundPlayer: play('explosion') - SoundPlayer->>AudioPlayer: 创建路由/效果器 - AudioPlayer-->>SoundPlayer: 音效ID - loop 播放周期 - SoundPlayer->>AudioPlayer: 更新空间参数 - end - User->>SoundPlayer: stop(id) - SoundPlayer->>AudioPlayer: 释放路由资源 -``` - ---- - -## 注意事项 - -1. **实例数量限制** - 同时播放音效建议不超过 32 个,可通过优先级系统管理 diff --git a/docs/api/user-client-modules/StreamLoader.md b/docs/api/user-client-modules/StreamLoader.md deleted file mode 100644 index f07ea57..0000000 --- a/docs/api/user-client-modules/StreamLoader.md +++ /dev/null @@ -1,208 +0,0 @@ -# StreamLoader API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - StreamLoader --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -## 类描述 - -`StreamLoader` 是流式加载大文件的核心类,支持分块读取网络资源并通过事件机制传递数据。继承自 `EventEmitter`,实现 `IStreamController` 接口,提供流传输控制能力。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| --------- | --------- | ---------------------- | -| `url` | `string` | 只读,要加载的资源 URL | -| `loading` | `boolean` | 当前是否处于加载状态 | - ---- - -## 构造方法 - -```typescript -function constructor(url: string): StreamLoader; -``` - -- **参数** - - `url`: 要加载的资源地址 - -**示例** - -```typescript -const loader = new StreamLoader('/api/large-file'); -``` - ---- - -## 方法说明 - -### `pipe` - -```typescript -function pipe(reader: IStreamReader): this; -``` - -将流数据管道传递给读取器对象。 - -- **参数** - - `reader`: 实现 `IStreamReader` 接口的对象 - -**示例** - -```typescript -class MyReader implements IStreamReader { - async pump(data, done) { - console.log('收到数据块:', data); - } - // ... 还有一些其他需要实现的方法,参考总是用示例 -} -loader.pipe(new MyReader()); -``` - ---- - -### `start` - -```typescript -function start(): Promise; -``` - -启动流传输流程(自动处理分块读取与分发)。 - ---- - -### `cancel` - -```typescript -function cancel(reason?: string): void; -``` - -终止当前流传输。 - -- **参数** - - `reason`: 终止原因描述(可选) - -**示例** - -```typescript -// 用户取消加载 -loader.cancel('用户手动取消'); -``` - ---- - -## 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| ------ | --------------------------------- | ------------------------ | -| `data` | `data: Uint8Array, done: boolean` | 每接收到一个数据块时触发 | - -**事件监听示例** - -```typescript -loader.on('data', (data, done) => { - if (done) console.log('传输完成'); -}); -``` - ---- - -## 相关接口说明 - -### IStreamReader - -```typescript -export interface IStreamReader { - /** - * 接受字节流流传输的数据 - * @param data 传入的字节流数据,只包含本分块的内容 - * @param done 是否传输完成 - */ - pump( - data: Uint8Array | undefined, - done: boolean, - response: Response - ): Promise; - - /** - * 当前对象被传递给加载流时执行的函数 - * @param controller 传输流控制对象 - */ - piped(controller: IStreamController): void; - - /** - * 开始流传输 - * @param stream 传输流对象 - * @param controller 传输流控制对象 - */ - start( - stream: ReadableStream, - controller: IStreamController, - response: Response - ): Promise; - - /** - * 结束流传输 - * @param done 是否传输完成,如果为 false 的话,说明可能是由于出现错误导致的终止 - * @param reason 如果没有传输完成,那么表示失败的原因 - */ - end(done: boolean, reason?: string): void; -} -``` - -- `pump`: 处理每个数据块 -- `piped`: 当读取器被绑定到流时调用 -- `start`: 流传输开始时调用 -- `end`: 流传输结束时调用 - ---- - -## 总使用示例 - -```typescript -// 创建流加载器 -const loader = new StreamLoader('/api/video-stream'); - -const videoElement = document.createElement('video'); - -// 实现自定义读取器 -class VideoStreamReader implements IStreamReader { - async pump(data, done) { - if (data) videoElement.appendBuffer(data); - if (done) videoElement.play(); - } - - piped(controller) { - console.log('流传输管道连接成功'); - } - - start() { - console.log('开始流式加载'); - } - - end() { - console.log('流式加载结束'); - } -} - -const reader = new VideoStreamReader(); - -// 绑定读取器并启动 -loader.pipe(reader); -loader.start(); - -// 监听进度 -loader.on('data', (_, done) => { - if (!done) updateProgressBar(); -}); - -// 错误处理 -videoElement.onerror = () => loader.cancel('视频解码错误'); -``` diff --git a/docs/api/user-client-modules/TextContentParser.md b/docs/api/user-client-modules/TextContentParser.md deleted file mode 100644 index b01cbae..0000000 --- a/docs/api/user-client-modules/TextContentParser.md +++ /dev/null @@ -1,157 +0,0 @@ -# TextContentParser API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 类描述 - -`TextContentParser` 是文字解析核心工具,用于处理文本排版、转义字符解析及动态样式管理。支持自动分词换行、图标嵌入和样式栈控制。 - ---- - -## 方法说明 - -### `parse` - -```typescript -function parse(text: string, width: number): ITextContentRenderObject; -``` - -解析文本并生成渲染数据对象: - -```typescript -interface ITextContentRenderObject { - lineHeights: number[]; // 每行高度 - lineWidths: number[]; // 每行宽度 - data: ITextContentRenderable[]; // 渲染元素集合 -} -``` - ---- - -## 转义字符语法说明 - -### 1. 颜色控制 `\r[color]` - -- **语法**:`\r[颜色值]` -- **栈模式**:支持嵌套,用`\r`恢复上一级颜色 -- **颜色格式**:支持 CSS 颜色字符串 - -```typescript -// 示例:红→黄→红→默认 -'\\r[red]危险!\\r[yellow]警告\\r恢复红色\\r默认'; -``` - -### 2. 字号控制 `\c[size]` - -- **语法**:`\c[字号(px)]` -- **栈模式**:用`\c`恢复上一级字号 - -```typescript -// 示例:24px→32px→24px -'普通\\c[24]标题\\c[32]超大标题\\c恢复'; -``` - -### 3. 字体家族 `\g[family]` - -- **语法**:`\g[字体名称]` -- **栈模式**:用`\g`恢复上一级字体 - -```typescript -'默认\\g[黑体]中文黑体\\g恢复默认'; -``` - -### 4. 粗体切换 `\d` - -- **语法**:`\d`(开关模式) - -```typescript -'正常\\d粗体\\d正常'; -``` - -### 5. 斜体切换 `\e` - -- **语法**:`\e`(开关模式) - -```typescript -'正常\\e斜体\\e正常'; -``` - -### 6. 等待间隔 `\z[wait]` - -- **语法**:`\z[等待字符数]` -- **计算规则**:`间隔时间 = 字符数 * 当前interval配置` - -```typescript -'开始对话\\z[10](暂停500ms)继续'; -``` - -### 7. 图标嵌入 `\i[icon]` - -- **语法**:`\i[图标ID]` -- **图标规范**:需预加载到资源管理器 - -```typescript -'攻击\\i[sword]造成伤害'; -``` - -### 8. 表达式 `${}` - -- **语法**:与模板字符串语法一致,不过是在渲染的时候实时计算,而非字符串声明时计算 - -```typescript -'${core.status.hero.atk * 10}'; // 显示勇士攻击乘 10 -'${core.status.hero.atk > 100 ? "高攻击" : "低攻击"}'; // 条件表达式 -'${(() => { if (a > 10) return 100; else return 10; })()}'; // 嵌入函数 -``` - ---- - -## 综合使用示例 - -### 战斗伤害提示 - -```typescript -const text = - '\\r[#ff0000]\\c[24]\\d敌人\\i[monster]对你造成\\c[32]\\r[yellow]500\\c\\r伤害!\\z[5]\\d\\e(按空格跳过)'; - -const result = parser.parse(text, 400); - -/* 解析结果: -[ - { type: 'text', color: '#ff0000', size:24, bold:true, text:'敌人' }, - { type: 'icon', id:'monster' }, - { type: 'text', color:'#ff0000', size:24, text:'对你造成' }, - { type: 'text', color:'yellow', size:32, text:'500' }, - { type: 'text', color:'#ff0000', size:24, text:'伤害!' }, - { type: 'wait', duration:250 }, // 假设 interval=50 - { type: 'text', bold:false, italic:true, text:'(按空格跳过)' } -] -*/ -``` - -### 多语言混合排版 - -```typescript -const multiLangText = - '\\g[Times New Roman]Hello\\g[宋体]你好\\i[globe]\\z[3]\\g切换为\\r[blue]Français'; - -// 效果:英文→中文+地球图标→等待→蓝色法文 -``` - ---- - -## 注意事项 - -1. **转义字符格式** - - - 必须使用 **双反斜杠**(`\\`)表示转义 - - 错误示例:`\r[red]`(单反斜杠 `\r` 可能会被识别为换行) - - 正确示例:`\\r[red]` - -2. **栈操作规则** - - ```typescript - // 颜色栈示例 - '默认\\r[red]红\\r[blue]蓝\\r恢复红\\r恢复默认'; - // 等效于:push(默认)→push(红)→push(蓝)→pop→pop - ``` diff --git a/docs/api/user-client-modules/TextContentTyper.md b/docs/api/user-client-modules/TextContentTyper.md deleted file mode 100644 index 9f76150..0000000 --- a/docs/api/user-client-modules/TextContentTyper.md +++ /dev/null @@ -1,245 +0,0 @@ -# TextContentTyper API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - TextContentTyper --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -## 类描述 - -`TextContentTyper` 是文字逐字输出(打字机效果)的核心控制器,继承自 `EventEmitter`。用于管理文字排版、渲染时序及样式配置,支持动态修改文本内容和样式。 - ---- - -## 核心属性说明 - -| 属性名 | 类型 | 描述 | -| -------- | ----------------------- | ---------------------------------------------- | -| `config` | `Required` | 当前文字渲染配置(包含字体、行高、对齐等参数) | -| `parser` | `TextContentParser` | 文字解析器实例(负责分词、分行等底层处理) | - ---- - -## 核心方法说明 - -### `constructor` - -```typescript -function constructor(config: Partial): TextContentTyper; -``` - -初始化打字机实例,接受文字配置参数: - -```typescript -interface ITextContentConfig { - /** 字体 */ - font: Font; - /** 是否持续上一次的文本,开启后,如果修改后的文本以修改前的文本为开头,那么会继续播放而不会从头播放(暂未实现,后续更新) */ - keepLast: boolean; - /** 打字机时间间隔,即两个字出现之间相隔多长时间 */ - interval: number; - /** 行高 */ - lineHeight: number; - /** 分词规则 */ - wordBreak: WordBreak; - /** 文字对齐方式 */ - textAlign: TextAlign; - /** 行首忽略字符,即不会出现在行首的字符 */ - ignoreLineStart: Iterable; - /** 行尾忽略字符,即不会出现在行尾的字符 */ - ignoreLineEnd: Iterable; - /** 会被分词规则识别的分词字符 */ - breakChars: Iterable; - /** 填充样式 */ - fillStyle: CanvasStyle; - /** 描边样式 */ - strokeStyle: CanvasStyle; - /** 线宽 */ - strokeWidth: number; - /** 文字宽度,到达这么宽之后换行 */ - width: number; -} -``` - ---- - -### `setConfig` - -```typescript -function setConfig(config: Partial): void; -``` - -动态更新配置参数(支持部分更新) - ---- - -### `setText` - -```typescript -function setText(text: string): void; -``` - -设置要显示的文本内容(自动重置播放进度) - ---- - -### `type` - -```typescript -function type(): void; -``` - -启动逐字显示效果 - ---- - -### `typeAll` - -```typescript -function typeAll(): void; -``` - -立即完整显示所有文字 - ---- - -### `setRender` - -```typescript -function setRender(render: TyperFunction): void; -``` - -设置自定义渲染逻辑: - -```typescript -type TyperFunction = ( - data: TyperRenderable[], // 待渲染元素 - typing: boolean // 是否正在播放中 -) => void; -``` - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| ----------- | ---- | ------------------ | -| `typeStart` | `[]` | 开始逐字显示时 | -| `typeEnd` | `[]` | 全部文字显示完成时 | - ---- - -## 使用示例 - -### 基础用法 - 对话框文字 - -```typescript -// 初始化配置 -const typer = new TextContentTyper({ - font: new Font('黑体', 18), - interval: 50, - lineHeight: 1.2, - width: 400 -}); - -// 设置文本内容 -typer.setText(`「这是逐字显示的文字效果... - 第二行会自动换行」`); - -// 注册渲染逻辑 -typer.setRender((elements, isTyping) => { - elements.forEach(element => { - if (element.type === TextContentType.Text) { - drawText(element.x, element.y, element.text); - } - }); -}); - -// 开始播放 -typer.type(); -``` - -### 动态样式修改 - -```typescript -// 修改为红色斜体 -typer.setConfig({ - font: new Font('楷体', 20), - fillStyle: '#ff0000' -}); - -// 修改播放速度 -typer.setConfig({ interval: 30 }); -``` - ---- - -## 底层数据结构 - -### 渲染元素类型 - -```typescript -type TyperRenderable = - | TyperTextRenderable // 文本元素 - | TyperIconRenderable // 图标元素 - | TyperWaitRenderable; // 等待间隔 -``` - -::: code-group - -```ts [TyperTextRenderable] -interface TyperTextRenderable { - type: TextContentType.Text; - x: number; - y: number; - text: string; - font: string; - fillStyle: CanvasStyle; - strokeStyle: CanvasStyle; - /** 文字画到哪个索引 */ - pointer: number; - /** 这段文字的总高度 */ - height: number; -} -``` - -```ts [TyperIconRenderable] -interface TyperIconRenderable { - type: TextContentType.Icon; - x: number; - y: number; - width: number; - height: number; - renderable: RenderableData | AutotileRenderable; -} -``` - -```ts [TyperWaitRenderable] -interface TyperWaitRenderable { - type: TextContentType.Wait; - wait: number; - waited: number; -} -``` - -::: - ---- - -## 注意事项 - -1. **性能优化** - 当处理长文本(>1000 字)时,建议预调用 `parser.parse()` 进行分页 - -2. **坐标系统** - 所有坐标基于初始化时设置的 `width` 参数进行相对计算 - -3. **动态修改限制** - 在播放过程中修改配置可能导致渲染异常,建议在 `typeEnd` 事件后操作 - -4. **使用场景** - 本接口的使用场景并不多,建议使用 `TextContent` 组件。如果必须使用的话,可以直接阅读源码来看一些实现细节。 diff --git a/docs/api/user-client-modules/TextboxStore.md b/docs/api/user-client-modules/TextboxStore.md deleted file mode 100644 index c6a8451..0000000 --- a/docs/api/user-client-modules/TextboxStore.md +++ /dev/null @@ -1,166 +0,0 @@ -# TextboxStore API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -```mermaid -graph LR - TextboxStore --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 类描述 - -`TextboxStore` 是文本框的集中管理器,继承自 `EventEmitter`。所有 `Textbox` 组件实例化时会自动注册到该类的静态 `list` 中,支持通过 ID 精准控制特定文本框。 - ---- - -## 核心方法说明 - -### `TextboxStore.get` - -```typescript -function get(id: string): TextboxStore | undefined; -``` - -**静态方法**:通过 ID 获取已注册的文本框控制器 - -- **参数** - `id`: 文本框的唯一标识符 -- **返回值** - 找到返回实例,否则返回 `undefined` - ---- - -### `setText` - -```typescript -function setText(text: string): void; -``` - -**动态更新文本内容** - -- **特性** - - 自动重置打字机进度 - - 触发重新排版计算 - ---- - -### `modify` - -```typescript -function modify(data: Partial): void; -``` - -**动态修改文本框配置** - -- **参数** - `data`: 需更新的属性(支持所有 `TextboxProps` 属性) - ---- - -### `endType` - -```typescript -function endType(): void; -``` - -**立即结束打字机动画** - -- **特性** - - 强制显示全部文本 - - 触发 `typeEnd` 事件 - ---- - -### `show` - -```ts -function show(): void; -``` - -### `hide` - -```ts -function hide(): void; -``` - -控制文本框的显示和隐藏。 - ---- - -## 使用示例 - -### 跨场景更新对话内容 - -```typescript -// 在剧情管理器中的调用 -const updateChapterDialog = (chapterId: string) => { - const store = TextboxStore.get(`chapter_${chapterId}`); - store?.setText(getChapterText(chapterId)); - store?.modify({ title: `第 ${chapterId} 章` }); -}; -``` - -### 紧急提示打断当前动画 - -```typescript -// 强制显示关键信息 -const showEmergencyAlert = () => { - const alertBox = TextboxStore.get('system_alert'); - alertBox?.setText('警告!基地即将爆炸!'); - alertBox?.endType(); // 跳过打字动画 - alertBox?.show(); -}; -``` - -### 动态样式调整 - -```typescript -// 根据昼夜切换对话框样式 -const updateDialogStyle = (isNight: boolean) => { - TextboxStore.list.forEach(store => { - store.modify({ - backColor: isNight ? '#1A1A32' : '#F0F0FF', - titleFill: isNight ? '#E6E6FA' : '#2F4F4F' - }); - }); -}; -``` - ---- - -## 注意事项 - -1. **ID 管理规范** - 建议显式指定可预测的 ID 格式: - - ```tsx - // 创建时指定可追踪 ID - - ``` - -2. **未找到实例处理** - 调用前需做空值检测: - - ```typescript - const store = TextboxStore.get('custom_id'); - if (!store) { - console.warn('文本框未注册: custom_id'); - return; - } - ``` - -3. **生命周期匹配** - 在组件卸载时自动注销实例,请勿持有长期引用 - -4. **批量操作优化** - 同时操作多个实例时建议使用迭代器: - ```typescript - // 隐藏所有对话框 - TextboxStore.list.forEach(store => store.hide()); - ``` diff --git a/docs/api/user-client-modules/TipStore.md b/docs/api/user-client-modules/TipStore.md deleted file mode 100644 index d8b7882..0000000 --- a/docs/api/user-client-modules/TipStore.md +++ /dev/null @@ -1,147 +0,0 @@ -# TipStore API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -`TipStore` 是提示框的集中管理器,提供全局访问和控制提示组件的能力。所有通过 `` 组件注册的实例会自动加入静态 `list` 容器,支持通过 ID 精准控制特定提示框。 - ---- - -## 核心方法说明 - -### `TipStore.get` - -```typescript -function get(id: string): TipStore | undefined; -``` - -**静态方法**:通过 ID 获取已注册的提示框控制器 - -| 参数 | 类型 | 必填 | 说明 | -| ---- | -------- | ---- | ---------------- | -| `id` | `string` | 是 | 提示框的唯一标识 | - -**返回值**:找到返回实例,否则返回 `undefined` - ---- - -### `TipStore.use` - -```typescript -function use(id: string, data: TipExpose): TipStore; -``` - -**静态方法**:注册提示框实例到全局管理器(通常在组件内部使用) - -| 参数 | 类型 | 必填 | 说明 | -| ------ | ----------- | ---- | ----------------------- | -| `id` | `string` | 是 | 提示框的唯一标识 | -| `data` | `TipExpose` | 是 | 来自 Tip 组件的暴露接口 | - ---- - -### `drawTip` - -```typescript -function drawTip(text: string, icon?: AllIds | AllNumbers): void; -``` - -**显示提示内容**(支持带图标的提示) - -| 参数 | 类型 | 必填 | 说明 | -| ------ | ---------------------- | ---- | ------------------------------- | -| `text` | `string` | 是 | 提示文字内容 | -| `icon` | `AllIds \| AllNumbers` | 否 | 图标资源 ID(字符串或数字形式) | - -**特性**: - -- 自动触发淡入动画 -- 3 秒无操作后自动淡出 -- 重复调用会重置计时器 - ---- - -## 使用示例 - -### 基础提示 - -```typescript -// 获取预先注册的提示框 -const tip = TipStore.get('item-get-tip'); - -// 显示纯文本提示 -tip?.drawTip('获得金币 x100'); - -// 显示带图标的提示 -tip?.drawTip('获得 传说之剑', 'legend_sword'); -``` - -### 全局广播提示 - -```typescript -// 向所有提示框发送通知 -TipStore.list.forEach(store => { - store.drawTip('系统将在5分钟后维护', 'warning'); -}); -``` - -### 动态内容提示 - -```typescript -// 组合动态内容 -const showDamageTip = (damage: number) => { - TipStore.get('combat-tip')?.drawTip( - `造成 ${damage} 点伤害`, - damage > 1000 ? 'critical_hit' : 'normal_hit' - ); -}; -``` - ---- - -## 生命周期管理 - -### 组件注册流程 - -```tsx -// 在组件定义时注册实例 -; - -// 在业务逻辑中调用 -const showQuestComplete = () => { - TipStore.get('quest-tip')?.drawTip('任务「勇者的试炼」完成!'); -}; -``` - ---- - -## 注意事项 - -1. **自动清理机制** - 组件卸载时自动注销实例,跨场景访问时需确保目标组件已挂载 - -2. **错误处理** - 建议封装安全访问方法: - - ```typescript - const safeDrawTip = (id: string, text: string) => { - const instance = TipStore.get(id); - if (!instance) { - console.error(`Tip ${id} not registered`); - return; - } - instance.drawTip(text); - }; - ``` - -3. **动画队列** - 连续调用时会中断当前动画,建议重要提示添加延迟: - ```typescript - tip.drawTip('第一条提示'); - setTimeout(() => { - tip.drawTip('第二条重要提示'); - }, 3200); // 等待淡出动画结束 - ``` diff --git a/docs/api/user-client-modules/WeatherController.md b/docs/api/user-client-modules/WeatherController.md deleted file mode 100644 index c8addd9..0000000 --- a/docs/api/user-client-modules/WeatherController.md +++ /dev/null @@ -1,217 +0,0 @@ -# WeatherController API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -```mermaid -graph LR - WeatherController --> IWeather -``` - -_实现 `IWeather` 接口_ - -## 类描述 - -`WeatherController` 是天气系统的核心控制器,支持动态管理多种天气效果(如雨、雪、雾等),可将天气效果绑定到任意渲染元素上,实现多场景独立天气控制。 - ---- - -## 属性说明 - -| 属性名 | 类型 | 描述 | -| -------------- | -------------------------------- | ----------------------------------------------------------------- | -| `id` | `string` | 只读,控制器的唯一标识符 | -| `active` | `Set` | 当前激活的天气实例集合 | -| `list`(静态) | `Map` | 静态属性,存储所有注册的天气类型(键为天气 ID,值为天气构造函数) | -| `map`(静态) | `Map` | 静态属性,存储所有控制器实例 | - ---- - -## 构造方法 - -```typescript -function constructor(id: string): WeatherController; -``` - -- **参数** - - `id`: 控制器的标识符 - -## 方法说明 - -### `activate` - -```typescript -function activate(id: string, level?: number): IWeather | undefined; -``` - -激活指定天气。 - -- **参数** - - `id`: 已注册的天气 ID - - `level`: 天气强度等级(可选) - ---- - -### `bind` - -```typescript -function bind(item?: RenderItem): void; -``` - -绑定/解绑渲染元素。 - -- **参数** - - `item`: 要绑定的渲染元素(不传则解绑) - ---- - -### `deactivate` - -```typescript -function deactivate(weather: IWeather): void; -``` - -关闭指定天气效果。 - ---- - -### `clearWeather` - -```typescript -function clearWeather(): void; -``` - -清空所有天气效果。 - ---- - -### `getWeather` - -```typescript -function getWeather( - weather: new (level?: number) => T -): T | null; -``` - -获取指定天气实例。 - -**示例** - -```ts -import { RainWeather } from '@user/client-modules'; - -const rain = controller.getWeather(RainWeather); -``` - ---- - -### `destroy` - -```typescript -function destroy(): void; -``` - -摧毁这个天气控制器,摧毁后不可继续使用。 - ---- - -## 静态方法说明 - -### `WeatherController.register` - -```typescript -function register(id: string, weather: Weather): void; -``` - -**静态方法**:注册新的天气类型。 - -- **参数** - - `id`: 天气唯一标识(如 "rain") - - `weather`: 天气类(需实现 `IWeather` 接口) - ---- - -### `WeatherController.get` - -```typescript -function get(id: string): WeatherController | undefined; -``` - -- **参数** - - `id`: 要获得的控制器标识符 - -## 天气接口说明 - -```typescript -interface IWeather { - activate(item: RenderItem): void; // 初始化天气效果 - frame(): void; // 每帧更新逻辑 - deactivate(item: RenderItem): void; // 清除天气效果 -} -``` - ---- - -## 内置天气 - -- `rain`: 下雨天气 - -## 总使用示例 实现滤镜天气效果 - -::: code-group - -```typescript [定义天气] -// 定义灰度滤镜天气 -class GrayFilterWeather implements IWeather { - private scale: number; - private now: number = 0; - private item?: RenderItem; - - constructor(level: number = 5) { - this.scale = level / 10; - } - - activate(item: RenderItem) { - // 添加灰度滤镜 - item.filter = `grayscale(0)`; - this.item = item; - } - - frame() { - // 动态调整滤镜强度(示例:正弦波动) - if (this.item) { - const intensity = ((Math.sin(Date.now() / 1000) + 1) * scale) / 2; - this.item.filter = `grayscale(${itensity})`; - } - } - - deactivate(item: RenderItem) { - item.filter = `none`; - } -} - -// 注册天气类型 -WeatherController.register('gray-filter', GrayFilterWeather); -``` - -```tsx [使用天气] -import { defineComponent, onMounted } from 'vue'; -import { Container } from '@motajs/render'; -import { useWeather } from '@user/client-modules'; - -const MyCom = defineComponent(() => { - const [controller] = useWeather(); - - const root = ref(); - - onMounted(() => { - // 绑定天气的渲染元素 - controller.bind(root.value); - // 激活天气效果 - controller.activate('gray-filter', 5); - }); - - return () => ; -}); -``` - -::: diff --git a/docs/api/user-client-modules/functions.md b/docs/api/user-client-modules/functions.md deleted file mode 100644 index 52e0514..0000000 --- a/docs/api/user-client-modules/functions.md +++ /dev/null @@ -1,411 +0,0 @@ -# 模块函数 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 钩子 - -### `onOrientationChange` - -```typescript -function onOrientationChange(hook: OrientationHook): void; -``` - -监听屏幕方向变化事件。需要在组件内或 UI 内调用。 -**参数** - -- `hook`: 方向变化回调函数 - -```typescript -type OrientationHook = ( - orientation: Orientation, // 当前方向 - width: number, // 窗口宽度 - height: number // 窗口高度 -) => void; -``` - -**示例** - 响应式布局 - -```typescript -import { onOrientationChange, Orientation } from './use'; - -// 组件内 -onOrientationChange((orient, width) => { - if (orient === Orientation.Portrait) { - // 竖屏模式 - adjustMobileLayout(width); - } else { - // 横屏模式 - resetDesktopLayout(); - } -}); -``` - ---- - -### `onLoaded` - -```typescript -function onLoaded(hook: () => void): void; -``` - -在游戏核心资源加载完成后执行回调(若已加载则立即执行)。 - ---- - -## 过渡动画控制 - -### 通用接口 - -```typescript -interface ITransitionedController { - readonly ref: Ref; // 响应式引用 - readonly value: T; // 当前值 - set(value: T, time?: number): void; // 设置目标值 - mode(timing: TimingFn): void; // 设置缓动曲线 - setTime(time: number): void; // 设置默认时长 -} -``` - -### `transitioned` - -```typescript -function transitioned( - value: number, // 初始值 - time: number, // 默认过渡时长(ms) - curve: TimingFn // 缓动函数(如 linear()) -): ITransitionedController | null; -``` - -创建数值渐变控制器(仅限组件内使用)。 - -**示例** - 旋转动画 - -```tsx -// Vue 组件内 -const rotate = transitioned(0, 500, hyper('sin', 'out')); - -// 触发动画 -rotate.set(Math.PI, 800); // 800ms 内旋转到 180 度 - -// 模板中使用 -; -``` - -### `transitionedColor` - -```typescript -function transitionedColor( - color: string, // 初始颜色(目前支持 #RGB/#RGBA/rgb()/rgba()) - time: number, // 默认过渡时长(ms) - curve: TimingFn // 缓动函数 -): ITransitionedController | null; -``` - -创建颜色渐变控制器(仅限组件内使用)。 - -**示例** - 背景色过渡 - -```tsx -// Vue 组件内 -const bgColor = transitionedColor('#fff', 300, linear()); - -// 触发颜色变化 -bgColor.set('rgba(255, 0, 0, 0.5)'); // 渐变为半透明红色 - -// 模板中使用 -; -``` - ---- - -### 注意事项 - -1. **组件生命周期**:过渡控制器必须在 Vue 组件内部创建,卸载时自动销毁 -2. **性能优化**:避免在频繁触发的回调(如每帧渲染)中创建新控制器 -3. **颜色格式**:`transitionedColor` 支持 HEX/RGB/RGBA,但不支持 HSL -4. **默认时长**:调用 `set()` 时不传时间参数则使用初始化时设置的时间 - -### 高级用法示例 - -#### 组合动画 - -```typescript -// 同时控制位置和透明度 -const posX = transitioned(0, 500, linear()); -const alpha = transitioned(1, 300, linear()); - -const moveAndFade = () => { - posX.set(200); - alpha.set(0); -}; - -// 组件卸载时自动清理动画资源 -``` - -## 组件控制 - -### `getConfirm` - -```typescript -function getConfirm( - controller: IUIMountable, // UI 控制器 - text: string, // 确认内容 - loc: ElementLocator, // 定位配置 - width: number, // 对话框宽度(像素) - props?: Partial // 扩展配置 -): Promise; -``` - ---- - -#### 参数说明 - -| 参数名 | 类型 | 必填 | 描述 | -| ------------ | -------------------------- | ---- | ---------------------------------------------- | -| `controller` | `IUIMountable` | 是 | UI 控制器实例(通常从组件 props 获取) | -| `text` | `string` | 是 | 需要用户确认的文本内容 | -| `loc` | `ElementLocator` | 是 | 对话框位置配置(需包含 x,y 坐标及锚点) | -| `width` | `number` | 是 | 对话框宽度(像素),高度自动计算 | -| `props` | `Partial` | 否 | 扩展配置项(支持所有 ConfirmBox 组件的 props) | - ---- - -#### 返回值 - -返回 `Promise`: - -- `true` 表示用户点击确认 -- `false` 表示用户取消或关闭 - ---- - -#### 使用示例 - -##### 基础用法 - 删除确认 - -```tsx -import { defineComponent } from 'vue'; -import { DefaultProps } from '@motajs/render'; -import { GameUI } from '@motajs/system-ui'; - -// 在业务逻辑中调用,注意,组件需要使用 UI 控制器打开,它会自动传递 controller 参数 -const MyCom = defineComponent(props => { - const handleDeleteItem = async (itemId: string) => { - const confirmed = await getConfirm( - props.controller, // 从组件 props 获取控制器 - `确认删除 ID 为 ${itemId} 的项目吗?`, - [208, 208, void 0, void 0, 0.5, 0.5], // 居中显示 - 208 - ); - - if (confirmed) { - api.deleteItem(itemId); - } - }; - - return () => ( - - {/* 假设有一个按钮在点击后触发上面的删除函数 */} - handleDeleteItem(item.id)} /> - - ); -}); - -export const MyUI = new GameUI('my-ui', MyCom); -``` - -##### 自定义按钮文本 - -```typescript -import { mainUIController } from '@user/client-modules'; -// 注意,如果在 client-modules/render/ui 下编写代码,应该引入: -import { mainUIController } from './controller.ts'; - -// 修改确认/取消按钮文案 -const result = await getConfirm( - // 传入主 UI 控制器也可以 - mainUIController, - '切换场景将丢失未保存进度', - [208, 208, void 0, void 0, 0.5, 0.5], - 320, - { - yesText: '继续切换', - noText: '留在当前', - selFill: '#e74c3c', - border: '#c0392b' - } -); -``` - ---- - -### `getChoice` - -```typescript -function getChoice( - controller: IUIMountable, // UI 控制器 - choices: ChoiceItem[], // 选项数组 - loc: ElementLocator, // 定位配置 - width: number, // 对话框宽度(像素) - props?: Partial // 扩展配置 -): Promise; -``` - -#### 参数说明 - -| 参数名 | 类型 | 必填 | 描述 | -| ------------ | ----------------------- | ---- | ------------------------------------------- | -| `controller` | `IUIMountable` | 是 | UI 控制器实例(通常从组件 props 获取) | -| `choices` | `ChoiceItem[]` | 是 | 选项数组,格式为 `[key, text]` 的元组 | -| `loc` | `ElementLocator` | 是 | 对话框位置配置(需包含 x,y 坐标及锚点) | -| `width` | `number` | 是 | 对话框宽度(像素),高度自动计算 | -| `props` | `Partial` | 否 | 扩展配置项(支持所有 Choices 组件的 props) | - -#### 返回值 - -返回 `Promise`: - -- 解析为选中项的 `key` 值 - -#### 使用示例 - -##### 基础用法 - 难度选择 - -```typescript -import { getChoice, mainUIController } from '@user/client-modules'; - -// 写到异步函数里面 -const selectedDifficulty = await getChoice( - mainUIController, - [ - ['easy', '新手模式'], - ['normal', '普通模式'], - ['hard', '困难模式'] - ], - [208, 208, void 0, void 0, 0.5, 0.5], // 居中显示 - 208, - { - title: '选择难度', - titleFont: new Font('黑体', 24) - } -); - -// 判断选择的内容 -if (selectedDifficulty === 'hard') { - applyHardcoreRules(); -} -``` - -##### 分页支持 - 角色选择 - -```typescript -import { getChoice, mainUIController } from '@user/client-modules'; - -// 生成 200 个角色选项 -const characterOptions = Array.from( - { length: 200 }, - (_, i) => [i, `角色 #${i + 1}`] as ChoiceItem -); - -const chosenId = await getChoice( - mainUIController, - characterOptions, - [208, 208, void 0, void 0, 0.5, 0.5], - 208, - { - maxHeight: 400, // 超过 400px 自动分页 - winskin: 'winskin.png', - interval: 12 - } -); -``` - -##### 动态样式配置 - -```typescript -import { getChoice, mainUIController } from '@user/client-modules'; - -// 自定义主题风格 -const choiceResult = await getChoice( - mainUIController, - [ - ['light', '浅色主题'], - ['dark', '深色主题'], - ['oled', 'OLED 深黑'] - ], - [208, 208, void 0, void 0, 0.5, 0.5], - 300, - { - color: 'rgba(30,30,30,0.9)', - border: '#4CAF50', - selFill: '#81C784', - titleFill: '#FFF59D' - } -); -``` - -### `waitbox` - -```typescript -function waitbox( - controller: IUIMountable, - loc: ElementLocator, - width: number, - promise: Promise, - props?: Partial> -): Promise; -``` - -#### 参数说明 - -| 参数名 | 类型 | 必填 | 默认值 | 描述 | -| ------------ | -------------------------- | ---- | ------ | ---------------------------------------------------- | -| `controller` | `IUIMountable` | 是 | - | UI 挂载控制器(通常传递父组件的 `props.controller`) | -| `loc` | `ElementLocator` | 是 | - | 定位参数 | -| `width` | `number` | 是 | - | 内容区域宽度(像素) | -| `promise` | `Promise` | 是 | - | 要监视的异步操作 | -| `props` | `Partial>` | 否 | `{}` | 扩展配置项(继承 `Background` + `TextContent` 属性) | - ---- - -#### 返回值 - -| 类型 | 说明 | -| ------------ | ----------------------------------------------------------------------------------- | -| `Promise` | 与传入 `Promise` 联动的代理 `Promise`,在以下情况会 `reject`:原始 `Promise` 被拒绝 | - ---- - -#### 使用示例 - -##### 等待网络请求 - -```typescript -// 获取用户数据 -const userData = await waitbox( - props.controller, - [400, 300, void 0, void 0, 0.5, 0.5], // 居中定位 - 300, - fetch('/api/user'), - { - text: '加载用户信息...', - winskin: 'ui/loading_panel' - } -); -``` - -### 注意事项 - -1. **控制器有效性** - 必须确保传入的 `controller` 已正确挂载且未销毁 - -2. **异步特性** - 需使用 `await` 或 `.then()` 处理返回的 Promise - -3. **定位系统** - Y 轴坐标基于 Canvas 坐标系(向下为正方向) - -4. **额外参考** - - [组件 ConfirmBox](./组件%20ConfirmBox.md) - - [组件 Choices](./组件%20Choices.md) - - [组件 WaitBox](./组件%20WaitBox.md) diff --git a/docs/api/user-client-modules/index.md b/docs/api/user-client-modules/index.md deleted file mode 100644 index ba85d58..0000000 --- a/docs/api/user-client-modules/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/client-modules - -目录: diff --git a/docs/api/user-client-modules/图标组件.md b/docs/api/user-client-modules/图标组件.md deleted file mode 100644 index 1231cd8..0000000 --- a/docs/api/user-client-modules/图标组件.md +++ /dev/null @@ -1,46 +0,0 @@ -# 图标组件 API - -## Props 属性说明 - -| 属性名 | 类型 | 必填 | 描述 | -| ------ | ---------------- | ---- | ---------- | -| `loc` | `ElementLocator` | 是 | 图标定位符 | - -图标比例固定,会自动根据传入的长宽缩放。 - -图标继承所有图形参数,参考[此文档](../motajs-render-vue/GraphicBaseProps.md) - -## 图标列表 - -- `RollbackIcon`: 回退图标 -- `RetweenIcon`: 回收图标 -- `ViewMapIcon`: 浏览地图图标 -- `DanmakuIcon`: 弹幕图标 -- `ReplayIcon`: 回放图标 -- `numpadIcon`: 数字键盘图标 -- `PlayIcon`: 开始播放图标 -- `PauseIcon`: 暂停播放图标 -- `DoubleArrow`: 双箭头图标(向右) -- `StepForward`: 单步向前图标 -- `SoundVolume`: 音量图标 -- `Fullscreen`: 全屏图标 -- `ExitFullscreen`: 退出全屏图标 -- `ArrowLeftTailless`: 无尾巴左箭头图标 -- `ArrowRightTailless`: 无尾巴右箭头图标 -- `ArrowUpTailless`: 无尾巴上箭头图标 -- `ArrowDownTailless`: 无尾巴下箭头图标 - -## 使用示例 - -```tsx -import { defineComponent } from 'vue'; -import { RollbackIcon } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - return () => ( - - - - ); -}); -``` diff --git a/docs/api/user-client-modules/组件 Arrow.md b/docs/api/user-client-modules/组件 Arrow.md deleted file mode 100644 index fca36f8..0000000 --- a/docs/api/user-client-modules/组件 Arrow.md +++ /dev/null @@ -1,50 +0,0 @@ -# Arrow 组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **两点连线**:通过坐标点绘制任意方向箭头 -- **头部定制**:可调节箭头尖端大小 -- **样式控制**:支持颜色自定义 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------- | ---------------------------------- | ------ | ----------------------------------------- | -| `arrow` | `[number, number, number, number]` | 必填 | 箭头坐标 [起点 x, 起点 y, 终点 x, 终点 y] | -| `head` | `number` | `8` | 箭头头部尺寸(像素) | -| `color` | `CanvasStyle` | `#fff` | 箭头颜色 | - ---- - -## 使用示例 - -### 基础箭头 - -```tsx -// 从 (50, 100) 到 (200, 300) 的基础箭头 - -``` - -### 定制样式 - -```tsx -// 红色粗箭头(头部尺寸12px) - -``` - -### 虚线箭头 - -```tsx -// 带虚线效果的箭头 - -``` diff --git a/docs/api/user-client-modules/组件 Background.md b/docs/api/user-client-modules/组件 Background.md deleted file mode 100644 index 37a6985..0000000 --- a/docs/api/user-client-modules/组件 Background.md +++ /dev/null @@ -1,78 +0,0 @@ -# Background 背景组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **双样式模式**:支持图片皮肤或纯色填充 -- **精准定位**:像素级坐标控制 -- **静态呈现**:无内置动画的稳定背景层 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| --------- | ---------------- | -------- | ----------------------------- | -| `loc` | `ElementLocator` | **必填** | 背景定位 | -| `winskin` | `ImageIds` | - | 皮肤图片资源 ID(优先级最高) | -| `color` | `CanvasStyle` | `"#333"` | 填充颜色(无皮肤时生效) | -| `border` | `CanvasStyle` | `"#666"` | 边框颜色(无皮肤时生效) | - ---- - -## 使用示例 - -### 图片皮肤模式 - -```tsx -// 使用预加载的UI背景图 - -``` - -### 纯色模式 - -```tsx -// 自定义颜色背景 - -``` - -### 对话框组合 - -```tsx -// 对话框容器 - - - - - -``` - ---- - -## 注意事项 - -1. **样式优先级** - 同时指定参数时的生效顺序: - - ```tsx - // 以下配置仅生效 winskin - - ``` - -2. **默认边框** - 未指定 border 时的行为: - - ```tsx - // 无边框(指定为透明色) - ; - - // 默认白色边框(当未指定任何参数时) - ; - ``` diff --git a/docs/api/user-client-modules/组件 Choices.md b/docs/api/user-client-modules/组件 Choices.md deleted file mode 100644 index 0de7953..0000000 --- a/docs/api/user-client-modules/组件 Choices.md +++ /dev/null @@ -1,159 +0,0 @@ -# Choices 选项框组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 组件特性 - -- **多选一机制**:从多个选项中选择一项 -- **自动分页**:通过 `maxHeight` 控制分页 -- **灵活样式**:支持图片背景/纯色背景 + 自定义字体 -- **键盘导航**:方向键选择 + Enter 确认 -- **动态内容**:支持异步加载选项数据 - ---- - -## Props 属性说明 - -```mermaid -graph LR - ConfirmBoxProps --> TextContentProps - - click TextContentProps "./组件%20TextContent" -``` - -本组件完全继承 `TextContent` 组件的参数,参考 [组件 TextContent](./组件%20TextContent.md) - -| 属性名 | 类型 | 默认值 | 描述 | -| ----------- | ---------------- | -------- | ------------------------------------- | -| `choices` | `ChoiceItem[]` | 必填 | 选项数组,格式为 `[key, text]` 的元组 | -| `loc` | `ElementLocator` | 必填 | 定位配置(需包含 x,y 坐标及锚点) | -| `width` | `number` | 必填 | 选项框宽度(像素) | -| `maxHeight` | `number` | `360` | 最大高度(超过时自动分页) | -| `text` | `string` | - | 主说明文本(显示在标题下方) | -| `title` | `string` | - | 标题文本 | -| `winskin` | `ImageIds` | - | 背景图片资源 ID(与 color 互斥) | -| `color` | `CanvasStyle` | `#333` | 背景颜色(未设置 winskin 时生效) | -| `border` | `CanvasStyle` | `gold` | 边框颜色/样式 | -| `selFont` | `Font` | 系统默认 | 选项文本字体 | -| `selFill` | `CanvasStyle` | `#fff` | 选项文本颜色 | -| `titleFont` | `Font` | 系统默认 | 标题字体 | -| `titleFill` | `CanvasStyle` | `gold` | 标题颜色 | -| `interval` | `number` | `16` | 选项间垂直间距(像素) | - ---- - -## Events 事件说明 - -| 事件名 | 参数 | 触发时机 | -| -------- | ---------------- | ---------------------- | -| `choose` | `key: ChoiceKey` | 用户选择任意选项时触发 | - ---- - -## 使用示例 - -### 基础用法 - 系统设置 - -```tsx -import { defineComponent } from 'vue'; -import { Choices, ChoiceItem } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const options: ChoiceItem[] = [ - ['low', '低画质'], - ['medium', '中画质'], - ['high', '高画质'] - ]; - - return () => ( - console.log(`Choose ${key}.`)} - /> - ); -}); -``` - -### 分页处理 - 角色选择 - -```tsx -import { defineComponent } from 'vue'; -import { Choices, ChoiceItem } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - // 生成 50 个角色选项 - const characters: ChoiceItem[] = Array.from( - { length: 50 }, - (_, i) => [`char_${i}`, `角色 ${i + 1}`] as ChoiceItem - ); - - return () => ( - - ); -}); -``` - -### 动态内容 + 自定义样式 - -```tsx -import { defineComponent } from 'vue'; -import { Choices, ChoiceItem } from '@user/client-modules'; -import { onTick } from '@motajs/render'; - -export const MyCom = defineComponent(() => { - const dynamicOptions = ref([]); - - onTick(() => { - // 每帧生成随机选项名称 - dynamicOptions.value = Array.from( - { length: 50 }, - (_, i) => - [ - `char_${i}`, - `随机数 ${Math.random().toFixed(8)}` - ] as ChoiceItem - ); - }); - - return () => ( - - ); -}); -``` - ---- - -## 注意事项 - -1. **选项键值唯一性** - 每个选项的 `key` 必须唯一,否则可能引发不可预期行为 - -2. **分页计算规则** - 分页依据 `maxHeight` 和字体大小自动计算,需确保字体大小一致 - -3. **使用更方便的函数**:多数情况下,你不需要使用本组件,使用包装好的函数往往会更加方便,参考 [`getChoice`](./functions.md#getchoice) diff --git a/docs/api/user-client-modules/组件 ConfirmBox.md b/docs/api/user-client-modules/组件 ConfirmBox.md deleted file mode 100644 index 31f5b4f..0000000 --- a/docs/api/user-client-modules/组件 ConfirmBox.md +++ /dev/null @@ -1,124 +0,0 @@ -# ConfirmBox 确认框组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 组件特性 - -- **双选项支持**:是/否选择 -- **灵活样式**:支持图片背景或纯色背景 -- **键盘交互**:支持按键操作 -- **自动布局**:根据内容动态计算高度 -- **事件驱动**:提供明确的用户选择反馈 - ---- - -## Props 属性说明 - -```mermaid -graph LR - ConfirmBoxProps --> TextContentProps - - click TextContentProps "./组件%20TextContent" -``` - -本组件完全继承 `TextContent` 组件的参数,参考 [组件 TextContent](./组件%20TextContent.md) - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------ | ---------------- | ------------ | --------------------------------- | -| `text` | `string` | 必填 | 显示的主文本内容 | -| `width` | `number` | 必填 | 确认框宽度(像素) | -| `loc` | `ElementLocator` | 必填 | 定位配置 | -| `winskin` | `ImageIds` | - | 背景图片资源 ID(与 color 互斥) | -| `color` | `CanvasStyle` | `'#333'` | 背景颜色(未设置 winskin 时生效) | -| `border` | `CanvasStyle` | `'gold'` | 边框颜色/样式 | -| `selFont` | `Font` | 系统默认字体 | 选项按钮字体 | -| `selFill` | `CanvasStyle` | `'#d48'` | 选项按钮文本颜色 | -| `yesText` | `string` | `'是'` | 确认按钮文本 | -| `noText` | `string` | `'否'` | 取消按钮文本 | -| `defaultYes` | `boolean` | `true` | 默认选中确认按钮 | - ---- - -## Events 事件说明 - -| 事件名 | 参数 | 触发时机 | -| ------ | ---- | --------------------------- | -| `yes` | - | 用户选择确认时触发 | -| `no` | - | 用户选择取消或按 Esc 时触发 | - ---- - -## 使用示例 - -### 基础用法 - 文本确认 - -```tsx -import { defineComponent } from 'vue'; -import { ConfirmBox } from '@user/client-modules'; - -export const MyCom defineComponent(() => { - return () => ( - console.log('用户确认保存')} - onNo={() => console.log('用户取消保存')} - /> - ); -}); -``` - -### 图片背景 + 自定义按钮 - -```tsx -import { defineComponent } from 'vue'; -import { ConfirmBox } from '@user/client-modules'; -import { Font } from '@motajs/render'; - -export const MyCom = defineComponent(() => { - return () => ( - - ); -}); -``` - -### 动态内容 + 编程控制 - -```tsx -import { defineComponent, computed } from 'vue'; -import { ConfirmBox } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const count = ref(0); - const myText = computed(() => `当前确认次数与取消次数差值:${count.value}`); - - return () => ( - void count.value++} // 每次确认次数加一 - onNo={() => void count.value--} // 每次确认次数减一 - /> - ); -}); -``` - -## 注意事项 - -1. **使用更方便的函数**:多数情况下,你不需要使用本组件,使用包装好的函数往往会更加方便,参考 [`getConfirm`](./functions.md#getconfirm) diff --git a/docs/api/user-client-modules/组件 FloorSelector.md b/docs/api/user-client-modules/组件 FloorSelector.md deleted file mode 100644 index 535643c..0000000 --- a/docs/api/user-client-modules/组件 FloorSelector.md +++ /dev/null @@ -1,85 +0,0 @@ -# FloorSelector 楼层选择器组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 组件描述 - -楼层选择器组件用于在地图浏览或地图选择场景中切换不同楼层,其尺寸与内置状态栏组件匹配,适合在地图浏览时将状态栏替换为此组件。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| -------- | ------------ | ------ | -------------------- | -| `floors` | `FloorIds[]` | 必填 | 可选择的楼层 ID 数组 | -| `now` | `number` | - | 当前选中的楼层索引 | - ---- - -## Events 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| ------------ | ------------------------------------ | ---------------------- | -| `close` | - | 点击关闭按钮时触发 | -| `update` | `(floor: number, floorId: FloorIds)` | 当选中的楼层改变时触发 | -| `update:now` | `(value: number)` | v-model 双向绑定事件 | - ---- - -## Slots 插槽说明 - -无插槽 - ---- - -## Exposed Methods 暴露方法 - -无暴露方法 - ---- - -## 使用示例 - -### 基础用法 - 地图浏览界面 - -```tsx -import { defineComponent, ref } from 'vue'; -import { FloorSelector, STATUS_BAR_HEIGHT } from '@user/client-modules'; - -export const MapBrowserCom = defineComponent(() => { - const currentFloor = ref(0); - - // 可用的楼层列表 - const availableFloors = core.floorIds; - - const handleFloorChange = (floorIndex: number, floorId: string) => { - console.log(`切换到楼层: ${floorId} (索引: ${floorIndex})`); - // 这里可以执行切换楼层的逻辑 - }; - - const handleClose = () => { - console.log('关闭楼层选择器'); - // 返回主界面或执行其他关闭逻辑 - }; - - return () => ( - - ); -}); -``` - ---- - -## 注意事项 - -1. **尺寸匹配**: 组件设计为与内置状态栏尺寸匹配,可直接替换 -2. **索引基准**: 楼层索引从 0 开始 -3. **内置集成**: 通常不需要直接使用,因为样板已内置完整的地图浏览界面 -4. **倒序排列**: 楼层列表会自动倒序排列 diff --git a/docs/api/user-client-modules/组件 Input.md b/docs/api/user-client-modules/组件 Input.md deleted file mode 100644 index 210a887..0000000 --- a/docs/api/user-client-modules/组件 Input.md +++ /dev/null @@ -1,113 +0,0 @@ -# Input 输入框组件 API 文档 - -本文档由 `DeepSeek` 生成并微调。 - -## 组件描述 - -输入框组件用于接收用户的文本输入,支持单行和多行模式,提供边框样式自定义和实时/确认两种值变化事件。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------------------- | ------------------- | ------- | ----------------------------------------------------- | -| `placeholder` | `string` | - | 输入框的提示内容 | -| `value` | `string` | - | 输入框的值 | -| `multiline` | `boolean` | `false` | 是否是多行输入,多行输入时允许换行 | -| `border` | `string` | - | 边框颜色 | -| `circle` | `RectRCircleParams` | - | 边框圆角 | -| `borderWidth` | `number` | - | 边框宽度 | -| `pad` | `number` | - | 内边距 | -| 继承自 `TextContentProps` | - | - | [查看完整属性](./组件%20TextContent#Props%20属性说明) | - ---- - -## Events 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| -------------- | ----------------- | ---------------------------------------- | -| `change` | `(value: string)` | 当输入框的值被确认时触发,例如失焦时 | -| `input` | `(value: string)` | 当输入框的值发生改变时触发,例如实时输入 | -| `update:value` | `(value: string)` | v-model 双向绑定事件 | - ---- - -## Slots 插槽说明 - -无插槽 - ---- - -## Exposed Methods 暴露方法 - -无暴露方法 - ---- - -## 使用示例 - -### 基础用法 - 单行输入框 - -```tsx -import { defineComponent, ref } from 'vue'; -import { Input } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const inputValue = ref(''); - - const handleChange = (value: string) => { - console.log('输入确认:', value); - }; - - const handleInput = (value: string) => { - console.log('实时输入:', value); - }; - - return () => ( - - ); -}); -``` - -### 多行文本输入 - -```tsx -import { defineComponent, ref } from 'vue'; -import { Input } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const multilineValue = ref(''); - - return () => ( - - ); -}); -``` - ---- - -## 注意事项 - -1. **事件区别**:`input` 事件在每次输入时触发,`change` 事件在失焦或确认时触发 -2. **多行模式**:启用 `multiline` 后支持换行输入,高度需要足够容纳多行文本 -3. **样式继承**:支持从 `TextContentProps` 继承文本相关样式属性 diff --git a/docs/api/user-client-modules/组件 InputBox.md b/docs/api/user-client-modules/组件 InputBox.md deleted file mode 100644 index a36a22f..0000000 --- a/docs/api/user-client-modules/组件 InputBox.md +++ /dev/null @@ -1,315 +0,0 @@ -# InputBox 输入对话框组件 API 文档 - -本文档由 `DeepSeek` 生成并微调。 - -## 组件描述 - -输入对话框组件是一个完整的弹出式输入界面,包含提示文本、输入框、确认和取消按钮。适用于需要用户输入文本的交互场景,提供了便捷的异步获取输入方法。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------------------- | ---------------- | -------- | ----------------------------------------------------- | -| `loc` | `ElementLocator` | 必填 | 输入框对话框的位置 | -| `input` | `InputProps` | - | 传递给内部 Input 组件的配置参数 | -| `winskin` | `ImageIds` | - | 窗口皮肤图片ID,用于对话框背景绘制 | -| `color` | `CanvasStyle` | - | 对话框背景颜色(未设置 winskin 时生效) | -| `border` | `CanvasStyle` | - | 对话框边框颜色(未设置 winskin 时生效) | -| `pad` | `number` | - | 对话框内部所有元素的内边距 | -| `inputHeight` | `number` | - | 内部输入框区域的高度 | -| `text` | `string` | - | 对话框顶部的提示文本 | -| `yesText` | `string` | `"确认"` | 确认按钮的显示文本 | -| `noText` | `string` | `"取消"` | 取消按钮的显示文本 | -| `selFont` | `Font` | - | 确认/取消按钮的字体样式 | -| `selFill` | `CanvasStyle` | - | 确认/取消按钮的文本颜色 | -| 继承自 `TextContentProps` | - | - | [查看完整属性](./组件%20TextContent#Props%20属性说明) | - ---- - -## Events 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| -------------- | ----------------- | ------------------------------------------ | -| `confirm` | `(value: string)` | 当确认输入框的内容时触发 | -| `cancel` | `(value: string)` | 当取消时触发 | -| `change` | `(value: string)` | 继承自 Input 组件 - 输入框值被确认时触发 | -| `input` | `(value: string)` | 继承自 Input 组件 - 输入框值实时变化时触发 | -| `update:value` | `(value: string)` | 继承自 Input 组件 - v-model 双向绑定 | - ---- - -## Slots 插槽说明 - -无插槽 - ---- - -## Exposed Methods 暴露方法 - -无暴露方法 - ---- - -## 工具函数 - -### `getInput(controller, text, loc, width, props?)` - -弹出一个输入框并异步返回用户输入的结果。 - -**参数** - -- `controller: IUIMountable` - UI 控制器 -- `text: string` - 提示文本内容 -- `loc: ElementLocator` - 确认框的位置 -- `width: number` - 确认框的宽度 -- `props?: InputBoxProps` - 额外的配置属性(可选) - -**返回值**: `Promise` - -### `getInputNumber(controller, text, loc, width, props?)` - -与 `getInput` 类似,但会将结果转换为数字。 - -**参数**: 同 `getInput` -**返回值**: `Promise` - ---- - -## 使用示例 - -### 基础用法 - 组件形式 - -```tsx -import { defineComponent, ref } from 'vue'; -import { InputBox } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const handleConfirm = (value: string) => { - console.log('用户输入:', value); - // 处理用户输入 - }; - - const handleCancel = (value: string) => { - console.log('用户取消输入,最后值为:', value); - }; - - return () => ( - - ); -}); -``` - -### 使用 getInput 工具函数(推荐) - -```tsx -import { defineComponent } from 'vue'; -import { getInput, getInputNumber } from '@user/client-modules'; - -export const MyCom = defineComponent(props => { - const handleGetName = async () => { - // 获取文本输入 - const name = await getInput( - props.controller, - '请输入您的姓名:', - [208, 208, void 0, void 0, 0.5, 0.5], - 280, - { - input: { - placeholder: '姓名' - }, - color: '#f8f8f8' - } - ); - - if (name) { - console.log('用户姓名:', name); - // 处理姓名 - } else { - console.log('用户取消了输入'); - } - }; - - const handleGetAge = async () => { - // 获取数字输入 - const age = await getInputNumber( - props.controller, - '请输入您的年龄:', - [208, 208, void 0, void 0, 0.5, 0.5], - 280 - ); - - if (!isNaN(age)) { - console.log('用户年龄:', age); - // 处理年龄 - } - }; - - return () => ( - - - - - ); -}); -``` - -### 带自定义样式的输入对话框 - -```tsx -import { defineComponent } from 'vue'; -import { getInput } from '@user/client-modules'; - -export const StyledInputCom = defineComponent(props => { - const handleStyledInput = async () => { - const value = await getInput( - props.controller, - '请输入任务描述:', - [240, 240, void 0, void 0, 0.5, 0.5], - 320, - { - text: '任务创建', - input: { - placeholder: '描述任务内容...', - multiline: true, - border: '#4CAF50', - borderWidth: 2, - circle: 8, - pad: 12 - }, - color: '#ffffff', - border: '#4CAF50', - borderWidth: 3, - pad: 20, - inputHeight: 80, - yesText: '创建', - noText: '取消', - selFill: '#4CAF50' - } - ); - - if (value) { - console.log('创建任务:', value); - } - }; - - return () => ( - - ); -}); -``` - -### 游戏中的设置界面 - -```tsx -import { defineComponent } from 'vue'; -import { getInput } from '@user/client-modules'; - -export const SettingsCom = defineComponent(props => { - const settings = ref({ - playerName: '玩家', - serverIP: '127.0.0.1' - }); - - const changePlayerName = async () => { - const newName = await getInput( - props.controller, - '修改玩家名称:', - [240, 240, void 0, void 0, 0.5, 0.5], - 280, - { - input: { - value: settings.value.playerName, - placeholder: '玩家名称', - border: '#FF9800', - borderWidth: 1 - } - } - ); - - if (newName) { - settings.value.playerName = newName; - console.log('玩家名称已更新:', newName); - } - }; - - const changeServerIP = async () => { - const newIP = await getInput( - props.controller, - '修改服务器IP:', - [240, 240, void 0, void 0, 0.5, 0.5], - 280, - { - input: { - value: settings.value.serverIP, - placeholder: '服务器IP地址', - border: '#2196F3', - borderWidth: 1 - } - } - ); - - if (newIP) { - settings.value.serverIP = newIP; - console.log('服务器IP已更新:', newIP); - } - }; - - return () => ( - - - - - ); -}); -``` - ---- - -## 注意事项 - -1. **推荐使用工具函数**: `getInput` 和 `getInputNumber` 提供了更简洁的异步输入获取方式 -2. **宽度设置**: 在使用工具函数时,高度由组件自动计算,只需指定宽度 -3. **皮肤优先级**: 如果设置了 `winskin`,则 `color` 和 `border` 设置将失效 -4. **异步处理**: 工具函数返回 Promise,需要使用 `await` 或 `.then()` 处理结果 -5. **空值处理**: 用户取消输入时,`getInput` 返回空字符串,`getInputNumber` 返回 `NaN` diff --git a/docs/api/user-client-modules/组件 List.md b/docs/api/user-client-modules/组件 List.md deleted file mode 100644 index 84c8efc..0000000 --- a/docs/api/user-client-modules/组件 List.md +++ /dev/null @@ -1,184 +0,0 @@ -# List 列表组件 API 文档 - -本文档由 `DeepSeek` 生成并微调。 - -## 组件描述 - -列表组件用于展示可选择的项目列表,内置滚动条功能和选中项高亮效果。适用于菜单选择、内容导航、设置选项等场景。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------ | -------------------- | ------ | ------------------------------- | -| `list` | `[string, string][]` | 必填 | 列表内容,[id, 显示文本] 的数组 | -| `selected` | `string` | 必填 | 当前选中的项 ID | -| `loc` | `ElementLocator` | 必填 | 列表的位置和尺寸 | -| `lineHeight` | `number` | `18` | 每行的高度 | -| `font` | `Font` | - | 列表项的字体样式 | -| `winskin` | `ImageIds` | - | 使用 winskin 作为光标背景 | -| `color` | `CanvasStyle` | - | 使用指定样式作为光标背景 | -| `border` | `CanvasStyle` | - | 使用指定样式作为光标边框 | -| `alphaRange` | `[number, number]` | - | 选择图标的不透明度范围 | - ---- - -## Events 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| ----------------- | ----------------- | ---------------------- | -| `update` | `(key: string)` | 当用户选中某一项时触发 | -| `update:selected` | `(value: string)` | v-model 双向绑定事件 | - ---- - -## Slots 插槽说明 - -无插槽 - ---- - -## Exposed Methods 暴露方法 - -无暴露方法 - ---- - -## 使用示例 - -### 基础用法 - 简单列表 - -```tsx -import { defineComponent, ref } from 'vue'; -import { List } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const selectedItem = ref('item1'); - - // 列表数据:[id, 显示文本] - const listData = [ - ['item1', '第一项'], - ['item2', '第二项'], - ['item3', '第三项'], - ['item4', '第四项'] - ]; - - const handleSelect = (key: string) => { - console.log('选中项:', key); - selectedItem.value = key; - }; - - return () => ( - - ); -}); -``` - -### 带样式的列表 - -```tsx -import { defineComponent, ref } from 'vue'; -import { List } from '@user/client-modules'; - -export const StyledListCom = defineComponent(() => { - const selected = ref('opt2'); - - const options = [ - ['opt1', '开始游戏'], - ['opt2', '游戏设置'], - ['opt3', '帮助文档'], - ['opt4', '关于我们'] - ]; - - return () => ( - - ); -}); -``` - -### 使用皮肤图片的列表 - -```tsx -import { defineComponent, ref } from 'vue'; -import { List } from '@user/client-modules'; - -export const SkinnedListCom = defineComponent(() => { - const currentSelection = ref('cat'); - - const animalList = [ - ['cat', '猫咪'], - ['dog', '小狗'], - ['bird', '小鸟'], - ['fish', '小鱼'] - ]; - - const handleUpdate = key => { - const item = animalList.find(item => item[0] === key)?.[1]; - currentSelection.value = key; - console.log('选择了:', item); - }; - - return () => ( - - ); -}); -``` - -### 长列表滚动示例 - -```tsx -import { defineComponent, ref } from 'vue'; -import { List } from '@user/client-modules'; - -export const ScrollListCom = defineComponent(() => { - const selected = ref('item5'); - - // 创建长列表数据 - const longList = Array.from({ length: 20 }, (_, i) => [ - `item${i + 1}`, - `列表项 ${i + 1}` - ]); - - return () => ( - - ); -}); -``` - ---- - -## 注意事项 - -1. **数据格式**: `list` 属性需要 `[id, text]` 格式的二维数组 -2. **选中状态**: `selected` 需要与列表项中的 id 匹配 -3. **滚动支持**: 自动显示滚动条 -4. **样式优先级**: 如果设置了 `winskin`,则 `color` 和 `border` 设置将失效 diff --git a/docs/api/user-client-modules/组件 ListPage.md b/docs/api/user-client-modules/组件 ListPage.md deleted file mode 100644 index da1fd98..0000000 --- a/docs/api/user-client-modules/组件 ListPage.md +++ /dev/null @@ -1,226 +0,0 @@ -# ListPage 列表页面组件 API 文档 - -本文档由 `DeepSeek` 生成并微调。 - -## 组件描述 - -列表页面组件结合了列表选择和内容展示功能,左侧显示可选项列表,右侧显示选中项的详细内容。适用于说明文档、设置界面、内容导航等场景。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------------ | ---------------- | ------- | ---------------------------------------------- | -| `loc` | `ElementLocator` | 必填 | 组件整体位置和尺寸 | -| `basis` | `number` | - | 列表区域所占比例 | -| `right` | `boolean` | `false` | 列表是否排列在右侧 | -| `close` | `boolean` | `false` | 是否显示关闭按钮 | -| `closeLoc` | `ElementLocator` | - | 关闭按钮的位置(相对于组件定位) | -| 继承自 `ListProps` | - | - | [查看完整属性](./组件%20List#Props%20属性说明) | - ---- - -## Events 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| ----------------- | ----------------- | ------------------------------------ | -| `close` | - | 当用户点击关闭按钮时触发 | -| `update` | `(key: string)` | 继承自 List - 当用户选中某一项时触发 | -| `update:selected` | `(value: string)` | 继承自 List - v-model 双向绑定事件 | - ---- - -## Slots 插槽说明 - -### `default` - -接收当前选中的 key 并返回对应的内容 - -**参数** - -- `key: string` 当前选中的项 ID - -### 具名插槽 - -以列表项 ID 为名称的具名插槽,用于定义每个选项对应的详细内容 - ---- - -## 使用示例 - -### 基础用法 - 说明文档界面 - -```tsx -import { defineComponent, ref } from 'vue'; -import { ListPage } from '@user/client-modules'; - -export const HelpCom = defineComponent(() => { - const selected = ref('intro'); - - const helpTopics = [ - ['intro', '功能介绍'], - ['usage', '使用方法'], - ['settings', '设置说明'], - ['faq', '常见问题'] - ]; - - return () => ( - - {{ - // 使用具名插槽定义每个选项的内容 - intro: () => ( - - - - - ), - usage: () => ( - - - - - - ), - settings: () => , - faq: () => - }} - - ); -}); -``` - -### 使用默认插槽 - -```tsx -import { defineComponent, ref } from 'vue'; -import { ListPage } from '@user/client-modules'; - -export const SimpleCom = defineComponent(() => { - const selected = ref('item1'); - - const items = [ - ['item1', '选项一'], - ['item2', '选项二'], - ['item3', '选项三'] - ]; - - return () => ( - - {key => ( - - item[0] === key)?.[1]}`} - loc={[0, 0]} - /> - - - )} - - ); -}); -``` - -### 带关闭按钮的弹窗 - -```tsx -import { defineComponent, ref } from 'vue'; -import { ListPage } from '@user/client-modules'; - -export const ModalCom = defineComponent(props => { - const selected = ref('weapon'); - - const gameItems = [ - ['weapon', '武器'], - ['armor', '防具'], - ['potion', '药水'], - ['material', '材料'] - ]; - - const handleClose = () => { - props.controller.close(props.instance); - console.log('关闭物品栏'); - }; - - return () => ( - - {{ - weapon: () => ( - - - - - - - ), - armor: () => , - potion: () => , - material: () => - }} - - ); -}); -``` - -### 列表在右侧的布局 - -```tsx -import { defineComponent, ref } from 'vue'; -import { ListPage } from '@user/client-modules'; - -export const RightListCom = defineComponent(() => { - const selected = ref('profile'); - - const menuItems = [ - ['profile', '个人资料'], - ['security', '安全设置'], - ['privacy', '隐私设置'], - ['notifications', '通知设置'] - ]; - - return () => ( - - {key => ( - - - - - )} - - ); -}); -``` - ---- - -## 注意事项 - -1. **插槽使用**: 可以使用具名插槽(以列表项 ID 为名)或默认插槽来定义内容 -2. **布局控制**: 通过 `basis` 控制列表区域比例,`right` 控制列表位置 -3. **关闭功能**: 设置 `close` 为 `true` 显示关闭按钮,通过 `closeLoc` 自定义位置 -4. **事件处理**: `close` 事件需要手动处理界面关闭逻辑 -5. **内容更新**: 切换列表选项时,右侧内容会自动更新为对应插槽的内容 diff --git a/docs/api/user-client-modules/组件 Page.md b/docs/api/user-client-modules/组件 Page.md deleted file mode 100644 index 01c0b4f..0000000 --- a/docs/api/user-client-modules/组件 Page.md +++ /dev/null @@ -1,192 +0,0 @@ -# Page 分页组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 组件描述 - -分页组件用于将大量内容分割为多个独立页面展示,支持通过编程控制或用户交互进行页面切换。适用于存档界面、多步骤表单等场景。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| -------------- | ---------------- | ----------------- | -------------------------------- | -| `pages` | `number` | 必填 | 总页数 | -| `loc` | `ElementLocator` | 必填 | 页码组件定位配置(坐标系及位置) | -| `font` | `Font` | `Font.defaults()` | 页码文本字体配置(可选) | -| `hideIfSingle` | `boolean` | `false` | 当总页数为 1 时是否隐藏页码组件 | - ---- - -## Events 事件说明 - -| 事件名 | 参数类型 | 触发时机 | -| ------------ | ---------------- | ------------------------------- | -| `pageChange` | `(page: number)` | 当前页码变化时触发(从 0 开始) | - ---- - -## Slots 插槽说明 - -### `default` - -接收当前页码(从 0 开始)并返回需要渲染的内容 -**参数** - -- `page: number` 当前页码索引(0-based) - ---- - -## Exposed Methods 暴露方法 - -| 方法名 | 参数 | 返回值 | 描述 | -| ------------ | --------------- | -------- | --------------------------------------------------- | -| `changePage` | `page: number` | `void` | 跳转到指定页码(0-based,自动约束在有效范围内) | -| `movePage` | `delta: number` | `void` | 基于当前页码进行偏移切换(如 +1 下一页,-1 上一页) | -| `now` | - | `number` | 获取当前页码索引(0-based) | - ---- - -## 使用示例 - -### 基础用法 - 多页文本展示 - -```tsx -import { defineComponent } from 'vue'; -import { Page, PageExpose } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - return () => ( - - {page => ( - - )} - - ); -}); -``` - -### 监听页面修改 - -```tsx -import { defineComponent, ref } from 'vue'; -import { Page, PageExpose } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - // 示例数据 - const pages = [ - { content: '第一页内容' }, - { content: '第二页内容' }, - { content: '第三页内容' } - ]; - - // 分页组件引用 - const pageRef = ref(); - - // 页码变化回调 - const handlePageChange = (currentPage: number) => { - // 可以使用参数获得当前页码,加一是因为页码是从 0 开始的 - console.log(`当前页码:${currentPage + 1}`); - // 或者也可以使用 Page 组件的接口获得当前页码 - console.log(`当前页码:${pageRef.value!.now() + 1}`); - }; - - return () => ( - - {page => } - - ); -}); -``` - -### 动态配置示例 - -```tsx -import { defineComponent, ref } from 'vue'; -import { Page, PageExpose } from '@user/client-modules'; - -// 带统计面板的复杂分页 -export const MyCom = defineComponent(() => { - const dataPages = [ - /* 复杂数据结构 */ - ]; - - // 暴露方法实现翻页逻辑 - const pageRef = ref(); - const jumpToAnalysis = () => pageRef.value?.changePage(3); // 1-based - - return () => ( - - {/* 分页内容 */} - - {page => ( - - {/* 这里面可以写一些复杂的渲染内容,或者单独写成一个组件,把页码作为参数传入 */} - - - - )} - - - {/* 自定义跳转按钮 */} - - - ); -}); -``` - -### 边缘检测示例 - -```tsx -import { defineComponent, ref } from 'vue'; - -// 边界处理逻辑 -export const MyCom = defineComponent(() => { - const pageRef = ref(); - - // 自定义边界提示 - const handleEdge = () => { - const current = pageRef.value?.now() ?? 0; - const total = pageRef.value?.pages ?? 0; - - // 到达边界时提示(可以换成其他提示方式) - if (current === 0) core.drawTip('已经是第一页'); - if (current === total - 1) core.drawTip('已经是最后一页'); - }; - - return () => ( - - {page => } - - ); -}); -``` - ---- - -## 注意事项 - -1. **自动约束**:切换页码时会自动约束在 `[0, pages-1]` 范围内 diff --git a/docs/api/user-client-modules/组件 Progress.md b/docs/api/user-client-modules/组件 Progress.md deleted file mode 100644 index 322da96..0000000 --- a/docs/api/user-client-modules/组件 Progress.md +++ /dev/null @@ -1,107 +0,0 @@ -# Progress 组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **动态进度显示**:支持 0.0~1.0 范围的进度可视化 -- **双色样式分离**:可分别定制已完成/未完成部分样式 -- **精准定位**:支持像素级坐标控制 -- **平滑过渡**:数值变化自动触发重绘 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------ | ---------------- | -------- | --------------------- | -| `loc` | `ElementLocator` | **必填** | 进度条容器坐标 | -| `progress` | `number` | **必填** | 当前进度值(0.0~1.0) | -| `success` | `CanvasStyle` | `green` | 已完成部分填充样式 | -| `background` | `CanvasStyle` | `gray` | 未完成部分填充样式 | -| `lineWidth` | `number` | `2` | 进度条线宽(像素) | - ---- - -## 使用示例 - -### 基础用法 - -```tsx -import { defineComponent, ref } from 'vue'; -import { Progress } from '@user/client-modules'; -import { onTick } from '@motajs/render'; - -export const MyCom = defineComponent(() => { - // 创建响应式进度值 - const loadingProgress = ref(0); - - // 模拟进度更新 - onTick(() => { - if (loadingProgress.value < 1) { - loadingProgress.value += 0.002; - } - }); - - return () => ( - - ); -}); -``` - -### 自定义样式 - -```tsx -// 自定义进度条的已完成和未完成部分的样式 - -``` - -### 垂直进度条 - -```tsx -// 通过旋转容器实现垂直效果,注意锚点的使用 - - - -``` - ---- - -## 动画效果实现 - -### 平滑过渡示例 - -```tsx -import { transitioned } from '@user/client-modules'; -import { pow } from 'mutate-animate'; - -const progressValue = transitioned(0, 2000, pow(2, 'out')); -progressValue.set(1); // 2秒内完成二次曲线平滑过渡 - -return () => ( - -); -``` - ---- - -## 注意事项 - -1. **坐标系统** - 实际渲染高度由 `loc[3]` 参数控制,会自动上下居中: - - ```tsx - // 情况1:显式指定高度为8px - - ``` diff --git a/docs/api/user-client-modules/组件 Scroll.md b/docs/api/user-client-modules/组件 Scroll.md deleted file mode 100644 index 0a08125..0000000 --- a/docs/api/user-client-modules/组件 Scroll.md +++ /dev/null @@ -1,129 +0,0 @@ -# Scroll 滚动组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 组件特性 - -- **虚拟滚动**:自动裁剪可视区域外元素 -- **双模式支持**:垂直/水平滚动(默认垂直) -- **性能优化**:动态计算可视区域,支持万级元素流畅滚动 -- **编程控制**:支持精准定位滚动位置 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ---------- | ---------------- | ------- | ------------------------------------------------- | -| `hor` | `boolean` | `false` | 启用水平滚动模式 | -| `noscroll` | `boolean` | `false` | 是否不显示滚动条,可用于一些特殊场景 | -| `loc` | `ElementLocator` | 必填 | 滚动容器定位配置 | -| `padEnd` | `number` | `0` | 滚动到底部/右侧的额外留白(用于修正自动计算误差) | - ---- - -## Exposed Methods 暴露方法 - -| 方法名 | 参数 | 返回值 | 描述 | -| ----------------- | --------------------------------- | -------- | --------------------------------------------------- | -| `scrollTo` | `position: number, time?: number` | `void` | 滚动到指定位置(单位:像素),time 为过渡时间(ms) | -| `getScrollLength` | - | `number` | 获取最大可滚动距离(单位:像素) | - ---- - -## Slots 插槽说明 - -### `default` - -接收滚动内容,**必须直接包含可渲染元素** -⚠️ 禁止嵌套 container 包裹,推荐平铺结构: - -```tsx -// ✅ 正确写法 - - - - -; - -// ❌ 错误写法(影响虚拟滚动计算) - - - // 会导致整体被视为单个元素 - - - -; -``` - ---- - -## 使用示例 - -### 基础垂直滚动 - -```tsx -import { defineComponent } from 'vue'; - -export const MyCom = defineComponent(() => { - const list = Array(200).fill(0); - - return () => ( - - {list.map((_, index) => ( - - ))} - - ); -}); -``` - -### 水平滚动 + 编程控制 - -```tsx -import { defineComponent, ref, onMounted } from 'vue'; - -export const MyCom = defineComponent(() => { - const list = Array(200).fill(0); - const scrollRef = ref(); - - // 滚动水平 100 像素位置,动画时长 500 毫秒 - onMounted(() => { - scrollRef.value?.scrollTo(100, 500); - }); - - return () => ( - - {list.map((_, index) => ( - - ))} - - ); -}); -``` - ---- - -## 性能优化指南 - -### 1. 替代方案建议 - -⚠️ **当子元素数量 > 1000 时**,推荐改用分页组件: - -```tsx -// 使用 Page 组件处理超大数据集 - - {page => renderChunk(data.slice(page * 50, (page + 1) * 50))} - -``` - -### 2. 缓存建议 - -如果子元素数量较多,建议给 `Scroll` 组件的所有子元素添加 `nocache` 标记: - -```tsx {2} - - - {/* 更多内容 */} - -``` diff --git a/docs/api/user-client-modules/组件 ScrollText.md b/docs/api/user-client-modules/组件 ScrollText.md deleted file mode 100644 index cc9e7ea..0000000 --- a/docs/api/user-client-modules/组件 ScrollText.md +++ /dev/null @@ -1,160 +0,0 @@ -# ScrollText 组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **自动滚动**:支持设定滚动速度的纵向滚动效果 -- **长文本支持**:内置高性能文本渲染引擎 -- **精准控制**:提供播放/暂停/调速等操作接口 -- **智能布局**:自动计算文本高度和滚动距离 - ---- - -## Props 属性说明 - -```mermaid -graph LR - ScrollTextProps --> TextContentProps - ScrollTextProps --> ScrollProps - - click TextContentProps "./组件%20TextContent" - click ScrollProps "./组件%20Scroll" -``` - -完全继承 `TextContent` 组件和 `Scroll` 组件的参数和事件。 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------- | ---------------- | ------ | --------------------------- | -| `speed` | `number` | 必填 | 滚动速度(像素/秒) | -| `width` | `number` | 必填 | 文本区域固定宽度(像素) | -| `loc` | `ElementLocator` | 必填 | 容器定位 [x,y,width,height] | -| `pad` | `number` | `16` | 首行前空白距离(像素) | - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| ----------- | ---- | -------------------- | -| `scrollEnd` | - | 滚动到文本末尾时触发 | - ---- - -## Exposed Methods 暴露方法 - -| 方法名 | 参数 | 返回值 | 描述 | -| ---------- | --------------- | ------ | --------------------------- | -| `pause` | - | void | 暂停滚动 | -| `resume` | - | void | 继续滚动 | -| `setSpeed` | `speed: number` | void | 动态调整滚动速度(像素/秒) | -| `rescroll` | - | void | 立即重置到起始位置重新滚动 | - ---- - -## 使用示例 - -### 基础滚动 - -```tsx -import { defineComponent } from 'vue'; -import { ScrollText } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const longText = '序幕\n'.repeat(100) + '——全剧终——'; - - return () => ( - - ); -}); -``` - -### 动态控制 - -```tsx -import { defineComponent, ref } from 'vue'; -import { ScrollText } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const longText = '序幕\n'.repeat(100) + '——全剧终——'; - const scrollRef = ref(); - - // 暂停/恢复控制 - const toggleScroll = () => { - if (scrollRef.value?.isPaused) { - scrollRef.value.resume(); - } else { - scrollRef.value?.pause(); - } - }; - - // 速度控制 - const accelerate = () => { - scrollRef.value?.setSpeed(200); - }; - - return () => ( - console.log('滚动结束')} - /> - ); -}); -``` - -### 复杂排版 - -```tsx -const staffText = - '\\c[32]====制作人员====\\c\n\n' + - '\\r[#FFD700]总监督\\r\t\t张三\n' + - '\\r[#00FF00]美术指导\\r\\t李四\n' + - '\\i[logo]\n' + - '特别感谢:某某公司'; - -; -``` - ---- - -## 注意事项 - -1. **容器尺寸** - 实际可滚动区域计算公式: - - ``` - 可视高度 = loc[3](容器高度) - 滚动距离 = 文本总高度 + pad(首行前空白) - ``` - -2. **速度控制** - 推荐速度范围 50-200 像素/秒 - -3. **组合动画** - 可与容器变换配合实现复杂效果: - ```tsx - - - - ``` diff --git a/docs/api/user-client-modules/组件 Selection.md b/docs/api/user-client-modules/组件 Selection.md deleted file mode 100644 index 3a86afd..0000000 --- a/docs/api/user-client-modules/组件 Selection.md +++ /dev/null @@ -1,77 +0,0 @@ -# Selection 组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **动态高亮**:自动呼吸动画效果 -- **双样式模式**:支持图片皮肤或纯色样式 -- **精准定位**:像素级坐标控制 -- **透明度动画**:可定制不透明度变化范围 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ------------ | ------------------ | -------------- | --------------------------------- | -| `loc` | `ElementLocator` | **必填** | 光标定位 | -| `winskin` | `ImageIds` | - | 图片资源 ID(优先级最高) | -| `color` | `CanvasStyle` | `#ddd` | 填充颜色(无皮肤时生效) | -| `border` | `CanvasStyle` | `gold` | 边框颜色(无皮肤时生效) | -| `alphaRange` | `[number, number]` | `[0.25, 0.55]` | 不透明度波动范围 [最小值, 最大值] | - ---- - -## 使用示例 - -### 图片皮肤模式 - -```tsx -// 使用预加载的游戏皮肤资源 - -``` - ---- - -### 纯色模式 - -```tsx -// 自定义颜色方案 - -``` - ---- - -## 注意事项 - -1. **样式优先级** - 同时指定 `winskin` 和颜色参数时: - - ```tsx - // 以下配置将忽略 color/border 参数 - - ``` - -2. **动画速度** - 呼吸动画固定为 2000ms/周期,暂不支持自定义时长 - -3. **点击反馈** - 建议配合事件系统实现点击效果: - ```tsx - - - - - ``` diff --git a/docs/api/user-client-modules/组件 TextContent.md b/docs/api/user-client-modules/组件 TextContent.md deleted file mode 100644 index 1dbe2c4..0000000 --- a/docs/api/user-client-modules/组件 TextContent.md +++ /dev/null @@ -1,184 +0,0 @@ -# TextContent 文本组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **自动布局**:根据宽度自动换行 -- **样式控制**:支持动态修改字体/颜色/描边 -- **打字机效果**:逐字显示支持 -- **动态高度**:自适应或固定高度模式 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ----------------- | ------------- | ----------------- | ------------------------------ | -| `text` | `string` | - | 显示文本(支持转义字符) | -| `width` | `number` | 必填 | 文本区域宽度(像素) | -| `fill` | `boolean` | `true` | 是否对文字填充 | -| `stroke` | `boolean` | `false` | 是否对文字描边 | -| `font` | `Font` | 系统默认字体 | 字体配置对象 | -| `lineHeight` | `number` | `0` | 行间距(像素) | -| `interval` | `number` | `50` | 打字机字符间隔(ms) | -| `autoHeight` | `boolean` | `false` | 是否根据内容自动调整高度 | -| `fillStyle` | `CanvasStyle` | `#fff` | 文字填充颜色 | -| `strokeStyle` | `CanvasStyle` | `#000` | 文字描边颜色 | -| `strokeWidth` | `number` | `1` | 描边宽度 | -| `textAlign` | `TextAlign` | `TextAlign.Left` | 文字对齐方式 | -| `wordBreak` | `WordBreak` | `WordBreak.Space` | 文本分词原则,将会影响换行表现 | -| `breakChars` | `string` | - | 会被分词规则识别的分词字符 | -| `ignoreLineStart` | `string` | - | 不允许出现在行首的字符 | -| `ignoreLineEnd` | `string` | - | 不允许出现在行尾的字符 | - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| -------------- | ---------------- | ------------------ | -| `typeStart` | - | 开始逐字显示时 | -| `typeEnd` | - | 全部文字显示完成时 | -| `updateHeight` | `height: number` | 文本高度变化时 | - ---- - -## Exposed Methods 暴露方法 - -| 方法名 | 参数 | 返回值 | 描述 | -| ----------- | ---- | -------- | ---------------------------- | -| `retype` | - | `void` | 从头开始重新打字 | -| `showAll` | - | `void` | 立刻结束打字机,显示所有文字 | -| `getHeight` | - | `number` | 获得当前文本的高度 | - ---- - -## 使用示例 - -### 基础用法 - 对话文本 - -```tsx -import { defineComponent } from 'vue'; -import { TextContent } from '@user/client-modules'; -import { Font } from '@motajs/render'; - -export const MyCom = defineComponent(() => { - return () => ( - console.log('显示完成')} // 打字机结束后执行 - /> - ); -}); -``` - -### 自定义样式 + 描边效果 - -```tsx -import { defineComponent } from 'vue'; -import { TextContent } from '@user/client-modules'; -import { Font } from '@motajs/render'; - -export const MyCom = defineComponent(() => { - return () => ( - - ); -}); -``` - -### 动态内容更新 - -```tsx -import { defineComponent, ref } from 'vue'; -import { TextContent } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const dynamicText = ref('初始内容'); - - setTimeout(() => { - dynamicText.value = '更新后的内容\\z[5]带暂停效果'; - }, 2000); - - return () => ( - console.log('当前高度:', h)} // 当高度发生变化时触发 - /> - ); -}); -``` - -### 禁用动画效果 - -```tsx -import { defineComponent } from 'vue'; -import { TextContent } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - return () => ( - - ); -}); -``` - -### 多语言复杂排版 - -```tsx -import { defineComponent } from 'vue'; -import { TextContent } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const complexText = - '\\g[Times New Roman]Hello\\g[宋体] 你好 \\i[flag]\\n' + - '\\r[#FF5733]Multi\\r[#3498db]-\\r[#2ECC71]Color\\r\\n' + - '\\c[18]Small\\c[24]Size\\c[30]Changes'; - - return () => ( - console.log('开始渲染复杂文本')} - /> - ); -}); -``` - ---- - -## 转义字符示例 - -```tsx -// 颜色/字体/图标综合使用 -const styledText = - '\\r[#FF0000]警告!\\g[方正粗宋]\\c[24]' + - '\\i[warning_icon]发现异常\\z[10]\\n' + - '请立即处理\\r\\g\\c'; - -; -``` - -转义字符具体用法参考 [TextContentParser](./TextContentParser.md#转义字符语法说明) diff --git a/docs/api/user-client-modules/组件 Textbox.md b/docs/api/user-client-modules/组件 Textbox.md deleted file mode 100644 index c0a2e44..0000000 --- a/docs/api/user-client-modules/组件 Textbox.md +++ /dev/null @@ -1,173 +0,0 @@ -# Textbox 对话框组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -本文档描述在 `TextContent` 基础上扩展的对话框组件,专为剧情对话、系统提示等场景设计,支持背景、标题等装饰元素。 - ---- - -## 特有属性说明 - -```mermaid -graph LR - TextboxProps --> TextContentProps - - click TextContentProps "./组件%20TextContent" -``` - -| 属性名 | 类型 | 默认值 | 描述 | -| -------------- | ------------- | -------------- | ------------------------------------- | -| `backColor` | `CanvasStyle` | `#222` | 背景颜色(与 `winskin` 互斥) | -| `winskin` | `ImageIds` | - | 背景图片资源 ID(优先于 `backColor`) | -| `padding` | `number` | `8` | 内容区域与边框的内边距(像素) | -| `title` | `string` | - | 标题文本内容 | -| `titleFont` | `Font` | `18px Verdana` | 标题字体配置 | -| `titleFill` | `CanvasStyle` | `gold` | 标题文字颜色 | -| `titleStroke` | `CanvasStyle` | `transparent` | 标题描边颜色 | -| `titlePadding` | `number` | `8` | 标题在其背景的间距(像素) | - ---- - -## 事件说明 - -```mermaid -graph LR - TextboxEmits --> TextContentEmits - - click TextContentEmits "./组件%20TextContent" -``` - -完全继承 `TextContent` 的事件。 - ---- - -## Exposed Methods 暴露方法 - -| 方法名 | 参数 | 返回值 | 描述 | -| --------- | ---- | ------ | ---------------------------- | -| `retype` | - | `void` | 从头开始重新打字 | -| `showAll` | - | `void` | 立刻结束打字机,显示所有文字 | -| `show` | - | `void` | 显示这个文本框 | -| `hide` | - | `void` | 隐藏这个文本框 | - ---- - -## Slots 插槽说明 - -### default - -背景插槽,传入后可以自定义背景 - -```tsx -// 例如使用一张图片作为背景 - - - - - -``` - -### title - -标题背景插槽,自定义标题背景 - -```tsx -// 与 default 一起使用 - - {{ - // 背景图 - default: () => , - // 标题背景图 - title: () => - }} - -``` - -## 使用示例 - -### 基础对话框 - -```tsx -import { defineComponent } from 'vue'; -import { Textbox } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - return () => ( - - ); -}); -``` - -### 纯色背景 + 复杂标题 - -```tsx -import { defineComponent } from 'vue'; -import { Textbox } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - return () => ( - - ); -}); -``` - -### 动态标题交互 - -```tsx -import { defineComponent } from 'vue'; -import { Textbox, TextbosExpose } from '@user/client-modules'; - -export const MyCom = defineComponent(() => { - const currentTitle = ref(); - - // 点击按钮切换标题 - const toggleTitle = () => { - currentTitle.value += 1; - }; - - return () => ( - - - - - ); -}); -``` - ---- - -## 布局结构示意图 - -```mermaid -graph TB - Dialog[对话框] --> Background[背景层] - Dialog --> Title[标题层] - Dialog --> Content[内容层] - - Background -->|winskin/backColor| 渲染背景 - Title -->|title 配置| 标题文本 - Content -->|padding 控制| 文字内容 -``` diff --git a/docs/api/user-client-modules/组件 Thumbnail.md b/docs/api/user-client-modules/组件 Thumbnail.md deleted file mode 100644 index e26c3eb..0000000 --- a/docs/api/user-client-modules/组件 Thumbnail.md +++ /dev/null @@ -1,119 +0,0 @@ -# Thumbnail 地图缩略图组件 API 文档 - -本文档由 `DeepSeek` 生成并微调。 - -## 组件描述 - -地图缩略图组件用于在游戏界面中显示当前楼层的迷你地图,可展示地图布局、角色位置、伤害区域等信息。适用于小地图显示、地图预览等场景。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 默认值 | 描述 | -| ---------- | ---------------- | ------ | -------------------------------------- | -| `loc` | `ElementLocator` | 必填 | 缩略图的位置和尺寸 | -| `floorId` | `FloorIds` | 必填 | 楼层 ID | -| `padStyle` | `CanvasStyle` | - | 缩略图填充样式 | -| `map` | `Block[]` | - | 楼层信息 | -| `hero` | `HeroStatus` | - | 角色信息 | -| `damage` | `boolean` | - | 是否显示地图伤害 | -| `all` | `boolean` | - | 是否完全展示地图(false 时只显示部分) | -| `noHD` | `boolean` | - | 是否使用高清模式 | -| `size` | `number` | - | 缩略图的比例(1 表示与实际地图一致) | - ---- - -## Events 事件说明 - -无事件 - ---- - -## Slots 插槽说明 - -无插槽 - ---- - -## Exposed Methods 暴露方法 - -无暴露方法 - ---- - -## 使用示例 - -### 基础用法 - 显示当前楼层缩略图 - -```tsx -import { defineComponent } from 'vue'; -import { Thumbnail } from '@user/client-modules'; - -export const MiniMapCom = defineComponent(() => { - return () => ( - - ); -}); -``` - -### 带角色位置的小地图 - -```tsx -import { defineComponent } from 'vue'; -import { Thumbnail } from '@user/client-modules'; - -export const GameHUDCom = defineComponent(props => { - const heroStatus: HeroStatus = { - // 角色状态信息 - loc: { x: 100, y: 150 } - // ... 其他角色属性 - }; - - return () => ( - - - - - ); -}); -``` - -### 完整地图预览 - -```tsx -import { defineComponent } from 'vue'; -import { Thumbnail } from '@user/client-modules'; - -export const MapPreviewCom = defineComponent(() => { - return () => ( - - ); -}); -``` - ---- - -## 注意事项 - -1. **楼层标识**: `floorId` 必须与游戏中的楼层标识匹配 -2. **比例控制**: `size` 参数控制缩略图与实际地图的比例关系 -3. **显示范围**: `all` 为 false 时,大地图只会显示当前可视区域 diff --git a/docs/api/user-client-modules/组件 Tip.md b/docs/api/user-client-modules/组件 Tip.md deleted file mode 100644 index 1eae585..0000000 --- a/docs/api/user-client-modules/组件 Tip.md +++ /dev/null @@ -1,65 +0,0 @@ -# Tip 组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - -## 参数说明(Props) - -| 参数名 | 类型 | 默认值 | 说明 | -| -------- | ------------------ | -------- | ---------------------------------------- | -| `loc` | `ElementLocator` | 必填 | 容器基础定位参数 [x,y,width,height] | -| `pad` | `[number, number]` | `[4,4]` | 图标与文字的边距配置 [水平边距,垂直边距] | -| `corner` | `number` | `4` | 圆角矩形的圆角半径 | -| `id` | `string` | 自动生成 | 提示框唯一标识(需全局唯一) | - -## 暴露接口(Expose) - -| 方法名 | 参数 | 返回值 | 说明 | -| --------- | ----------------------------------------------- | ------ | --------------------------------------------------- | -| `drawTip` | `text: string`
`icon?: AllIds \| AllNumbers` | `void` | 显示带图标的提示文本(图标支持字符串 ID 或数字 ID) | - -## 使用示例 - -```tsx -import { defineComponent } from 'vue'; -import { Tip } from './tip'; - -// 在游戏界面中定义提示组件 -export const MyCom = defineComponent(() => { - return () => ( - - - - ); -}); - -// 在业务代码中调用提示,使用 TipStore 类 -const tip = TipStore.get('global-tip'); -tip?.drawTip('宝箱已解锁!', 'chest_icon'); -``` - -## 特性说明 - -1. **自动布局**: - - - 根据图标尺寸自动计算容器宽度 - - 文字垂直居中显示 - - 图标与文字间距自动适配 - -2. **动画效果**: - - - 默认带有 500ms 双曲正弦缓动的淡入动画 - - 3 秒无操作后自动淡出 - - 支持通过`alpha`参数自定义过渡效果 - -## 注意事项 - -1. **全局单例**:建议通过 `TipStore` 进行全局管理,避免重复创建 -2. **ID 唯一性**:未指定 `id` 时会自动生成格式为 `@default-tip-数字` 的标识 -3. **自动隐藏**:调用 `drawTip` 后 3 秒自动隐藏,连续调用会重置计时 -4. **性能优化**:使用 `lodash` 的 `debounce` 进行隐藏操作防抖 -5. **动画配置**:可通过修改 `hyper('sin', 'in-out')` 参数调整动画曲线 diff --git a/docs/api/user-client-modules/组件 WaitBox.md b/docs/api/user-client-modules/组件 WaitBox.md deleted file mode 100644 index 9cd2d94..0000000 --- a/docs/api/user-client-modules/组件 WaitBox.md +++ /dev/null @@ -1,104 +0,0 @@ -# WaitBox 等待框组件 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心特性 - -- **Promise 绑定**:自动监控 `Promise` 状态 -- **复合式组件**:集成背景+文字+加载动画 -- **双重控制**:支持自动/手动完成等待 -- **动态布局**:根据内容自动计算高度 - ---- - -## 组件定位 - -> 💡 更推荐使用 `waitbox` 工具函数,该组件主要用于需要深度定制的场景。参考 [此文档](./functions.md#waitbox)。 - ---- - -## Props 属性说明 - -| 属性名 | 类型 | 必填 | 描述 | -| --------- | ---------------- | ---- | ----------------------- | -| `promise` | `Promise` | 否 | 要监控的 `Promise` 对象 | -| `loc` | `ElementLocator` | 是 | 容器定位 | -| `width` | `number` | 是 | 内容区域宽度(像素) | -| `text` | `string` | 否 | 等待提示文字 | -| `pad` | `number` | `16` | 文字与边缘间距 | - -### 继承属性 - -- 支持所有 `Background` 背景属性 -- 支持所有 `TextContent` 文本属性 - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| --------- | ---- | -------------------------- | -| `resolve` | `T` | `Promise` 完成时返回结果值 | - ---- - -## Exposed Methods 暴露方法 - -| 方法名 | 参数 | 描述 | -| --------- | --------- | ---------------------------- | -| `resolve` | `data: T` | 手动完成等待(立即触发事件) | - ---- - -## 使用示例 - -### 基础组件用法 - -```tsx -// 等待网络请求 -const fetchPromise = fetchData(); - - console.log('收到数据:', data)} -/>; -``` - -### 手动控制示例 - -```tsx -const waitRef = ref>(); - -// 手动结束等待 -const forceComplete = () => { - waitRef.value?.resolve(Date.now()); -}; - -return () => ( - -); -``` - ---- - -## 注意事项 - -1. **推荐用法** - 90% 场景应使用 `waitbox` 函数,以下情况才需要直接使用组件: - - - 需要永久显示的等待界面 - - 需要组合复杂子组件 - - 需要复用同一个等待实例 diff --git a/docs/api/user-data-base/index.md b/docs/api/user-data-base/index.md deleted file mode 100644 index ae599ee..0000000 --- a/docs/api/user-data-base/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/data-base - -目录: diff --git a/docs/api/user-data-base/钩子.md b/docs/api/user-data-base/钩子.md deleted file mode 100644 index 4fc989b..0000000 --- a/docs/api/user-data-base/钩子.md +++ /dev/null @@ -1,97 +0,0 @@ -# 游戏事件钩子 API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## GameLoading 加载进度钩子 - -### 核心事件说明 - -| 事件名 | 触发时机 | 参数 | -| ------------------ | -------------------------- | ---- | -| `coreLoaded` | 核心脚本加载完成时 | 无 | -| `autotileLoaded` | 所有自动元件资源加载完成时 | 无 | -| `coreInit` | 核心类初始化完成时 | 无 | -| `loaded` | 所有启动必要资源加载完成时 | 无 | -| `registered` | 客户端和数据端都完成挂载时 | 无 | -| `dataRegistered` | 数据端服务挂载完成时 | 无 | -| `clientRegistered` | 渲染端挂载完成时 | 无 | - ---- - -### 使用示例 - -```typescript -import { loading } from '@user/data-base'; - -// 监听核心初始化事件 -loading.on('coreInit', () => { - console.log('核心系统已就绪'); - initializeCustomModules(); -}); - -// 监听完整加载事件 -loading.once('loaded', () => { - showMainMenu(); - preloadOptionalAssets(); -}); -``` - ---- - -## GameEvent 游戏运行时钩子 - -### 核心事件说明 - -| 事件名 | 触发时机 | 参数 | -| ------------------ | ------------------------------------ | ------------------------------------------------------------------------------- | -| `reset` | 游戏初始化时,例如读档后、进入游戏后 | 无 | -| `mounted` | 游戏 DOM 挂载完成后 | 无 | -| `statusBarUpdate` | 状态栏更新时 | 无 | -| `renderLoaded` | 渲染端加载完成时 | 无 | -| `afterGetItem` | 拾取道具后 | `[itemId: 道具ID, x: 坐标X, y: 坐标Y, isGentleClick: 是否轻击]` | -| `afterOpenDoor` | 成功开门后 | `[doorId: 门动画ID, x: 坐标X, y: 坐标Y]` | -| `afterChangeFloor` | 楼层切换完成后 | `[floorId: 新楼层ID]` | -| `moveOneStep` | 玩家移动一步后 | `[x: 新坐标X, y: 新坐标Y, floorId: 当前楼层ID]` | -| `afterBattle` | 战斗结算完成后 | `[enemy: 敌人数据对象, x?: 战斗坐标X, y?: 战斗坐标Y]` | -| `changingFloor` | 楼层切换过程中(动画播放时) | `[floorId: 目标楼层ID, heroLoc: 玩家位置对象]` | -| `setBlock` | 地图图块被修改时 | `[x: 坐标X, y: 坐标Y, floorId: 楼层ID, newBlock: 新图块值, oldBlock: 旧图块值]` | -| `enemyExtract` | 解析敌人数据时 | `[col: 敌人集合对象]` | -| `restart` | 从游戏返回标题界面时 | 无 | -| `setBgFgBlock` | 设置背景/前景图块时 | `[name: 图层名称, number: 图块值, x: 坐标X, y: 坐标Y, floorId: 楼层ID]` | -| `replayStatus` | 录像播放状态切换时 | `[replaying: 是否正在回放]` | -| `loadData` | 加载存档时 | 无 | - ---- - -### 使用示例 - -```typescript -// 监听玩家移动事件 -hook.on('moveOneStep', (x, y, floorId) => { - console.log(x, y, floorId); -}); - -// 监听战斗结束事件 -hook.on('afterBattle', (enemy, x, y) => { - console.log(enemy, x, y); -}); - -// 监听存档加载事件 -hook.once('loadData', () => { - console.log('读档成功!'); -}); -``` - ---- - -## 弃用说明 - -```typescript -/** - * @deprecated 自 v2.B 起废弃的 GameListener - * 计划在 v2.C 移除,请使用新的 UI 交互系统代替 - */ -export const gameListener = new GameListener(); -``` diff --git a/docs/api/user-data-fallback/index.md b/docs/api/user-data-fallback/index.md deleted file mode 100644 index 259983b..0000000 --- a/docs/api/user-data-fallback/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/data-fallback - -目录: diff --git a/docs/api/user-data-state/ObjectMoverBase.md b/docs/api/user-data-state/ObjectMoverBase.md deleted file mode 100644 index 09400bd..0000000 --- a/docs/api/user-data-state/ObjectMoverBase.md +++ /dev/null @@ -1,424 +0,0 @@ -# ObjectMoverBase API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 类描述 - -游戏中可移动对象的基类控制器,提供面向方向、移动队列管理和动画协调的通用移动能力。继承自 EventEmitter3,用于实现图块、角色等元素的移动控制。 - -```mermaid -graph LR - ObjectMoverBase --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - ---- - -## 核心属性 - -| 属性名 | 类型 | 说明 | -| ------------ | ----------------- | ------------------------ | -| `moveSpeed` | `number` | 当前移动速度(毫秒/格) | -| `moveDir` | `Dir2` | 当前移动方向(八方向) | -| `moving` | `boolean` | 是否处于移动状态 | -| `faceDir` | `Dir2` | 当前面朝方向(八方向) | -| `controller` | `IMoveController` | 当前移动控制实例(只读) | - ---- - -## 事件说明 - -| 事件名 | 参数 | 触发时机 | -| ----------- | ------------- | ------------------ | -| `stepEnd` | `MoveStepDir` | 单步移动完成时 | -| `moveEnd` | - | 整个移动队列完成时 | -| `moveStart` | `MoveStep[]` | 移动队列开始执行时 | - ---- - -## 方法说明 - -### `startMove` - -```typescript -function startMove(): IMoveController | null; -``` - -**功能** -启动移动队列执行 - -**返回值** -`IMoveController`:移动控制器实例(可追加指令) -`null`:队列为空或已在移动中时返回 - -**示例** - -```typescript -const controller = mover.startMove(); -if (controller) { - controller.push({ type: 'dir', value: 'right' }); -} -``` - ---- - -### `insertMove` - -```typescript -function insertMove(...move: MoveStep[]): void; -``` - -| 参数 | 类型 | 说明 | -| ------ | ------------ | ------------ | -| `move` | `MoveStep[]` | 移动指令序列 | - -**功能** -向队列末尾插入移动指令 - -**示例** - -```typescript -// 添加转向+加速指令 -mover.insertMove({ type: 'dir', value: 'left' }, { type: 'speed', value: 200 }); -``` - ---- - -### `clearMoveQueue` - -```typescript -function clearMoveQueue(): void; -``` - -**功能** -清空所有待执行移动指令 - -**注意** -不影响已开始的移动步骤 - ---- - -### `oneStep` - -```typescript -function oneStep(step: Move2): void; -``` - -| 参数 | 类型 | 说明 | -| ------ | ------- | --------------------------------- | -| `step` | `Move2` | 移动方向(支持八向/前后相对方向) | - -**功能** -添加单步方向移动指令 - -**示例** - -```typescript -// 添加面朝方向移动指令 -mover.oneStep('forward'); -``` - ---- - -### `moveAs` - -```typescript -function moveAs(steps: MoveStep[]): void; -``` - -| 参数 | 类型 | 说明 | -| ------- | ------------ | ------------------ | -| `steps` | `MoveStep[]` | 结构化移动指令序列 | - -**功能** -批量加载复杂移动路径 - -**示例** - -```typescript -mover.moveAs([ - { type: 'dir', value: 'up' }, // 向上移动 - { type: 'speed', value: 150 }, // 修改速度为每 150ms 移动一格 - { type: 'dir', value: 'rightup' } // 右上45度移动 -]); -``` - ---- - -### `setFaceDir` - -```typescript -function setFaceDir(dir: Dir2): void; -``` - -| 参数 | 类型 | 说明 | -| ----- | ------ | -------------- | -| `dir` | `Dir2` | 八方向面朝方向 | - -**限制** -仅在非移动状态生效 - -**示例** - -```typescript -// 设置角色面朝左上方 -mover.setFaceDir('leftup'); -``` - ---- - -### `setMoveDir` - -```typescript -function setMoveDir(dir: Dir2): void; -``` - -| 参数 | 类型 | 说明 | -| ----- | ------ | ------------ | -| `dir` | `Dir2` | 基础移动方向 | - -**注意** -影响`forward/backward`指令的实际方向 - ---- - -## 抽象方法 - -### `abstract onMoveStart` - -```typescript -function onMoveStart(controller: IMoveController): Promise; -``` - -**触发时机** -移动队列开始执行时 - ---- - -### `abstract onMoveEnd` - -```typescript -function onMoveEnd(controller: IMoveController): Promise; -``` - -**触发时机** -移动队列完成或被中断时 - ---- - -### `abstract onStepStart` - -```typescript -function onStepStart( - step: MoveStepDir, - controller: IMoveController -): Promise; -``` - -| 参数 | 类型 | 说明 | -| ------------ | ----------------- | ------------ | -| `step` | `MoveStepDir` | 当前移动步骤 | -| `controller` | `IMoveController` | 移动控制器 | - -**返回值** -`Promise`:步骤执行标识码(用于后续传递) - ---- - -### `abstract onStepEnd` - -```typescript -function onStepEnd( - step: MoveStepDir, - code: number, - controller: IMoveController -): Promise; -``` - -| 参数 | 类型 | 说明 | -| ------------ | ----------------- | -------------------------- | -| `code` | `number` | `onStepStart` 返回的标识码 | -| `controller` | `IMoveController` | 移动控制器 | - ---- - -### `abstract onSetMoveSpeed` - -```typescript -function onSetMoveSpeed(speed: number, controller: IMoveController): void; -``` - -| 参数 | 类型 | 说明 | -| ------------ | ----------------- | -------------- | -| `speed` | `number` | 新的移动速度值 | -| `controller` | `IMoveController` | 移动控制器 | - ---- - -## BlockMover - -`BlockMover` 是基于 `ObjectMoverBase` 的内置类,用于实现图块移动。 - -```mermaid -graph LR - BlockMover --> ObjectMoverBase - ObjectMoverBase --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -### 新增方法 - -```typescript -function bind( - x: number, - y: number, - floorId: FloorIds, - layer: FloorLayer, - dir: Dir = 'down' -): boolean; -``` - -| 参数 | 类型 | 说明 | -| --------- | ------------ | -------------------- | -| `x` | `number` | 图块 X 坐标 | -| `y` | `number` | 图块 Y 坐标 | -| `floorId` | `FloorIds` | 所在楼层 ID | -| `layer` | `FloorLayer` | 图层类型(bg/fg 等) | -| `dir` | `Dir` | 初始方向 | - -**返回值**:绑定成功返回 `true`,若目标正在移动则返回 `false` - -**示例** - -```typescript -if (blockMover.bind(5, 8, 'floor1', 'bg', 'up')) { - blockMover.insertMove({ type: 'dir', value: 'right' }); -} -``` - ---- - -## HeroMover - -`HeroMover` 是基于 `ObjectMoverBase` 的内置类,用于实现勇士移动。 - -```mermaid -graph LR - HeroMover --> ObjectMoverBase - ObjectMoverBase --> EventEmitter - - click EventEmitter "https://nodejs.org/api/events.html#class-eventemitter" -``` - -### 覆盖方法 - -```ts -function startMove( - ignoreTerrain: boolean = false, - noRoute: boolean = false, - inLockControl: boolean = false, - autoSave: boolean = false -): IMoveController | null; -``` - -| 参数 | 说明 | -| --------------- | ---------------------------------------- | -| `ignoreTerrain` | 是否忽略地形,即是否穿墙 | -| `noRoute` | 是否不计入录像 | -| `inLockControl` | 是否是在锁定控制中移动的,例如事件中移动 | -| `autoSave` | 在必要时刻是否自动存档 | - -其余用法与基类相同。 - ---- - -## 使用示例 - -### 勇士移动控制 - -```typescript -import { heroMoverCollection } from '@user/data-state'; - -// 获取勇士移动控制器单例 -const heroMover = heroMoveCollection.mover; - -// 设置面朝方向为右侧 -heroMover.setFaceDir('right'); - -// 添加移动指令:前进三步 -heroMover.insertMove( - { type: 'dir', value: 'forward' }, - { type: 'dir', value: 'forward' }, - { type: 'dir', value: 'forward' } -); - -// 启动移动并获取控制器 -const controller = heroMover.startMove( - false, // 不允许穿墙 - true, // 不计入录像 - false, // 不在录像锁定中触发 - false // 不进行自动存档 -); - -if (controller) { - // 动态追加移动指令 - controller.push({ type: 'dir', value: 'leftup' }); - // 监听移动完成事件 - controller.onEnd.then(() => { - console.log('勇士移动完成'); - }); -} -``` - -### 图块移动控制 - -```typescript -import { BlockMover } from '@user/data-state'; - -// 创建图块移动器实例 -const blockMover = new BlockMover(); - -// 绑定到(5,8)位置的背景图块 -if (blockMover.bind(5, 8, 'floor1', 'bg', 'up')) { - // 添加螺旋移动路径 - blockMover.moveAs([ - { type: 'dir', value: 'right' }, - { type: 'dir', value: 'down' }, - { type: 'dir', value: 'left' }, - { type: 'dir', value: 'up' } - ]); - - // 设置移动速度为200像素/秒 - blockMover.insertMove({ type: 'speed', value: 200 }); - - // 启动移动 - const ctrl = blockMover.startMove(); -} -``` - ---- - -## 移动指令类型 - -```typescript -type MoveStep = - | { type: 'dir'; value: Move2 } // 方向指令 - | { type: 'speed'; value: number }; // 速度指令 -``` - ---- - -## 注意事项 - -1. **方向优先级** - `forward/backward` 基于当前面朝方向计算,修改 faceDir 会影响实际移动方向 - -2. **速度叠加规则** - 多个 speed 指令按队列顺序覆盖,最终生效最后一个速度值 - -3. **移动中断处理** - 调用 controller.stop() 会立即中断移动并触发 moveEnd 事件 diff --git a/docs/api/user-data-state/index.md b/docs/api/user-data-state/index.md deleted file mode 100644 index 6360999..0000000 --- a/docs/api/user-data-state/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/data-state - -目录: diff --git a/docs/api/user-data-utils/index.md b/docs/api/user-data-utils/index.md deleted file mode 100644 index bfaa99e..0000000 --- a/docs/api/user-data-utils/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/data-utils - -目录: diff --git a/docs/api/user-entry-client/index.md b/docs/api/user-entry-client/index.md deleted file mode 100644 index 6579b3d..0000000 --- a/docs/api/user-entry-client/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/entry-client - -目录: diff --git a/docs/api/user-entry-data/Mota.md b/docs/api/user-entry-data/Mota.md deleted file mode 100644 index ad02971..0000000 --- a/docs/api/user-entry-data/Mota.md +++ /dev/null @@ -1,251 +0,0 @@ -# Mota API 文档 - -本文档由 `DeepSeek R1` 模型生成并微调。 - ---- - -## 核心功能 - -模块化管理系统,提供跨进程模块注册与获取能力,支持数据端与渲染端分离架构。用于解决服务端录像验证与客户端渲染的模块隔离问题。 - ---- - -## 全局访问 - -```typescript -// 浏览器环境 -Mota.require('@motajs/client'); - -// ESM 环境 -import { Mota } from '@motajs/core'; -``` - ---- - -## 核心方法 - -### `require` - -```typescript -function require(key: K): ModuleInterface[K]; -function require(key: string): T; -``` - -**功能** -获取已注册的模块实例 - -| 参数 | 类型 | 说明 | -| ----- | -------- | -------------------------- | -| `key` | `string` | 模块标识符或自定义命名空间 | - -**返回值** -对应模块的导出对象 - -**预定义模块列表**: - -```typescript -interface ModuleInterface { - // ---------- 样板库 - '@motajs/client': typeof Client; - '@motajs/client-base': typeof ClientBase; - '@motajs/common': typeof Common; - '@motajs/legacy-client': typeof LegacyClient; - '@motajs/legacy-common': typeof LegacyCommon; - '@motajs/legacy-system': typeof LegacySystem; - '@motajs/legacy-ui': typeof LegacyUI; - '@motajs/render': typeof Render; - '@motajs/render-core': typeof RenderCore; - '@motajs/render-elements': typeof RenderElements; - '@motajs/render-style': typeof RenderStyle; - '@motajs/render-vue': typeof RenderVue; - '@motajs/system': typeof System; - '@motajs/system-action': typeof SystemAction; - '@motajs/system-ui': typeof SystemUI; - // ---------- 用户扩展 - '@user/client-modules': typeof ClientModules; - '@user/data-base': typeof DataBase; - '@user/data-fallback': typeof DataFallback; - '@user/data-state': typeof DataState; - '@user/data-utils': typeof DataUtils; - '@user/legacy-plugin-client': typeof LegacyPluginClient; - '@user/legacy-plugin-data': typeof LegacyPluginData; - // ---------- 必要的第三方库 - MutateAnimate: typeof MutateAnimate; - Vue: typeof Vue; - Lodash: typeof Lodash; -} -``` - -**示例**: - -```typescript -// 获取动画引擎 -const Animate = Mota.require('MutateAnimate'); - -// 获取Vue实例 -const Vue = Mota.require('Vue'); - -// 获取旧版UI系统 -const LegacyUI = Mota.require('@motajs/legacy-ui'); -``` - ---- - -### `register` - -```typescript -function register( - key: K, - data: ModuleInterface[K] -): void; -function register(key: string, data: unknown): void; -``` - -**功能** -注册模块到全局系统 - -| 参数 | 类型 | 说明 | -| ------ | -------- | ------------ | -| `key` | `string` | 模块标识符 | -| `data` | `any` | 模块导出对象 | - -**注意事项** - -- 重复注册会触发控制台警告 -- 推荐在游戏初始化阶段注册 - -**示例**: - -```typescript -// 注册自定义模块 -class MyCustomModule { - static version = '1.0.0'; -} -Mota.register('@user/custom-module', MyCustomModule); - -// 使用自定义模块 -const custom = Mota.require('@user/custom-module'); -console.log(custom.version); // 输出 1.0.0 -``` - ---- - -## 渲染进程控制 - -### `r` - -```typescript -function r(fn: (this: T) => void, thisArg?: T): void; -``` - -**功能** -包裹只在渲染进程执行的代码 - -| 参数 | 类型 | 说明 | -| --------- | ---------- | -------------------- | -| `fn` | `Function` | 需要渲染端执行的函数 | -| `thisArg` | `any` | 函数执行上下文 | - -**特性** - -- 在录像验证和服务端环境下不会执行 -- 无返回值设计 - -**示例**: - -```typescript -// 播放仅客户端可见的特效 -Mota.r(() => { - const animate = Mota.require('MutateAnimate'); - animate(heroSprite).shake(5, 1000); -}); -``` - ---- - -### `rf` - -```typescript -function rf any, T>( - fn: F, - thisArg?: T -): (...params: Parameters) => ReturnType | undefined; -``` - -**功能** -生成渲染进程安全函数 - -| 参数 | 类型 | 说明 | -| --------- | ---------- | ---------------- | -| `fn` | `Function` | 需要包装的原函数 | -| `thisArg` | `any` | 函数执行上下文 | - -**返回值** -经过安全包裹的函数,在非渲染环境调用返回 `undefined` - -**示例**: - -```typescript -// 创建安全渲染函数 -const safeAlert = Mota.rf((msg: string) => { - alert(`客户端提示: ${msg}`); -}); - -// 调用时自动判断执行环境 -safeAlert('仅在客户端显示'); // 服务端返回 undefined -``` - ---- - -## 架构示意图 - -```mermaid -graph TD - subgraph 数据端 - Validator[录像验证系统] - CoreLogic[核心逻辑] - end - - subgraph 渲染端 - UI[用户界面] - Effects[特效系统] - Render[渲染引擎] - end - - MotaSystem -->|require/register| Validator - MotaSystem -->|r/rf| Render - CoreLogic -->|跨进程通信| Effects -``` - ---- - -## 注意事项 - -1. **模块隔离** - 数据端模块与渲染端模块物理隔离,数据端不可直接引用渲染端,而渲染端是可以直接引用数据端的 - -2. **版本兼容** - 遗留系统模块(legacy-\*)将在未来版本逐步废弃,这些模块也不再提供 API 文档,如果需要自行阅读源码。 - -3. **性能优化** - 高频调用模块建议缓存引用: - - ```typescript - // 推荐 - const Animate = Mota.require('MutateAnimate'); - - // 不推荐 - function update() { - const Animate = Mota.require('MutateAnimate'); // 每次调用都查找 - } - ``` - -4. **错误处理** - 使用 try-catch 包裹高风险模块获取: - ```typescript - try { - const LegacyUI = Mota.require('@motajs/legacy-ui'); - } catch (e) { - fallbackUI(); - } - ``` diff --git a/docs/api/user-entry-data/index.md b/docs/api/user-entry-data/index.md deleted file mode 100644 index e41a4ff..0000000 --- a/docs/api/user-entry-data/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/entry-data - -目录: diff --git a/docs/api/user-legacy-plugin-client/index.md b/docs/api/user-legacy-plugin-client/index.md deleted file mode 100644 index b1780c5..0000000 --- a/docs/api/user-legacy-plugin-client/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/legacy-plugin-client - -目录: diff --git a/docs/api/user-legacy-plugin-data/index.md b/docs/api/user-legacy-plugin-data/index.md deleted file mode 100644 index 6da78f1..0000000 --- a/docs/api/user-legacy-plugin-data/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# @user/legacy-plugin-data - -目录: diff --git a/docs/dev/common/face-manager.md b/docs/dev/common/face-manager.md deleted file mode 100644 index a22c7a3..0000000 --- a/docs/dev/common/face-manager.md +++ /dev/null @@ -1,85 +0,0 @@ -# 需求综述 - -动态图块移动功能完成后,发现 `FaceDirection` 朝向相关操作(位移、反方向、旋转方向、降级)分散在独立工具函数中(`getFaceMovement`、`nextFaceDirection`、`degradeFace` 等),缺乏统一抽象,导致调用点分散、扩展性不足(例如:四方向与八方向切换时需要手动处理,不同移动场景难以共享同一套逻辑)。 - -目标是设计一套统一的朝向管理接口:`IFaceHandler` 代表一组朝向并提供相应操作,`IFaceManager` 作为注册中心管理多个 handler,同时内置两个 handler 分别对应四方向和八方向。新接口位于 `@user/data-base/src/common/`,与 `FaceDirection` 同包,同时取代现有的 `DirectionMapper`。 - -# 实现思路 - -## 1. 设计 IFaceHandler 接口 - -`IFaceHandler` 代表**一组朝向**,泛型 `T` 表示本组的朝向类型(通常为 `FaceDirection`,也可以是自定义枚举以支持拓展)。内部隐含一个该组支持的方向集合,通过 `directions` 成员暴露。对于不在集合内的输入方向,通过 `degrade` 方法将其映射到集合内最合适的方向,其余操作方法在调用时**均先执行 degrade**。 - -`mapDirection` 同时取代 `DirectionMapper.map()` 的功能,不再需要单独维护 `DirectionMapper`。 - -主要成员与方法: - -- `degrade(dir: number): T`:将任意朝向值(含其他枚举类型或 `FaceDirection`)降级为本组支持的方向。对于无法合理降级的方向(包括 `Unknown`),返回 `FaceDirection.Unknown`(数值 `0`,兼容所有 `T`)。 - -- `movement(dir: number): IFaceDescriptor`:获取指定方向的坐标偏移量,输入先经过 `degrade`,`Unknown` 返回 `{ x: 0, y: 0 }`。 - -- `move(dir: number, count: number): IFaceDescriptor`:获取指定方向走 `count` 步的坐标偏移量,等价于 `movement * count`。`count` 允许为负数,表示反向位移。输入先经过 `degrade`。 - -- `opposite(dir: number): T`:获取本组内的反方向,输入先经过 `degrade`,`Unknown` 返回 `Unknown`。 - -- `next(dir: number, anticlockwise?: boolean): T`:在本组方向集合内,顺时针(默认)或逆时针旋转一步,输入先经过 `degrade`,`Unknown` 返回 `Unknown`。 - -- `mapDirection(): Iterable`:迭代本组支持的所有朝向,包含 `Unknown`(其与其他方向一视同仁,不作特例处理)。 - -- `mapMovement(): Iterable<[T, IFaceDescriptor]>`:迭代本组所有朝向及其对应的坐标描述器,包含 `Unknown`(对应 `{ x: 0, y: 0 }`)。 - -## 2. 设计 IFaceManager 接口 - -`IFaceManager` 是 handler 的注册中心,同时支持数字 key 与字符串 id 两种注册与查找方式。数字 key 适合内置组的高频调用,字符串 id 适合使用频率较低的自定义场景: - -- `register(group: number, handler: IFaceHandler): void`:以数字 key 注册一个 handler。 -- `registerById(id: string, handler: IFaceHandler): void`:以字符串 id 注册一个 handler。 -- `get(group: number): IFaceHandler | null`:按数字 key 查找 handler,未找到返回 `null`。 -- `getById(id: string): IFaceHandler | null`:按字符串 id 查找 handler,未找到返回 `null`。 - -内置的数字 key 组用新增的 `InternalFaceGroup` 枚举标识(与现有的 `InternalDirectionGroup` 风格一致)。 - -## 3. 内置 Handler 实现 - -### Dir8FaceHandler(八方向) - -- `directions`:包含全部八个有效方向与 `Unknown`,共九个成员。 -- `degrade`:直接返回输入(转为 `T`),无需降级。 -- `next`:`Unknown` 返回 `Unknown`;按 45° 步进,顺时针顺序:Up → RightUp → Right → RightDown → Down → LeftDown → Left → LeftUp → Up。 -- `opposite`:`Unknown` 返回 `Unknown`;Up↔Down,Left↔Right,LeftUp↔RightDown,RightUp↔LeftDown。 -- `movement`:与现有 `getFaceMovement` 一致。 - -### Dir4FaceHandler(四方向) - -- `directions`:包含 Up、Down、Left、Right 四个方向与 `Unknown`,共五个成员。 -- `degrade`:四方向不变;斜向降级为水平分量(LeftUp/LeftDown → Left,RightUp/RightDown → Right);`Unknown` → `Unknown`。与现有 `degradeFace` 行为一致。 -- `next`:先 degrade,`Unknown` 返回 `Unknown`;再按 90° 步进,顺时针:Up → Right → Down → Left → Up。 -- `opposite`:先 degrade,`Unknown` 返回 `Unknown`;Up↔Down,Left↔Right。 -- `movement`:先 degrade,再返回偏移量。 - -## 4. 实现 FaceManager 类 - -实现 `IFaceManager`,不导出全局单例,应将实例挂载到游戏实例下。两个内置 handler(`Dir8FaceHandler` 与 `Dir4FaceHandler`)不在构造时注册,而在游戏实例初始化阶段注册,key 分别为 `InternalFaceGroup.Dir8` 和 `InternalFaceGroup.Dir4`。 - -## 5. 现有代码处理 - -- 现有 `getFaceMovement`、`nextFaceDirection`、`degradeFace`、`fromDirectionString` 等工具函数**暂时保留**,不做删改;新代码直接使用新接口,旧代码的迁移视后续情况另行处理。 -- `@motajs/common` 中的 `DirectionMapper` 及 `IDirectionMapper` 接口将被废弃,其调用方(如 `range.ts`)的迁移视后续情况另行处理。 - -# 涉及文件 - -## 需要修改的文件 - -### `@user/data-base/src/common/faceManager.ts`(新增文件) - -- [ ] 新增 `InternalFaceGroup` 枚举:包含 `Dir4` 与 `Dir8` 两个成员,作为 `IFaceManager` 的内置数字 key -- [ ] 新增 `IFaceDescriptor` 接口:描述一个方向的坐标增量,包含 `x` 与 `y` 两个只读成员 -- [ ] 新增 `IFaceHandler` 接口:包含 `degrade`、`movement`、`move`、`opposite`、`next`、`mapDirection`、`mapMovement` 七个成员 -- [ ] 新增 `IFaceManager` 接口:包含 `register`、`registerById`、`get`、`getById` 四个方法 -- [ ] 实现 `Dir8FaceHandler` 类(`implements IFaceHandler`) -- [ ] 实现 `Dir4FaceHandler` 类(`implements IFaceHandler`) -- [ ] 实现 `FaceManager` 类(`implements IFaceManager`) - -### `@user/data-base/src/common/index.ts` - -- [ ] 导出新增的枚举、接口与类 diff --git a/docs/dev/enemy/enemy-combat-flow.md b/docs/dev/enemy/enemy-combat-flow.md deleted file mode 100644 index 3e5784c..0000000 --- a/docs/dev/enemy/enemy-combat-flow.md +++ /dev/null @@ -1,209 +0,0 @@ -# 需求综述 - -`packages-user/data-system/src/combat/types.ts` 中的战斗流程接口已经是本次实现的权威设计,文档只负责解释这套接口的目的、运行语义与落地方式,不再额外改造公共接口。 - -本次需要补齐的是 `packages-user/data-system/src/combat/combat.ts` 中真正的战斗流程实现,使 `ICombatFlow` 可以驱动一次完整的实际战斗,而不是只停留在伤害预估层。 - -按照当前 `types.ts` 的设计,战斗流程分为三个阶段: - -1. 战前阶段:按优先级执行 `before` 脚本。 -2. 战斗阶段:取得本次战斗的 `IEnemyDamageInfo`,并执行战前 hook。 -3. 战后阶段:执行战后脚本,再执行战后 hook。 - -当前设计已经明确区分了两种入口: - -1. `battle(enemy: IEnemyView)`:面向怪物视图对象。 -2. `battleComputed(enemy: IReadonlyEnemy)`:面向计算后怪物对象。 - -是否“在地图上”不能仅由参数类型决定,而应以当前绑定上下文能否反查出有效坐标为准。对于不在地图上的怪物,当前设计已经明确: - -1. 它会被视为“战斗时隶属于当前绑定的 `IEnemyContext`”。 -2. 它不会被真正放入上下文。 -3. `handler.context` 使用当前绑定的上下文。 -4. `handler.locator` 使用原点 `(0, 0)`。 -5. `handler.onMap` 为 `false`。 - -另外,当前流程对象的失败语义也已经确定:`hero`、`context`、`damage` 任意一个未绑定,则本次战斗直接返回 `null`。 - -# 接口设计与预期 - -## IEnemyDamageInfo - -`IEnemyDamageInfo` 直接作为战斗结果对象使用,不新增额外结果结构。 - -- `IEnemyDamageInfo.damage`:预期频率**高频**。大多数消费战斗结果的代码首先关心伤害值。典型使用场景:战后脚本根据伤害值扣除勇士生命或触发保命逻辑。 -- `IEnemyDamageInfo.turn`:预期频率**低频**。回合数重要,但直接消费它的逻辑明显少于伤害值。 -- `IEnemyDamageInfo.handler`:预期频率**中频**。脚本或日志逻辑经常会顺手读取本次战斗关联的怪物、勇士与定位信息。典型使用场景:战后脚本通过 `handler.enemy` 读取掉落,或通过 `handler.locator` 定位当前怪物。 - -## ICombatFlowHandler - -`ICombatFlowHandler` 是脚本阶段使用的可写信息对象,职责是把战斗执行阶段真正需要改动的数据集中到一个 handler 中。 - -- `ICombatFlowHandler.onMap`:预期频率**低频**。它只在脚本需要区分“地图内战斗”和“离图战斗”时才会被使用。 -- `ICombatFlowHandler.hero`:预期频率**高频**。几乎所有真正产生副作用的战斗脚本都会碰到勇士对象。典型使用场景:战后脚本扣除生命、增加金币或写入状态。 -- `ICombatFlowHandler.enemy`:预期频率**中频**。不少脚本会读取或修改怪物对象,但频率仍低于直接操作勇士对象。典型使用场景:战后脚本读取怪物奖励,或修改怪物的战后状态。 -- `ICombatFlowHandler.context`:预期频率**低频**。只有需要反查地图对象或联动上下文的脚本才会使用它。 -- `ICombatFlowHandler.locator`:预期频率**低频**。只有与地图位置强相关的逻辑才会读取坐标。 -- `ICombatFlowHandler.state`:预期频率**低频**。它主要服务于跨系统联动,不是常规战斗脚本的主输入。 - -`handler` 的组装语义需要分成两条路径: - -1. `battle(enemyView)`: - - 先尝试从绑定上下文反查该视图的真实坐标 - - 若能反查到坐标,则 `onMap = true` - - 若不能反查到坐标,则按离图战斗处理,`onMap = false` - - `enemy` 使用该视图对应的可写怪物对象 - - `context`、`state` 使用绑定上下文中的真实运行信息 -2. `battleComputed(enemy)`: - - 若能从绑定上下文反查到视图,则按在图战斗处理,`onMap = true` - - 若无法反查到视图,则按离图战斗处理,`onMap = false` - - 离图时 `context` 仍使用绑定上下文,`locator` 固定使用原点 `(0, 0)` - - 离图时怪物不真正加入上下文,但本次战斗仍要提供一个可写的怪物对象作为 `handler.enemy` - -## ICombatFlowHook - -`ICombatFlowHook` 是实例级观察型扩展点,它只接收 `IEnemyDamageInfo`,不直接接触可写 handler。 - -- `ICombatFlowHook.onBeforeCombat()`:预期频率**低频**。更适合动画、日志、录像等系统层观察逻辑。典型使用场景:战前记录一次战斗开始事件。 -- `ICombatFlowHook.onAfterCombat()`:预期频率**低频**。同样偏观察与收尾,不负责流程控制。典型使用场景:战后统一记录本次战斗结果。 - -当前设计下,hook 的定位很明确: - -1. 它们只拿到 `info`。 -2. 它们不直接承担状态修改职责。 -3. 它们位于脚本阶段之后,只负责观察已经成立的流程结果。 - -## ICombatScript - -`ICombatScript` 是战斗流程对象上的注册式扩展点,用于承载真正参与流程的逻辑。 - -- `ICombatScript.priority`:预期频率**中频**。所有脚本都要声明优先级,但它主要出现在定义脚本和排查顺序问题时。典型使用场景:让扣血脚本先于奖励脚本,或让删怪脚本压到最后执行。 -- `ICombatScript.before()`:预期频率**中频**。只有需要拦截战斗或做战前准备的脚本才会实现它。典型使用场景:根据 `info.damage` 判断本次战斗是否允许继续进行。 -- `ICombatScript.after()`:预期频率**中频**。绝大多数默认行为都更适合放在战后脚本里。典型使用场景:扣血、删怪、发奖励。 - -脚本层的规则已经确定: - -1. `before()` 可以返回 `false` 来取消本次战斗。 -2. `after()` 只负责战后逻辑,不再影响战斗是否成立。 -3. 同一 `priority` 的脚本不允许并存;注册时需要警告并拒绝新增。 -4. 脚本按 `priority` 从高到低执行。 - -## ICombatFlow - -`ICombatFlow` 是对外暴露的流程对象,本体负责绑定依赖、组织阶段顺序与执行脚本。 - -- `ICombatFlow.hero`:预期频率**低频**。它主要用于只读检查或调试。 -- `ICombatFlow.context`:预期频率**低频**。通常只在调试或初始化检查中使用。 -- `ICombatFlow.damage`:预期频率**低频**。它更偏运行时绑定状态的公开暴露,而不是常规调用点。 -- `ICombatFlow.bindHero()`:预期频率**中频**。初始化流程对象时必须绑定勇士。典型使用场景:状态系统创建完勇士属性对象后注入给战斗流程。 -- `ICombatFlow.bindContext()`:预期频率**中频**。初始化或切换上下文时需要重绑战斗目标来源。典型使用场景:进入新楼层后,将新的怪物上下文绑定给战斗流程。 -- `ICombatFlow.bindDamage()`:预期频率**中频**。战斗流程本身不做伤害计算,必须依赖外部提供的伤害上下文。典型使用场景:伤害系统初始化完成后绑定给战斗流程。 -- `ICombatFlow.battle()`:预期频率**低频**。它面向怪物视图对象,适合最常规的战斗入口。典型使用场景:交互逻辑在玩家尝试攻击某个怪物视图时直接传入 `IEnemyView`。 -- `ICombatFlow.battleComputed()`:预期频率**低频**。它主要服务于“只有计算后怪物对象,没有视图对象”的调用点。典型使用场景:脚本逻辑对一个临时怪物对象发起离图战斗。 -- `ICombatFlow.addCombatScript()`:预期频率**中频**。所有默认行为和扩展模块都要通过它接入流程。典型使用场景:初始化阶段注册扣血、删怪、奖励等脚本。 - -结合这些成员可以看出,`ICombatFlow` 的设计目的不是把所有逻辑都塞进一个类里,而是提供一个可被上层系统组装的公共流程壳。 - -## 流程顺序 - -当前流程顺序已经确定为: - -1. 先计算本次战斗的 `IEnemyDamageInfo`。 -2. 按优先级执行全部 `before()` 脚本。 -3. 若战前脚本未取消战斗,则执行 `onBeforeCombat(info)` hook。 -4. 执行全部 `after()` 脚本。 -5. 执行 `onAfterCombat(info)` hook。 -6. 返回本次战斗信息。 - -也就是说,hook 并不是包裹整段脚本流程的最外层,而是位于脚本之后的观察阶段:`before scripts -> before hook -> after scripts -> after hook`。 - -# 预期体量 - -预期代码体量为 140-220 行。分析如下: - -1. `combat/combat.ts` 需要新建 `CombatFlow`,实现 hook 能力、绑定逻辑、脚本存储、重复优先级拒绝以及 `battle(...)` / `battleComputed(...)` 两条入口,这部分是主要体量来源。 -2. `combat/combat.ts` 还需要补齐离图 `battleComputed(...)` 的虚拟上下文语义,包括 `onMap = false`、原点定位与不真正加入上下文的处理。 -3. `combat/index.ts` 只需要增加 `export * from './combat'`,改动很小。 -4. `combat/types.ts` 作为已确认的设计源头,本次文档不计划修改其公共接口。 - -# 可能风险 - -本次文档将 `combat/types.ts` 视为已确认设计,不计划改动其中的公共接口,因此这里不额外展开实现风险。 - -# 实现思路 - -## 1. 新增 `combat/combat.ts` - -新增 `CombatFlow` 类,使用 `@motajs/common` 的 `Hookable` / `HookController` 体系实现 `ICombatFlow`。 - -## 2. 完成绑定与脚本存储 - -类内部保存三个绑定对象与脚本列表: - -1. `hero`:保存可写勇士对象,同时对外按接口暴露只读视图。 -2. `context`:保存当前战斗所属的怪物上下文。 -3. `damage`:保存当前使用的伤害上下文。 -4. `scriptList`:保存已注册脚本,并在注册时检查重复优先级。 - -## 3. 完成两条战斗入口的数据组装 - -1. `battle(enemyView)`: - - 检查绑定是否完整,不完整直接返回 `null` - - 通过 `damage.getDamageInfo(enemyView)` 获取伤害信息 - - 通过 `context.getEnemyLocatorByView(enemyView)` 判断当前是否在图 - - 若能拿到坐标,则组装 `onMap = true` 的 `ICombatFlowHandler` - - 若拿不到坐标,则按离图语义组装 `onMap = false`、`locator = (0, 0)` 的 `ICombatFlowHandler` -2. `battleComputed(enemy)`: - - 检查绑定是否完整,不完整直接返回 `null` - - 先尝试从 `context.getViewByComputed(enemy)` 反查视图 - - 若反查成功,则复用在图路径处理 - - 若反查失败,则按离图语义处理:`onMap = false`、`locator = (0, 0)`、怪物不加入上下文 - - 离图时的伤害计算直接使用绑定 `context` 上当前 `IDamageSystem` 的 calculator,基于绑定上下文、原点坐标与目标怪物组装只读 handler 进行纯计算 - -## 4. 完成 `battle(...)` 主流程 - -主流程按固定顺序执行: - -1. 先取得 `IEnemyDamageInfo` 与 `ICombatFlowHandler`。 -2. 按优先级执行全部 `before()` 脚本,若任一脚本返回 `false`,则立刻结束并返回 `null`。 -3. 执行 `onBeforeCombat(info)` hook。 -4. 执行全部 `after()` 脚本,处理真正的副作用。 -5. 执行 `onAfterCombat(info)` hook。 -6. 返回本次战斗信息。 - -## 5. 更新 `combat/index.ts` - -在当前导出列表基础上补上 `export * from './combat'`,让上层可以直接拿到流程实现。 - -# 涉及文件 - -## 需要引用的文件 - -1. `@motajs/common`:用于 `IHookBase`、`IHookable`、`IHookController`、`Hookable`、`HookController`、`logger`。 -2. `@user/data-base`:用于 `IEnemy`、`IReadonlyEnemy`、`IHeroAttribute`、`IReadonlyHeroAttribute`、`IStateBase`。 -3. `combat/types.ts`:本次实现的权威接口来源,重点使用 `ICombatFlow`、`ICombatFlowHandler`、`ICombatFlowHook`、`ICombatScript`、`IEnemyDamageInfo`、`IEnemyView`。 -4. `combat/context.ts`:需要使用 `getViewByComputed(...)`、`getEnemyLocatorByView(...)`、`getDamageSystem()` 等能力。 -5. `combat/damage.ts`:需要复用 `IDamageContext.getDamageInfo(...)`、`IDamageContext.getDamageInfoByComputed(...)` 与 `IDamageSystem.getCalculator()` 的现有语义。 -6. `combat/enemy.ts`:需要依赖怪物视图提供的可写怪物入口。 - -## 需要修改的文件 - -### `packages-user/data-system/src/combat/combat.ts` - -- [ ] 新增 `CombatFlow` 类:实现 `ICombatFlow` 的完整流程壳。 -- [ ] 新增绑定成员:保存 `hero`、`context`、`damage` 三个外部注入对象。 -- [ ] 新增脚本存储成员:保存已注册的战斗脚本,并在注册时拒绝重复优先级。 -- [ ] 新增私有组装方法:分别处理 `battle(IEnemyView)` 与 `battleComputed(IReadonlyEnemy)` 的 handler 组装。 -- [ ] 编写 `CombatFlow.battle(...)` 与 `CombatFlow.battleComputed(...)`:按接口设计执行战前、战斗、战后三阶段。 - -### `packages-user/data-system/src/combat/index.ts` - -- [ ] 新增 `export * from './combat'`:导出战斗流程实现。 - -# 问题 - -1. `battle(enemy: IEnemyView)` 传入怪物视图时,是否应当与 `battleComputed(...)` 保持完全一致的离图判定,即只要 `context.getEnemyLocatorByView(enemy)` 返回空,就按 `onMap = false`、原点坐标处理? - -> 使用所有的可能查询方式,包括 `getEnemyLocator` `getEnemyLocatorByView` `getViewByComputed` 等都查一遍,如果还是没有才按不在地图上处理。还有为啥要写成离图在图?看着不别扭吗,直接写成在地图上和不在地图上多清晰。 - -2. 离图战斗时提供给 `handler.enemy` 的可写怪物对象,是否直接使用传入视图上的可写对象 / 传入 computed 怪物的克隆对象即可,还是还有额外约束? diff --git a/docs/dev/enemy/enemy-manager-save.md b/docs/dev/enemy/enemy-manager-save.md deleted file mode 100644 index c08cf0c..0000000 --- a/docs/dev/enemy/enemy-manager-save.md +++ /dev/null @@ -1,194 +0,0 @@ -# 需求综述 - -对怪物管理器 `IEnemyManager` 进行存档适配,使其能够参与游戏存档系统。 -由于大多数情况下怪物模板不会被修改,不需要全量存储, -只需对比"参考状态"(游戏加载完成时的初始模板),仅保存发生了变化的模板。 - -为此需要: - -- `IEnemy` 和 `ISpecial` 继承 `ISaveableContent` 以支持自身序列化; -- 给 `ISpecial` 添加 `deepEqualsTo` 接口用于特殊属性间的深度比较; -- `IEnemyManager` 继承 `ISaveableContent`,新增 `compareWith`、`modifyPrefabAttribute`、 - `attachEnemyComparer`、`getEnemyComparer` 接口; -- `IEnemyManager` 内部维护 dirty 集合,以首次 `compareWith` 传入的参考为唯一基准; -- `getPrefab` / `getPrefabById` 返回值收窄为 `IReadonlyEnemy`, - 统一由 `modifyPrefabAttribute` 承担模板修改职责。 - ---- - -# 实现思路 - -## 1. 新增存档状态类型 - -在 `types.ts` 中新增如下类型,用于序列化怪物与管理器的状态: - -```ts -/** 单个 IEnemy 的存档状态 */ -interface IEnemySaveState { - readonly attrs: TAttr; - // 特殊属性按 code 映射,值为各 ISpecial.saveState() 的结果 - readonly specials: ReadonlyMap; -} - -/** IEnemyManager 的存档状态,只保存与参考状态不同的模板 */ -interface IEnemyManagerSaveState { - // code -> 变更后的 IEnemySaveState - readonly modified: ReadonlyMap>; -} -``` - -## 2. 新增 `IEnemyComparer` 接口 - -由于管理器外部没有比较怪物属性的需求,将比较逻辑封装为独立的比较器, -附着在 `EnemyManager` 上。比较器接口如下: - -```ts -interface IEnemyComparer { - compare( - enemyA: IReadonlyEnemy, - enemyB: IReadonlyEnemy - ): boolean; -} -``` - -由用户在初始化时通过 `attachEnemyComparer` 提供。若未提供比较器, -在调用 `modifyPrefabAttribute` 或 `changePrefab` 时需发出警告,且视所有怪物均为脏。 - -## 3. `ISpecial` 继承 `ISaveableContent` - -- `saveState` 返回 `structuredClone(this.value)`(即 `getValue()` 的深拷贝); -- `loadState` 调用 `setValue(state)`; -- 新增 `deepEqualsTo(other: ISpecial): boolean`:先对比 `code`, - 再对 `value` 进行深度比较。 - -各内置实现类的比较策略: - -- `NonePropertySpecial`:只需比较 `code`,`value` 为 `void` 无需对比; -- `CommonSerializableSpecial`:`value` 为普通可序列化对象, - 使用 `lodash-es` 的 `isEqual` 进行递归深度比较。 - -## 4. `IEnemy` 继承 `ISaveableContent>` - -- `saveState(compression)`:深拷贝 `attrs`,对每个 special 调用 - `saveState(compression)` 收集到 `specials` Map,返回 `IEnemySaveState`; -- `loadState(state, compression)`:以 `state.attrs` 还原属性, - 然后对已有的每个 special 按 code 查找存档中的对应条目并调用 `loadState`; - 若存档中出现当前怪物未注册的 special code,发出 logger 警告并跳过。 - -## 5. `IEnemyManager` 接口修改 - -### 5a. 继承 `ISaveableContent>` - -- `saveState(compression)`:遍历 dirty 集合,对每个脏模板调用 - `prefab.saveState(compression)`,汇总为 `IEnemyManagerSaveState` 并返回; -- `loadState(state, compression)`:遍历 `state.modified`, - 找到 code 对应的现有模板,调用 `prefab.loadState(enemyState, compression)` 还原; - 若某 code 不在当前 prefab 表中,发出 logger 警告并跳过; - **不清空 dirty 集合**,始终以首次 `compareWith` 提供的参考为唯一基准; - `loadState` 结束后重新用比较器对每个已有脏模板进行比对, - 刷新 dirty 集合(避免加载后实际已恢复初始值的模板仍停留在 dirty 中)。 - -### 5b. 新增 `compareWith` - -```ts -compareWith(reference: ReadonlyMap>): void; -``` - -- 由调用方在游戏初始化完成后提供参考快照,外部传入,管理器保存引用; -- **首次调用**:直接存储参考,清空 dirty 集合; -- **非首次调用**:通过 logger 发出警告,提示此操作风险高, - 请作者确认操作意图,但仍然执行覆盖(直接替换参考,重置 dirty 集合)。 - -### 5c. `getPrefab` / `getPrefabById` 返回值改为 `IReadonlyEnemy` - -原来返回 `IEnemy`,外部可以直接修改模板。 -改为只读引用,外部不能直接修改,必须通过 `modifyPrefabAttribute` 完成。 - -### 5d. 新增 `modifyPrefabAttribute` - -```ts -modifyPrefabAttribute( - code: number | string, - modify: (prefab: IEnemy) => IEnemy -): void; -``` - -执行流程: - -1. 根据 `code`(数字或 id 字符串)找到对应的模板; -2. 将模板以可写引用传入 `modify`,获得修改结果; -3. 若 `modify` 返回的是**新引用**(与传入的不同),则将该新对象替换模板表条目 - (同时更新 `prefabByCode` 与 `prefabById`); -4. 将最终生效的模板与 `compareWith` 中提供的参考模板进行 `IEnemyComparer.compare` 比较: - - 若不相等,则将此 code 加入 dirty 集合; - - 若相等(改回了初始值),则从 dirty 集合中移除; -5. 若未附加比较器,则始终视为脏,并发出 logger 警告。 - -### 5e. 新增 `attachEnemyComparer` / `getEnemyComparer` - -```ts -attachEnemyComparer(comparer: IEnemyComparer): void; -getEnemyComparer(): IEnemyComparer | null; -``` - -- `attachEnemyComparer`:设置当前管理器使用的比较器; -- `getEnemyComparer`:返回当前比较器,如未设置则返回 `null`, - 允许外部在特殊场景下借用比较器。 - -### 5f. `changePrefab` 也参与 dirty 追踪 - -`changePrefab` 直接替换模板表,修改完成后同样与参考模板进行比较, -更新 dirty 集合(逻辑与 `modifyPrefabAttribute` 步骤 4 相同)。 - -`deletePrefab` 不参与 dirty 追踪,存档时直接跳过被删除的模板。 - ---- - -# 涉及文件 - -## 需要引用的文件 - -- `lodash-es`:`CommonSerializableSpecial.deepEqualsTo` 中使用 `isEqual` 进行深度比较 -- `@motajs/common`:引用 `logger` 接口 -- `@user/data-base/common/types.ts`:引用 `ISaveableContent`、`SaveCompression` - -## 需要修改的文件 - -### `packages-user/data-base/src/enemy/types.ts` - -- [ ] 新增 `IEnemySaveState` 类型:单个怪物的存档状态 -- [ ] 新增 `IEnemyManagerSaveState` 类型:管理器的存档状态 -- [ ] 新增 `IEnemyComparer` 接口:包含 `compare` 方法,由用户实现 -- [ ] 修改 `ISpecial`:继承 `ISaveableContent`, - 新增 `deepEqualsTo(other: ISpecial): boolean` -- [ ] 修改 `IEnemy`:继承 `ISaveableContent>` -- [ ] 修改 `IEnemyManager`:继承 `ISaveableContent>`, - 新增 `compareWith`、`modifyPrefabAttribute`、`attachEnemyComparer`、`getEnemyComparer`; - 修改 `getPrefab` 与 `getPrefabById` 返回类型为 `IReadonlyEnemy` - -### `packages-user/data-base/src/enemy/enemy.ts`(`Enemy` 类) - -- [ ] 实现 `saveState(compression): IEnemySaveState` -- [ ] 实现 `loadState(state, compression): void` - -### `packages-user/data-base/src/enemy/manager.ts`(`EnemyManager` 类) - -- [ ] 新增 `private readonly dirtySet: Set` 成员:记录脏模板的 code -- [ ] 新增 `private referenceByCode: Map>` 成员: - 保存参考快照 -- [ ] 新增 `private comparer: IEnemyComparer | null` 成员:比较器 -- [ ] 新增 `private hasReference: boolean` 成员:标记是否已首次调用 `compareWith` -- [ ] 实现 `compareWith`:存储参考快照,非首次调用发出警告,重置 dirty 集合 -- [ ] 实现 `modifyPrefabAttribute`:调用 modify、处理引用变化、比较、更新 dirty 集合 -- [ ] 修改 `changePrefab`:替换模板后同步更新 dirty 集合 -- [ ] 修改 `getPrefab` / `getPrefabById` 返回类型(仅类型,实现无需改动) -- [ ] 实现 `attachEnemyComparer` / `getEnemyComparer` -- [ ] 实现 `saveState`:遍历 dirty 集合,序列化并返回 -- [ ] 实现 `loadState`:根据存档恢复脏模板,恢复后重新刷新 dirty 集合 - -### 引擎内置特殊属性(当前包内) - -- [ ] `NonePropertySpecial`:实现 `saveState`、`loadState`、`deepEqualsTo` - (`value` 为 `void`,`deepEqualsTo` 只比较 `code`) -- [ ] `CommonSerializableSpecial`:实现 `saveState`、`loadState`、`deepEqualsTo` - (`deepEqualsTo` 的 value 比较使用 `lodash-es` 的 `isEqual`) diff --git a/docs/dev/enemy/enemy-types-restructure.md b/docs/dev/enemy/enemy-types-restructure.md deleted file mode 100644 index e84256c..0000000 --- a/docs/dev/enemy/enemy-types-restructure.md +++ /dev/null @@ -1,54 +0,0 @@ -# 怪物系统文件结构重构 - -**目的:** 使 `IEnemyHandler` 能持有 `IStateBase` 引用而不产生循环引用。 - -**循环根源:** `enemy/types.ts` 混写了两层接口,`data-base/src/types.ts` 经由 `./enemy` index 引入 `IEnemyManager`,导致无法反向引用。 - -## 接口归属 - -**`enemy/types.ts`(Layer 1 — 存档/管理层,保留)** - -只保留以下接口,其余全部迁出: - -- `IEnemySaveState`、`IEnemyManagerSaveState`、`IEnemyComparer` -- `ISpecial`、`IReadonlyEnemy`、`IEnemy` -- `SpecialCreation`、`IEnemyLegacyBridge`、`IEnemyManager` - -只允许引用 `@motajs/common`、`../common`,不得引用 `../types`。 - -**`enemy/utils.ts`(Layer 0 — 工具层,与实现类并列)** - -从 `enemy/types.ts` 迁出以下接口(实现类 `MapLocIndexer` 已在此文件): - -- `IMapLocHelper`、`IMapLocIndexer` - -**`combat/types.ts`(Layer 2 — 战斗/上下文层,现为空文件)** - -从 `enemy/types.ts` 迁入以下接口: - -- `IEnemyHandler`、`IReadonlyEnemyHandler`、`IEnemyView` -- `IEnemySpecialModifier`、`IAuraView`、`IEnemyAuraView`、`IAuraConverter` -- `IEnemySpecialQueryModifier`、`IEnemySpecialQueryEffect`、`IEnemyCommonQueryEffect`、`IEnemyFinalEffect` -- `IMapDamageInfoExtra`、`IMapDamageInfo`、`IMapDamageView`、`IMapDamageConverter`、`IMapDamageReducer`、`IMapDamage` -- `IEnemyDamageInfo`、`IEnemyCritical`、`CriticalableHeroStatus`、`IDamageCalculator`、`IDamageContext`、`IDamageSystem` -- `IEnemyContext` - -需引用 `../enemy/types`(Layer 1)、`../enemy/utils`(Layer 0)、`../types`(`IStateBase`,现在无循环)。在 `IEnemyHandler` 和 `IReadonlyEnemyHandler` 中新增 `readonly state: IStateBase`。 - -## 文件修改清单 - -| 文件 | 操作 | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| `combat/types.ts` | 迁入所有 Layer 2 接口;新增三条 import(`../enemy/types`、`../enemy/utils`、`../types`);`IEnemyHandler` 系列加 `state` 字段 | -| `combat/context.ts` | 从 `enemy/context.ts` **移动**;import 路径改为从 `./types`、`../enemy/types`、`../enemy/utils` 引入 | -| `combat/damage.ts` | 从 `enemy/damage.ts` **移动**;import 路径同上 | -| `combat/mapDamage.ts` | 从 `enemy/mapDamage.ts` **移动**;import 路径同上 | -| `combat/index.ts` | 新建,`export * from './types/context/damage/mapDamage'` | -| `enemy/types.ts` | 删除所有 Layer 2 接口(迁出后仅剩 Layer 1 内容) | -| `enemy/utils.ts` | 新增 `IMapLocHelper`、`IMapLocIndexer` 接口定义 | -| `enemy/index.ts` | 删除对 `context`、`damage`、`mapDamage` 的导出 | -| `data-base/src/index.ts` | 新增 `export * from './combat'` | -| `data-base/src/types.ts` | `import { IEnemyManager }` 改从 `./enemy/types` 直接引入(`enemy/types.ts` 不再引用 `../types`,无循环) | -| `enemy/enemy.ts` | import 路径无需改动(`IEnemy` 等仍在 `./types`) | -| `enemy/manager.ts` | import 路径无需改动(全部 Layer 1 接口仍在 `./types`) | -| `enemy/special.ts` | import 路径无需改动 | diff --git a/docs/dev/hero/hero-equipment.md b/docs/dev/hero/hero-equipment.md deleted file mode 100644 index 572ab20..0000000 --- a/docs/dev/hero/hero-equipment.md +++ /dev/null @@ -1,204 +0,0 @@ -# 需求综述 - -为勇士添加装备系统,实现装备的装上、卸下及属性加成。核心工作分为四层:实例存储层(`IHeroEquipsStore`,不可堆叠,以唯一 `uid` 管理每件装备)、装备状态层(`IEquipmentState`,纯数据,持有修饰器实例并负责存档读档)、属性加成层(通用 `ValueModifier` 与 `PercentageModifier`)、装备系统层(`IHeroEquipment`,操作装上/卸下,管理槽位)。 - -# 需求理解 - -## 明确需求 - -1. 独立 uid 管理。装备道具不沿用 `count` 合并计数,每件实例以自增 `uid` 唯一标识 -2. 通用修饰器加成。属性加成通过 `ValueModifier` 和 `PercentageModifier` 实现,百分比 `priority` 高于数值 -3. 修饰器存档隔离。装备修饰器不入勇士状态存档——需在 `addModifier` 中新增 `nosave` 参数 -4. 排序与对比。提供 `IEquipmentSorter` 自定义排序和 `compareEquip` 属性对比 -5. 槽位系统。槽位名称通过 `setSlots` 动态设置,装备声明可用槽位 - -## 隐含需求 - -1. 实例与定义分离。修饰器的值可独立于定义数据变化(强化通过 `setValue`),但修饰器的存在(有哪些属性)始终由定义决定 -2. 装上/卸下必须可逆。卸下后勇士属性恢复到装上前的状态,修饰器的添加和移除必须对称 -3. 重复装上处理策略。同一装备不能重复装上同一个槽,但已在其他槽的装备需通过 `autoUnload` 或直接拒绝来处理 -4. 排序影响 UI 展示。装备实例的排序结果直接影响背包和装备栏的展示顺序,排序标准由游戏作者定义 - -# 设计前提 - -1. 装备不可堆叠。装备可能拥有不同的强化状态,同名装备的属性值可以不同,因此无法像消耗品一样合并计数——每件装备都是独立个体 -2. 固定槽位序列。槽位是勇士身上若干个固定位置按顺序排列而成,每件装备只能安装到其声明兼容的槽位中 -3. 修饰器是唯一加成载体。装备对勇士属性的影响完全通过修饰器实现,不存在修饰器之外的加成方式 - -# 核心概念定义 - -## 装备 - -装备是 `ItemCategory.Equipment` 类别的道具。与永久道具和消耗道具不同,装备不可堆叠——同名但强化状态不同的两件装备拥有不同的运行时数据,必须分立管理。每件装备的定义数据中包含可安装的槽位信息和属性加成定义(数值加成与百分比加成),实际属性影响由从定义派生的修饰器产生。 - -## 装备槽 - -装备槽是勇士身上若干固定位置的序列。每个槽位由一个字符串标识其名称(如"武器"、"头盔"),由游戏作者定义。每件装备在其定义中声明兼容的槽位列表,只有目标槽位名称在声明中时方可装上。槽位索引与槽位名称是两个维度:传入槽名时可能在多个同名槽中自动选择空槽;传入索引时精确定位。同一槽位同时最多容纳一件装备;若目标槽位已被占用,装上时自动卸载原装备。 - -## 装备装上行为 - -装备装上行为是指一件装备实例与勇士身上某个槽位之间建立的绑定关系。此关系使得装备的修饰器在该期间对勇士属性生效。此关系是临时的、可逆的——绑定可随时解除。 - -## 装备卸下行为 - -装备卸下行为是指解除装备实例与槽位之间绑定关系的操作。解除后装备实例本身不会消失,仅槽位恢复为空,装备的修饰器不再影响勇士属性。 - -## 装备修饰器 - -装备修饰器是装备对勇士属性产生影响的抽象单元。每件装备从定义数据派生出若干修饰器,每个修饰器持有一个值(加成的量)并拥有一种计算规则。修饰器分为两类:固定数值加成——直接在属性值上叠加;百分比加成——基于原始基础属性按比例计算。修饰器之间具有先后关系:百分比修饰器先于数值修饰器计算。修饰器的值与其所属装备实例绑定,可随强化等操作独立变化。 - -# 接口设计分析 - -## `IEquipmentState` - -### 设计思路 - -由于装备修饰器的值会因强化而变化,独立于装备的定义数据,因此需要一个与装备实例一一对应的运行时状态对象,用于持有全部修饰器并在构造时从定义数据派生创建。修饰器本身不应直接序列化,而是保存其上游数据(即当前的 `value` 和 `percentage` 映射),读档时通过 `setValue` 恢复。这样设计也使得 `SaveCompression` 的差异化存储成为可能:`NoCompression` 完整存储,其他级别仅存与定义值不同的条目。 - -### 接口分析 - -- 成员 `uid: number`:预期频率**中频**。实例在存储层和装备系统中的唯一标识 -- 成员 `item: IItemRawData`:预期频率**中频**。装备定义数据引用,获取名称、槽位等信息 -- 成员 `modifiers: [SelectKey, IHeroModifier][]`:预期频率**中频**。该装备的全部修饰器,装上/卸下时遍历 - -### 预期体量 - -合计约 80 行。 - -- 构造器预期 15 行:两组循环分别从 `IItemEquipData.value` 和 `IItemEquipData.percentage` 创建 `ValueModifier` 和 `PercentageModifier`,修饰器数量通常为个位数 -- 存档逻辑预期 30 行:两个压缩分支,每个分支内遍历两种修饰器、比较值、写入 `Map` -- 读档逻辑预期 25 行:与存档对称,差异模式下还需处理键缺失时回退到定义数据的逻辑 - -## `IHeroEquipsStore` - -### 设计思路 - -由于装备不可堆叠(设计前提 1),需要以 `uid` 为键的独立存储模型,因此设计 `IHeroEquipsStore` 以 `Map` 管理所有实例。作为 `IHeroItems.equipment` 成员暴露,替代原有基于 `count` 的装备分表。 - -由于游戏作者可能需要自定义装备在背包中的排序,因此提供 `useSorter` 注入 `IEquipmentSorter` 回调,由 `instances` 和 `instancesOf` 调用。 - -由于装备栏 UI 需要展示装备切换前后的属性变化,因此提供 `compareEquip`,它先卸载目标槽位当前装备的修饰器,再分别模拟装上两件候选装备,通过 `getFinalAttribute` 对比差值。 - -### 接口分析 - -- 方法 `add(item: number | string): number`:预期频率**高频**。创建装备实例分配 `uid`,返回新 `uid` -- 方法 `delete(uid: number): void`:预期频率**中频**。按 `uid` 移除实例 -- 方法 `get(uid: number): IEquipmentState | null`:预期频率**高频**。获取实例状态,与是否当前装备无关 -- 方法 `count(item: number | string): number`:预期频率**中频**。按装备类型统计实例数量 -- 方法 `compareEquip(equipA: number, equipB: number, slot: number): Readonly>`:预期频率**中频**。模拟装上两件装备后对比属性差 -- 方法 `useSorter(comparer: IEquipmentSorter | null): void`:预期频率**低频**。注入自定义排序器 -- 方法 `instancesOf(equip: number | string): IEquipmentState[]`:预期频率**中频**。按类型过滤并排序 -- 方法 `instances(): IEquipmentState[]`:预期频率**中频**。排序返回全部实例 - -### 预期体量 - -合计约 120 行。 - -- 增删查预期 20 行:`add` 中包含从类型标识解析图块数字、从物品存储获取定义数据、创建 `EquipmentState`,其余为纯 `Map` 操作 -- 计数预期 8 行:遍历所有实例,按 `item.num` 比较并计数 -- 排序预期 15 行:根据是否注册了排序器分两分支,有排序器时需构造 `IEquipmentSortHandler` 并调用回调 -- 实例列表获取预期 20 行:`instances` 收集全部实例并排序,`instancesOf` 多一层类型过滤 -- 属性对比预期 35 行:两次 `clone` 属性对象,先卸载目标槽位已有装备的修饰器,再分别遍历候选装备的 `modifiers` 调用 `addModifier`,收集受影响的键并通过 `getFinalAttribute` 比对差值 -- 存档读档预期 20 行:遍历所有实例调用各自存档读档方法 - -## `IHeroEquipment` - -### 设计思路 - -由于装上/卸下操作需要同时访问装备实例存储(获取 `IEquipmentState` 及修饰器)和勇士属性对象(执行修饰器的添加与移除),且槽位占用关系需要独立维护(`Map<槽索引, uid>`),因此需要一个持有两者引用的系统层组件。 - -由于 UI 需要在装上之前判断兼容性且不能产生副作用,因此提供 `canEquipTo` 做只读检查。装上过程涉及多层判断(槽位兼容、已装检查、替换处理),因此 `equip` 返回被替换的 `uid` 供调用方跟进。卸下时按槽索引精确定位并清理,返回卸下的 `uid`。 - -由于装备栏 UI 需要按槽位顺序渲染,因此 `getEquips` 按 `slots` 顺序输出实例数组,空槽填 `null`,通过数组下标直接对应槽位。 - -### 接口分析 - -- 成员 `slots: readonly string[]`:预期频率**低频**。当前槽位名称序列 -- 方法 `setSlots(slots: string[]): void`:预期频率**低频**。运行时设置槽位名称 -- 方法 `canEquipTo(uid: number, slot: number | string): EquipStatus`:预期频率**高频**。无副作用的兼容性检查 -- 方法 `equip(uid: number, slot: number | string, autoUnload?: boolean): number | undefined`:预期频率**高频**。装上并自动处理冲突,返回被替换 `uid` -- 方法 `unequip(slot: number): number | undefined`:预期频率**中频**。按槽卸下,返回被卸下 `uid` -- 方法 `equipped(uid: number): boolean`:预期频率**高频**。按 `uid` 查询是否已在某槽 -- 方法 `getEquips(): (IEquipmentState | null)[]`:预期频率**中频**。按槽位顺序输出,空槽为 `null` - -### 预期体量 - -合计约 140 行。 - -- 兼容性检查预期 30 行:分 `typeof slot === 'number'` 和字符串两分支,数字分支直接查 `Map`,字符串分支需遍历 `slots` 查找匹配槽名并判断空槽 -- 装上逻辑预期 50 行:同 `uid` 跨槽检查与 `autoUnload` 策略、数字/字符串两分支下的槽位查找与替换、卸载旧装备后再装新装备 -- 卸下逻辑预期 15 行:按槽索引取值、遍历修饰器移除、清空槽位 -- 装备列表获取预期 15 行:按 `slots` 数组长度遍历,对每个下标查 `Map` 获取 `uid` 再查存储获取实例 -- 查询与存档读档预期 20 行:`equipped` 遍历 `Map.values()` 比对;存档写 `Map` 和 `slots`,读档清空后按存档顺序逐个 `equip` - -## 通用修饰器 - -### 设计思路 - -由于装备的属性加成只有固定数值和基于基础属性的百分比两种形式,因此提供 `ValueModifier` 和 `PercentageModifier` 两个通用修饰器即可覆盖全部需求。二者均继承 `BaseHeroModifier`,`type` 硬编码以便读档时通过工厂重建,`priority` 由构造器参数传入——装备创建时分别传 0 和 10 以确保百分比先于数值计算。 - -### 预期体量 - -合计约 40 行。 - -- 构造器与 `type` 存储预期 15 行:接收 `priority` 与 `value`,存储 `type` 为硬编码字符串 -- `modify` 方法预期 6 行:分别实现加法(`value + this.value`)和百分比计算(`value + baseValue * this.value`) -- 无独立存档逻辑:修饰器值的变化由 `EquipmentState` 的 `value`/`percentage` 映射管理 - ---- - -# 实现思路 - -## 存档差异化存储 - -1. `saveState` 时判断 `SaveCompression`:`NoCompression` 直接存入完整的 `value` 和 `percentage` 映射 -2. 其他级别将每个修饰器的当前值与 `this.item.equip` 中的原始定义值逐一比对,仅当不同时写入映射 -3. `loadState` 反向处理:`NoCompression` 对所有修饰器 `setValue`;其他级别查询存档中的值,键缺失时回退到原始定义值 - -## `compareEquip` 属性模拟 - -1. 克隆当前属性,若目标槽位已有装备,先移除其修饰器 -2. 将装备 A 的修饰器加到副本上,通过 `getFinalAttribute` 获取各属性值 -3. 对另一副本重复步骤 1 和 2,换为装备 B -4. 收集两件装备 `modifiers` 覆盖到的所有属性键,计算差值 - -# 涉及文件 - -## `@user/data-base/hero/types.ts` - -- [ ] 新增 `IEquipmentStateSave`:装备状态存档接口,按照本文描述完成设计 -- [ ] 新增 `IHeroEquipsStoreSave`:实例仓库存档接口,按照本文描述完成设计 -- [ ] 新增 `IHeroEquipmentSave`:装备系统存档接口,按照本文描述完成设计 -- [ ] 新增 `IEquipmentState`:装备状态纯数据层接口,按照本文描述完成设计 -- [ ] 新增 `IHeroEquipsStore`:装备实例存储接口,按照本文描述完成设计 -- [ ] 新增 `IEquipmentSortHandler`:排序上下文接口,按照本文描述完成设计 -- [ ] 新增 `IEquipmentSorter`:排序回调接口,按照本文描述完成设计 -- [ ] 新增 `EquipStatus`:装备兼容判断结果枚举,按照本文描述完成设计 -- [ ] 新增 `IHeroEquipment`:装备系统层接口,按照本文描述完成设计 -- [ ] 修改 `IHeroItems`:新增装备存储成员以接入装备系统 -- [ ] 修改 `IHeroAttribute.addModifier`:新增存档隔离参数 -- [ ] 修改 `IHeroStateSave`:新增装备系统成员以接入存档流程 -- [ ] 修改 `IHeroState`:新增装备系统成员以接入读档流程 - -## `@user/data-base/hero/equipment.ts` - -- [ ] 编写 `EquipmentState` 类:按照本文描述实现装备状态层 -- [ ] 编写 `HeroEquipsStore` 类:按照本文描述实现实例存储层 -- [ ] 编写 `HeroEquipment` 类:按照本文描述实现装备系统层 - -## `@user/data-base/hero/modifier.ts` - -- [ ] 编写 `ValueModifier` 类:按照本文描述实现数值修饰器 -- [ ] 编写 `PercentageModifier` 类:按照本文描述实现百分比修饰器 - -## `@user/data-base/hero/attribute.ts` - -- [ ] 修改 `HeroAttribute` 类:按照本文描述支持修饰器存档隔离 - -## `@user/data-base/hero/items.ts` - -- [ ] 修改 `HeroItems` 类:按照本文描述传递勇士属性引用并接入装备存储 - -## `@user/data-base/hero/state.ts` - -- [ ] 修改 `HeroState` 类:按照本文描述初始化装备系统并改写存档读档流程 diff --git a/docs/dev/hero/hero-follower-interfaces.md b/docs/dev/hero/hero-follower-interfaces.md deleted file mode 100644 index dcae1c6..0000000 --- a/docs/dev/hero/hero-follower-interfaces.md +++ /dev/null @@ -1,140 +0,0 @@ -# 需求综述 - -对勇士跟随者相关接口进行结构性重构。核心动机是让跟随者复用勇士已有的位置与渲染接口(`IHeroLocation`、`IHeroRendering`),而非为跟随者定义独立的位置/渲染类型。这样跟随者与勇士共享同一套移动与渲染模型,简化接口层级,避免重复定义。 - -重构四个接口:`IHeroLocation`、`IHeroRendering`、`IHeroFollower`、`IHeroFollowersController`。前两者是勇士与跟随者共用的基础接口,后两者专属于跟随者管理。同时需要将旧的 `HeroMoveController` 类拆分为 `HeroLocation`、`HeroRendering`、`HeroFollowersController` 三个独立类,`HeroFollower` 为新实现类,并修改 `HeroState` 适配新接口。 - -# 接口设计分析 - -## IHeroLocation - -### 接口综述 - -勇士位置接口,代表一个可移动对象在游戏世界中的坐标与朝向,并挂载移动器负责移动控制。该接口继承 `ISaveableContent` 实现存档读档,继承 `IObjectMovable` 获得基础位置读写能力。存档格式为 `IFacedTileLocator`(包含 `x`、`y`、`direction`)。移动能力由 `IObjectMover` 提供,支持行走、跳跃、瞬移、朝向控制等全部需求。 - -### 接口分析 - -- `IHeroLocation.mover`:预期频率**高频**。几乎所有移动操作都经由 `mover` 进行,在剧情演出与逻辑中频繁出现。典型使用场景:演出中让角色沿指定路线移动。 - -## IHeroRendering - -### 接口综述 - -勇士渲染信息接口,存在于数据端,存放无需进入渲染端即可管理的渲染相关数据。当前仅包含透明度(`alpha`),后续可能扩展。继承 `ISaveableContent` 实现存档读档,继承 `IHookable` 支持透明度变化时触发回调。旧实现中的 `image` 作为纯渲染端概念被移除。 - -### 接口分析 - -- `IHeroRendering.alpha`:预期频率**低频**。直接读取透明度值的需求极少。 -- `IHeroRendering.setAlpha`:预期频率**低频**。设置透明度仅在特殊效果中出现,一座塔中调用次数有限。 - -## IHeroFollower - -### 接口综述 - -跟随者接口,包含跟随者的渲染信息与位置信息。直接复用 `IHeroRendering` 和 `IHeroLocation`,使跟随者拥有与勇士完全一致的移动与渲染能力。存档格式 `IHeroFollowerSave` 嵌套包含 `IHeroRenderingSave` 和 `IHeroLocationSave`。对比旧 `IHeroFollower = { num, identifier, alpha }` 的纯数据结构,新接口让跟随者从静态数据升级为拥有完整移动能力的独立对象。 - -### 接口分析 - -- `IHeroFollower.rendering`:预期频率**低频**。 -- `IHeroFollower.location`:预期频率**低频**。 - -> 跟随者本身就是一个使用场景极低的需求,大多数游戏中都不会使用到一两次,所以几乎所有跟随者相关接口都应该是低频。 - -## IHeroFollowersController - -### 接口综述 - -跟随者控制器接口,负责跟随者的增删查改与聚集操作。继承 `IHookable`,提供添加、移除、聚集三个事件的钩子支持。对比旧实现:索引取代 `identifier` 作为查找方式,`setFollowerAlpha` 移除(改为通过 `follower.rendering.setAlpha` 操作),`addFollower` 的 `num` 参数合并了旧的图块数字与标识符。 - -### 接口分析 - -- `IHeroFollowersController.addFollower`:预期频率**低频**。 -- `IHeroFollowersController.getFollower`:预期频率**低频**。 -- `IHeroFollowersController.getFollowersById`:预期频率**低频**。 -- `IHeroFollowersController.getAllFollowers`:预期频率**低频**。 -- `IHeroFollowersController.removeFollower`:预期频率**低频**。 -- `IHeroFollowersController.removeAllFollowers`:预期频率**低频**。 -- `IHeroFollowersController.gatherFollowers`:预期频率**低频**。 -- `IHeroFollowersController.gatherFollowersSync`:预期频率**低频**。 - -### 预期体量 - -预期代码体量合计约 250–300 行。分析如下: - -- `HeroLocation`:需实现 `IObjectMovable` 和 `ISaveableContent`,并创建内部 `ObjectMover` 子类,预计 70–90 行。 -- `HeroRendering`:仅管理 `alpha` 的存取和钩子触发,预计 25–35 行。 -- `HeroFollower`:组合 `HeroRendering` 和 `HeroLocation` 并实现存档读档,预计 35–45 行。 -- `HeroFollowersController`:维护跟随者列表和增删查改聚集操作,预计 120–150 行。 - -# 涉及文件 - -## 需要修改的文件 - -### `@user/data-base/hero/types.ts` - -- [x] 重构 `IHeroLocation` 接口:改为继承 `ISaveableContent` 与 `IObjectMovable`,仅保留 `mover` 成员。 -- [x] 新增 `IHeroLocationSave` 类型别名。 -- [x] 重构 `IHeroRendering` 接口:新增继承 `ISaveableContent` 与 `IHookable`。 -- [x] 新增 `IHeroRenderingSave` 接口。 -- [x] 新增 `IHeroRenderingHooks` 接口。 -- [x] 新增 `IHeroFollowerSave` 接口。 -- [x] 重构 `IHeroFollower` 接口:改为组合 `rendering` 与 `location`,继承 `ISaveableContent`。 -- [x] 重构 `IHeroFollowersController` 接口:修改为上文中的新设计。 -- [x] 新增 `IHeroFollowersControllerHooks` 接口。 -- [x] 修改 `IHeroState` 接口:新增 `location`、`rendering`、`followers` 成员,移除旧的 `mover`、`attachMover`、`getHeroMover`。 -- [x] 修改 `IHeroStateSave`:`locator` 类型改为 `IHeroLocationSave`,`followers` 类型改为 `readonly IHeroFollowerSave[]`。 -- [ ] 删除旧的 `IHeroMoveController`、`IHeroMoveControllerHooks` 接口及旧 `IHeroFollower`、`HeroAnimateDirection` 枚举。 - -### `@user/data-base/hero/mover.ts` - -删除此文件,内容拆分至 `location.ts`、`rendering.ts`、`followersController.ts`。 - -### `@user/data-base/hero/location.ts` - -新建文件。 - -- [ ] 实现 `HeroLocation` 类:基于新 `IHeroLocation` 接口。 -- [ ] 内部创建 `ObjectMover` 子类实例,挂载为 `mover`。 - -### `@user/data-base/hero/rendering.ts` - -新建文件。 - -- [ ] 实现 `HeroRendering` 类:基于新 `IHeroRendering` 接口。 -- [ ] 实现 `setAlpha`:更新值并触发 `onSetAlpha` 钩子。 - -### `@user/data-base/hero/follower.ts` - -新建文件。 - -- [ ] 实现 `HeroFollower` 类:组合 `HeroRendering` 与 `HeroLocation` 实例,实现新 `IHeroFollower` 接口。 -- [ ] 内部保存 `num`,供 `getFollowersById` 查询匹配。 - -### `@user/data-base/hero/followersController.ts` - -新建文件。 - -- [ ] 实现 `HeroFollowersController` 类:基于新 `IHeroFollowersController` 接口。 -- [ ] 内部持有勇士 `IHeroLocation` 引用,供聚集操作使用。 -- [ ] 实现 `addFollower(num)`:创建 `HeroFollower`,加入列表,触发 `onAddFollower` 钩子。 -- [ ] 实现 `getFollower(index)`:按索引返回跟随者,越界返回 `null`。 -- [ ] 实现 `getFollowersById(num)`:返回匹配 `num` 的 `[index, follower]` 迭代器。 -- [ ] 实现 `getAllFollowers()`:返回跟随者列表。 -- [ ] 实现 `removeFollower(index)`:按索引移除跟随者,触发 `onRemoveFollower` 钩子,动画结束后兑现。 -- [ ] 实现 `removeAllFollowers()`:清空跟随者列表,触发钩子,动画结束后兑现。 -- [ ] 实现 `gatherFollowers()`:将所有跟随者移动至勇士位置并同步朝向,动画结束后兑现。 -- [ ] 实现 `gatherFollowersSync()`:立即将所有跟随者瞬移至勇士位置并同步朝向。 - -### `@user/data-base/hero/state.ts` - -修改 `HeroState` 类。 - -- [ ] 修改构造函数参数并新增 `location`、`rendering`、`followers` 成员,替代旧的 `mover`。 -- [ ] 删除 `attachMover`、`getHeroMover` 方法。 -- [ ] 修改 `saveState`:通过 `location.saveState`、`rendering.saveState` 及各跟随者的 `saveState` 获取存档数据。 -- [ ] 修改 `loadState`:通过 `location.loadState`、`rendering.loadState` 恢复位置与渲染,重建跟随者列表。 - -### `@user/data-base/hero/index.ts` - -- [ ] 新增导出 `location.ts`、`rendering.ts`、`follower.ts`、`followersController.ts`。 -- [ ] 移除 `mover.ts` 的导出。 diff --git a/docs/dev/hero/hero-items.md b/docs/dev/hero/hero-items.md deleted file mode 100644 index 823bc1b..0000000 --- a/docs/dev/hero/hero-items.md +++ /dev/null @@ -1,131 +0,0 @@ -# 需求综述 - -为勇士添加背包系统,用于存储永久道具(Constant)、消耗道具(Consumable)和装备道具(Equipment)。即捡即用道具(Pick)在 addItem/getItem 时直接执行效果,不进入背包。 - -背包系统由 `IHeroItems` 接口定义,作为 `IHeroState` 的新成员挂载。三种类型的道具分别存储在三张 `Map` 中,键为道具图块数字 `num`。背包需要支持存档读档,继承 `ISaveableContent`。 - -# 接口设计分析 - -## IHeroItemState - -### 设计意图 - -单个道具在背包中的运行时状态,存储道具的基础标识信息与当前数量。`raw` 为对 `IItemRawData` 的引用,提供 `category`、`name` 等定义数据的便捷访问,读档时通过 `state.itemStore.getData(num)` 恢复。 - -### 接口分析 - -- `IHeroItemState.id`:预期频率**中频**。道具的字符串标识符,用于通过 id 寻址时做匹配。典型使用场景:通过 id 判断是否持有某特定道具。 -- `IHeroItemState.num`:预期频率**高频**。道具的图块数字,是 Map 键及与地图关联的核心标识。典型使用场景:地图事件中检查玩家是否拥有 `num` 对应的钥匙。 -- `IHeroItemState.raw`:预期频率**高频**。道具原始定义数据引用,提供 category、name、effect 等属性。典型使用场景:显示道具名称、判断道具类型、执行道具效果。 -- `IHeroItemState.count`:预期频率**中频**。道具当前持有数量。三种可存储类型的道具均正常存储数量,不存在时视为无此道具。 - -### 预期体量 - -约 12-16 行。 - -## IHeroItemSave - -### 设计意图 - -单个道具的存档格式,仅需存储可序列化的字段(`num`、`count`),`id` 和 `raw` 在读档时通过 `itemStore` 和 `tileStore` 恢复。 - -### 预期体量 - -约 5-8 行。 - -## IHeroItemsSave - -### 设计意图 - -整个背包的存档格式,包含三种类型道具的存档数组。由于三种类型的道具分表存储,存档也对应分表。 - -### 预期体量 - -约 5-8 行。 - -## IHeroItems - -### 设计意图 - -勇士背包管理接口,作为 `IHeroState` 的新成员。提供道具的增减查用等基础操作,所有操作均支持通过 `num`(数字)或 `id`(字符串)寻址。 - -`addItem` 是核心接口,负责向勇士给予道具(`count` 可为负数以实现扣除)。`getItem` 是 `addItem(item, 1)` 的便捷写法,语义为勇士在游戏中"拾取"一个道具。`getItemState` 是查询接口,获取道具的只读状态。 - -对于 Pick 类型道具,`addItem` / `getItem` 不将其存入背包,而是直接执行 `raw.effect.pickEffect`。 - -### 接口分析 - -- `IHeroItems.addItem(item, count?)`:预期频率**中频**。向勇士添加或减少道具。若道具为 Pick 类型则立刻执行其效果;否则根据 `category` 选择对应分表,增加或减少 `count`,数量归零或负数时自动删除条目。典型使用场景:勇士在地图上走到道具格后拾取道具;事件脚本中给予或扣除玩家道具。 -- `IHeroItems.getItem(item)`:预期频率**高频**。使勇士获取一个道具,即 `addItem(item, 1)` 的另一种写法。Pick 类型立即执行效果,其他类型背包数量加一。典型使用场景:勇士走到地图道具格上触发拾取。 -- `IHeroItems.getItemState(item)`:预期频率**高频**。获取指定道具的只读状态,是所有查询操作的入口。返回 `null` 表示未持有。典型使用场景:NPC 检查玩家是否持有某任务道具;UI 显示道具详情。 -- `IHeroItems.useItem(item)`:预期频率**中频**。使用道具,仅对 Constant 和 Consumable 类型生效。调用 `raw.effect.useEffect`,Consumable 类型使用后数量减一。返回 `boolean` 表示是否使用成功。 -- `IHeroItems.itemCount(item)`:预期频率**高频**。获取道具当前持有数量,不存在时返回 0。典型使用场景:UI 中显示道具剩余数量。 - -### 预期体量 - -接口约 20-25 行。 - -实现类 `HeroItems` 约 80-100 行。内部维护三张 `Map`,提供增删查用及存档读档逻辑。 - -# 实现思路 - -## id 与 num 的统一寻址 - -所有接口参数均接受 `number | string` 类型的 `item`。当传入为数字时,直接用作 Map 键;当传入为字符串时,需要先通过 `state.tileStore.idToNumber(item)` 转换为 `num`,再用于查找。若转换结果为 `null` 则视为不存在。 - -注意:道具 `id` 与 `num` 的转换依赖 `ITileStore.idToNumber`,而非 `IItemStore`(道具本身是 tile 的一类具体属性,通过 tile 即可定位,`IItemStore` 只负责道具定义存储,不需要 id↔num 双向索引)。 - -## 道具存取流程 - -1. `addItem(item, count)`:将 `item` 解析为 `num`,调用 `state.itemStore.getData(num)` 获取 `raw`。若 `raw` 为 `null` 则忽略。若 `raw.category === ItemCategory.Pick`,执行 `raw.effect.pickEffect(raw)` 后直接返回。否则,根据 `raw.category` 选择对应分表 `Map`,若已存在条目则加大 `count`,结果 ≤ 0 则删除条目,否则创建新条目插入。 -2. `getItem(item)`:调用 `addItem(item, 1)`。 -3. `getItemState(item)`:将 `item` 解析为 `num`,通过 `state.itemStore.getCategory(num)` 获取道具类型并定位对应分表,从分表中获取条目并返回只读快照,不存在返回 `null`。 -4. `useItem(item)`:通过 `getItemState` 获取条目,若不存在或非 Constant/Consumable 类型则返回 `false`;若 `raw.effect.canUse` 为 `false` 则返回 `false`。调用 `raw.effect.useEffect`。若为 Consumable 类型,`count--`,归零时删除条目。返回 `true`。 -5. `itemCount(item)`:通过 `getItemState` 获取条目,存在则返回 `count`,不存在返回 `undefined`。 - -## 存档读档 - -- `saveState`:遍历三张分表,将每个条目提取 `{ num, count }` 形成 `IHeroItemSave`,构造 `IHeroItemsSave` 并返回。 -- `loadState`:遍历 `IHeroItemsSave` 中三种类型数组,对每条 `IHeroItemSave` 调用 `state.itemStore.getData(num)` 恢复 `raw`,重建分表。 - -# 涉及文件 - -## 需要引用的文件 - -- `@user/data-common`:引用 `ISaveableContent`、`IDataCommonExtended`、`SaveCompression`、`IItemRawData`、`ItemCategory`、`IDataCommon`。 -- `@user/data-base/hero/types.ts`:引用 `IHeroState`,向其新增 `items` 成员。 - -## 需要修改的文件 - -### `@user/data-base/hero/types.ts` - -- [x] 新增 `IHeroItemSave` 接口:包含 `readonly num: number`、`readonly count: number`。 -- [x] 新增 `IHeroItemsSave` 接口:包含 `readonly constants: readonly IHeroItemSave[]`、`readonly consumables: readonly IHeroItemSave[]`、`readonly equipments: readonly IHeroItemSave[]`。 -- [x] 新增 `IHeroItemState` 接口:包含 `readonly id: string`、`readonly num: number`、`readonly raw: IItemRawData`、`count: number`。 -- [x] 新增 `IHeroItems` 接口:继承 `ISaveableContent`、`IDataCommonExtended`,提供 `addItem`、`getItem`、`getItemState`、`useItem`、`itemCount` 方法。 -- [x] 修改 `IHeroStateSave`:新增 `readonly items: IHeroItemsSave` 成员。 -- [x] 修改 `IHeroState`:新增 `readonly items: IHeroItems` 成员。 - -### `@user/data-base/hero/items.ts`(新建) - -- [ ] 实现 `HeroItemState` 类:实现 `IHeroItemState` 接口,存储 `id`、`num`、`raw`、`count`。 -- [ ] 实现 `HeroItems` 类:实现 `IHeroItems` 接口,按功能分区(查询 / 增删 / 使用 / 存档 / 私有辅助)。 -- [ ] `HeroItems` 内部维护 `constants`、`consumables`、`equipments` 三张 `Map`。 -- [ ] 实现 `internalGetItemState` 私有方法:通过 `itemStore.getCategory` 定位分表后从中获取,返回可修改引用。 -- [ ] 实现 `getItemState`:包装 `internalGetItemState`,返回 `Readonly` 快照。 -- [ ] 实现 `addItem`:通过 `state.itemStore.getData` 获取 `raw`,Pick 类型执行效果后直接返回;其他类型按 `category` 插入对应分表,支持负数删除。 -- [ ] 实现 `getItem`:转发至 `addItem(item, 1)`。 -- [ ] 实现 `useItem`:仅对 Constant/Consumable 调用 `raw.effect.useEffect`,Consumable 减量,返回成功与否。 -- [ ] 实现 `itemCount`:通过 `getItemState` 返回 count 或 0。 -- [ ] 实现 `mapToSave` / `loadMap`:存档仅存 `{ num, count }`,读档通过 `itemStore.getData` 恢复 `raw`。 -- [ ] 实现 `saveState` / `loadState`:序列化与反序列化三张分表。 - -### `@user/data-base/hero/state.ts` - -- [ ] `HeroState` 构造函数中新增 `this.items = new HeroItems(state)`。 -- [ ] `saveState` 中新增 `items: this.items.saveState(compression)`。 -- [ ] `loadState` 中新增 `this.items.loadState(state.items, compression)`。 - -### `@user/data-base/hero/index.ts` - -- [ ] 新增 `export * from './items'` 导出。 diff --git a/docs/dev/hero/hero-modifier-save.md b/docs/dev/hero/hero-modifier-save.md deleted file mode 100644 index b5a87e1..0000000 --- a/docs/dev/hero/hero-modifier-save.md +++ /dev/null @@ -1,110 +0,0 @@ -# 需求综述 - -当前勇士属性修饰器 `IHeroModifier` 是非注册式的,无法直接存档。 -目标是将其改为注册式,每种修饰器在使用前需先在属性对象上注册, -并实现 `ISaveableContent` 接口以支持存档读档。 - -注册接口签名:`registerModifier(identifier: string, cons: () => IHeroModifier): void` - -# 实现思路 - -## 1. 修改 `IHeroModifier` 接口 - -- 将 `identifier` 改为 `type`:一类修饰器可能被添加多次, - `type` 比 `identifier` 更准确地表达"修饰器类型"的含义。 -- 新增泛型参数 `S = unknown` 作为存档类型,让接口继承 `ISaveableContent`: - `IHeroModifier` -- 大多数修饰器的可变状态只有 `value`,因此 `BaseHeroModifier` 将 `S` 默认为 `V`。 - 若修饰器需要特殊存储结构,可以不继承 `BaseHeroModifier`,自行编写实现类。 - -## 2. 新增 `IModifierStateSave` 类型 - -`IModifierStateSave` 记录单条修饰器的存档信息: - -```ts -interface IModifierStateSave { - readonly name: PropertyKey; // 属性名,如 'atk' - readonly type: string; // 修饰器类型(与注册时的 key 对应) - readonly state: unknown; // 修饰器 saveState 结果 -} -``` - -## 3. 修改 `IHeroStateSave`,新增 `modifiers` 字段 - -`attribute` 字段维持原来的 `THero` 只保存基础属性值, -修饰器列表单独作为顶层字段: - -```ts -readonly modifiers: readonly IModifierStateSave[]; -``` - -## 4. 修改 `IHeroAttribute`,新增 `iterateModifiers` 方法 - -`HeroAttribute` 不再继承 `ISaveableContent`,也不负责存读档, -保留现有的 `toStructured(): THero`。 -新增 `iterateModifiers` 方法,供 `HeroState` 在存档时遍历所有已挂载的修饰器: - -```ts -iterateModifiers(): Iterable<[keyof THero, IHeroModifier]>; -``` - -## 5. 修改 `BaseHeroModifier`,新增 `S` 泛型并实现 `ISaveableContent` - -- 改为 `BaseHeroModifier` 隐式以 `S = V` 实现 `ISaveableContent` -- 将 `abstract readonly identifier: string` 改为 `abstract readonly type: string` -- `saveState()` 直接返回 `this.currentValue` -- `loadState(state)` 调用 `this.setValue(state)` 恢复值 - -## 6. 修改 `HeroState` - -修饰器注册表移至 `HeroState`: - -- 新增 `private readonly registry: Map IHeroModifier>` 存储工厂函数 -- 实现 `registerModifier(type, cons)`:向 `registry` 写入工厂, - 并同步注册到接口签名中 -- 实现 `createModifier(type)`:从 `registry` 取出对应工厂, - 调用工厂函数创建并返回修饰器实例,类型为 `IHeroModifier` -- 实现 `createAndInsertModifier(type, name)`: - 调用 `createModifier` 创建实例后,自动调用 `this.attribute.addModifier(name, modifier)` 插入属性对象, - 返回该修饰器实例,类型与 `createModifier` 一致 -- 修改 `saveState()`: - 1. `attribute` 字段调用 `this.attribute.toStructured()` 获取基础属性值(与现在一致) - 2. 遍历 `this.attribute.iterateModifiers()`,对每个修饰器调用 - `modifier.saveState(compression)` 并拼装 `IModifierStateSave[]`, - 写入 `modifiers` 字段 -- 修改 `loadState()`: - 1. 创建新的 `HeroAttribute` 实例(使用 `state.attribute` 还原基础属性值,与现在一致) - 2. 遍历 `state.modifiers`,通过 `registry.get(type)` 创建修饰器实例, - 调用 `modifier.loadState(state)` 恢复值,再 `addModifier(name, modifier)` 挂载 - -# 涉及文件 - -## 需要修改的文件 - -### `@user/data-base/hero/types.ts` - -- [x] 修改 `IHeroModifier` 接口: - 改为 `IHeroModifier`,`identifier` 改名为 `type`, - 继承 `ISaveableContent` -- [x] 新增 `IModifierStateSave` 接口:单条修饰器的存档格式 -- [x] 修改 `IHeroStateSave`:新增 `readonly modifiers: readonly IModifierStateSave[]` 字段 -- [x] 修改 `IReadonlyHeroAttribute`:新增 `iterateModifiers()` 方法签名 -- [x] 修改 `IHeroState`:新增以下方法签名 - `registerModifier(type: string, cons: () => IHeroModifier): void` - `createModifier(type: string): IHeroModifier` - `createAndInsertModifier(type: string, name: K): IHeroModifier` - -### `@user/data-base/hero/attribute.ts` - -- [x] 修改 `BaseHeroModifier`: - 将 `abstract readonly identifier` 改为 `abstract readonly type`; - 实现 `saveState` / `loadState` -- [x] 修改 `HeroAttribute`:实现 `iterateModifiers()` - -### `@user/data-base/hero/state.ts` - -- [x] 修改 `HeroState`:新增 `private readonly registry: Map IHeroModifier>` 成员 -- [x] 实现 `HeroState.registerModifier`:将工厂函数写入 `registry` -- [x] 实现 `HeroState.createModifier`:从 `registry` 取出工厂并调用,返回新实例; - 若 `type` 未注册则抛出错误 -- [x] 实现 `HeroState.createAndInsertModifier`:调用 `createModifier` 后, - 再调用 `this.attribute.addModifier(name, modifier)`,返回同一实例 -- [x] 修改 `HeroState.saveState`:遍历 `iterateModifiers()` 写入 `modifiers` 字段 -- [x] 修改 `HeroState.loadState`:遍历 `state.modifiers` 重建修饰器并挂载 diff --git a/docs/dev/hero/hero-mover.md b/docs/dev/hero/hero-mover.md deleted file mode 100644 index 3869506..0000000 --- a/docs/dev/hero/hero-mover.md +++ /dev/null @@ -1,121 +0,0 @@ -# 需求综述 - -将旧样板中的 `HeroMover`(`packages-user/data-state/src/legacy/move.ts`)基于新的 `IObjectMover` / `ObjectMover` 基类进行彻底性重构,编写新的 `IHeroMover` 接口和 `HeroMover` 实现类。 - -新实现的核心变化: - -- 基类从旧 `ObjectMoverBase`(EventEmitter 模式)迁移至 `ObjectMover`(Hook 模式)。 -- `inLockControl` 删除,重构后不再需要。 -- 移动速度不再由 `HeroMover` 存储,速度变更通过 `ObjectMover.speed()` 方法体现,实际动画时长在 Hook 中自行处理。 -- `noRoute`、`ignoreTerrain`、`autoSave` 不再作为公开属性暴露,改为通过 `config()` / `getConfig()` 方法管理,配置对象为 `IHeroMoverConfig`。 -- 摒弃所有 `core.xxx` 调用,这些功能将在后续通过新接口逐步补充。 -- `HeroMover` 的实例化由 `IHeroLocation` 的实现类内部完成,构造时传入 `this` 作为 `tile`,解决循环依赖。 - -# 接口设计分析 - -`IHeroMover` 直接扩展 `IObjectMover`,以下仅分析新增内容。 - -## IHeroMover - -### 接口综述 - -`IHeroMover` 在通用对象移动器基础上,通过 `config()` 方法接收一个 `IHeroMoverConfig` 配置对象来控制本次移动的行为模式(是否忽略地形、是否记录路径、是否触发自动存档)。这种方法将三个相关标志聚合到统一的配置接口中,调用方便且后续扩展新配置项时无需新增独立属性。`getConfig()` 返回当前生效的只读配置,供内部逻辑和 Hook 查询移动上下文。 - -### 接口分析 - -- `IHeroMover.config(config)`:预期频率**中频**。在移动开始前统一配置本次移动的行为标志,一次性设置多个相关选项,比分别设置三个独立属性更加便利。典型使用场景:自动寻路系统在每次移动前调用 `mover.config({ noRoute: true }).forward(3).start()`,通过链式调用一气呵成。 - -- `IHeroMover.getConfig()`:预期频率**低频**。查询当前移动器的配置快照,主要用于 Hook 内部判断移动上下文,或调试时检查配置状态。典型使用场景:Hook 中的 `onStepStart` 获取 `mover.getConfig()` 以判断当前步是否需要路线记录。 - -### IHeroMoverConfig 成员分析 - -- `noRoute`:预期频率**中频**。控制本次移动是否记录进路线系统。路线回放、自动寻路等系统均需跳过路线记录,涉及面较广,故为中频。 - -- `ignoreTerrain`:预期频率**低频**。控制本次移动是否跳过地形碰撞检测,仅用于瞬移、强制移动等特殊场景。 - -- `autoSave`:预期频率**低频**。控制本次移动是否在特定时机触发自动存档,仅在地图伤害相关全局系统内部设置。 - -> 三个配置项在 `config()` 参数中均为可选,未显式设置的项保持当前值不变,`getConfig()` 返回的是已补全默认值的完整配置只读快照。 - -### 预期体量 - -接口定义部分(`IHeroMover`、`IHeroMoverConfig`)约 15-20 行。 - -`HeroMover` 类的预期代码体量为 200-250 行。分析如下: - -- 类定义与私有属性(`noRoute`、`ignoreTerrain`、`autoSave` 各自独立声明),以及 `config` / `getConfig` 实现,约 30 行。 -- `onMoveStart` 实现(同步渲染端状态、预检查首步地形等),约 30 行。 -- `onMoveEnd` 实现(恢复状态、通知渲染端结束、清除自动寻路状态),约 20 行。 -- `onStepStart` 实现(地形检测、自动存档判断、渲染端动画调用等),约 55 行。 -- `onStepEnd` 实现(根据移动代码分发处理、返回新坐标、路线记录、触发器等),约 65 行。 -- 辅助方法(坐标计算、地形检测等),约 30 行。 -- `HeroMoveCode` 枚举定义,约 10 行。 - -### 可能风险 - -- `IHeroLocation` 接口已定义 `readonly mover: IObjectMover`。由于 `IHeroMover extends IObjectMover`,若 `HeroMover` 的构造在 `IHeroLocation` 内部完成(传入 `this`),类型可自然满足且无循环依赖问题。但需确保 `IHeroLocation` 的实现类确实在其构造过程中创建并持有 `HeroMover` 实例。 - -# 实现思路 - -## 1. 在 `types.ts` 中新增相关接口与枚举 - -新增 `IHeroMoverConfig` 接口,包含 `noRoute`、`ignoreTerrain`、`autoSave` 三个可选布尔成员。 - -新增 `IHeroMover` 接口,继承 `IObjectMover`,新增 `config(config)` 和 `getConfig()` 两个方法。 - -新增 `HeroMoveCode` 枚举,公开导出,包含正常移动、停止、撞击、不可移动等代码,供 Hook 用户判断每步移动结果。 - -## 2. 完成 `HeroMover` 类 - -在 `packages-user/data-state/src/hero/mover.ts` 中实现 `HeroMover extends ObjectMover`。 - -构造函数接收两个参数: -- `tile: IHeroLocation` — 勇士位置对象,同时满足 `IObjectMovable`,作为基类移动操作的绑定目标。 -- `faceHandler: IFaceHandler` — 朝向处理器,由 `IHeroLocation` 实现类在创建 `HeroMover` 时传入(本样式中为 `Dir4FaceHandler`)。 - -`config(config)`:将传入配置中已显式提供的字段分别赋值到对应的私有属性 `noRoute`、`ignoreTerrain`、`autoSave`,未传入的字段保持原值,返回 `this` 以支持链式调用。 - -`getConfig()`:从三个私有属性分别读取当前值,组装为 `Readonly` 返回,默认值均为 `false`。 - -`onMoveStart`:移动开始时读取当前配置,通过 `IHeroLocation.mover`(渲染端 `HeroMoveController` 实例)启动渲染端移动动画。若 `ignoreTerrain` 为 `false`,预检查第一步是否能通过。 - -`onMoveEnd`:移动结束时通知渲染端移动结束,清除自动寻路相关状态。 - -`onStepStart`:根据 `ignoreTerrain` 判断是否进行地形检测,若不可通过则返回对应的 `HeroMoveCode`。处理 `autoSave` 的存档判断逻辑。启动渲染端单步移动动画。根据 `noRoute` 判断是否记录路线。 - -> **注意**:地形检测、路线记录、触发器、中毒伤害等具体逻辑依赖的新接口尚不存在,`core.xxx` 调用在此重构中摒弃。当前在这些位置以 `// TODO:` 标记占位,等待后续接口补充。 - -`onStepEnd`:根据 `onStepStart` 返回的 `HeroMoveCode` 执行对应逻辑: -- `Step`:返回目标坐标 `ITileLocator` 让基类更新位置,执行路线记录与后置逻辑。 -- `Hit`:停止移动,触发目标格触发器。 -- `CannotMove`:停止移动,不触发触发器。 -- `Stop`:停止移动,清除自动寻路。 - -## 3. 在 `hero/index.ts` 中导出 - -在 `packages-user/data-state/src/hero/index.ts` 中新增 `export * from './mover'`。 - -# 涉及文件 - -## 需要引用的文件 - -- `@motajs/common`:引用 `ITileLocator` 等基础接口。 -- `@user/data-common`:引用 `IObjectMover`、`IObjectMovable`、`ObjectMover`、`ObjectMoveStep`、`ObjectMoveStepType`、`FaceDirection`、`IFaceHandler`、`IFaceDescriptor` 等类型。 -- `@user/data-base`:引用 `IHeroLocation`、`IHeroMoveController` 等勇士相关接口。 -- `types.ts`(同包顶层):引用 `IHeroMover`、`IHeroMoverConfig`、`HeroMoveCode`(类实现接口时自引用)。 - -## 需要修改的文件 - -### `packages-user/data-state/src/types.ts` - -- [ ] 新增 `IHeroMoverConfig` 接口:定义移动配置对象的三个可选布尔成员 `noRoute`、`ignoreTerrain`、`autoSave`。 -- [ ] 新增 `IHeroMover` 接口:继承 `IObjectMover`,新增 `config()` 和 `getConfig()` 方法。 -- [ ] 新增 `HeroMoveCode` 枚举:定义勇士移动步骤返回码,公开导出供 Hook 使用。 - -### `packages-user/data-state/src/hero/mover.ts`(新建) - -- [ ] 实现 `HeroMover` 类:继承 `ObjectMover`,实现 `config`、`getConfig` 及基类四个抽象方法。 - -### `packages-user/data-state/src/hero/index.ts` - -- [ ] 新增 `export * from './mover'`:导出新模块。 diff --git a/docs/dev/hero/hero-terrain-check.md b/docs/dev/hero/hero-terrain-check.md deleted file mode 100644 index 2c7c3ee..0000000 --- a/docs/dev/hero/hero-terrain-check.md +++ /dev/null @@ -1,120 +0,0 @@ -# 需求综述 - -`HeroLocation` 当前仅有坐标信息(`x`/`y`),无法确定勇士当前所处的楼层,导致 `HeroMover.checkTerrain` 无法定位到正确的楼层进行地形判定。同时 `checkTerrain` 仍为 TODO 存根,直接返回 `HeroMoveCode.Step`,无法完成通行判定。 - -本次需要完成以下内容: - -1. 为 `HeroLocation` 新增楼层标识信息,保存楼层 id(`string | undefined`)而非 `ILayerState` 对象。勇士允许不处在任何楼层,此时 `floorId` 为 `undefined`。 -2. 完成 `HeroMover` 的地形通行判定逻辑,并引入可插拔的 `ITerrainPassChecker` 接口,允许用户注入自定义判定器。当未注入判定器时,地形判定直接返回不可通行。 - -# 接口设计分析 - -## ITerrainPassChecker - -### 接口综述 - -`ITerrainPassChecker` 是地形通行判定的可插拔抽象,仅含一个判定方法。HeroMover 每步移动前调用它来判定前方一格是否可通行(高频操作)。用户通过在 `HeroMover` 上调用 `useTerrainChecker` 注入判定器。若未注入(`null`),则 `checkTerrain` 直接返回 `HeroMoveCode.CannotMove`,即所有地形均不可通行。 - -该接口定义在 Layer 1(`@user/data-base`)。由于 `IHeroLocation` / `IHeroMover` 同为 Layer 1 对象,无法持有 `IStateBase` 引用(`IStateBase` 是 Layer 1 的顶层接口,其中 `maps` 需要 `IStateBase` 才可访问),因此 `canPass` 仅接收 `floorId` 作为楼层标识,具体如何获取 `ILayerState` 由判定器实现方自行处理(判定器实现一般在 Layer 2,可持有 `IStateBase` 引用)。 - -参考 `IDamageCalculator` 的设计模式:职责单一、实例注入、无外部依赖。 - -### 接口分析 - -- `ITerrainPassChecker.canPass(locator, direction, floorId)`:预期频率**高频**。每步移动都需要判定前方地形是否可通行,故为单单词命名。典型使用场景:`HeroMover.onStepStart` 中拿到当前步的 `moveDirection` 后调用 `canPass` 进行判定。`floorId` 可能为 `undefined`(勇士不处于任何楼层时),判定器需自行处理。 - -### 预期体量 - -接口定义约 6 行。HeroMover 中 `checkTerrain` 修改约 10 行,`useTerrainChecker` 约 5 行。 - -## IHeroLocation - -### 接口变化 - -`IHeroLocation` 新增 `readonly floorId: string | undefined` 只读成员。这是楼层标识符,与 `IMapStore.getLayerState(id)` 的 `id` 参数一致。`undefined` 表示勇士尚未处于任何楼层。 - -## IHeroMover - -### 接口变化 - -`IHeroMover` 新增 `useTerrainChecker(checker)` 方法,传入 `null` 移除判定器(默认状态为 `null`)。 - -预期频率:**低频**。一般仅在初始化时调用一次以注入自定义判定逻辑。 - -## 默认判定器 - -默认判定器(基于图块 `inPass`/`outPass` 的标准通行判定)作为 `ITerrainPassChecker` 的独立实现,放在 `@user/data-state/src/hero/` 下(Layer 2)。该实现持有 `IStateBase` 引用,可在 `canPass` 中通过 `floorId` 查找 `ILayerState`,进而获取事件层图块数据完成通行判定。 - -由于默认判定器不在本次需求的核心范围内,仅在设计层面预留位置,后续单独实现。 - -# 实现思路 - -## 1. 在 `types.ts` 中新增接口与类型 - -新增 `ITerrainPassChecker` 接口: - -```ts -export interface ITerrainPassChecker { - canPass( - locator: ITileLocator, - direction: FaceDirection, - floorId: string | undefined - ): boolean; -} -``` - -修改 `IHeroLocation`:新增 `readonly floorId: string | undefined`。 - -修改 `IHeroLocationSave`:新增 `readonly floorId: string | undefined`。由于原定义为 `extends Readonly`,新增 `floorId` 后需展开为显式成员声明,避免将 `floorId` 类型污染到 `IFacedTileLocator`。 - -修改 `IHeroMover`:新增 `useTerrainChecker(checker: ITerrainPassChecker | null): void`。 - -## 2. 修改 `HeroLocation` 类 - -- 新增 `floorId: string | undefined` 成员,初始值 `undefined`。 -- `saveState` 中将 `floorId` 写入 `IHeroLocationSave`。 -- `loadState` 中从 `IHeroLocationSave` 恢复 `floorId`。 - -由于暂不提供 `setFloorId`,`floorId` 的赋值留待后续处理。 - -## 3. 修改 `HeroMover` 类 - -- 新增 `private terrainChecker: ITerrainPassChecker | null = null`。 -- 实现 `useTerrainChecker(checker)`:`this.terrainChecker = checker ?? null`。 -- 修改 `checkTerrain` 方法: - 1. 若 `terrainChecker` 为 `null`,直接返回 `HeroMoveCode.CannotMove`。 - 2. 否则以当前坐标(`{ x: tile.x, y: tile.y }`)、`this.moveDirection`、`tile.floorId` 为参数调用 `terrainChecker.canPass`。 - 3. 若 `canPass` 返回 `false`,返回 `HeroMoveCode.CannotMove`;返回 `true` 则返回 `HeroMoveCode.Step`。 - -## 4. 导出 - -`ITerrainPassChecker` 已定义在 `hero/types.ts`,通过 `hero/index.ts` 导出,无需额外操作。 - -# 涉及文件 - -## 需要引用的文件 - -- `@motajs/common`:引用 `ITileLocator`。 -- `@user/data-common`:引用 `FaceDirection`、`IObjectMover`、`IObjectMovable`、`IFaceHandler`、`IMoverController`、`ObjectMoveStep`、`ObjectMoveStepType`、`getFaceMovement`。 -- `@user/data-base`:引用 `IHeroLocation`、`IHeroMover`、`IHeroMoverConfig`、`HeroMoveCode`。 - -## 需要修改的文件 - -### `packages-user/data-base/src/hero/types.ts` - -- [ ] 新增 `ITerrainPassChecker` 接口:提供可插拔的地形通行判定能力。 -- [ ] 修改 `IHeroLocation`:新增 `readonly floorId: string | undefined`。 -- [ ] 修改 `IHeroLocationSave`:新增 `readonly floorId: string | undefined`。将 `extends Readonly` 展开为显式成员声明 `readonly x: number`、`readonly y: number`、`readonly direction: FaceDirection`、`readonly floorId: string | undefined`。 -- [ ] 修改 `IHeroMover`:新增 `useTerrainChecker(checker: ITerrainPassChecker | null): void`。 - -### `packages-user/data-base/src/hero/location.ts` - -- [ ] 新增 `floorId: string | undefined` 成员,初始 `undefined`。 -- [ ] 修改 `saveState`,新增 `floorId` 字段。 -- [ ] 修改 `loadState`,恢复 `floorId` 字段。 - -### `packages-user/data-base/src/hero/mover.ts` - -- [ ] 新增 `private terrainChecker: ITerrainPassChecker | null = null`。 -- [ ] 实现 `useTerrainChecker` 方法。 -- [ ] 修改 `checkTerrain` 方法:无判定器时返回 `CannotMove`,有判定器时委托调用 `canPass`。 diff --git a/docs/dev/item/item-store.md b/docs/dev/item/item-store.md deleted file mode 100644 index ff3e66e..0000000 --- a/docs/dev/item/item-store.md +++ /dev/null @@ -1,121 +0,0 @@ -# 需求综述 - -参考 `ITileStore` 的设计,为道具系统定义 Layer 0 的原始数据存储层,包含 `IItemRawData`、`IItemLegacyConverter`、`IItemStore` 接口及 `ItemStore` 实现类,并挂载至 `IDataCommon`。道具本身是一类 tile 的具体属性,通过 tile 即可定位,不需要独立的 id↔num 双向索引。 - -# 接口设计分析 - -## ItemCategory 枚举 - -在 `store/types.ts` 中与 `TileType` 并列定义,用于标识道具分类: - -- `Unknown`:未知或尚未归类的道具。 -- `Constant`:永久道具,获得后常驻生效。 -- `Consumable`:一次性道具,使用后消耗。 -- `Pick`:即捡即用道具,捡到立刻生效。 -- `Equipment`:装备类道具,可装备 / 卸下。 - -### 设计意图 - -放在 `store/types.ts` 而非 `data-common/src/types.ts`,以避免 `types.ts` ↔ `store/types.ts` 之间的循环引用。`types.ts` 通过 `./store` 导入 `IItemStore` 挂载到 `IDataCommon`;若将 `ItemCategory` 放在 `types.ts`,`store/types.ts` 引用它时会产生循环。 - -## IItemRawData - -### 设计意图 - -参考 `ITileRawData` 的设计,`IItemRawData` 是道具的原始数据定义,存储于 `IItemStore` 中。除基础数据字段外,通过 `readonly effect: IItemEffect` 成员承载道具效果。旧样板字符串效果由 `IItemLegacyConverter` 在转换时通过 `new Function` 编译为 `IItemEffect` 实例。 - -`IItemStore` 作为 Layer 0 的基础设施被 `IDataCommon` 持有,与 `ITileStore` 平级。后续 `IItemManager`(Layer 1)通过 `IDataCommonExtended.state.itemStore` 访问原始数据,据此创建 `IItem` 运行时实例。 - -### 接口分析 - -- `IItemRawData.num`:预期频率**高频**。道具在地图上的图块数字,是地图→道具的核心关联键。典型使用场景:事件触发器读取地图上某图块对应的道具信息。 -- `IItemRawData.id`:预期频率**高频**。道具的字符串唯一标识符。典型使用场景:通过 id 跨地图引用道具。 -- `IItemRawData.category`:预期频率**中频**。道具分类,决定使用时的基础行为分支。典型使用场景:拾取道具时判断是否为 `Pick` 类以立即触发效果。 -- `IItemRawData.name`:预期频率**高频**。道具显示名称,UI 展示的核心字段。 -- `IItemRawData.text`:预期频率**低频**。道具描述文本,仅详情展示时出现。 -- `IItemRawData.effect`:预期频率**中频**。道具效果对象,包含 `pickEffect`、`useEffect`、`canUse` 三个方法。典型使用场景:拾取道具时调用 `effect.pickEffect(state)` 触发即捡即用效果。 - -### 预期体量 - -`IItemRawData` 约 15–20 行(含 `effect` 成员)。 - -## IItemEffect - -### 设计意图 - -道具效果接口,与 `IItemRawData` 并列定义在 `store/types.ts` 中。包含 `pickEffect`、`useEffect`、`canUse` 三个方法,参数为当前道具的 `IItemRawData`。由 `IItemLegacyConverter` 在转换时通过 `new Function` 编译实例化,编译函数内部自行持有 `IStateSystem` 引用(由实现类构造函数传入),方法内据此调用编译函数。 - -### 接口分析 - -- `IItemEffect.pickEffect`:预期频率**低频**。拾取道具时调用(仅 `Pick` 类型),参数为当前道具数据。 -- `IItemEffect.useEffect`:预期频率**中频**。使用道具时调用(`Constant` 和 `Consumable` 类型),参数为当前道具数据。典型使用场景:使用消耗道具触发属性变化。 -- `IItemEffect.canUse`:预期频率**中频**。判定道具是否可用(`Constant` 和 `Consumable` 类型),参数为当前道具数据。典型使用场景:开门钥匙判断是否满足使用条件。 - -### 预期体量 - -`IItemEffect` 约 12–15 行。 - -## IItemLegacyConverter - -### 设计意图 - -参考 `ITileLegacyConverter`,定义从旧样板道具对象到 `IItemRawData` 的转换器接口。转换时需将旧样板中的字符串效果(`itemEffect`、`useItemEffect`、`canUseItemEffect`)通过 `new Function` 编译为 `IItemEffect` 实例,赋给 `IItemRawData.effect`。编编译函数签名为 `(state: IStateSystem) => void`(`canUse` 返回 `boolean`),实现类内部自行持有 `IStateSystem`,接口方法仅传递 `IItemRawData`。 - -与后续 `IItemLegacyBridge` 不同——`IItemLegacyConverter` 位于 store 层负责原始数据的导入(含效果编译),`IItemLegacyBridge`(Layer 1)负责运行时属性的转换。二者分层独立,分别处理静态数据与运行时对象。 - -### 接口分析 - -- `IItemLegacyConverter.fromLegacy`:预期频率**低频**。仅在样板加载阶段调用,执行旧格式→新格式的批量转换,含字符串效果的编译。 - -### 预期体量 - -一个方法,约 8–10 行。 - -## IItemStore - -### 设计意图 - -参考 `ITileStore` 的接口模式,作为道具原始数据的集中存储。道具本身是一类 tile 的具体属性,通过 tile 即可查找到对应的道具信息,因此不需要 `ITileStore` 那样的双向 id↔num 索引。但数据内仍存储 `id` 与 `num`,方便在需要时进行查询。 - -### 接口分析 - -- `IItemStore.getData`:预期频率**高频**。按图块数字获取完整原始数据,是查询道具定义的最常用入口。典型使用场景:地图图层解析到某图块数字后读取道具定义。 -- `IItemStore.getCategory`:预期频率**中频**。快速判断道具分类,避免完整获取数据。典型使用场景:拾取判定时只关心是否为 `Pick` 类型。 -- `IItemStore.addItem`:预期频率**低频**。添加一个道具原始定义,仅在样板加载时调用。 -- `IItemStore.attachLegacyConverter`:预期频率**低频**。挂载旧样板转换器,初始化阶段调用。 -- `IItemStore.fromLegacy`:预期频率**低频**。使用当前转换器转换并写入一个旧样板道具定义。 - -### 预期体量 - -接口约 20–25 行,实现类 `ItemStore` 约 50–70 行(内部仅维护 `dataMap: Map` 一张表,与 `TileStore` 相比少了双向索引表)。 - -# 涉及文件 - -## 需要引用的文件 - -- `@user/data-common/src/store/types.ts`:参考 `ITileRawData`、`ITileLegacyConverter`、`ITileStore` 的接口模式,用于设计 `IItemStore`。 -- `@user/data-common/src/store/tileStore.ts`:参考 `TileStore` 的实现模式,用于实现 `ItemStore`。 - -## 需要修改的文件 - -### `@user/data-common/src/store/types.ts` - -- [x] 新增 `ItemCategory` 枚举:包含 `Unknown`、`Constant`、`Consumable`、`Pick`、`Equipment` 五个成员。放在 `store/types.ts` 以避免与 `types.ts` 的循环引用。 -- [x] 补齐 `IItemRawData` 接口:新增 `readonly effect: IItemEffect` 成员。 -- [x] 新增 `IItemEffect` 接口:包含 `pickEffect(item: IItemRawData): void`、`useEffect(item: IItemRawData): void`、`canUse(item: IItemRawData): boolean` 三个方法。 -- [x] 新增 `IItemLegacyConverter` 接口:定义旧样板道具→`IItemRawData` 的转换方法,负责将旧样板字符串效果编译为 `IItemEffect` 实例。 -- [x] 新增 `IItemStore` 接口:提供 `getData`、`getCategory`、`addItem`、`attachLegacyConverter`、`fromLegacy` 方法,不需要 id↔num 双向索引。 - -### `@user/data-common/src/store/itemStore.ts`(新建) - -- [x] 新增 `ItemStore` 类:实现 `IItemStore` 接口,参考 `TileStore` 的实现模式。 -- [x] 内部维护 `dataMap: Map` 一张表,无需 id num 双向映射。 -- [x] 实现 `fromLegacy`:无 converter 时抛出错误。 - -### `@user/data-common/src/store/index.ts` - -- [x] 新增 `export * from './itemStore'` 导出。 - -### `@user/data-common/src/types.ts` - -- [x] 修改 `IDataCommon` 接口:新增 `readonly itemStore: IItemStore` 成员。 diff --git a/docs/dev/map/dynamic-tile-move.md b/docs/dev/map/dynamic-tile-move.md deleted file mode 100644 index ea4c905..0000000 --- a/docs/dev/map/dynamic-tile-move.md +++ /dev/null @@ -1,595 +0,0 @@ -# 动态图块移动系统 - -> 本文档为 [动态图块系统](./dynamic-tile.md) 的移动子系统设计文档, -> 重点描述双方向模型、`IDynamicMover` 计划式移动接口及执行流程。 -> 本文档中新增的类型与接口同样需要添加至 `@user/data-base/src/map/types.ts`。 - ---- - -# 背景与动机 - -`IDynamicLayer.moveDynamicStep` / `moveDynamicWith` 是命令式简单接口,适用于 -一次性的简单移动,但对以下场景力不从心: - -- **后退移动**:角色面朝上、向下移动时,朝向不变但位移反向, - 单一 `FaceDirection` 参数无法同时描述朝向和移动方向; -- **连续路径中途动态追加步骤**:命令式接口执行后无法继续追加; -- **移动速度切换**:路径中途需要变速,命令式接口不支持; -- **复杂动画编排**:需要按序执行多段计划时,纯 `Promise` 链表达不够直观。 - -因此引入以图块为粒度绑定的 `IDynamicMover`,采用**先建计划、再执行**的设计理念, -参考 `@motajs/animate/types.ts` 的链式计划构建模式, -以及 `@user/data-state/legacy/move.ts` 中 `ObjectMoverBase` 的双方向与队列设计。 - ---- - -# 实现思路 - -## 1. 双方向模型 - -每个动态图块维护两个方向字段: - -| 字段 | 含义 | 影响 | -| --------------- | ---------------------------- | --------------------------------------- | -| `faceDirection` | **朝向**,视觉上「面朝哪里」 | 通过 `IRoleFaceBinder` 决定渲染图块数字 | -| `moveDirection` | **移动方向**,「向哪边走」 | 结合 `getFaceMovement` 计算位移 | - -两者分离后可以表达: - -- **后退**:`moveDirection = Up`,执行 `backward()` 步, - `moveDirection = Down`,`faceDirection = Down`,位移向下,朝向同步翻转。 -- **横向移动不转身**:使用 `stepFace(move, face)` 单独指定朝向, - `moveDirection` 与 `faceDirection` 独立设置。 - -绝对方向步骤(传入 `FaceDirection`)默认将两者同时更新; -`forward()` 沿当前 `moveDirection` 移动并对齐 `faceDirection`; -`backward()` 将 `moveDirection` 取反并同步 `faceDirection`。 - -## 2. 方向步语义 - -绝对方向步(`IDynamicMoveStepDir` / `IDynamicMoveStepDirFace`)直接传入 `FaceDirection`: - -- `step(dir, count?: number)`:追加 `count`(默认 1)个绝对方向步,`moveDirection` 和 `faceDirection` 均更新为 `dir`; -- `stepFace(move, face, count?: number)`:追加 `count`(默认 1)个绝对方向步,`moveDirection` 更新为 `move`, - `faceDirection` 更新为 `face`——用于移动方向与朝向不一致的场景(如斜切移动保持正向)。 - -相对方向步(`IDynamicMoveStepSpecial`)通过独立方法构建: - -- `forward(count?)`:沿当前 `tile.moveDirection` 移动,`moveDirection` 不变, - `faceDirection` 对齐为 `moveDirection`; -- `backward(count?)`:沿 `opposite(tile.moveDirection)` 移动,`moveDirection` 取反, - `faceDirection` 同步更新为新的 `moveDirection`,自动使用逆向动画。 - -## 3. DynamicMoveStep 类型 - -步骤类型通过 `const enum` 区分,避免字符串比较。 -前进/后退方向用独立的 `DynamicSpecialStep` 枚举表示; -动画播放方向用独立的 `DynamicAnimDirection` 枚举表示,两者互不依赖: - -```ts -const enum DynamicMoveStepType { - /** 绝对方向步,同步更新 moveDirection 和 faceDirection */ - Dir, - /** 绝对方向步,单独指定 faceDirection */ - DirFace, - /** 速度步 */ - Speed, - /** 纯转向步,不产生位移 */ - Face, - /** 特殊步(前进或后退) */ - Special, - /** 动画方向步,不影响 moveDirection/faceDirection */ - AnimDir -} - -/** 特殊步方向:前进或后退 */ -const enum DynamicSpecialStep { - Forward, - Backward -} - -/** 动画播放方向:正向或逆向 */ -const enum DynamicAnimDirection { - Forward, - Backward -} - -/** 绝对方向步:move 方向既是移动方向也是朝向 */ -interface IDynamicMoveStepDir { - type: DynamicMoveStepType.Dir; - move: FaceDirection; -} - -/** 绝对方向步:move 是移动方向,face 是显式指定的朝向 */ -interface IDynamicMoveStepDirFace { - type: DynamicMoveStepType.DirFace; - move: FaceDirection; - face: FaceDirection; -} - -/** 速度步:修改后续移动的每格耗时(单位 ms,越小越快) */ -interface IDynamicMoveStepSpeed { - type: DynamicMoveStepType.Speed; - value: number; -} - -/** 转向步:仅更新 faceDirection,不产生位移,等待渲染动画完成 */ -interface IDynamicMoveStepFace { - type: DynamicMoveStepType.Face; - value: FaceDirection; -} - -/** 特殊步:前进(Forward)或后退(Backward)。`forward(n)`/`backward(n)` 构建时 n>1 会 push n 个相同步骤 */ -interface IDynamicMoveStepSpecial { - type: DynamicMoveStepType.Special; - direction: DynamicSpecialStep; -} - -/** 动画方向步:指定后续步骤动画正向播放(Forward)还是逆向播放(Backward) */ -interface IDynamicMoveAnimDir { - type: DynamicMoveStepType.AnimDir; - dir: DynamicAnimDirection; -} - -type DynamicMoveStep = - | IDynamicMoveStepDir - | IDynamicMoveStepDirFace - | IDynamicMoveStepSpeed - | IDynamicMoveStepFace - | IDynamicMoveStepSpecial - | IDynamicMoveAnimDir; -``` - -## 4. IMoverController 接口 - -`IDynamicMover.start()` 返回 `IMoverController`,用于控制进行中的移动。 - -```ts -interface IMoverController { - /** 本次移动是否已全部完成 */ - readonly done: boolean; - /** 当本次移动全部步骤完成时兑现 */ - readonly onEnd: Promise; - /** - * 向当前队列末尾追加步骤(仅在移动进行中有效,完成后追加无效) - */ - push(...steps: DynamicMoveStep[]): void; - /** - * 停止移动,等待当前步骤完成后停止,返回的 Promise 在停止后兑现 - */ - stop(): Promise; -} -``` - -## 5. IDynamicMover 接口 - -每个 `IDynamicTile` 持有一个**绑定**的 `IDynamicMover` 实例(`tile.mover`), -无需单独创建或管理生命周期。 - -### 5.1 状态读取 - -```ts -// 以下状态属性来自 IObjectMover,IDynamicMover 全部继承 -interface IObjectMover { - /** 是否正在移动 */ - readonly moving: boolean; - /** 当前朝向(与 tile.faceDirection 同步) */ - readonly faceDirection: FaceDirection; - /** 当前移动方向(与 tile.moveDirection 同步) */ - readonly moveDirection: FaceDirection; - /** 所属的可移动对象 */ - readonly tile: T; - // ... -} -``` - -### 5.2 计划构建(链式) - -所有构建方法均返回 `this`,支持链式调用。 -计划在调用 `start()` 前不执行,仅追加到内部队列。 - -```ts -interface IDynamicMover { - // ... - /** 追加 count(默认 1)个绝对方向步,同步更新 moveDirection 和 faceDirection */ - step(dir: FaceDirection, count?: number): this; - /** 追加 count(默认 1)个绝对方向步,单独指定 faceDirection(用于移动方向与朝向不一致的场景) */ - stepFace(move: FaceDirection, face: FaceDirection, count?: number): this; - /** 追加前进步,沿当前 moveDirection 移动,faceDirection 对齐为 moveDirection */ - forward(count?: number): this; - /** 追加后退步,moveDirection 取反,faceDirection 同步更新,自动使用逆向动画 */ - backward(count?: number): this; - /** 追加一个速度步,改变后续移动的每格耗时(ms) */ - speed(value: number): this; - /** 追加一个纯转向步,仅更新 faceDirection,不产生位移 */ - face(dir: FaceDirection): this; - /** 追加一个动画方向步,控制后续步骤的动画播放方向(Forward 正向 / Backward 逆向) */ - animDir(dir: DynamicAnimDirection): this; - /** 清空尚未执行的计划队列 */ - clear(): this; - // ... -} -``` - -**用法示例**: - -```ts -// 先向下走 2 步,切换为半速,再向右走 1 步 -tile.mover - .step(FaceDirection.Down, 2) - .speed(200) - .step(FaceDirection.Right) - .start(); - -// 后退 3 步(moveDirection/faceDirection 翻转,逆向动画) -tile.mover.backward(3).start(); - -// 向右移动但保持朝下(如横向滑步) -tile.mover.stepFace(FaceDirection.Right, FaceDirection.Down, 2).start(); -``` - -### 5.3 执行控制 - -```ts -interface IDynamicMover { - // ... - /** - * 开始执行计划队列,返回控制对象。 - * 若已在移动则返回 null(不中断当前移动)。 - */ - start(): IMoverController | null; -} -``` - -## 6. 移动执行流程 - -每次 `start()` 启动后,执行器按以下总流程处理队列: - -触发 `onMoveStart` → 循环每步:[ -`abstract onStepStart(step, tile, mover)` 执行并返回 `code: number`(子类计算本步场景)→ -触发 `IObjectMoverHooks.onStepStart(code, step, tile, mover)` 钩子(渲染预处理,此时 `tile.x/y` 为**移动前坐标**)→ -等待所有 Promise → -`abstract onStepEnd(code, step, tile, mover)` 执行**逻辑效果**(子类根据 code 决定如何执行)→ -触发 `IObjectMoverHooks.onStepEnd(code, step, tile, mover)` 钩子(此时 `tile.x/y` 为**移动后坐标**) -] → 触发 `onMoveEnd`。 - -### 6.1 绝对方向步(`DynamicMoveStepType.Dir`) - -**onStepStart 阶段**(`tile.x/y` 为原始坐标): - -1. `abstract onStepStart(step, tile, mover)` 执行,子类计算本步 `code`(正常移动、碰墙等)并返回 `Promise`。 -2. 父类拿到 `code`,触发 `IObjectMoverHooks.onStepStart(code, step, tile, mover)` 钩子——渲染端在此阶段播放移动动画, - 可由 `step.move` 与当前 `tile.x/y` 推算目标位置。 -3. `await Promise.all(promises)`——等待全部动画完成。 - -**onStepEnd 阶段**(逻辑效果): - -1. `abstract onStepEnd(code, step, tile, mover)` 执行——子类依据 `code` 决定本步实际效果: - 更新 `tile.moveDirection = step.move`。 -2. 计算 `{dx, dy} = getFaceMovement(step.move)`,**立即**更新 `tile.x += dx`、`tile.y += dy`。 -3. 同步更新 `posMap`。 -4. 执行转向逻辑(见 [dynamic-tile.md §8](./dynamic-tile.md#8-转向逻辑)), - 更新 `tile.faceDirection` 与 `tile.num`。 -5. 子类返回后,父类触发 `IObjectMoverHooks.onStepEnd(code, step, tile, mover)` 钩子,此时 `tile.x/y` 已为新坐标。 - -### 6.1.1 含显式朝向的绝对方向步(`DynamicMoveStepType.DirFace`) - -与 `Dir` 步骤相同,但步骤 5 改为直接将 `tile.faceDirection` 设为 `step.face`, -并通过 `getFaceOf(tile.num, step.face)` 更新 `tile.num`,不经过 `degradeFace` 降级。 - -### 6.2 速度步(`DynamicMoveStepType.Speed`) - -更新内部速度状态,立即生效,无位移,无需等待。 - -### 6.3 纯转向步(`DynamicMoveStepType.Face`) - -经由 `onStepStart` 触发钩子(`tile.x/y` 不变,`step.type === DynamicMoveStepType.Face` 供渲染端区分); -等待渲染端动画完成后,`onStepEnd` 阶段执行转向逻辑,更新 `faceDirection` 与 `tile.num`, -触发 `onStepEnd` 钩子。 - -### 6.4 特殊步(`DynamicMoveStepType.Special`) - -**语义说明**:`forward`/`backward` 以**上一步的移动方向**(即当前 `tile.moveDirection`)为基准; -若无上一步,则回退到当前 `faceDirection`;若图块无朝向绑定,则抛出错误。 - -**Forward**(`DynamicSpecialStep.Forward`): - -1. `actualDir = tile.moveDirection`(不改变 moveDirection); -2. 执行与 `Dir` 步骤相同的位移流程; -3. 步骤 2:跳过(`moveDirection` 保持不变); -4. 步骤 5:执行转向逻辑,以 `actualDir` 更新 `tile.faceDirection` 与 `tile.num`。 - -**Backward**(`DynamicSpecialStep.Backward`): - -1. `actualDir = opposite(tile.moveDirection)`; -2. 在执行前临时将逆向动画标志设为开启(步骤完成后恢复); -3. 执行与 `Dir` 步骤相同的位移流程; -4. 步骤 2:`tile.moveDirection = actualDir`; -5. 步骤 5:执行转向逻辑,以 `actualDir` 更新 `tile.faceDirection` 与 `tile.num`。 - -### 6.5 动画方向步(`DynamicMoveStepType.AnimDir`) - -指定后续步骤动画是**正向播放**(`DynamicAnimDirection.Forward`)还是**逆向播放**(`DynamicAnimDirection.Backward`), -与图块朝向无关(朝向始终由 `faceDirection` 决定)。 -修改 mover 内部的 `currentAnimDir` 状态。 -渲染端在 `onStepStart` 收到 `AnimDir` 步骤时读取并更新本地动画方向状态,后续步骤的 `onStepStart` 触发时依据此状态决定播放方向。 -`onStepEnd` 阶段无逻辑操作,立即完成,不影响坐标与方向。 - -## 7. onStepStart / onStepEnd 钩子返回值 - -`IObjectMoverHooks.onStepStart` 与 `onStepEnd` 均为可选钩子(`?`),返回类型为 `Promise`, -执行器始终通过 `Promise.all` 等待所有订阅方完成后再继续。 - -关键时序:`onStepStart` 所有 Promise 兑现后,才执行逻辑效果(abstract onStepEnd); -`onStepEnd` 全部兑现后,才进入下一步。 -多个订阅方通过 `Promise.all` 并行等待,与 `forEachHook` 返回所有值的设计天然契合—— -`const promises = forEachHook(hook => hook.onStepStart?.(code, step, tile, mover));` -`await Promise.all(promises);` - -## 8. 与 IDynamicLayer / IDynamicTile 接口的关系 - -`IDynamicLayer` 和 `IDynamicTile` **仅保留 `step(dir)` 单步便捷接口** -(等价于 `tile.mover.step(dir).start()`),不再提供 `moveDynamicWith` / `moveWith` 等批量接口。 -复杂移动能力(`forward`/`backward`/`animDir` 等)通过 `tile.mover` 访问。 - -**渲染端订阅移动事件的流程**: - -1. 订阅 `IDynamicLayer.onCreateDynamicTile`; -2. 创建图块时获取 `tile.mover` 引用,直接向其添加钩子; -3. 移动事件从 `tile.mover` 触发,无需经过 `IDynamicLayer` 转发。 - -为此,`IDynamicMover` 扩展为可订阅对象, -实现 `IHookable>`(见接口定义汇总)。 - ---- - -# 涉及文件 - -## 需要修改的文件 - -### `@user/data-base/src/map/types.ts` - -修改现有接口(移动相关新增类型已移至 `move/types.ts`): - -- [ ] `IDynamicTile` 新增 `readonly mover: IDynamicMover` -- [ ] `IDynamicTile` 已有 `readonly faceDirection: FaceDirection` 与 - `readonly moveDirection: FaceDirection`(已在主文档确认) -- [ ] `IObjectMovable` 接口(`x`、`y`、`moveDirection`、`faceDirection` 只读属性 + - `setPos`/`setMoveDirection`/`setFaceDirection`,定义在 `dynamic-tile.md`) -- [ ] `IDynamicLayer` / `IDynamicTile` 仅保留 `step(dir)` 便捷接口, - 移除 `moveDynamicWith` / `moveDynamicStep` / `moveWith` / `moveStep` - (主文档接口汇总需同步更新) - -#### `@user/data-base/src/map/types.ts` - -- [ ] `DynamicMoveStepType` `const enum`: - 含 `Dir`/`DirFace`/`Speed`/`Face`/`Special`/`AnimDir` 六个成员 -- [ ] `DynamicSpecialStep` `const enum`:含 `Forward`/`Backward` 两个成员 -- [ ] `DynamicAnimDirection` `const enum`:含 `Forward`/`Backward` 两个成员 -- [ ] `IDynamicMoveStepDir` 接口(`type: DynamicMoveStepType.Dir`,`move: FaceDirection`) -- [ ] `IDynamicMoveStepDirFace` 接口(`type: DynamicMoveStepType.DirFace`,`move`/`face: FaceDirection`) -- [ ] `IDynamicMoveStepSpeed` 接口(`type: DynamicMoveStepType.Speed`) -- [ ] `IDynamicMoveStepFace` 接口(`type: DynamicMoveStepType.Face`) -- [ ] `IDynamicMoveStepSpecial` 接口(`type: Special`,`direction: DynamicSpecialStep`;无 `count` 字段,`forward(n)`/`backward(n)` 改为 push n 个步骤对象) -- [ ] `IDynamicMoveAnimDir` 接口(`type: DynamicMoveStepType.AnimDir`,`dir: DynamicAnimDirection`) -- [ ] `DynamicMoveStep` 联合类型(含以上六种步骤) -- [ ] `IMoverController` 接口 -- [ ] `IObjectMoverHooks` 接口(含 `onMoveStart`/`onMoveEnd`/`onStepStart`/`onStepEnd`; - `onStepStart`/`onStepEnd` 参数顺序为 `(code, step, tile, mover)`,按使用频率排列) -- [ ] `IObjectMover` 接口(含 `faceDirection`/`moveDirection` 只读属性及全部计划构建方法;`step`/`stepFace` 含 `count?` 参数) -- [ ] `IDynamicMover extends IObjectMover`(无额外成员,继承基类全部能力;`IDynamicMoverHooks` 已移除,直接使用 `IObjectMoverHooks`) - -#### `@user/data-base/src/map/mover.ts` - -- [ ] `abstract class ObjectMover` 抽象基类: - - 实现 `IObjectMover`,在此提供全部**计划构建方法**(`step`/`stepFace`/`forward`/ - `backward`/`speed`/`face`/`animDir`/`clear`/`start`)及队列执行调度逻辑; - - 含四个**抽象方法**,子类必须实现,组成移动核心控制流: - - `abstract onMoveStart(tile, mover): Promise`: 整次移动开始时调用 - - `abstract onMoveEnd(tile, mover): Promise`: 整次移动结束时调用 - - `abstract onStepStart(step, tile, mover): Promise`: 单步逻辑执行前调用, - 子类计算并**返回** `code`;父类将 `code` 传给 `IObjectMoverHooks.onStepStart` 钩子后等待 - - `abstract onStepEnd(code, step, tile, mover): Promise`: 单步钩子等待完成后调用,子类依据 `code` 执行本步实际逻辑效果 -- [ ] `class DynamicMover extends ObjectMover implements IDynamicMover`: - 持有 `tile: IDynamicTile` 引用,维护 `moveQueue`、`moving`、 - `faceDirection`、`moveDirection` 状态;实现四个抽象方法(含 posMap 更新、转向逻辑等) -- [ ] `DynamicTile` 类新增 `readonly mover: DynamicMover`, - 在构造时以 `this` 为参数创建 - ---- - -# 接口定义汇总 - -```ts -const enum DynamicMoveStepType { - Dir, - DirFace, - Speed, - Face, - Special, - AnimDir -} - -const enum DynamicSpecialStep { - Forward, - Backward -} - -const enum DynamicAnimDirection { - Forward, - Backward -} - -interface IDynamicMoveStepDir { - type: DynamicMoveStepType.Dir; - move: FaceDirection; -} - -interface IDynamicMoveStepDirFace { - type: DynamicMoveStepType.DirFace; - move: FaceDirection; - face: FaceDirection; -} - -interface IDynamicMoveStepSpeed { - type: DynamicMoveStepType.Speed; - value: number; -} - -interface IDynamicMoveStepFace { - type: DynamicMoveStepType.Face; - value: FaceDirection; -} - -/** forward(n)/backward(n) 构建时 n>1 会 push n 个相同步骤,步骤对象本身不携带 count */ -interface IDynamicMoveStepSpecial { - type: DynamicMoveStepType.Special; - direction: DynamicSpecialStep; -} - -interface IDynamicMoveAnimDir { - type: DynamicMoveStepType.AnimDir; - dir: DynamicAnimDirection; -} - -type DynamicMoveStep = - | IDynamicMoveStepDir - | IDynamicMoveStepDirFace - | IDynamicMoveStepSpeed - | IDynamicMoveStepFace - | IDynamicMoveStepSpecial - | IDynamicMoveAnimDir; - -interface IMoverController { - readonly done: boolean; - readonly onEnd: Promise; - push(...steps: DynamicMoveStep[]): void; - stop(): Promise; -} - -interface IObjectMoverHooks extends IHookBase { - onMoveStart(mover: IObjectMover, tile: T): Promise; - onMoveEnd(mover: IObjectMover, tile: T): Promise; - /** 触发时 tile.x/y 为移动前坐标,适合渲染端在此播放动画 */ - onStepStart?( - code: number, - step: DynamicMoveStep, - tile: T, - mover: IObjectMover - ): Promise; - /** 触发时 tile.x/y 已更新为移动后坐标 */ - onStepEnd?( - code: number, - step: DynamicMoveStep, - tile: T, - mover: IObjectMover - ): Promise; -} - -interface IObjectMover extends IHookable< - IObjectMoverHooks -> { - readonly moving: boolean; - readonly tile: T; - readonly faceDirection: FaceDirection; - readonly moveDirection: FaceDirection; - - step(dir: FaceDirection, count?: number): this; - stepFace(move: FaceDirection, face: FaceDirection, count?: number): this; - forward(count?: number): this; - backward(count?: number): this; - speed(value: number): this; - face(dir: FaceDirection): this; - animDir(dir: DynamicAnimDirection): this; - clear(): this; - start(): IMoverController | null; -} - -// 这里的 onStepStart 不再接受 code 作为参数,因为这应当是其返回值 `Promise`, -// 具体来说,这四个方法是整个移动的核心方法而非钩子,它应当提供接口让子类实现, -// 子类应当真正执行移动效果,并进行一定的控制。显然子类是没办法知道 onStepStart 中的信息的, -// 所以才提供了一个 code,用于子类向 ObjectMover 提供信息,然后父类再传递给子类, -// 从而子类了解到 onStepStart 中的信息,再进一步决定这一步真正应该如何执行。 -// 而对于钩子,实际上是在子类的 onStepStart 执行完毕后进行的,所以是知道 code 的,所以才是参数。 -// 你应该好好想一想这之间的关系。 -// 以及子类是要求必须实现的,所以不应该会有返回 `void` 的场景,四个方法都应该返回 `Promise`。 -// 总的来说,父类是一个“系统级”的流程控制器,它通过这四个接口来实现真正的移动控制, -// 就像 legacy ObjectMoverBase 中的 startMove 一样。 -abstract class ObjectMover< - T extends IObjectMovable -> implements IObjectMover { - abstract onMoveStart(tile: T, mover: IObjectMover): Promise; - abstract onMoveEnd(tile: T, mover: IObjectMover): Promise; - /** 子类计算并返回本步 code;父类取得 code 后再触发 IObjectMoverHooks.onStepStart 钩子 */ - abstract onStepStart( - step: DynamicMoveStep, - tile: T, - mover: IObjectMover - ): Promise; - /** 子类依据 code 执行本步实际逻辑效果;父类在此之后触发 IObjectMoverHooks.onStepEnd 钩子 */ - abstract onStepEnd( - code: number, - step: DynamicMoveStep, - tile: T, - mover: IObjectMover - ): Promise; - // 计划构建方法(step/stepFace/forward/backward/speed/face/animDir/clear/start)在此实现 -} - -// faceDirection/moveDirection 已提升至 IObjectMover,IDynamicMover 无需额外声明 -// IDynamicMoverHooks 已移除,直接使用 IObjectMoverHooks -interface IDynamicMover extends IObjectMover { - // 继承自 IObjectMover:moving, faceDirection, moveDirection, tile, 全部计划构建方法及钩子 -} -``` - ---- - -# 问题 - -1. **`backward()` 的移动方向基准** ✅ 已确定 - - `backward()` 以 `opposite(tile.moveDirection)` 作为移动方向(基于 `moveDirection` 取反,非 `faceDirection`)。 - `moveDirection` 更新为反向,`faceDirection` 通过转向逻辑跟随同步更新。 - -2. **`forward()` 是否更新 faceDirection** ✅ 已确定 - - `forward()` 沿当前 `moveDirection` 移动,`moveDirection` 不变, - `faceDirection` 通过转向逻辑对齐为 `moveDirection`。 - -3. **转向步触发钩子方式** ✅ 已确定 - - 统一经由 `onStepStart(code, step, tile, mover)` 钩子处理,在步骤逻辑执行前触发。 - 渲染端通过 `step.type === DynamicMoveStepType.Face` 区分纯转向步与位移步; - `onStepStart` 触发时 `tile.x/y` 为原始坐标,渲染端可直接读取,无需反推。 - 转向逻辑(`faceDirection`/`num` 更新)在 `onStepEnd` 阶段执行。 - -4. **`animDir` 与渲染钩子集成** ✅ 已确定 - - `onStepStart` 统一接收所有步骤,`IDynamicMoveAnimDir` 步骤本身即为信息载体。 - 渲染端在 `onStepStart` 收到 `AnimDir` 步骤时更新本地动画方向状态; - 后续方向步的 `onStepStart` 触发时,渲染端依据本地状态决定动画播放方向。 - -5. **玩家移动与图块移动的复用** ✅ 已确定 - - 玩家(勇士)移动逻辑与 `IDynamicMover` 高度一致,共同抽象为 - `IObjectMover`(参考 legacy `ObjectMoverBase`,适配新 API); - 核心抽象类为 `abstract class ObjectMover`,含四个抽象方法组成控制流: - - **计划构建方法**(`step`/`stepFace`/`forward`/`backward`/ - `speed`/`face`/`animDir`/`clear`/`start`)全部在 `ObjectMover` 基类实现,子类无需重复声明。 - - **移动流程钩子**(`IObjectMoverHooks`,实现 `IHookable>`): - - `onMoveStart(mover, tile)`:整次移动队列开始时触发; - - `onMoveEnd(mover, tile)`:整次移动队列结束时触发; - - `onStepStart(code, step, tile, mover)`:单步开始前触发(`tile.x/y` 为移动前坐标); - - `onStepEnd(code, step, tile, mover)`:单步完成后触发(`tile.x/y` 已更新); - 其中 `code: number` 标识移动场景(如碰墙、遇敌、循环地图等),由具体实现层设置。 - - **四个抽象方法**(`onMoveStart`/`onMoveEnd`/`onStepStart`/`onStepEnd`):子类(`DynamicMover`/`HeroMover`) - 通过实现这四个方法完全控制移动行为,执行流程由 `ObjectMover` 基类统一调度。 - 关键:`onStepStart` 返回 `Promise`(code),父类将此 code 传给钩子后再调用 `onStepEnd(code, ...)`, - 子类在 `onStepEnd` 中依据 code 执行实际状态变更。四个方法均返回 `Promise`,无 `void` 选项。 - - **泛型参数** `T extends IObjectMovable`:`IObjectMovable` 是可移动对象的最小接口 - (`x`、`y`、`moveDirection`、`faceDirection` 只读属性 + - `setPos`/`setMoveDirection`/`setFaceDirection` 方法),定义在 `dynamic-tile.md`; - `IDynamicTile extends IObjectMovable`,玩家侧使用 `IHeroMovable extends IObjectMovable`。 - - **`IDynamicMover extends IObjectMover`**: - 无额外成员,直接继承基类全部能力(含 `faceDirection`/`moveDirection` 及 `IObjectMoverHooks`)。 diff --git a/docs/dev/map/dynamic-tile.md b/docs/dev/map/dynamic-tile.md deleted file mode 100644 index 3d59a4e..0000000 --- a/docs/dev/map/dynamic-tile.md +++ /dev/null @@ -1,407 +0,0 @@ -# 需求综述 - -当前地图系统仅支持静态图块(通过 `IMapLayer` / `Uint32Array` 存储), -无法支持可移动的动态图块(例如 NPC、推箱子、可交互物件等)。 -动态图块的核心特点: - -1. **允许重叠**——同一格点可同时存在多个动态图块; -2. **整数坐标**——始终处于格点整数坐标,不出现小数,便于渲染优化与交互; -3. **异步移动**——移动接口返回 `Promise`,配合渲染动画; -4. **朝向转向**——部分图块移动时需同步更新朝向(如四方向 NPC 行走图)。 - ---- - -# 实现思路 - -## 1. 动态图块标识方案 - -**采用引用标识**:`createDynamicTile` 与 `transferToDynamic` 均返回 -`IDynamicTile` 对象引用,调用方持有该引用作为后续操作(移动、删除、 -设置朝向等)的唯一凭证。无需维护任何 ID,无唯一性管理负担。 - -调用方可将返回的 `IDynamicTile` 引用存储在自己的数据结构中, -`tile.x`、`tile.y`、`tile.num`、`tile.direction` 等字段始终反映图块的 -最新状态——内部实现类持有可变字段,接口以 `readonly` 暴露给外部, -保证外部不能直接修改,内部可通过类实例更新。 - -## 2. 坐标表示 - -使用独立的 `x: number, y: number` 参数,与现有 `IMapLayer` 接口风格 -(如 `setBlock(block, x, y)`、`getBlock(x, y)`)保持一致,不引入 `ITileLocator`。 - -## 3. 方向表示 - -移动方向与转向方向统一使用 `FaceDirection` 枚举: - -- 语义明确,与已有 `IRoleFaceBinder`(朝向绑定)直接对接; -- 已有工具函数 `getFaceMovement(dir)` 可将其转换为坐标偏移,无需重复实现; -- 多步路径(`moveDynamicWith` 的 `steps` 参数)使用 `FaceDirection[]`。 - -`IDirectionDescriptor` 更偏向纯数学计算(用于范围迭代等), -语义上不适合表达「图块朝向某方向移动」的含义。 - -**已确定**:移动方向(`moveDirection`)与朝向(`faceDirection`)分离为两个独立字段。 -后退等相对移动必须依赖两者分离才能正确表达(例如面朝上、向下后退时,`faceDirection` -保持 `Up`,`moveDirection` 更新为 `Down`)。 - -移动系统另立文档独立设计,包含双方向模型、`IDynamicMover` 抽象、 -计划式移动接口及执行流程等完整方案,见 -[动态图块移动系统](./dynamic-tile-move.md)。 - -`moveDynamicStep` / `moveDynamicWith` 保留为命令式便捷接口,适用于单次简单移动; -复杂路径和计划式移动统一使用 `IDynamicMover`。 - -## 4. 动态图块数据模型与图层归属 - -动态图块按**所属静态图层**组织层级关系:每个 `IMapLayer` 持有一个 -`IDynamicLayer`,动态图块的渲染深度即为其所属 `IMapLayer` 的 `zIndex`, -与静态图块一致。因此 `IDynamicLayer` 接口挂载在 `IMapLayer` 上, -而不是 `ILayerState` 上(见第 5 节)。 - -每个动态图块存储以下信息: - -```ts -/** 可移动对象的最小公共接口,供 `IObjectMover` 泛型约束使用 */ -interface IObjectMovable { - readonly x: number; - readonly y: number; - readonly moveDirection: FaceDirection; - readonly faceDirection: FaceDirection; - setPos(x: number, y: number): void; - setMoveDirection(dir: FaceDirection): void; - setFaceDirection(dir: FaceDirection): void; -} - -interface IDynamicTile extends IObjectMovable { - readonly num: number; // 图块数字(决定渲染图像) - readonly layer: IDynamicLayer; // 所属动态图层 - readonly mover: IDynamicMover; // 绑定的移动器(计划式移动) - /** 删除此图块,转发至 layer.deleteDynamic */ - delete(): void; - /** 还原为静态图块,转发至 layer.transferToStatic */ - toStatic(): void; - /** 还原为静态图块,如果当前位置有东西则不转换,转发只 layer.transferToStaticIfSafe */ - toStaticIfSafe(): boolean; - /** - * 单步便捷移动接口,等价于 `mover.step(dir, count); return mover.start();`。 - * 适用于简单移动场景,复杂路径通过 `tile.mover` 访问。 - */ - step(dir: FaceDirection, count?: number): IMoverController | null; -} -``` - -`x`、`y`、`moveDirection`、`faceDirection` 及 `setPos`/`setMoveDirection`/`setFaceDirection` -均由 `IObjectMovable` 提供,`IDynamicTile` 不再重复声明。 -`IObjectMover` 以此接口为泛型约束, -使图块和玩家 mover 共享核心执行逻辑;具体渲染效果通过 `IObjectMoverHooks` 的 `onStepStart`/`onStepEnd` 钩子实现。 - -`DynamicLayer` 内部维护两个结构: - -- `tileSet: Set` — 所有图块的集合,用于迭代与归属判断; -- `posMap: Map>>` — 按坐标索引 - (外层 key = y,内层 key = x,value = 该格点所有图块对象集合), - 图块移动时同步更新,支持越界坐标(见第 4.1 节)。 - -调用方持有 `IDynamicTile` 对象引用即可完成所有操作, -`posMap` 仅供按坐标查询(`getDynamicTilesAt`)使用, -因使用频率低,嵌套 `Map` 的开销完全可以接受。 - -`IDynamicTile` 上的 `moveStep`/`moveWith`/`delete`/`setDirection`/`setPos`/`toStatic` -均为便捷转发方法,内部直接调用 `tile.layer` 对应接口,不修改任何内部状态。 -调用方可直接通过 `tile.xxx()` 操作,无需额外持有 `IDynamicLayer` 引用。 - -### 4.1 越界移动 - -动态图块的坐标不受楼层 `width`/`height` 约束,允许出现负值或超出地图范围的坐标。 -这满足了如「图块飞出屏幕」「过场动画」等临时越界需求。 -`DynamicLayer` 无需感知楼层尺寸,`resizeLayer` 也无需通知 `DynamicLayer`。 - -## 5. IDynamicLayer 挂载在 IMapLayer 上 - -动态图块的存储结构与 `IMapLayer`(Uint32Array)本质不同,独立封装为 `IDynamicLayer`。 -`IMapLayer` 新增 `readonly dynamicLayer: IDynamicLayer` 属性, -调用方通过具体的图层对象操作该层的动态图块,z 层级天然与静态图块一致。 - -`IDynamicLayer` 实现 `IHookable` 以支持渲染端订阅变更事件。 -`IRoleFaceBinder` 通过 `setFaceBinder(binder)` 方法注入(见第 8 节), -不在构造时传入,使多楼层共用同一个 `binder` 实例更灵活。 - -`transferToDynamic(x, y)` 不需要 `layer` 参数, -因为它隶属于某个具体的 `IDynamicLayer`,直接从同级 `IMapLayer` 读写图块即可, -并返回创建的 `IDynamicTile` 引用。 - -## 6. 移动函数的坐标来源 - -`moveDynamicStep(direction, tile)` 与 `moveDynamicWith(steps, tile)` 均不接受 -`ox, oy` 参数,改为直接接受 `IDynamicTile` 引用。当前坐标直接读取 `tile.x`、 -`tile.y`,无需调用方额外传入。 - -内部行为: - -- 直接读取 `tile.x`、`tile.y` 作为出发点; -- **立即**将图块逻辑坐标更新为目标位置(单步 `(tile.x+dx, tile.y+dy)`); -- 同步更新 `posMap`; -- 触发 `onMoveTile` 钩子,收集所有钩子返回的 `Promise | void`; -- `await Promise.all(promises)`—— - 等待所有渲染动画完成后,本步才算兑现,再进入下一步。 - -详细多步路径与计划式移动的执行流程见 -[动态图块移动系统](./dynamic-tile-move.md)。 - -## 7. transferToDynamic 与 transferToStatic - -`transferToDynamic(x, y)` 不需要额外的 `layer` 参数。 -`DynamicLayer` 在构造时持有对所属 `MapLayer` 的引用, -直接调用 `mapLayer.getBlock(x, y)` 读取图块数字,再调用 `mapLayer.setBlock(0, x, y)` 清除, -最后创建对应的动态图块并返回其引用。 -若该位置图块为 0(空白),则发出 logger 警告并仍然创建 `num = 0` 的动态图块。 - -新增 `transferToStaticIfSafe(tile)` 作为安全版本:仅当目标位置静态图块为 0(空白)时才执行还原, -否则不转换并返回 `false`;转换成功返回 `true`。适用于不确定目标格是否已有图块的场景。 - -新增 `transferToStatic(tile)` 作为逆操作:将动态图块还原为静态图块。 -对于「只移动一次就固定」的图块(如推箱子放到指定位置后不再移动), -及时转回静态存储可以降低存档体积、简化渲染订阅。执行流程: - -1. 读取 `tile.x`、`tile.y`、`tile.num`; -2. 若坐标不在所属 `MapLayer` 的合法范围内(即 `x < 0`、`y < 0`、 - `x >= width` 或 `y >= height`),发出 logger 警告并放弃操作; -3. 若 `mapLayer.getBlock(tile.x, tile.y) !== 0`,在开发环境下发出 logger 警告 - (目标格点已有静态图块内容,将被覆盖); - 调用 `mapLayer.setBlock(tile.num, tile.x, tile.y)` 写回静态图块; -4. 从 `tileSet` 和 `posMap` 中移除该图块,触发 `onDeleteTile` 钩子。 - -## 8. 转向逻辑 - -`moveDynamicStep` / `moveDynamicWith` 在每一步移动时自动更新朝向(使用双方向模型, -详见 [动态图块移动系统](./dynamic-tile-move.md)): - -1. 更新 `tile.moveDirection` 为本步实际移动方向; -2. 通过外部注入的 `IRoleFaceBinder` 调用 `getFaceOf(tile.num, direction)` 查询 - 该方向对应的图块数字; -3. 若图块无朝向绑定(返回 `null`),则不修改 `num`,仅更新 `faceDirection`; -4. 若有绑定,将 `tile.num` 与 `tile.faceDirection` 更新为查询结果。 - -`IRoleFaceBinder` 通过 `setFaceBinder(binder: IRoleFaceBinder): void` 方法注入, -不在构造时传入;初始状态视为无朝向绑定(`getFaceOf` 始终返回 `null`)。 -若多个楼层共用同一个 `RoleFaceBinder` 实例,直接对各楼层各图层分别调用 `setFaceBinder` 即可。 - -对于八方向移动,转向查询逻辑扩展如下: - -1. 更新 `tile.moveDirection` 为本步实际移动方向; -2. 调用 `getFaceOf(tile.num, direction)` 查询; -3. 若返回 `null` 且当前方向为斜向(八方向之一), - 调用 `degradeFace(direction)` 降级为四方向后再查询一次; -4. 若仍返回 `null`,不修改 `num`,仅更新 `faceDirection`; -5. 若查询到结果,将 `tile.num` 更新为结果图块数字,并更新 `faceDirection`。 - -手动设置朝向通过独立接口 `setDynamicDirection(tile, direction)` 完成, -逻辑与上述步骤 1–5 相同,但不触发移动。 - -## 9. IDynamicLayerHooks 与 ILayerStateHooks - -`IDynamicLayerHooks` 定义三个钩子: - -- `onCreateTile(tile: IDynamicTile)`:图块被创建(含转换)时触发; -- `onDeleteTile(tile: IDynamicTile)`:图块被删除时触发(传入删除前的快照); -- `onMoveTile(tile: IDynamicTile, fromX: number, fromY: number): Promise | void`: - 图块移动一步时触发,`tile` 为更新后的状态,`fromX`/`fromY` 为移动前坐标。 - 返回 `Promise` 时,移动器将等待其兑现后再进行下一步(配合 `Promise.all` 并行等待所有订阅方)。 - -`ILayerStateHooks` 新增对应的三个转发钩子,额外携带 `layer: IMapLayer` 参数, -与现有的 `onUpdateLayerArea`、`onResizeLayer` 等钩子风格一致: - -- `onCreateDynamicTile(layer: IMapLayer, dynamicLayer: IDynamicLayer, tile: IDynamicTile)` -- `onDeleteDynamicTile(layer: IMapLayer, dynamicLayer: IDynamicLayer, tile: IDynamicTile)` -- `onMoveDynamicTile(layer: IMapLayer, dynamicLayer: IDynamicLayer, tile: IDynamicTile, fromX: number, fromY: number)` - -`LayerState` 在为每个 `MapLayer` 注册 `StateMapLayerHook` 时, -同步订阅该层 `dynamicLayer` 的三个钩子,将事件加上 `layer`、`dynamicLayer` 参数后转发至楼层钩子。 - ---- - -# 涉及文件 - -> 移动相关的接口(`DynamicMoveDir`、`IDynamicMoveStep`、`IMoverController`、`IDynamicMover`) -> 设计详情见 [动态图块移动系统](./dynamic-tile-move.md), -> 同样需要添加至 `types.ts`。 - -## 需要引用的文件 - -- `@motajs/common`:`IHookable`, `IHookBase`, `Hookable`, `logger` -- `@user/data-base/src/common`:`FaceDirection`, `IRoleFaceBinder`, - `getFaceMovement`, `degradeFace` -- `@user/data-base/src/map/types.ts`:`IMapLayer`, `IMapLayerHooks`, - `ILayerState`, `ILayerStateHooks` - -## 需要修改的文件 - -### `@user/data-base/src/map/types.ts` - -- [ ] 新增 `IObjectMovable` 接口: - 含 `x`、`y`、`moveDirection`、`faceDirection` 四个只读属性及 - `setPos`/`setMoveDirection`/`setFaceDirection` 三个方法; - 供 `IObjectMover` 泛型约束使用 -- [ ] 新增 `IDynamicTile extends IObjectMovable` 接口: - 含 `num`、`layer`、`mover` 三个只读属性及 `delete`/`toStatic` 两个便捷方法; - 无 `id` 字段,坐标/方向成员由 `IObjectMovable` 提供 -- [ ] 新增移动相关类型(详见移动文档接口定义汇总,已移至 `move/types.ts`): - `DynamicMoveStepType`、`DynamicMoveStep`、`IMoverController`、 - `IObjectMoverHooks`、`IObjectMover`、`IDynamicMover` -- [ ] 新增 `IDynamicLayerHooks extends IHookBase` 接口: - 含 `onCreateTile`、`onDeleteTile`、`onMoveTile` 三个钩子方法 -- [ ] 新增 `IDynamicLayer extends IHookable` 接口: - - [ ] `createDynamicTile(num, x, y): IDynamicTile`: 在指定位置创建动态图块, - 返回图块引用 - - [ ] `transferToDynamic(x, y): IDynamicTile`: 从所属静态图层读取并清除 - 指定位置图块,创建对应动态图块并返回引用 - - [ ] `transferToStatic(tile: IDynamicTile)`: 将动态图块还原为静态图块; - 坐标越界则警告并放弃,否则写回静态图层并触发 `onDeleteTile` - - [ ] `transferToStaticIfSafe(tile: IDynamicTile): boolean`: 仅当目标位置静态图块为 0 - 时才还原,否则不转换;返回是否转换成功 - - [ ] `deleteDynamicTile(tile: IDynamicTile)`: 删除指定图块,不在此层则警告 - - [ ] `getDynamicTilesAt(x, y)`: 获取指定格点所有动态图块的可迭代对象 - - [ ] `moveDynamicWith` / `moveDynamicStep` 已由 `IDynamicMover` 权其责。 - `IDynamicLayer` 不再提供此两个方法,删除对应条目。 - - [ ] `setDynamicDirection(tile: IDynamicTile, direction)`: 手动设置朝向, - 同步更新 `direction` 与 `num`(若有朝向绑定) - - [ ] `setDynamicPos(tile: IDynamicTile, x: number, y: number)`: 直接设置图块位置, - 同步更新 `posMap`,触发 `onMoveTile` 钩子,不更新朝向 - - [ ] `setFaceBinder(binder: IRoleFaceBinder)`: 注入朝向绑定器 -- [ ] 修改 `IMapLayer`:新增 `readonly dynamicLayer: IDynamicLayer` -- [ ] 修改 `ILayerStateHooks`:新增 `onCreateDynamicTile`、`onDeleteDynamicTile`、 - `onMoveDynamicTile` 三个转发钩子,额外携带 `layer: IMapLayer` 与 - `dynamicLayer: IDynamicLayer` 两个参数 - -### `@user/data-base/src/map/dynamicLayer.ts`(新文件) - -- [ ] 实现 `DynamicLayer extends Hookable` 类, - 实现 `IDynamicLayer` -- [ ] 实现内部类 `DynamicTile implements IDynamicTile`: - 持有 `layer: IDynamicLayer` 引用,`delete`/`toStatic` 转发至 `layer`; - `step(dir, count?)` 封装为便捷方法 -- [ ] `DynamicTile` 构造时同步创建 `readonly mover: DynamicMover`, - 移动调度由 `move/dynamicMover.ts` 中的 `DynamicMover` 类实现 -- [ ] `private mapLayer: IMapLayer`:构造参数,所属静态图层引用, - 供 `transferToDynamic` / `transferToStatic` 读写使用 -- [ ] `private faceBinder: IRoleFaceBinder | null = null`:朝向绑定器, - 通过 `setFaceBinder` 注入 -- [ ] `private tileSet: Set`:所有图块的集合,用于迭代与归属判断 -- [ ] `private posMap: Map>>`: - 按坐标索引(外层 key = y,内层 key = x),支持越界坐标 -- [ ] 实现全部接口方法,移动时同步更新 `tileSet` 与 `posMap` - -### `@user/data-base/src/map/mapLayer.ts` - -- [ ] 新增 `readonly dynamicLayer: DynamicLayer`:构造时以 `this` 为参数创建 - -### `@user/data-base/src/map/layerState.ts` - -- [ ] 修改 `StateMapLayerHook`(或 `addLayer` 中的订阅逻辑): - 在为每个 `MapLayer` 注册钩子时,同时订阅其 `dynamicLayer` 的三个钩子, - 将事件加上 `layer`、`dynamicLayer` 参数后转发至楼层的 `ILayerStateHooks` - ---- - -# 接口定义汇总 - -以下为本次新增与修改的完整接口签名,供实现时参考。 - -移动相关接口(`IObjectMoverHooks`、`IObjectMover`、`IDynamicMover`、`IMoverController`、`DynamicMoveStep` 等) -详见 [dynamic-tile-move.md](./dynamic-tile-move.md) 接口定义汇总,此处不再重复。 - -## 新增接口 - -### IObjectMovable - -```ts -interface IObjectMovable { - readonly x: number; - readonly y: number; - readonly moveDirection: FaceDirection; - readonly faceDirection: FaceDirection; - setPos(x: number, y: number): void; - setMoveDirection(dir: FaceDirection): void; - setFaceDirection(dir: FaceDirection): void; -} -``` - -### IDynamicTile - -```ts -interface IDynamicTile extends IObjectMovable { - readonly num: number; - readonly layer: IDynamicLayer; - readonly mover: IDynamicMover; - delete(): void; - toStatic(): void; - toStaticIfSafe(): boolean; - /** 等价于 mover.step(dir, count); return mover.start(); */ - step(dir: FaceDirection, count?: number): IMoverController | null; -} -``` - -### IDynamicLayerHooks - -```ts -interface IDynamicLayerHooks extends IHookBase { - onCreateTile(tile: IDynamicTile, layer: IDynamicLayer): void; - onDeleteTile(tile: IDynamicTile, layer: IDynamicLayer): void; -} -``` - -### IDynamicLayer - -```ts -interface IDynamicLayer extends IHookable { - createDynamic(num: number, x: number, y: number): IDynamicTile; - transferToDynamic(x: number, y: number): IDynamicTile; - transferToStatic(tile: IDynamicTile): void; - transferToStaticIfSafe(tile: IDynamicTile): boolean; - deleteDynamic(tile: IDynamicTile): void; - getDynamicTilesAt(x: number, y: number): Iterable; - setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): void; - setDynamicPos(tile: IDynamicTile, x: number, y: number): void; - setFaceBinder(binder: IRoleFaceBinder): void; -} -``` - -## 修改接口 - -### IMapLayer(新增属性) - -```ts -interface IMapLayer { - // ...现有成员... - readonly dynamicLayer: IDynamicLayer; -} -``` - ---- - -# 问题 - -1. **`ox, oy` 是否为必要参数?** ✅ 已确定 - - 已确定移除。调用方持有 `IDynamicTile` 引用,坐标直接从 `tile.x`、`tile.y` 读取, - `ox, oy` 为冗余参数,接口简化为 `moveDynamicStep(direction, tile)` 和 - `moveDynamicWith(steps, tile)`。 - -2. **`IRoleFaceBinder` 的注入方式** ✅ 已确定 - - 已确定使用 `setFaceBinder(binder)` 方法注入,不在构造时传入。 - 实际修改 `faceBinder` 的场景极少,接口保持简单。 - -3. **动态图块的存档支持** ⏸ 暂缓 - - 动态图块状态(NPC 当前位置、推箱子位置等)属于游戏核心存档内容, - 长期来看必须纳入存档体系。但当前设计与旧引擎完全不同, - 存档格式需从头设计,建议在动态图块功能稳定后单独立项设计存档方案。 - **本次实现不涉及存档,`IDynamicLayer` 暂不实现 `ISaveableContent`。** - -4. **`IDynamicLayer` 是否需要感知楼层尺寸** ✅ 已确定 - - 已确定不感知。标识方案改为引用,`posMap` 同步改为 - `Map>>` 嵌套结构(外层 key = y,内层 key = x), - 彻底去掉字符串键,天然支持越界坐标,`resizeLayer` 无需通知 `DynamicLayer`。 - `getDynamicTilesAt` 接口保留,使用频率低,嵌套 Map 开销完全可接受。 diff --git a/docs/dev/map/map-store-improve.md b/docs/dev/map/map-store-improve.md deleted file mode 100644 index f477b92..0000000 --- a/docs/dev/map/map-store-improve.md +++ /dev/null @@ -1,212 +0,0 @@ -# 需求综述 - -本次改动目标: - -1. **自动化分区激活器**:将楼层按游戏进程划分为若干"区域", - 到达新区域时自动激活对应楼层、失活旧区域楼层, - 从而替代目前繁琐的手动 `setActiveStatus` 调用。 -2. **楼层尺寸上移至 `LayerState`**:同一楼层的所有图层尺寸应当 - 保持一致,因此将尺寸的权威来源从 `MapLayer` 移至 `LayerState`。 - ---- - -# 实现思路 - -## 1. 有序地图 id 列表 - -当前 `IMapStore.maps` 是 `ReadonlySet`,无序且随楼层创建 -自动填充。区域功能需要以**下标**标识范围,因此需改为有序数组。 - -修改方案: - -- `IMapStore.maps` 类型改为 `ReadonlyArray`; -- 新增 `setMapList(maps: string[]): void`,由外部显式指定有序列表 - (一般在游戏初始化时调用一次); -- 新增 `useManualOrder(sort: (arr: string[]) => string[]): void`, - 允许自定义地图列表排序函数。调用时将当前 `maps` 的 `slice` 拷贝 - 传入 `sort`,再对输出做合法性校验:将新旧数组各转为 `Set`, - 校验 `size` 相等且新集合是旧集合的子集(利用 `Set.prototype.isSubsetOf`); - 校验通过后用返回值替换内部的 `maps`。这样当地图是动态生成时, - 作者依然可以自定义顺序,而不必手动维护全量列表; -- `createLayerState` 不再维护 `maps`,`maps` 完全由 `setMapList` 管理; -- 若 `createLayerState` 传入的 id 不在 `maps` 中,仍可正常创建, - 不影响存档逻辑,但该楼层不参与任何区域判断。 - -## 2. 区域定义与管理 - -### 类型定义 - -```ts -/** 单段闭区间 [start, end],start 和 end 均为 maps 下标 */ -export interface IMapAreaInterval { - readonly start: number; - readonly end: number; -} - -/** 一个区域由一个或多个独立区间组成 */ -export type MapArea = IMapAreaInterval[]; -``` - -### 接口 - -- `setArea(areas: Set): void`:一次性设置所有区域信息, - 覆盖原有区域定义;每个元素代表一个区域,区域可包含多个区间, - 使用 `Set` 表示无序区域集合; -- `activeArea(id: string): void`:手动激活指定楼层所在区域的所有楼层。 - 系统遍历 `areaList`,找到包含该楼层 id 的区域后,对该区域内的所有 - 楼层调用 `setMapActiveStatus(floor, true)`; -- `deactiveArea(id: string): void`:手动取消激活指定楼层所在区域的 - 所有楼层,逻辑与 `activeArea` 对称;判断时遍历 `areaList`, - 此操作为低频调用,无需缓存; - -## 3. 自动分区激活器 - -### 接口 - -- `useAutoActivitor(enable: boolean): void`:是否启用自动激活器。 - -### 触发接口 - -需要一个通知接口供玩家相关模块调用: - -- `notifyEnterFloor(id: string): void`:玩家进入指定楼层时调用此接口, - 通知地图管理器进行自动激活判断。 - -### 逻辑 - -`notifyEnterFloor(id)` 的执行流程(每次进入楼层均调用,内部短路): - -1. 若自动激活器未启用,直接返回; -2. 若 `isMapActive(id)` 为 `true`,直接返回(楼层已激活,无需操作); -3. 遍历 `areaList`,找出包含 `id` 的区域; -4. 若未找到,直接返回(该楼层不在任何区域内); -5. 若 `lastFloorId !== null`,调用 `deactiveArea(lastFloorId)` 失活上一个区域; -6. 调用 `activeArea(id)` 激活新区域,更新 `lastFloorId = id`。 - -### 内部状态 - -`MapStore` 新增: - -- `private areaList: Set`:所有区域定义; -- `private lastFloorId: string | null = null`:上一次触发 `notifyEnterFloor` - 的楼层 id,用于定位并失活上一个激活区域; -- `private autoActivitorEnabled: boolean = false`:自动激活器开关。 - -## 4. 楼层尺寸上移至 LayerState - -### 动机 - -当前 `MapLayer.width` / `MapLayer.height` 存储在图层中, -但同一楼层的所有图层尺寸必须一致,权威来源应当是 `LayerState`。 - -### 接口变动 - -**`ILayerState` 新增**: - -```ts -readonly width: number; -readonly height: number; -``` - -**`addLayer` 签名调整**: - -目前 `addLayer(width: number, height: number): IMapLayer`, -移除 width/height 参数,改为 `addLayer(): IMapLayer`, -使用 `LayerState` 内部存储的尺寸创建图层。 - -楼层尺寸在 `createLayerState` 创建时指定,`createLayerState` 签名改为: - -```ts -createLayerState(id: string, width: number, height: number): ILayerState; -``` - -运行时仍可通过 `resizeLayer` 修改楼层尺寸,该方法会同步对楼层内所有 -图层执行 resize,保持尺寸一致。 - -**`resizeLayer` 签名调整**: - -当前 `resizeLayer(layer, width, height, keepBlock?)` 只 resize 单个图层, -但既然尺寸是楼层级的,建议改为对该楼层的所有图层同步 resize: - -```ts -resizeLayer(width: number, height: number, keepBlock?: boolean): void; -``` - -**`IMapLayer.resize` / `IMapLayer.resize2`**: - -从 `IMapLayer` 接口中移除,保留为 `MapLayer` 的内部实现, -仅由 `LayerState.resizeLayer` 调用。 - -**`IMapLayer.width` / `IMapLayer.height`**: - -保留在 `IMapLayer` 接口中,供外部通过图层对象直接获取尺寸。 -其值始终与所属 `LayerState` 的 `width`/`height` 保持一致。 - ---- - -# 附加建议结论 - -1. **`IMapLayer.setMapRef` 可见性**:保留现有设计,偶尔有外部需求。 -2. **`active` 状态管理**:不需要单独维护区域激活状态; - `activeArea(id)` / `deactiveArea(id)` 是 `setMapActiveStatus` 的 - 快捷方式,遍历区域楼层批量调用即可,无需额外的区域状态字段。 -3. **`notifyEnterFloor` 返回值**:暂不添加,后续有需求再改进。 - ---- - -# 涉及文件 - -## 需要引用的文件 - -- `@user/data-base/src/map/types.ts`: 全部现有地图接口 -- `@user/data-base/src/map/mapStore.ts`: `MapStore` 实现类 -- `@user/data-base/src/map/layerState.ts`: `LayerState` 实现类 -- `@user/data-base/src/map/mapLayer.ts`: `MapLayer` 实现类 - -## 需要修改的文件 - -### `@user/data-base/src/map/types.ts` - -- [x] 新增 `IMapAreaInterval` 接口:区间定义,含 `start`、`end` -- [x] 新增 `MapArea` 类型别名:`IMapAreaInterval[]`,表示一个区域 -- [x] 修改 `ILayerState`: - - [x] 新增 `readonly width: number` 和 `readonly height: number` - - [x] 修改 `addLayer` 签名,移除 `width`/`height` 参数(使用 `LayerState` 自身尺寸) - - [x] 修改 `resizeLayer` 签名:移除 `layer` 参数,改为对整个楼层所有图层同步 resize -- [x] 修改 `IMapLayer`: - - [x] 移除 `resize` / `resize2`(改为 `MapLayer` 内部方法) -- [x] 修改 `IMapStore`: - - [x] 将 `readonly maps` 类型改为 `ReadonlyArray` - - [x] 修改 `createLayerState` 签名:新增 `width: number`、`height: number` 参数 - - [x] 新增 `setMapList(maps: string[]): void` - - [x] 新增 `useManualOrder(sort: (arr: string[]) => string[]): void` - - [x] 新增 `setArea(areas: Set): void` - - [x] 新增 `activeArea(id: string): void` - - [x] 新增 `deactiveArea(id: string): void` - - [x] 新增 `useAutoActivitor(enable: boolean): void` - - [x] 新增 `notifyEnterFloor(id: string): void` - -### `@user/data-base/src/map/mapStore.ts` - -- [x] 将 `maps: Set` 改为 `maps: string[]` -- [x] 修改 `createLayerState`:添加 `width`/`height` 参数,不再维护 `maps` -- [x] 实现 `setMapList` -- [x] 实现 `useManualOrder` -- [x] 新增 `private areaList: Set` -- [x] 新增 `private lastFloorId: string | null` -- [x] 新增 `private autoActivitorEnabled: boolean` -- [x] 实现 `setArea`、`activeArea`、`deactiveArea` -- [x] 实现 `useAutoActivitor` -- [x] 实现 `notifyEnterFloor` - -### `@user/data-base/src/map/layerState.ts` - -- [x] 新增 `width: number` 和 `height: number` 成员(由构造参数初始化) -- [x] 修改 `addLayer`,移除 `width`/`height` 参数,使用 `this.width`/`this.height` -- [x] 修改 `resizeLayer`,移除 `layer` 参数,改为对所有图层同步 resize - -### `@user/data-base/src/map/mapLayer.ts` - -- [x] 将 `resize`/`resize2` 改为内部方法(从公共接口移除) - ---- diff --git a/docs/dev/map/map-store-save.md b/docs/dev/map/map-store-save.md deleted file mode 100644 index 1af0da1..0000000 --- a/docs/dev/map/map-store-save.md +++ /dev/null @@ -1,294 +0,0 @@ -# 需求综述 - -当前 `LayerState` 只存储当前激活地图的数据,切换地图时原地图内容彻底丢失, -无法参与存档系统。为此引入 `IMapStore`,集中管理所有楼层的 `LayerState`, -并实现 `ISaveableContent` 接口以支持存档读档。 - -核心目标: - -- 多楼层数据同时存在于内存中,通过 id 访问; -- 通过 `active` 标记区分"玩家可能到达"与"无需关注"的楼层,节省存档开销; -- 通过 `compareWith` 提供参考基准,配合分级压缩大幅减少存档体积; -- `IStateBase.layer` 类型由 `ILayerState` 改为 `IMapStore`, - 操作楼层必须先通过 `getLayerState(id)` 取得具体楼层。 - ---- - -# 实现思路 - -## 1. 给 LayerState 添加 active 成员 - -`ILayerState` 新增 `readonly active: boolean` 和 `setActiveStatus(active: boolean): void` -两个接口,`LayerState` 实现类中 `active` 默认为 `false`。 - -两者的关系: - -- `ILayerState.setActiveStatus`:直接操作楼层对象; -- `IMapStore.setMapActiveStatus(id, active)`:通过 id 操作, - 内部查找对应楼层后调用其 `setActiveStatus`。 - -## 2. 脏数据追踪(dirty tracking) - -为支持 `LowCompression` 和 `HighCompression` 的差分存档, -需要知道哪些楼层相对于参考基准是否发生了修改。 - -**推荐方案:楼层级简单脏标记 + 存档时实际比较** - -在 `LayerState` 内部维护 `private dirty: boolean = false`: - -- 当楼层内任意 `MapLayer` 触发 `onUpdateBlock`、`onUpdateArea`、`onResize` - 钩子时,将 `dirty` 置为 `true`; -- `dirty` 只在 `compareWith` 首次调用时根据实际数据对比结果初始化, - 初始化后的 gameplay 过程中不再重置(仅置 true)。 - -在 `saveState` 时: - -- 若 `dirty = false`,跳过该楼层(初始化后从未被触碰过); -- 若 `dirty = true`: - - **LowCompression**:与参考基准进行全量比较,若完全一致则跳过 - (消除"改了又改回"场景下的误判),否则存储所有行; - - **HighCompression**:逐行与参考基准比较,只存储不一致的行。 - -在 `loadState` 时:若存档中此楼层没有任何数据(即未出现在 `floors` 中), -读档后将 `dirty` 置为 `false`(视为与参考基准一致)。 - -**不在 `MapLayer` 内维护 `dirtyRows`**,行级比较在 `saveState` 时直接对照参考基准进行。 -这避免了每次 `setBlock`/`putMapData` 都更新行级标记的热路径开销, -且存档时实际比较已能消除误判,无需 `probablyDirty` + `setInterval` 或哈希方案。 - -存档时比较的开销:Uint32Array 内存连续,实际耗时极低,且保存操作本身是低频的, -若将来发现存档耗时问题,可考虑将比较逻辑移至 Web Worker。 - -## 3. compareWith 接口与参数类型 - -```ts -compareWith(ref: Map>): void; -``` - -外层 `Map` 以楼层 id 为键,内层 `Map` 以图层 `zIndex` 为键, -值为对应图层的完整图块数组(Uint32Array,含所有行的扁平数据)。 - -使用此类型而非 `IMapStore` 的理由:接口更轻量,调用方可直接从游戏原始数据构建, -无需额外持有一个完整的 `IMapStore` 实例。 - -关于图层标识符:继续使用 `zIndex`,在单个楼层内 `zIndex` 是语义唯一的, -与已有 `MapLayer.zIndex` 接口保持一致。 - -**`compareWith` 以首次调用为唯一基准**,再次调用不更新参考(以游戏原始数据为基准, -避免存档之间产生依赖关系)。 - -实现步骤: - -1. 若 `refData` 已存在,直接返回; -2. 保存 `ref` 引用到 `private refData`; -3. 遍历当前所有楼层,对每个楼层在 `ref` 中查找对应 id: - - 不存在:`dirty = true`(新楼层,视为全脏); - - 存在:对每个 `MapLayer`(按 `zIndex` 匹配)做全量比较, - 若所有行与参考数据完全一致则 `dirty = false`,否则 `dirty = true`。 - -## 4. 楼层的创建与管理 - -`MapStore` 内部以 `Map` 存储所有楼层。 -`getLayerState(id)` 对不存在的 id 直接返回 `null`,不自动创建。 - -只提供一个创建接口: - -- `createLayerState(id: string): ILayerState`:创建并注册一个空白楼层 - (无任何 `MapLayer`,用户拿到后再调用 `addLayer` 配置图层结构),返回楼层对象。 - -注册时若 id 已存在,发出 logger 警告并覆盖。 - -若 `compareWith` 已调用后再通过上述接口新增楼层,新楼层直接视为全脏(`dirty = true`), -因为 `refData` 中不存在对应数据。 - -## 5. 存档数据格式 - -### 类型定义 - -```ts -/** 单个 MapLayer 的存档数据 */ -interface IMapLayerSave { - readonly width: number; - readonly height: number; - /** - * key = 行索引,value = 该行完整的 Uint32Array 数据; - * NoCompression/LowCompression 时包含所有行(0 到 height - 1); - * HighCompression 时只包含与参考基准不同的行; - * 读档时,不在此 Map 中的行从参考基准还原。 - */ - readonly rows: ReadonlyMap; -} - -/** 单个楼层的存档数据 */ -interface ILayerStateSave { - readonly background: number; - /** - * key = zIndex,value = 对应图层存档数据; - * 使用 Map 格式以支持图层的动态增删。 - */ - readonly layers: ReadonlyMap; -} - -/** 整个 MapStore 的存档数据 */ -interface IMapStoreSave { - /** - * key = 楼层 id,只包含 active 的楼层; - * inactive 的楼层不写入,读档时无需处理。 - */ - readonly floors: ReadonlyMap; -} -``` - -### 各压缩等级存储策略 - -| 压缩级别 | 楼层粒度 | 行粒度 | -| ----------------- | --------------------------------------------------------------- | ------------------------ | -| `NoCompression` | 存储所有 active 楼层 | 存储该楼层所有行 | -| `LowCompression` | 跳过 `dirty = false` 的楼层;dirty 楼层全量比较后仍一致的也跳过 | 存储该楼层所有行 | -| `HighCompression` | 同 LowCompression | 只存储与参考基准不同的行 | - -### 读档策略 - -读档时直接操作数组引用(通过 `setMapRef`),避免逐行拷贝的额外开销: - -1. 若参考基准(`refData`)未设置,抛出 logger 错误,**不进行任何读档操作**; -2. 遍历 `state.floors`,对每个楼层 id: - - 若当前 `MapStore` 中不存在该 id,发出 logger 警告并跳过; - - 对该楼层每个图层,先从参考基准取出对应 `zIndex` 的数组, - 将其深拷贝为新数组作为底层(确保未存档行使用参考基准值); - - 再将 `ILayerStateSave.layers` 中对应图层的 `rows` 数据写入该数组的对应行; - - 调用 `MapLayer.setMapRef(array)` 直接替换内部引用,无需额外拷贝; -3. 对未出现在 `state.floors` 中的 active 楼层, - 从参考基准深拷贝完整数组后调用 `setMapRef` 还原,并将 `dirty` 置为 `false`。 - -## 6. saveState / loadState 实现 - -根据压缩等级分别编写三个存档函数和三个读档函数, -`saveState(compression)` 和 `loadState(state, compression)` 根据 `compression` 分发, -无需在每个楼层的遍历循环内部判断等级: - -- `private saveNoCompression(): IMapStoreSave` -- `private saveLowCompression(): IMapStoreSave` -- `private saveHighCompression(): IMapStoreSave` -- `private loadNoCompression(state: IMapStoreSave): void` -- `private loadLowCompression(state: IMapStoreSave): void` -- `private loadHighCompression(state: IMapStoreSave): void` - -`saveState` 结果需通过 `structuredClone` 深拷贝后返回。 - -## 7. IMapStore 接口设计(新增到 `map/types.ts`) - -```ts -interface IMapStore extends ISaveableContent { - /** 所有楼层的 id 集合 */ - readonly maps: ReadonlySet; - - // --- 楼层访问 --- - /** 获取指定 id 的楼层状态,不存在则返回 null */ - getLayerState(id: string): ILayerState | null; - /** 获取指定 id 的楼层状态,要求楼层必须是 active 的,否则返回 null */ - getActiveMap(id: string): ILayerState | null; - - // --- 楼层管理 --- - /** 创建并注册一个空白楼层,返回楼层状态对象 */ - createLayerState(id: string): ILayerState; - - // --- active 管理 --- - /** 获取指定 id 的楼层是否激活,不存在的 id 返回 false */ - isMapActive(id: string): boolean; - /** 设置指定 id 楼层的激活状态 */ - setMapActiveStatus(id: string, active: boolean): void; - /** 迭代所有 active 的楼层,yield [id, ILayerState] */ - iterateActiveMaps(): Iterable<[string, ILayerState]>; - /** 迭代所有 inactive 的楼层,yield [id, ILayerState] */ - iterateInactiveMaps(): Iterable<[string, ILayerState]>; - /** 迭代所有楼层,yield [id, ILayerState] */ - iterateAllMaps(): Iterable<[string, ILayerState]>; - - // --- 差分压缩基准 --- - /** - * 设置压缩参考基准,以首次调用为唯一基准,再次调用不更新。 - * @param ref 外层 key = 楼层 id,内层 key = zIndex,value = 图层完整图块数据 - */ - compareWith(ref: Map>): void; -} -``` - -## 8. ILayerState 接口修改 - -在现有 `ILayerState` 上新增: - -```ts -/** 此楼层是否处于激活状态 */ -readonly active: boolean; -/** 设置楼层激活状态 */ -setActiveStatus(active: boolean): void; -``` - -## 9. IStateBase 修改 - -将 `IStateBase.layer: ILayerState` 改为 `IStateBase.layer: IMapStore`。 - ---- - -# 涉及文件 - -## 需要引用的文件 - -- `@user/common/types.ts`: `ISaveableContent`, `SaveCompression` -- `@user/data-base/map/types.ts`: 全部现有地图接口(`IMapLayer`, `ILayerState`, 等) - -## 需要修改的文件 - -### `@user/data-base/src/map/types.ts` - -- [x] 新增 `IMapLayerSave` 接口:单个 MapLayer 存档数据格式 -- [x] 新增 `ILayerStateSave` 接口:单个楼层存档数据格式 -- [x] 新增 `IMapStoreSave` 接口:MapStore 整体存档数据格式 -- [x] 修改 `ILayerState`:新增 `readonly active: boolean` 和 - `setActiveStatus(active: boolean): void` -- [x] 修改 `IMapLayer`:新增 `setMapRef(array: Uint32Array): void` -- [x] 新增 `IMapStore` 接口:继承 `ISaveableContent`, - 含全部接口(见第 7 节) - -### `@user/data-base/src/map/mapLayer.ts` - -### `@user/data-base/src/map/layerState.ts` - -- [x] 新增 `active: boolean = false` 成员:楼层激活状态 -- [x] 实现 `setActiveStatus(active: boolean): void` -- [x] 新增 `private dirty: boolean = false` 成员:楼层级脏标记 -- [x] 修改 `StateMapLayerHook.onUpdateArea`、`onUpdateBlock`、`onResize`: - 在转发钩子的同时,将 `state.dirty` 置 `true` -- [x] 新增 `isDirty(): boolean` 方法:返回 `this.dirty`,供 `MapStore` 读取 -- [x] 新增 `setDirty(dirty: boolean): void` 方法: - 供 `MapStore.compareWith` 时根据实际比较结果设置 - -### `@user/data-base/src/map/mapLayer.ts` - -- [x] 新增 `setMapRef(array: Uint32Array): void` 方法: - 直接替换内部图块数组引用,跳过拷贝,供 `MapStore` 读档时使用。 - 需确保传入数组长度与 `width × height` 匹配, - 并触发必要的钩子通知(不触发 `onResize`,应触发 `onUpdateArea` 通知全区域更新)。 - 在方法注释中明确标注:调用后不得再持有或修改传入的数组。 - -### `@user/data-base/src/map/mapStore.ts`(新文件) - -- [x] 实现 `MapStore` 类,实现 `IMapStore` -- [x] `private mapData: Map`:楼层 id 到状态对象的映射 -- [x] `readonly maps: ReadonlySet`:所有楼层 id 的只读集合视图 -- [x] `private refData: Map> | null`:参考基准 -- [x] 实现 `getLayerState`、`getActiveMap`、`createLayerState` -- [x] 实现 `isMapActive`、`setMapActiveStatus`、`iterateActiveMaps`、`iterateInactiveMaps`、`iterateAllMaps` -- [x] 实现 `compareWith` -- [x] 实现 `saveNoCompression`、`saveLowCompression`、`saveHighCompression` -- [x] 实现 `loadNoCompression`、`loadLowCompression`、`loadHighCompression` -- [x] 实现 `saveState(compression)` 和 `loadState(state, compression)` 分发 - -### `@user/data-base/src/map/index.ts` - -- [x] 补充导出 `mapStore.ts` - -### `@user/data-base/src/types.ts` - -- [x] 将 `IStateBase.layer` 类型由 `ILayerState` 改为 `IMapStore` diff --git a/docs/dev/map/tile-info.md b/docs/dev/map/tile-info.md deleted file mode 100644 index 0f61c5d..0000000 --- a/docs/dev/map/tile-info.md +++ /dev/null @@ -1,283 +0,0 @@ -# 需求综述 - -重新考虑之后,这一层需求更适合收敛成“图块数据”和“触发器数据”两套独立结构,而不是先抽象一层通用 `ITileInfo` 对象。 - -原因如下: - -1. `IMapLayer` 内部的 `Uint32Array` 主要服务于高频查询与渲染,本身就应该尽量纯粹; -2. 大多数格点只是墙壁、地板、空气,不含任何触发器,没有必要给每个格点额外挂一个信息对象; -3. 当前触发器系统在数据层真正需要的并不是 `ITrigger` 对象,而只是“该载体对应哪一种触发器类型”; -4. 动态图块是实例对象,天然适合作为触发器的运行时载体,触发器跟随动态图块移动也更自然; -5. 存档是否需要记录触发器,取决于“触发器是跟随地图状态定义,还是跟随图块定义”,这应当单独讨论,而不是先把对象模型写死。 - -因此,本文不再优先设计 `IWrappedTileInfo`、`ITileInfo`、`createTileInfo`、`bindTileInfo` 这一套通用信息接口,而改为先构建一套**以 `ITileStore` 默认值为主、地图稀疏覆盖为辅的触发器方案**: - -1. `ILayerState` 持有 `ITileStore` 引用,静态格点未主动设置时使用图块默认触发器类型; -2. `IMapLayer` 仅按坐标稀疏存储“手动覆盖值”,并提供 `revertTrigger` 恢复默认触发器; -3. 动态图块实例直接持有自己的触发器类型数字; -4. `ITrigger` 对象仍只存在于 Layer 2,在收集阶段按需实例化; -5. `setBlock` 与触发器解绑,不再通过 `keepInfo` 一类参数耦合两者。 - ---- - -# 接口设计与预期 - -## ILayerState.tileStore - -- `ILayerState.tileStore`:当前图层状态持有的图块定义 store 引用。 -- 预期频率:**低频到中频**。外部直接访问它的频率不会太高,但 `IMapLayer.getTriggerType` 等底层逻辑会稳定依赖它作为默认触发器来源。 -- 典型使用场景:静态格点没有手动设置触发器时,`IMapLayer` 先读取当前图块数字,再通过 `layerState.tileStore.getTrigger(num)` 获取默认触发器类型。 - -## IMapLayer.getTriggerType - -- `IMapLayer.getTriggerType(x, y)`:按坐标读取当前静态格点的“有效触发器类型”。若该点存在手动覆盖则返回覆盖值,否则回退到当前图块在 `ITileStore` 中的默认触发器类型。 -- 预期频率:**高频**。触发器收集时每次都需要先确认目标静态格点最终会暴露出哪一种触发器。 -- 典型使用场景:`ITriggerCollector.collect(x, y, layer)` 读取 `layer` 在 `(x, y)` 上的静态触发器类型。 -- 返回值建议:与 `ITileStore` 保持一致,越界或不存在触发器时统一返回 `-1`。 - -## IMapLayer.setTriggerType - -- `IMapLayer.setTriggerType(type, x, y)`:为指定静态格点写入手动覆盖的触发器类型数字。 -- 预期频率:**中频**。主要出现在地图初始化、编辑器写回、运行时脚本修改事件配置等场景。 -- 典型使用场景:给某一格配置战斗触发器;或显式将某个原本有默认触发器的图块覆盖为“无触发器”。 -- 语义建议:`setTriggerType` 只负责写覆盖值,不负责恢复默认值。若 `type = -1`,表示显式覆盖为“无触发器”;若要恢复为图块默认触发器,应调用 `revertTrigger`。 - -## IMapLayer.revertTrigger - -- `IMapLayer.revertTrigger(x, y)`:删除指定静态格点的手动触发器覆盖,使其重新回退到 `ITileStore` 的默认触发器类型。 -- 预期频率:**低频到中频**。主要出现在编辑器撤销覆盖、脚本恢复默认配置、动静态图块转换回写时。 -- 典型使用场景:某格点曾被脚本临时设置为其他触发器,演出结束后恢复为该图块原本的默认触发器。 - -## IDynamicTile.triggerType - -- `IDynamicTile.triggerType`:当前动态图块携带的触发器类型数字,`-1` 表示无触发器。 -- 预期频率:**中频**。收集器在读取某格所有动态图块时需要访问;运行时若有脚本操作某个动态图块事件,也可能直接读写。 -- 典型使用场景:某个 NPC 作为动态图块移动时,其战斗或对话触发器跟随该实例一起移动。 - -## IDynamicTile.setTriggerType - -- `IDynamicTile.setTriggerType(type)`:修改当前动态图块绑定的触发器类型。 -- 预期频率:**低频到中频**。通常只在创建动态图块、转换动静态图块、特殊脚本重配置时使用。 -- 典型使用场景:演出过程中把一个原本只是装饰的动态图块改成可交互单位。 - -## IDynamicLayer.transferToDynamic - -- `IDynamicLayer.transferToDynamic(x, y, keepTrigger?)`:把静态图块转换为动态图块,并按参数决定是否保留原先的触发器。 -- 预期频率:**中频**。每次把地图中的静态物件实例化为可移动对象时都会用到。 -- 典型使用场景:把地图上的 NPC 图块转成 `IDynamicTile`,并让对话或战斗触发器跟着它一起移动。 -- 语义建议:`keepTrigger` 默认值先定为 `true`。当 `keepTrigger = true` 时,将静态格点当前的有效触发器类型迁移到新建的动态图块;当 `keepTrigger = false` 时,新动态图块不保留原触发器,并且静态格点上的手动覆盖应一并清除。 - -## IDynamicLayer.transferToStatic - -- `IDynamicLayer.transferToStatic(tile, keepTrigger?)`:把动态图块还原为静态图块,并按参数决定是否把动态图块携带的触发器写回静态格点。 -- 预期频率:**中频**。会移动的图块在结束移动后重新落回地图时使用。 -- 典型使用场景:可推动箱子停止后重新固化为静态图块,同时决定是否保留它携带的触发器。 -- 语义建议:`keepTrigger` 默认值先定为 `true`。当 `keepTrigger = true` 时,将 `tile.triggerType` 写回目标静态格点;若写回值与该图块默认触发器一致,则应直接 `revertTrigger`,只在不一致时存入覆盖值。当 `keepTrigger = false` 时,不写回动态图块携带的触发器,静态格点自然回退到图块默认触发器。 - -## IDynamicLayer.transferToStaticIfSafe - -- `IDynamicLayer.transferToStaticIfSafe(tile, keepTrigger?)`:在满足安全回写条件时将动态图块还原为静态图块,并保持与 `transferToStatic` 一致的触发器迁移语义。 -- 预期频率:**中频**。用于需要先判断目标格点是否允许回写的动静态转换场景。 -- 典型使用场景:某个可移动物体尝试停回地图时,若目标格点安全则回写图块和触发器,否则保留动态状态。 -- 语义建议:为保持三组转换接口的对称性,`keepTrigger` 也应加入该接口,且默认值同样先定为 `true`。 - -## ITriggerCollector.collect - -- `ITriggerCollector.collect(x, y, layer)`:当前不再从 `getTileInfo` 中读取对象信息,而是分别读取静态格点与动态图块上的触发器类型数字,再按需实例化 `ITrigger`。 -- 预期频率:**中频**。玩家移动、交互判定、脚本触发等场景都会使用。 -- 典型使用场景: - 1. 读取 `layer.getTriggerType(x, y)`; - 2. 迭代 `layer.dynamicLayer.getDynamicTilesAt(x, y)`,读取每个 `tile.triggerType`; - 3. 将所有非 `-1` 的触发器类型交给 `ITriggerRegistry` 创建运行时 `ITrigger` 对象; - 4. 排序后组成 `ITriggerCollection`。 - -这里需要注意:**数据层不再存 `ITrigger` 对象,只存触发器类型数字;对象实例化延后到收集阶段完成。** 这样既保留了 Layer 2 的运行时灵活性,又降低了 Layer 1 的存储复杂度。 - -## 当前不再优先设计的接口 - -以下内容在这轮设计中不再作为主方案: - -1. `ITileInfo` / `IWrappedTileInfo` 一类通用格点信息对象; -2. `createTileInfo()` / `bindTileInfo()` 一类对象工厂接口; -3. `setBlock(block, x, y, keepInfo?)` 这类把图块写入与触发器保留策略绑在一起的接口。 - -这并不表示将来永远不会有通用格点附加信息,只是当前真实需求已经收敛到“触发器类型数字”,继续上抽象只会把接口设计得更重。 - ---- - -# 实现思路 - -## 1. 图块数组与触发器数据彻底分离 - -`IMapLayer` 内部继续保留 `Uint32Array` 作为图块数字的权威存储,仅服务于: - -1. 高频 `getBlock` 查询; -2. 区域拷贝与渲染; -3. 与现有 `openDoor`、`closeDoor`、`putMapData`、`setMapRef` 等图块相关操作配合。 - -触发器数据则不再混入图块数组逻辑中,而是作为独立结构维护。 - -## 2. 静态图层使用“默认值 + 稀疏覆盖” - -静态图层不应自行持有整份默认触发器表,而应通过 `ILayerState.tileStore` 获取默认值,只在地图侧额外存储“手动覆盖值”。 - -当前建议结构如下: - -```ts -Map; -``` - -- key = `y * width + x` 形式的格点下标; -- value = 手动覆盖的触发器类型数字。 - -`IMapLayer.getTriggerType(x, y)` 的读取顺序建议为: - -1. 若坐标越界,返回 `-1`; -2. 若该点存在手动覆盖,直接返回覆盖值; -3. 否则读取当前图块数字,并通过 `tileStore.getTrigger(num)` 返回默认触发器类型。 - -这样可以自然满足“仅存必要”的目标: - -1. 没有手动修改过触发器的格点不占额外存储; -2. 大多数格点直接复用 `ITileStore` 中的默认定义,不需要在地图层重复抄一份; -3. 只有真正偏离默认值的点,才进入地图侧稀疏映射; -4. `revertTrigger` 只需删除覆盖记录即可恢复默认。 - -## 3. 动态图块直接携带触发器类型 - -动态图块已经是实例对象,因此不需要再为它额外建一层稀疏映射。更自然的方案是: - -1. `DynamicTile` 内部直接保存 `triggerType: number`; -2. 图块移动时,触发器天然跟随这个实例走; -3. 收集器按坐标枚举动态图块后,直接读取其 `triggerType`。 - -这样“触发器跟着动态图块走”就不再需要额外维护坐标索引迁移,只要图块对象本身移动即可。 - -## 4. 动静态转换只负责迁移或丢弃触发器 - -因为静态格点与动态图块现在是两个不同的触发器载体,所以 `transferToDynamic` / `transferToStatic` / `transferToStaticIfSafe` 的核心职责也更清晰了: - -1. `transferToDynamic(..., keepTrigger = true)`:把静态格点上的触发器迁到新动态图块; -2. `transferToDynamic(..., keepTrigger = false)`:只转换图块,不保留原触发器,并清理静态格点上的手动覆盖; -3. `transferToStatic(..., keepTrigger = true)`:把动态图块携带的触发器写回静态格点;若与默认值一致则回退默认,否则记为手动覆盖; -4. `transferToStatic(..., keepTrigger = false)`:只还原图块,不回写动态图块触发器,让静态格点自然回退到图块默认触发器; -5. `transferToStaticIfSafe(..., keepTrigger = true)`:在安全回写条件满足时,保持与 `transferToStatic` 一致的迁移语义。 - -这比把“是否保留触发器”揉进 `setBlock` 更符合职责边界,因为真正发生触发器载体切换的地方只有这些转换接口。 - -## 5. 图块写接口不再隐式影响触发器 - -既然图块数组与触发器数据已经拆开,则以下接口默认不应直接修改触发器: - -1. `setBlock` -2. `putMapData` -3. `setMapRef` -4. `openDoor` -5. `closeDoor` - -这样做的好处是: - -1. 图块外观变化与逻辑触发变化彻底解耦; -2. 不再需要 `keepInfo` / `keepTrigger` 之类参数污染高频图块写路径; -3. 调用方若确实希望同步改动触发器,应显式调用 `setTriggerType` 或走动静态转换接口。 - -这里唯一需要特殊处理的是 `resize` / `resize2`:当地图缩小时,越界格点对应的稀疏触发器记录必须一起裁剪。 - -## 6. 触发器对象延后到 Layer 2 实例化 - -当前触发器接口仍然可以保留 `ITrigger` 对象模型,但这个对象不应该提前存进地图数据层。 - -更合理的职责划分是: - -1. Layer 1 只存 `triggerType: number`; -2. `ITriggerCollector` 在收集时根据 `triggerType` 向 `ITriggerRegistry` 要工厂; -3. 再由工厂创建运行时 `ITrigger` 对象并执行后续排序、触发流程。 - -这样既满足“地图上只存一个触发器类型数字即可”的诉求,也不需要推翻当前 Layer 2 的触发器执行模型。 - -## 7. 当前存档边界 - -静态格点的触发器来源当前已经明确为“图块默认值 + 地图手动覆盖值”两层结构,因此存档边界也可以先做阶段性收敛: - -1. 图块默认触发器由 `ITileStore` 提供,不需要在地图层重复存储; -2. 若静态格点的手动覆盖值需要进入存档,那么只需要保存地图侧的稀疏覆盖映射,而不需要保存整份默认触发器表; -3. 动态图块的存储方案目前尚未设计完成,因此其 `triggerType` 是否进入存档、读档后如何恢复,暂不在本轮接口设计中拍死。 - -因此,这一节当前只确认静态覆盖的存储边界;动态图块相关存档语义后续再单独设计。 - -## 8. 单个载体当前只存一个触发器类型 - -本轮设计中,每个静态格点与每个动态图块都只保存一个 `triggerType: number`。 - -这样做的原因是: - -1. 当前触发器本身不接受用户自定义参数,地图侧没有必要提前背负对象列表; -2. 对绝大多数格点而言,一个触发器类型已经足够; -3. 同一点多触发器仍可通过“静态格点 1 个 + 多个动态图块各 1 个”的方式聚合。 - -当前这条限制已经足够。以现有触发器定位来看,一个触发器类型只是告诉系统“这次行为该走哪种处理分支”;更复杂的行为应当放到自定义事件等更高自由度的描述层中,而不是在单个图块上堆叠多个触发器类型。 - ---- - -# 涉及文件 - -## 需要引用的文件 - -- `@user/data-base/src/map/types.ts`:当前 `ILayerState`、`IMapLayer`、`IDynamicLayer`、`IDynamicTile` 的权威接口定义 -- `@user/data-base/src/store/types.ts`:`ITileStore` 的权威接口定义 -- `@user/data-base/src/map/mapLayer.ts`:静态图层当前的图块数组实现 -- `@user/data-base/src/map/dynamicLayer.ts`:动静态图块转换逻辑的当前实现 -- `@user/data-base/src/map/dynamicTile.ts`:动态图块实例对象,适合新增 `triggerType` -- `docs/dev/map/trigger.md`:当前触发器文档仍假定从 `getTileInfo` 读取信息,后续需要同步调整 - -## 需要修改的文件 - -### `@user/data-base/src/map/types.ts` - -- [ ] 不再新增 `ITileInfo` / `IWrappedTileInfo` 作为本轮主接口 -- [ ] 为 `ILayerState` 新增 `readonly tileStore: ITileStore` -- [ ] 为 `IMapLayer` 新增 `getTriggerType(x, y): number` -- [ ] 为 `IMapLayer` 新增 `setTriggerType(type: number, x: number, y: number): void` -- [ ] 为 `IMapLayer` 新增 `revertTrigger(x: number, y: number): void` -- [ ] 为 `IDynamicTile` 新增 `readonly triggerType: number` -- [ ] 为 `IDynamicTile` 新增 `setTriggerType(type: number): void` -- [ ] 修改 `IDynamicLayer.transferToDynamic`:新增 `keepTrigger?: boolean` -- [ ] 修改 `IDynamicLayer.transferToStatic`:新增 `keepTrigger?: boolean` -- [ ] 修改 `IDynamicLayer.transferToStaticIfSafe`:新增 `keepTrigger?: boolean` - -### `@user/data-base/src/map/mapLayer.ts` - -- [ ] 新增静态手动覆盖触发器类型的稀疏存储结构 -- [ ] 实现 `getTriggerType` / `setTriggerType` / `revertTrigger` -- [ ] 保持 `setBlock`、`putMapData`、`setMapRef`、`openDoor`、`closeDoor` 与触发器逻辑解耦 -- [ ] 在 `resize` / `resize2` 中裁剪越界触发器记录 - -### `@user/data-base/src/map/dynamicTile.ts` - -- [ ] 新增 `triggerType` 成员与对应写接口 - -### `@user/data-base/src/map/dynamicLayer.ts` - -- [ ] 在 `transferToDynamic` 中实现静态格点与动态图块之间的触发器迁移或丢弃逻辑 -- [ ] 在 `transferToStatic` 中实现动态图块与静态格点之间的触发器迁移或丢弃逻辑 -- [ ] 若保留 `transferToStaticIfSafe` 的对称性,同步补齐其触发器迁移逻辑 - -### `@user/data-base/src/map/mapStore.ts` - -- [ ] 若静态手动覆盖需要进入存档,仅保存地图侧稀疏覆盖映射 -- [ ] 动态图块触发器字段的存档逻辑待动态图块存储方案定稿后再补齐 - -### `docs/dev/map/trigger.md` - -- [ ] 将 `collect` 过程从“读取 `getTileInfo` 中的对象信息”改为“读取静态与动态载体上的触发器类型数字,再按需实例化 `ITrigger`” - ---- - -# 当前结论 - -1. 静态格点触发器来源采用“`ITileStore` 默认值 + 地图手动覆盖值”两层结构,`revertTrigger` 用于恢复默认值。 -2. `getTriggerType(x, y)` 与 `ITileStore.getTrigger(num)` 对齐,越界或不存在触发器时统一返回 `-1`。 -3. `transferToDynamic`、`transferToStatic`、`transferToStaticIfSafe` 的 `keepTrigger` 默认值暂定为 `true`。 -4. 静态图层只稀疏存储手动覆盖值,不重复保存整份默认触发器表。 -5. 动态图块触发器的存档语义暂不拍死,待动态图块存储方案单独定稿后再处理。 -6. 单个载体只保存一个 `triggerType` 当前已经足够,复杂行为应交由自定义事件等更高层描述。 diff --git a/docs/dev/map/trigger-impl.md b/docs/dev/map/trigger-impl.md deleted file mode 100644 index 0d65120..0000000 --- a/docs/dev/map/trigger-impl.md +++ /dev/null @@ -1,44 +0,0 @@ -# 需求综述 - -触发器系统主体已经完成,下一步开始逐步补充内置触发器类型。当前第一批只先设计两个最基础的内置触发器:战斗触发器与开门触发器。 - -本文只确认这两个类型的职责、使用频率预期与当前待补充的设计边界,不展开实现思路,也不列涉及文件。后续如果继续新增对话、商店、自定义事件等内置触发器,再在此文档上继续补充即可。 - ---- - -# 共通约束 - -这两个内置触发器都只是 `ITrigger` 的内置实现。当前阶段不建议先给它们增加额外公共配置成员,怪物信息、门信息原则上应跟随当前触发位置上的图块或运行时载体,而不是在触发器实例里重复保存一份。 - -- `type`:预期频率**低频**。由注册表和类型定义决定,通常只在注册、调试或排查问题时关注。 -- `priority`:预期频率**低频**。大多数情况下使用固定默认值即可,只有极少数“同格混合触发”场景才需要显式调整。 -- `trigger(handler)`:预期频率**中频**。平时更多通过 `ITriggerCollection.trigger(...)` 间接触发,直接持有某个内置触发器并手动调用的场景相对有限。 -- `collection()`:预期频率**中频**。继承 `ITrigger` 的统一包装能力,使用方式与其他触发器一致,这里不做额外语义扩展。 - ---- - -# 类型设计与预期 - -## 战斗触发器(暂定 `IBattleTrigger`) - -- 核心职责:在当前触发位置执行一次与怪物的战斗。 -- `IBattleTrigger.trigger(handler)`:预期频率**中频**。典型使用场景:玩家移动到怪物格后,移动系统收集到该触发器并交给集合统一触发;或脚本手动触发某个战斗事件。 -- 校验要求:触发前必须确认当前指定图块或对应的运行时载体确实是怪物。若不是怪物,发出警告并终止本次触发,不进入战斗流程。 -- 数据要求:需要能从当前触发位置解析出“这是怪物”这一事实,以及后续进入战斗流程所需的怪物信息。 -- 职责边界:该触发器当前只先负责“确认目标是怪物并进入战斗分支”。战斗后怪物移除、地图更新、事件派发等后续职责,先不在本文中拍死。 - -## 开门触发器(暂定 `IOpenDoorTrigger`) - -- 核心职责:在当前触发位置执行一次开门行为。 -- `IOpenDoorTrigger.trigger(handler)`:预期频率**中频**。典型使用场景:玩家撞到门格或主动交互门格后,系统收集到该触发器并尝试开门;或脚本在演出中手动触发某扇门的开启。 -- 校验要求:触发前必须确认当前指定图块或对应的运行时载体包含门相关信息。若不包含,发出警告并终止本次触发,不执行开门行为。 -- 数据要求:需要能从当前触发位置读取门相关信息,并定位到既有的 `openDoor` 流程。 -- 职责边界:该触发器当前只先负责“确认目标是门并进入开门分支”。钥匙校验、动画等待、图块替换、开门后事件等具体流程,先依附既有开门系统,不在本文中提前拆开。 - ---- - -# 当前待补充设计 - -1. 运行时需要一条统一路径,让触发器能从 `handler` 提供的上下文中读取当前位置对应的怪物信息或门信息;这一层目前还没有最终定稿。 -2. 这两个内置触发器原则上都不应在实例上重复保存业务对象;更合理的方向是由当前位置的图块或运行时载体提供数据。若后续发现这条路线不够用,再单独讨论是否为内置触发器补配置成员。 -3. 这两个触发器当前都只先定义“校验 + 进入对应系统”的第一层职责。后续如果需要细分成更多内置类型,例如强制战斗、免钥匙开门、条件开门等,再在这个基础上继续扩展。 diff --git a/docs/dev/map/trigger.md b/docs/dev/map/trigger.md deleted file mode 100644 index 9583cc4..0000000 --- a/docs/dev/map/trigger.md +++ /dev/null @@ -1,103 +0,0 @@ -# 需求综述 - -当前地图系统已完成图层管理与动态图块的构建,但地图上每个格点可能存在的**事件逻辑**(如战斗、对话、商店等)尚无统一抽象。本需求旨在建立一套触发器系统,将地图事件的注册、收集与触发解耦为三个独立接口层,最终可通过统一的方式触发地图上某一点所有匹配的事件。 - -触发器本身不与地图绑定,可独立于地图存在。由于触发的执行依赖 Layer 2 的上下文(`IStateBase` 等),整个触发器系统均归属 Layer 2(`@user/data-system`),地图在 Layer 1 中只存储可序列化的图块数据,不直接持有 `ITrigger`。 - ---- - -# 接口设计与预期 - -## ITrigger - -- `ITrigger.type`:预期频率**低频**,触发器的类型标识符,以数字表示,用于区分不同种类的触发器(如战斗、对话等);一般仅在实例化时通过构造参数传入一次,运行期基本不读写,故为低频。 -- `ITrigger.priority`:预期频率**低频**,仅在创建触发器时设置一次,用于控制同格点多触发器的执行顺序;绝大多数情况下一个格点只会有一个触发器,多触发器仅为少量拓展场景预留,在 `collect` 场景下,同一次收集中的 `priority` 重复会被视为配置冲突,故为低频。 -- `ITrigger.trigger(handler)`:预期频率**中频**,用户在脚本中有时会直接持有某个触发器引用并手动触发,但大多数事件触发都通过 `ITriggerCollection.trigger` 完成,直接调用此方法的场合相对有限,故为中频。接受 `ITriggerHandler` 上下文对象(必选 `state: IStateBase`,可选 `layer?: ILayerState`、`mapLayer?: IMapLayer`、`locator?: ITileLocator`),返回 `Promise`。典型使用场景:脚本中已持有某个战斗触发器引用,希望直接触发而不经过格点收集流程。 -- `ITrigger.collection()`:预期频率**中频**,当用户希望将单个触发器包装为集合并走统一触发流程时使用,场景较为固定但有一定出现概率,故为中频。典型使用场景:脚本中手动构造只含单个触发器的集合,再调用 `collection.trigger(...)` 统一触发。 - -## ITriggerRegistry - -以触发器**类型**(`type: number`)为核心提供注册与查询;`registerString` / `getString` 是触发器自身 id 的字符串别名,与地图图块绑定无关,不参与 `collect` 的自动收集流程。共四个方法,不拆分为子接口。 - -- `ITriggerRegistry.register(type, factory)`:预期频率**低频**,按触发器类型注册触发器工厂函数(`(type: number) => ITrigger`),由注册表在实例化时将当前 `type` 透传给工厂,使 `ITrigger.type` 跟随注册表,`collect` 时每次调用工厂创建独立实例,一种触发器类型只注册一次,故为低频。 -- `ITriggerRegistry.get(type)`:预期频率**低频**,主要供 `ITriggerCollector` 内部调用,用户代码几乎不会直接使用,故为低频。 -- `ITriggerRegistry.registerString(id, factory)`:预期频率**低频**,为触发器注册字符串别名(`() => ITrigger`),仅用于手动按字符串 id 查询,与地图图块收集无关,故为低频。 -- `ITriggerRegistry.getString(id)`:预期频率**低频**,按字符串 id 查询触发器工厂,调用方手动获取后实例化,主要供内部调用,故为低频。 - -## ITriggerCollection - -- `ITriggerCollection.count`:预期频率**低频**,在"当格点无任何触发器时跳过处理"的场景下可快速判断,避免额外迭代,故为低频。 -- `ITriggerCollection.trigger(handler)`:预期频率**高频**,这是触发器系统对用户暴露的最主要入口,凡是需要触发某格事件的地方都会出现此调用,故为高频。接受 `ITriggerHandler` 上下文对象,顺序异步执行所有触发器,返回 `Promise`。典型使用场景:玩家移动到某格后,移动系统调用 `collection.trigger(handler)` 依次执行该格所有已排序的触发器。 -- `ITriggerCollection.triggerIter(handler)`:预期频率**低频**,返回 `AsyncGenerator`,允许调用方逐个手动推进触发器执行,每次 `next(handler)` 可传入新的上下文(传 `null` 则沿用初始 `handler`),故为低频。典型使用场景:需要在两个触发器之间插入额外效果(如战斗结束后立刻播放特效)的进阶场景。 -- `ITriggerCollection.iterate()`:预期频率**低频**,仅在需要检查当前集合包含哪些触发器时使用(如 UI 显示交互提示),多数情况下直接触发而无需遍历,故为低频。 -- `ITriggerCollection.push(trigger)`:预期频率**低频**,在脚本中偶尔需要向已有集合末尾追加一个触发器(如特殊演出追加额外效果),直接插入末尾不重新排序,故为低频。 -- `ITriggerCollection.unshift(trigger)`:预期频率**低频**,与 `push` 对称,向集合头部强制插入触发器,直接插入头部不重新排序,场景更为罕见,故为低频。 -- `ITriggerCollection.concat(...others)`:预期频率**低频**,将当前集合与一个或多个其他集合按自身在前、传入参数依序在后的顺序合并为新集合,不重新排序。此接口需求存疑,先行提供,故为低频。 - -## ITriggerCollector - -`ITriggerCollector` 独立存在于 Layer 2(`@user/data-system`),不附属于 `ILayerState`。调用方持有 collector 引用,调用 `collect(x, y, layer)` 时显式传入目标图层(`IMapLayer`)。收集时会依赖后续由 `IMapLayer.getTileInfo` 暴露的图块信息,其中包含触发器类型。引擎层会利用 `ILayerState.eventLayer` 自动调用 `collect` 实现默认收集行为,该行为不在本接口设计范围内;若需对多个图层收集,调用方自行多次调用并合并结果,合并顺序、跨层排序结果与跨层冲突处理均由调用方自行决定。 - -- `ITriggerCollector.collect(x, y, layer)`:预期频率**中频**,在移动系统或交互系统中需要确认某格事件时调用,使用场景固定且有一定频率,故为中频。`layer` 为必选,调用方显式指定要收集的目标图层(`IMapLayer`)。典型使用场景:玩家向某格移动时,移动系统持有 collector 引用并调用 `collector.collect(x, y, layer)` 获取目标格的 `ITriggerCollection`,再决定是否阻断移动或直接触发。 -- `ITriggerCollector.attachRegistry(registry)`:预期频率**低频**,仅在初始化或切换注册表时调用,故为低频。 - ---- - -# 实现思路 - -## 1. 触发器对象 ITrigger - -`ITrigger` 代表一类事件逻辑(如"战斗触发器"、"对话触发器"),是整个系统的原子单元。`type` 成员以数字标识触发器种类,在常规注册流程下由注册表在实例化时通过构造参数注入;`priority` 控制同格点的执行顺序,并在 `collect` 场景下充当唯一执行位。触发器本身是极其宽泛的东西,一般只有几种类型,比如战斗、触发系统事件、触发自定义事件等,不会每种战斗和每种对话都开一种触发器。触发时接受外部传入的 `ITriggerHandler` 上下文参数,执行对应逻辑。 - -`ITrigger` 提供 `collection()` 方法,将单个触发器包装为 `ITriggerCollection`,供不需要收集步骤的场景直接使用。 - -## 2. 触发器注册接口 ITriggerRegistry - -触发器以类型为单位注册。为保证每个收集场景下的 `ITrigger` 实例相互独立(触发器可在内部保存状态而不互相影响),注册时传入工厂函数 `(type: number) => ITrigger`,由注册表在创建实例时将当前类型透传给工厂,使实例上的 `ITrigger.type` 跟随注册表;`collect` 时按需调用工厂创建新实例。字符串别名注册仍用于手动查询场景,不参与 `collect` 自动收集。`ITriggerRegistry` 直接包含以下四个方法: - -- `register(type: number, factory: (type: number) => ITrigger)`:按触发器类型注册触发器工厂 -- `get(type: number)`:按触发器类型查询触发器工厂 -- `registerString(id: string, factory: () => ITrigger)`:为触发器注册字符串别名(仅用于手动查询,不参与 `collect` 自动收集) -- `getString(id: string)`:按字符串 id 查询触发器工厂 - -每个 key 只对应一个工厂。重复注册同一 key 时,发出警告并以新工厂覆盖旧的。 - -## 3. 触发收集 ITriggerCollector - -`ITriggerCollector` 属于 Layer 2(`@user/data-system`),独立于 `ILayerState` 存在,不挂载到任何 Layer 1 接口上。地图在 Layer 1 中只存储可序列化的图块数据(如图块编号),不持有 `ITrigger` 实例。收集时,调用方显式传入目标图层 `layer`(`IMapLayer`),`ITriggerCollector` 读取该图层中该格点的图块数据——包括静态图块(通过 `IMapLayer.getBlock`)以及动态图块(通过 `IMapLayer.dynamicLayer`)——并结合后续由 `IMapLayer.getTileInfo` 暴露的图块信息读取其中记录的触发器类型,再通过 `ITriggerRegistry` 获取工厂并构造对应的 `ITrigger`,收集后排序,返回 `ITriggerCollection`。注册表通过 `attachRegistry(registry)` 设置。 - -排序规则:按触发器自身 `priority` 降序排列。这里的 `priority` 不仅表示执行顺序,也表示单次收集内的唯一执行位;正常情况下同一坐标只会有一个触发器,多触发器支持仅用于极少数拓展场景。收集阶段若发现两个或以上触发器的 `priority` 相同,则视为配置冲突,发出警告,并将本次结果中所有该优先级的触发器全部剔除,不进入 `ITriggerCollection`。 - -## 4. 触发器集合 ITriggerCollection - -`ITriggerCollection` 是一组已排序的 `ITrigger` 的载体,提供统一的触发入口 `trigger(...)` 与迭代方法 `iterate()`、数量属性 `count`。`push` / `unshift` / `concat` 均不对集合重新排序,语义为"强制指定执行位置",与 `priority` 排序体系完全分离(`ITriggerCollection` 本身与地图无关,不应自行排序)。可由: - -- `ITriggerCollector.collect` 返回; -- `ITrigger.collection()` 方法直接构造(单触发器集合)。 - -触发执行策略:`trigger(handler)` 顺序异步执行(返回 `Promise`),触发器之间串行等待;`triggerIter(handler)` 返回异步生成器,调用方可逐个推进执行并为每一步单独传入上下文,适用于需要在触发器间插入额外逻辑的进阶场景。 - ---- - -# 涉及文件 - -## 需要引用的文件 - -- `@user/data-base`(跨包引用):`@user/data-system/trigger/types.ts` 中的 `ITriggerHandler` 需要引用 `IStateBase`(全局状态)、`ILayerState`(楼层状态)、`IMapLayer`(图层)与 `ITileLocator`;`ITriggerCollector.collect` 同样需要引用 `IMapLayer`,并依赖其后续提供的 `getTileInfo` 读取图块信息中的触发器类型 - -## 需要修改的文件 - -### `@user/data-base` 中的 `ILayerState` - -- [x] 新增 `readonly eventLayer: IMapLayer | null` 属性:表示该楼层的默认事件图层,供引擎默认收集行为使用(引擎默认收集逻辑不在本设计范围内) -- [x] 新增 `setEventLayer(layer: IMapLayer | null): void` 方法:设置默认事件图层 - -## 需要新建的文件 - -### `@user/data-system/trigger/types.ts` - -- [ ] 新增 `ITriggerHandler` 接口:触发时传入的上下文对象,包含必选的 `state: IStateBase` 以及可选的 `layer?: ILayerState`、`mapLayer?: IMapLayer`、`locator?: ITileLocator` -- [ ] 新增 `ITrigger` 接口:触发器原子对象,代表一类事件逻辑;包含 `type: number` 类型标识、`priority` 优先级成员、`trigger(handler: ITriggerHandler): Promise` 触发方法与 `collection()` 方法以便包装为集合 -- [ ] 新增 `ITriggerRegistry` 接口:包含 `register(type, factory: (type: number) => ITrigger)` `get(type)` `registerString(id, factory: () => ITrigger)` `getString(id)` 四个方法;以触发器类型 `type` 为核心管理注册与查询,字符串 id 为触发器自身别名,不参与 `collect` 自动收集;数字注册路径下由注册表透传 `type` 给工厂,保证实例的 `ITrigger.type` 与注册项一致 -- [ ] 新增 `ITriggerCollection` 接口:包含已排序的触发器集合,提供 `trigger(handler)` 顺序异步触发入口(`Promise`)与 `triggerIter(handler)` 异步迭代触发入口(`AsyncGenerator`)、`iterate()` 迭代方法、`count` 数量属性、`push` / `unshift` 向集合末尾或头部插入(不重排序)的方法,以及 `concat(...others)` 按自身在前的顺序合并多个集合(不重排序)的方法 -- [ ] 新增 `ITriggerCollector` 接口:包含 `collect(x: number, y: number, layer: IMapLayer)` 收集方法与 `attachRegistry(registry: ITriggerRegistry | null)` 注册表设置方法 diff --git a/docs/dev/replay/replay-array.md b/docs/dev/replay/replay-array.md deleted file mode 100644 index f97c067..0000000 --- a/docs/dev/replay/replay-array.md +++ /dev/null @@ -1,151 +0,0 @@ -# 需求综述 - -录像由数万条录像步组成,每条录像步包含可变数量的参数,参数类型涵盖数字、字符串、布尔值和 bigint。直接操作 `ArrayBuffer` 的字节布局会导致系统耦合存储格式细节、修改录像困难。需要一个独立工具封装底层存储的读写,并提供插入、删除、修改等编辑能力。 - -# 需求理解 - -## 明确需求 - -1. 变长指令编码。指令码使用 `uint8` 或 `uint16`,宽度取决于已注册命令的最大 ID。 -2. 自描述参数格式。参数使用类型标识 + 变长内容的编码,每条录像步的参数数量可变。类型标识同时承担短字符串的长度信息,节省额外字节。 -3. 录像可编辑。支持在任意有效位置插入、删除、修改录像步——不是只追加的日志。 -4. 顺序流式读取。播放是按顺序逐帧进行的,需要流式读取器避免每次随机访问的 O(n) 偏移扫描。 - -## 隐含需求 - -1. 容量管理。`ArrayBuffer` 不可动态扩容,写入前需检查容量,不足时分配新缓冲并复制。 -2. 偏移累积。指令数组中的每条录像步长度不固定,随机访问某一条时需从头扫描累积偏移。流式读取器可以利用顺序访问特性只扫描一次。 - -# 设计前提 - -1. 双缓冲存储。指令数组和参数数组各自使用独立的 `ArrayBuffer`,通过 `DataView` 读写。 -2. 不可变元数据。`IReplayMetadata` 中的格式参数在初始化时确定,读取时不修改。 -3. 边解码边播放。不一次性解码全部数据——沙箱通过流式读取器按需解码。 - -# 核心概念定义 - -## 指令数组 - -录像中所有指令码与参数数量的序列化存储。每条录像步在指令数组中占用 1+W 字节(W 为指令码宽度):第一个 `uint8` 为参数数量,后续 `uint8` 或 `uint16` 为指令码。 - -## 参数数组 - -录像中所有参数内容的序列化存储。每个参数由一个 `uint8` 类型标识开头,后接对应类型的二进制内容。类型标识同时编码短字符串的长度(8-256 → 长度 n-7)。 - -## 流式读取器 - -持有一个内部偏移指针,每次调用 `read` 解码并返回当前指针指向的录像步,然后推进指针。由于指令数组中每条录像步的总字节数不固定,指针以步索引而非字节偏移为单位——读取器内部维护已扫描过的累积字节偏移,每次 `read` 仅解码一步。 - -## 指令宽度 - -指令码的存储字节数。初始为 `uint8`(1 字节)。命令 ID 上限在 `registerCommand` 注册时即可获知——若注册了 > 255 的 ID,直接初始化为 `uint16`,不涉及运行时重编码。若需要变更已存在录像的指令宽度(例如切换为更大的指令码空间以兼容新增命令,或压缩为更小的空间以节省体积),则通过 `setCommandWidth` 方法将全部指令条目按新宽度重编码。 - -# 接口设计分析 - -## `IReplayArray` - -### 设计思路 - -由于录像需要支持任意有效位置的插入和删除——比如玩家在已录制的操作序列中间补充了额外操作,或者修复了某段错误的录像——只提供追加写入是不够的。因此需要 `insert` 和 `delete` 方法,以及在原位置修改的 `set` 方法。追加仍然是最频繁的操作——因此需要 `add` 方法。 - -由于沙箱播放是顺序推进的,每次调用 `get(index)` 都需要从头扫描积累偏移——因此设计 `createReadStream` 创建流式读取器。流式读取器内部维护步索引指针和已累积字节偏移,每次 `read` 仅解码一步,将偏移计算从 O(n²) 降为 O(n)。同时保留 `get` 用于跳转播放等非顺序场景。 - -由于命令 ID 上限在注册阶段已知,指令宽度在 `IReplayArray` 构造时即确定——若最大 ID > 255 则初始化为 `uint16`。但已存在的录像可能需要变更宽度——例如加载旧 `uint8` 录像后注册了 > 255 的新命令需要升级,或者想将 `uint16` 录像压缩回 `uint8` 以节省存储。因此需要 `setCommandWidth` 方法将全部指令条目按新宽度重编码。 - -### 接口分析 - -- 成员 `length: number`:预期频率**中频**。安全系统检测步数变化、沙箱判断播放是否结束。 -- 成员 `metadata: IReplayMetadata`:预期频率**低频**。存档时读取以持久化格式参数。 -- 方法 `add(id: number, params: ReplayParamValue[])`:预期频率**高频**。每次 `record` 调用一次。 -- 方法 `insert(index: number, id: number, params: ReplayParamValue[])`:预期频率**低频**。`index` 等于 `length` 时等同于 `add`;`index` 超出 `length` 时触发警告并忽略。 -- 方法 `delete(index: number)`:预期频率**低频**。 -- 方法 `set(index: number, id: number, params: ReplayParamValue[])`:预期频率**低频**。 -- 方法 `get(index: number): IReplayStepHandler`:预期频率**低频**。跳转播放时使用。 -- 方法 `createReadStream(startIndex?: number): IReplayReadStream`:预期频率**中频**。沙箱播放时调用。 -- 方法 `setCommandWidth(width: ReplayCommandWidth)`:预期频率**低频**。变更已存在录像的指令码宽度,将全部指令重编码。 - -### 预期体量 - -预期代码体量 300-360 行。 - -- 参数编码预期 100 行。七种参数类型各自需要独立的写入逻辑:布尔值为类型 0 接 1 字节;int8/int16/int32/int64/float64 各为一个单字节类型标识(1-5)接对应宽度的定长写入;bigint 需类型 6 接 `uint16` 长度再接 n 个 `int32`;短字符串将长度编码进类型字节(8-256,长度 = n-7),`TextEncoder` 编码后直接写入内容;长字符串用类型 7 接 `uint16` 长度再写内容。每种类型还需容量检查与扩容,七种类型的分支代码量相当可观。 -- 参数解码预期 80 行。与编码对称的七种读取分支,根据类型字节分发。数值类型按宽度读,bigint 读长度后拼接 int32,短字符串从类型字节反推长度后 `TextDecoder` 解码,长字符串先读长度。每条分支均含边界检查。 -- 指令读写预期 60 行。写入时根据当前 `commandWidth` 决定写入 1 还是 2 字节,读取时同理。每条录像步的指令部分 = `uint8` 参数数量 + W 字节指令码。`get` 和 `createReadStream` 均需维护字节偏移——每次读取或跳过一条录像步时,根据上一条的参数数量和参数总字节长度计算下一条的起始偏移。 -- 插入与删除预期 60 行。`insert` 需将指定位置及之后的所有指令和参数数据向后移动以腾出空间,再在空位写入新步。`delete` 则反向——将之后的数据向前移动覆盖被删除步。参数数组的移动量取决于被操作步的参数总字节数,需先解码获取。`set` 本质是先 `delete` 后 `insert`。 -- 容量管理与复制预期 30 行。两个 `ArrayBuffer` 各自独立管理容量,写入前检查,不足时分配更大新缓冲(如 2 倍增长),逐字节复制旧数据后再继续写入。 -- 流式读取器预期 25 行。内部维护步索引、指令数组字节偏移、参数数组字节偏移。`read` 解码当前步并推进三个变量,返回 `IReplayStepHandler`,超出 `length` 时返回 null。构造时可选 `startIndex`,需扫描至指定位置。 -- 指令宽度变更预期 25 行。`setCommandWidth` 遍历全部已写指令条目——解析每条录像步的参数数量和原宽度的指令码——按新宽度重新写入指令数组。`commandWidth` 可能变大也可能变小,编码前需根据新宽度判断每条指令码是否在范围内(例如 `uint8` 模式下指令 ID > 255 应报错)。 - -## `IReplayReadStream` - -### 设计思路 - -由于沙箱播放是顺序推进的,流式读取器持有当前步索引指针及已累积的字节偏移,每次调用 `read` 仅解码一步且指针自动推进——避免了 `get(n)` 每次 O(n) 扫描的累积开销。读取完毕后返回 null,调用方可判断播放结束。 - -### 接口分析 - -- 成员 `index: number`:预期频率**低频**。显示当前播放进度。 -- 成员 `length: number`:预期频率**低频**。用于判断是否已读取完毕。 -- 方法 `read(): IReplayStepHandler | null`:预期频率**高频**。沙箱每步调用一次。 - -### 预期体量 - -预期代码体量 25 行。 - -## `IReplayMetadata` - -### 设计思路 - -指令码宽度在读取时必须知晓才能正确解析每条录像步。`uint8` 和 `uint16` 是不同的编码模式,不是一个可变参数——因此用枚举 `ReplayCommandWidth` 标记,存档时持久化此枚举值。 - -### 接口分析 - -- 成员 `commandWidth: ReplayCommandWidth`:预期频率**低频**。初始化时根据最大命令 ID 设定,指令读写时使用。 - -### 预期体量 - -预期代码体量 5 行(含枚举定义)。 - ---- - -# 实现思路 - -## 参数编码格式 - -| 类型标识 | 参数类型 | 内容编码 | -| -------- | --------------------- | ------------------------------------------------- | -| 0 | 布尔值 | 1 字节:0 或 1 | -| 1 | int8 | 1 字节 | -| 2 | int16 | 2 字节 | -| 3 | int32 | 4 字节 | -| 4 | int64 | 8 字节 | -| 5 | float64 | 8 字节 | -| 6 | bigint | `uint16` 长度 N,后接 N 个 `int32` | -| 7 | 长字符串(≥250 字节) | `uint16` 长度 N,后接 `TextEncoder` 编码的 N 字节 | -| 8-256 | 短字符串(n-7 字节) | `TextEncoder` 编码的 n-7 字节,无额外长度字段 | - -短字符串方案:类型标识本身编码了长度(8 表示 1 字节,256 表示 249 字节),`TextEncoder` 编码后直接写内容。大多数录像参数(道具 ID 名、操作描述等)落在此范围内,基本无需额外长度字段。 - -## 指令宽度确定 - -1. `IReplayArray` 构造时传入最大命令 ID(由 `IReplaySystem.registerCommand` 的累积结果提供)。若 maxId > 255,`commandWidth` 初始化为 `Uint16`;否则为 `Uint8`。 -2. 注册阶段即确定宽度,后续 `add`/`insert` 不涉及宽度切换。 -3. `setCommandWidth` 用于变更已存在录像的指令宽度:遍历全部指令条目,将原宽度的指令码按新宽度重写,更新 `metadata`。若缩小宽度时存在超过新宽度表示范围的指令 ID,应报错。 - -## 流式读取器内部状态 - -`read()` 每步仅解码当前指针指向的录像步: - -1. 从指令数组当前字节偏移读 `uint8` 参数数量,再按 `commandWidth` 读指令码。 -2. 根据参数数量,从参数数组当前字节偏移逐参数解码。 -3. 将指令数组字节偏移增加 `1 + commandWidth`,参数数组字节偏移增加已解码参数的总字节数。 -4. 返回 `IReplayStepHandler`,`index` 为当前步索引。指针推进到下一步。 - -# 涉及文件 - -## `@user/data-common/src/replay/array.ts` - -- [ ] 新增 `ReplayCommandWidth` 枚举 -- [ ] 编写 `IReplayArray`、`IReplayReadStream`、`IReplayMetadata` 接口 -- [ ] 编写 `ReplayArray` 类:实现 `IReplayArray`,封装双 `ArrayBuffer` 操作、指令宽度管理与变更、CRUD 编辑、流式读取 -- [ ] 编写 `ReplayReadStream` 类:实现 `IReplayReadStream` diff --git a/docs/dev/replay/replay-safety.md b/docs/dev/replay/replay-safety.md deleted file mode 100644 index ad84a41..0000000 --- a/docs/dev/replay/replay-safety.md +++ /dev/null @@ -1,80 +0,0 @@ -# 需求综述 - -录像系统依赖作者在各逻辑系统中手动调用 `record`,这一依赖关系使得录像极易因疏忽而失效。需要一种机制来检测这类失效。 - -# 需求理解 - -## 明确需求 - -1. 录像存在失效风险。录像能否正确还原游戏流程完全取决于作者是否在每个状态修改处都调用了 `record`,遗漏是不可避免的风险。 - -## 隐含需求 - -1. 检测不能误报。系统内部的状态修改(如战斗结算中的属性计算)不应被检测为遗漏——只有从玩家操作入口派生的修改才需要检测。 -2. 播放时同样需检测。沙箱播放期间原始录像系统重新录制用于交叉验证——如果播放逻辑存在遗漏,同样应被检测到。 - -# 设计前提 - -1. 已知方案。`@shouldReplay` 标记影响状态的方法,`@ignoreReplay` 标记特定的延迟或被动代码路径,`begin`/`end` 界定检测区间。以下设计基于此方案推导。 - -# 核心概念定义 - -## 检测区间 - -由一对 `begin`/`end` 调用界定的代码执行范围。区间内收集标记,结束时比较区间前后的录像步数变化,结合标记信息决定是否告警。 - -## 状态影响标记 - -数据端方法上的 `@shouldReplay` 修饰器。表示此方法的调用应伴随录像步的写入——它是系统判断"此处有状态修改"的唯一依据。 - -## 检测抑制标记 - -特定方法上的 `@ignoreReplay` 修饰器。某些场景中,`@shouldReplay` 方法确实被调用,但录像步的写入发生在此区间之外(如异步回调中),不加抑制会误报。它不应用于所有渲染端交互函数——那样系统永不会告警。 - -# 接口设计分析 - -## 检测体系 - -### 设计思路 - -由于录像失效来源于作者忘记 `record`,而系统无法自动区分"状态修改来自玩家操作"还是"系统内部结算"——计算机不可能判断一次属性修改是玩家打怪触发的还是战斗计算过程——因此必须由作者显式标注哪些方法会影响状态。由此设计 `@shouldReplay` 修饰器。 - -标注了哪些方法会影响状态之后,还需要界定"在什么范围内检测"。一次玩家操作可能触发若干 `@shouldReplay` 方法——它们应当在同一区间内被收容,区间结束时统一评判。因此需要 `beginReplaySafetyCollection` 和 `endReplaySafetyCollection`。区间需要知道录像是否增长——`begin` 接收 `IReplaySystem` 参数以记录初始步数。 - -仅有标注和区间还不够——有些代码路径中 `@shouldReplay` 方法确实会被调用,但录像步的写入并不在当前区间内完成。例如玩家点击购买道具,购买逻辑触发属性修改(`@shouldReplay`),但对应的 `record` 在异步事件处理中延迟执行。若不加抑制,这些场景都会误报。因此需要 `@ignoreReplay` 修饰器,标记此类延迟或被动触发路径。 - -### 接口分析 - -- 修饰器 `@shouldReplay(description: string)`:预期频率**中频**。每个影响状态的方法标注一次。 -- 修饰器 `@ignoreReplay(description: string)`:预期频率**中频**。每个需抑制误报的方法标注一次。 -- 函数 `beginReplaySafetyCollection(replay: IReplaySystem): void`:预期频率**低频**。仅出现在渲染端交互入口和沙箱播放循环两处。 -- 函数 `endReplaySafetyCollection(): void`:预期频率**低频**。与 `begin` 配对。 - -### 预期体量 - -预期代码体量 50 行。 - -- 修饰器工厂各 10 行。 -- `begin`/`end` 及区间管理预期 30 行。 - ---- - -# 实现思路 - -## 检测流程 - -1. 渲染端交互入口调用 `beginReplaySafetyCollection(replay)`。 -2. 代码执行——`@shouldReplay` 和 `@ignoreReplay` 修饰的方法被调用时向区间注册标记。 -3. 调用 `endReplaySafetyCollection()`,检查:有 `@shouldReplay` 标记、无 `@ignoreReplay` 标记、步数未增 → 警告。 -4. 沙箱每步包裹 `begin`/`end`,用于交叉验证。 - -# 涉及文件 - -## `@user/data-common/src/replay/safety.ts` - -- [ ] 编写 `shouldReplay` 和 `ignoreReplay` 修饰器工厂 -- [ ] 编写 `beginReplaySafetyCollection` 和 `endReplaySafetyCollection` 函数 - -# 待确认问题 - -无。 diff --git a/docs/dev/replay/replay-system.md b/docs/dev/replay/replay-system.md deleted file mode 100644 index 5e4ba5a..0000000 --- a/docs/dev/replay/replay-system.md +++ /dev/null @@ -1,210 +0,0 @@ -# 需求综述 - -需要一个能录制并回放游戏过程的系统,通过录像唯一还原一段游戏流程。 - -# 需求理解 - -## 明确需求 - -1. 游戏过程可回放。能通过录像唯一地还原出游戏从起点到终点的一段完整流程。 -2. 录像步可自定义。录像步的类型不限于系统内置——移动、使用道具等——也允许作者自定义。 - -## 隐含需求 - -1. 异步播放。由于播放每个录像步都伴随渲染动画,动画未结束前不能执行下一步——播放必须是异步的。 -2. 渲染端被动通知。架构约束下渲染端不能主动拉取数据——需要钩子将每步信息推送给渲染端。 -3. 录像需跨会话持久化。录像可能录制于这次游戏,在下次游戏中被播放——需要存档。 - -## 未确定需求 - -无。 - -# 设计前提 - -1. 确定性执行。计入录像的操作在相同输入下产生相同输出。 -2. 单向依赖。录像系统位于 `@user/data-common`,不依赖 Layer 1/2。渲染端被动接收信息。 -3. 外部主动录制。系统不自动录制——作者在逻辑系统中手动调用录像步写入接口。 -4. 底层存储独立。录像的二进制存储由 `IReplayArray` 统一管理(详见 `replay-array.md`),本系统不直接操作 `ArrayBuffer`。 - -# 核心概念定义 - -## 录像步 - -一段游戏流程中不可再分的最小操作单元。由一个指令标识和一组数量可变的参数构成:指令标识区分操作类型,参数描述操作的细节。 - -## 录像 - -一组按时间顺序排列的录像步的序列。完整记录从某初始状态到某最终状态的全部操作,是还原一段游戏流程的充要条件。 - -## 录像录制 - -将玩家操作转化为录像步并通过 `IReplayArray.add` 追加到末尾的过程。由作者在各逻辑系统(商店、移动等)中主动调用写入接口完成。 - -## 录像播放 - -从 `IReplayArray` 中通过流式读取器依次取出录像步并驱动数据端状态变化的过程。由于播放时需要从初始状态逐步重建到目标状态——这是整个系统中唯一需要批量修改全局状态的场景——使用沙箱隔离播放过程。 - -# 接口设计分析 - -## `IReplaySystem` - -### 设计思路 - -由于要还原游戏流程,需要记录玩家操作。不同操作对应不同录像步类型——因此需要 `registerCommand` 注册命令,使每种命令 ID 映射到对应的执行对象。 - -注册了命令类型后,运行时需要向 `IReplayArray` 中追加新步——因此需要 `record` 方法,接收命令 ID 和可变长度的参数,内部委托给 `IReplayArray.add`。 - -由于需要回放录像,且回放时原始录像系统仍需录制新步(做交叉验证),播放需要有独立的执行环境。此外播放前需要重置全局状态,但重置逻辑属于数据端不应被 Layer 0 直接依赖——因此需要 `createReplaySandbox` 创建沙箱,通过 `IStateReseter` 接口间接访问重置能力。播放结束需要销毁沙箱——因此需要 `releaseSandbox`。 - -由于播放期间渲染端和数据端需要切换行为,需要一个成员标记当前是否有活跃沙箱——因此需要 `replaying`。外部可能需要获取当前沙箱实例——因此需要 `sandbox` 成员。 - -### 接口分析 - -- 成员 `replaying: boolean`:预期频率**高频**。 -- 成员 `sandbox: IReplaySandbox | null`:预期频率**中频**。 -- 方法 `registerCommand(id: number, command: IReplayCommand)`:预期频率**低频**。 -- 方法 `record(id: number, ...params: ReplayParamValue[])`:预期频率**高频**。 -- 方法 `createReplaySandbox(reseter: IStateReseter, save?: Map): IReplaySandbox`:预期频率**低频**。 -- 方法 `releaseSandbox()`:预期频率**低频**。 - -### 预期体量 - -预期代码体量 60-80 行。 - -- 命令注册与管理预期 15 行:`Map` 的增删查。 -- 沙箱创建与销毁预期 20 行:深拷贝 `IReplayArray`、注入命令注册表和 `IStateReseter`、创建 `ReplaySandbox` 实例。 -- 存档实现预期 25 行:`saveState` 从 `IReplayArray` 导出 `ArrayBuffer` 及 `IReplayMetadata`,`loadState` 从存档数据重建 `IReplayArray` 并恢复标记。 - -## `IReplayCommand` - -### 设计思路 - -由于作者可以自定义录像步类型,播放不同步类型时需要执行不同的操作逻辑。录像系统本身无法知晓这些逻辑,需要作者提供。这些逻辑往往需要持有数据端引用,纯函数回调无法满足——因此需要接口对象 `IReplayCommand`,作者编写实现类通过构造器持有所需引用。沙箱执行时传递当前步信息——因此 `execute` 直接接收 `IReplayStepHandler`。 - -### 接口分析 - -- 方法 `execute(step: IReplayStepHandler)`:预期频率**低频**。每个命令编写一个实现类。 - -### 预期体量 - -预期代码体量 5 行。 - -## `IReplaySandbox` - -### 设计思路 - -由于需要回放录像,播放过程中需要流程控制——连续推进、暂停、单步前进、终止。因此需要 `play`、`pause`、`resume`、`step`、`stop` 五个方法。 - -由于需求隐含要求异步播放和渲染端被动通知——项目已有 `IHookable` 通用钩子系统,沙箱扩展 `IHookable`,每步触发钩子并 `await` 其完成。 - -由于播放期间原始录像系统仍在录制,沙箱不能引用正在增长的 `IReplayArray`——创建时深拷贝,播放时通过 `IReplayReadStream` 顺序读取每一步,避免每次随机访问的 O(n) 偏移扫描。播放速度需要调节,渲染端据此调整动画时长——因此需要 `speed` 只读成员和 `setSpeed` 方法。 - -### 接口分析 - -- 成员 `replaying: boolean`:预期频率**高频**。 -- 成员 `pausing: boolean`:预期频率**中频**。 -- 成员 `speed: number`:预期频率**中频**。播放倍率,只读。 -- 方法 `setSpeed(speed: number)`:预期频率**低频**。 -- 方法 `play()`:预期频率**低频**。 -- 方法 `pause()`:预期频率**低频**。 -- 方法 `resume()`:预期频率**低频**。 -- 方法 `stop()`:预期频率**低频**。 -- 方法 `step()`:预期频率**中频**。返回 `Promise`。 - -### 预期体量 - -预期代码体量 120-160 行。 - -- 播放循环预期 80 行。在循环中通过 `IReplayReadStream.read()` 逐帧解码,传给 `IReplayCommand.execute`,再触发 `onStep` 钩子并 `await`。连续模式下循环自动推进,单步模式执行一步后挂起等待下个 `step` 调用,暂停冻结循环但不丢失索引,终止跳出循环并清空流式读取器。四种模式的状态切换组合逻辑占主要行数。 -- 状态管理预期 30 行。`play`/`pause`/`resume`/`stop`/`step` 之间的互斥与合法性检查——未播放时 `pause` 无效果,已暂停时 `resume` 才恢复,`stop` 后一切操作无效。 -- 钩子集成预期 20 行。扩展 `Hookable` 后在循环中调用 `forEachHook`,收集 `onStep` 的 Promise 并全部 `await`。 - -## `IStateReseter` - -### 设计思路 - -由于沙箱播放前需将全局状态恢复到基准状态——从头播放恢复为初始状态,从存档继续播放恢复为存档状态。重置能力属于数据端,不应被 `@user/data-common` 直接依赖——因此设计 `IStateReseter` 接口隔离此依赖,`reset` 接收可选存档数据覆盖两种场景。 - -### 接口分析 - -- 方法 `reset(save?: Map)`:预期频率**低频**。 - -### 预期体量 - -预期代码体量 3 行。 - -## `IReplaySandboxHook` - -### 设计思路 - -由于架构约束下渲染端被动接收信息,沙箱需要钩子将每步信息推送给渲染端。项目已有 `IHookable`/`IHookBase` 体系——因此沙箱钩子扩展 `IHookBase`,提供 `onStep` 方法接收 `IReplayStepHandler`。沙箱 `await` 其完成后再推进下一步。 - -### 接口分析 - -- 方法 `onStep?(step: IReplayStepHandler): Promise`:预期频率**低频**。渲染端注册时定义一次。 - -### 预期体量 - -预期代码体量 4 行。 - -## `IReplayStepHandler` - -### 设计思路 - -由于钩子和命令执行器都需要当前步的完整信息(操作类型、参数、索引),因此设计统一的 handler 风格数据接口 `IReplayStepHandler`,包含 `id`、`params`、`index`。 - -### 接口分析 - -- 成员 `id: number`:预期频率**高频**。 -- 成员 `params: readonly ReplayParamValue[]`:预期频率**高频**。 -- 成员 `index: number`:预期频率**低频**。 - -### 预期体量 - -预期代码体量 5 行。 - -## 类型别名 - -`ReplayParamValue = number | string | boolean | bigint` - ---- - -# 实现思路 - -## 沙箱播放与交叉验证 - -1. 创建沙箱时深拷贝当前 `IReplayArray`。 -2. 调用 `reseter.reset(save)` 重置全局状态。 -3. 创建 `IReplayReadStream`,循环:`read()` 解码一步 → `IReplayCommand.execute` → 触发 `onStep` → `await`。 -4. 原始录像系统清空并重新录制——播放中沙箱执行的命令触发 `record`,若遗漏则安全系统警告。 - -# 涉及文件 - -## `@user/data-common/src/replay/types.ts` - -- [ ] 新增 `IReplaySystem`、`IReplaySandbox`、`IReplayCommand`、`IStateReseter`、`IReplaySandboxHook`、`IReplayStepHandler` 接口与 `ReplayParamValue` 类型别名 - -## `@user/data-common/src/replay/replay.ts` - -- [ ] 编写 `ReplaySystem` 类:命令注册、沙箱创建、存档实现 - -## `@user/data-common/src/replay/sandbox.ts` - -- [ ] 编写 `ReplaySandbox` 类:扩展 `Hookable` 实现 `IReplaySandbox` - -## `@user/data-common/src/replay/array.ts` - -- [ ] 详见 `replay-array.md` - -## `@user/data-common/src/types.ts` - -- [ ] 新增 `IDataCommon` 成员 `replay` - -## `@user/data-common/src/index.ts` - -- [ ] 新增 `export * from './replay'` - -## `@user/data-state/src/core.ts` - -- [ ] 新增 `ReplaySystem` 实例化与 `IStateReseter` 实现 -- [ ] 新增存档注册 diff --git a/docs/dev/store/tile-store.md b/docs/dev/store/tile-store.md deleted file mode 100644 index 2f1fae4..0000000 --- a/docs/dev/store/tile-store.md +++ /dev/null @@ -1,308 +0,0 @@ -# 需求综述 - -当前关于地图触发器的设计卡在“图块定义本身尚未抽象出来”这一前置问题上。继续讨论 [docs/dev/map/tile-info.md](docs/dev/map/tile-info.md) 中的地图触发器存储方案,会立刻撞上一个更底层的问题: - -1. 某个图块数字到底对应什么 id; -2. 某个图块默认携带什么触发器类型; -3. 某个图块属于哪一类图块; -4. 旧引擎中的 `blocksInfo` 应该如何转移到新接口。 - -因此,下一步更合适的顺序不是继续扩展地图侧接口,而是先补齐一个独立的 `ITileStore`。它属于 Layer 0,不参与存档,也不承载运行时动态状态,只负责提供“图块定义查询”这一底层能力。 - -这次设计的目标如下: - -1. 将当前挂在全局状态上的 `idNumberMap` 与 `numberIdMap` 收拢到 `ITileStore` 内; -2. 为图块定义提供统一查询入口:`getData`、`getTrigger`、`getType`; -3. 提供统一写入口 `addTile`,用于初始化阶段录入图块定义; -4. 提供 `attachLegacyConverter` 与 `fromLegacy` 两个接口,用于从旧引擎迁移图块定义; -5. 明确这部分能力不属于存档系统,且应当放在 Layer 0。 - ---- - -# 接口设计与预期 - -## ITileRawData - -`ITileRawData` 表示单个图块的最小原始定义。按照当前结论,需要包含四个字段:图块数字、图块 id、触发器类型、图块类型。 - -- `ITileRawData.num`:预期频率**中频**。图块数字是整个图块定义系统的主键,`addTile`、`getData` 与旧引擎导入都会依赖它,但日常脚本中通常不会反复直接读写,故为中频。 -- `ITileRawData.id`:预期频率**中频**。图块 id 常用于脚本层、兼容层与素材层之间的衔接,出现频率不低,但通常通过 `idToNumber` 间接使用,故为中频。典型使用场景:旧接口或脚本给出 `yellowDoor` 这类字符串 id,希望先查到对应图块数字。 -- `ITileRawData.trigger`:预期频率**中频**。当前触发器设计已经收敛到“数据层只存触发器类型数字”,因此这是图块定义中的核心字段之一,但大多数代码仍会优先通过 `getTrigger` 访问,故为中频。典型使用场景:地图初始化时根据图块默认定义,给某个格点填入默认触发器类型。 -- `ITileRawData.type`:预期频率**中频**。图块类型会被 `getType` 高效访问,但在定义录入阶段仍应直接作为图块原始数据的一部分保存,避免再人为拆成第二套并行数据源。典型使用场景:地图或逻辑层拿到某个图块定义后,希望直接知道其属于地形、怪物、NPC 还是道具。 - -当前建议接口如下: - -```ts -export interface ITileRawData { - readonly num: number; - readonly id: string; - readonly trigger: number; - readonly type: TileType; -} -``` - -之所以推荐使用 `num` 而不是 `number`,是因为当前仓库内地图与图块相关接口几乎都使用 `num` 表示图块数字,延续这一命名更自然。 - -## TileType - -`getType` 需要返回一个图块类型枚举。按照当前需求,建议先定义以下八种: - -1. `Unknown` -2. `None` -3. `Terrain` -4. `Animate` -5. `Item` -6. `Enemy` -7. `Npc` -8. `Tileset` - -这里的目的不是完整复刻旧引擎的所有 `cls`,而是先给当前执行层与地图层提供足够稳定、足够粗粒度的类型划分。基于旧引擎的现有分类,当前建议映射关系如下: - -1. `0` 号空白图块映射为 `None` -2. `terrains` 与 `autotile` 统一映射为 `Terrain` -3. `animates` 映射为 `Animate` -4. `items` 映射为 `Item` -5. `enemys` 与 `enemy48` 统一映射为 `Enemy` -6. `npcs` 与 `npc48` 统一映射为 `Npc` -7. `tileset` 映射为 `Tileset` -8. 其他尚未归类或不存在的图块映射为 `Unknown` - -这样处理的原因是:当前数据端真正需要的是“足够稳定的逻辑分类”,而不是把渲染素材维度的细分 `cls` 原封不动搬进底层接口。 - -## ITileStore - -`ITileStore` 是图块定义的统一查询与写入接口。由于它本身不会进入存档,也不承担运行时状态变化,因此整体频率分布会明显偏向“读取高于写入”。 - -- `ITileStore.getData(num)`:预期频率**中频**。这个接口会返回完整的 `ITileRawData`,适合调试、兼容层、编辑器与初始化阶段使用,但在真正的高频逻辑中,调用方通常只关心某个单独字段,因此为中频。典型使用场景:兼容层需要同时读取图块数字、id 与默认触发器。 -- `ITileStore.getTrigger(num)`:预期频率**高频**。这是 `getData(num).trigger` 的快捷接口,后续地图初始化、事件绑定与触发器相关逻辑都会优先使用这一接口,故为高频。典型使用场景:根据某个图块数字读取其默认触发器类型,再决定是否写入地图触发器稀疏表。 -- `ITileStore.getType(num)`:预期频率**高频**。图块类型分类会直接影响地图逻辑、兼容层判断、后续的地图对象设计,因此它和 `getTrigger` 一样属于高频读取接口。典型使用场景:逻辑层拿到某个图块数字后,需要快速判断它属于地形、道具、怪物还是 NPC。 -- `ITileStore.addTile(data)`:预期频率**低频**。图块定义在正常运行期不会动态修改,`addTile` 主要用于初始化与旧数据导入阶段,故为低频。 -- `ITileStore.idToNumber(id)`:预期频率**中频**。它是 `idNumberMap` 的方法化替代,兼容层、脚本层和部分初始化逻辑都需要从字符串 id 反查图块数字,故为中频。典型使用场景:旧接口 `setBlock('yellowDoor', x, y)` 需要先把 id 转成图块数字。 -- `ITileStore.numberToId(num)`:预期频率**中频**。它是 `numberIdMap` 的方法化替代,主要用于调试、兼容层与少量需要回推图块 id 的场景,故为中频。典型使用场景:拿到地图上的图块数字后,希望恢复出旧引擎语义下的图块 id。 -- `ITileStore.attachLegacyConverter(converter)`:预期频率**低频**。仅在初始化或切换兼容转换器时调用,负责把旧引擎图块定义的解释规则注入到 store 中,故为低频。 -- `ITileStore.fromLegacy(num, legacy)`:预期频率**低频**。用于将单个旧样板图块定义转换并写入 store,整体使用方式应与 `IEnemyManager.fromLegacyEnemy` 类似,由外层自行遍历 legacy store 后逐个调用,故为低频。典型使用场景:初始化时遍历 `core.maps.blocksInfo`,对每一项执行 `tileStore.fromLegacy(num, block)`。 - -当前建议接口如下: - -```ts -export interface ITileStore { - getData(num: number): ITileRawData | null; - getTrigger(num: number): number; - getType(num: number): TileType; - addTile(data: ITileRawData): void; - idToNumber(id: string): number | null; - numberToId(num: number): string | null; - attachLegacyConverter( - converter: ITileLegacyConverter - ): void; - fromLegacy(num: number, legacy: TLegacy): ITileRawData; -} -``` - -返回值语义建议如下: - -1. `getData(num)`:若图块不存在,返回 `null` -2. `getTrigger(num)`:若图块不存在或未配置触发器,返回 `-1` -3. `getType(num)`:若图块不存在或尚未归类,返回 `TileType.Unknown` -4. `idToNumber(id)` / `numberToId(num)`:不存在时返回 `null` - -之所以让 `getTrigger` 和 `getType` 在缺失场景下返回稳定默认值,是因为这两者更偏“高频逻辑查询”,热路径上不适合层层判空。 - -## ITileLegacyConverter / attachLegacyConverter / fromLegacy - -`ITileStore` 本身不应直接理解旧引擎里 `blocksInfo` 的全部细节,而应通过用户层提供的 legacy converter 完成转换。这样做的原因是:旧引擎中的默认触发器来源并不统一,既有显式 `trigger` 字段,也有通过 `cls` 或其他成员隐式决定的情况。 - -从 [public/project/maps.js](public/project/maps.js) 当前样板可以直接看到两类典型情况: - -1. 部分图块显式写了 `trigger`,例如黄门的 `openDoor`、箱子的 `pushBox`、冰面或滑板的特殊触发; -2. 部分图块没有显式 `trigger`,但其行为仍然会根据 `cls` 或其他规则隐式确定,例如怪物图块。 - -因此更合适的设计是: - -```ts -export interface ITileLegacyConverter { - fromLegacy(num: number, legacy: TLegacy): ITileRawData; -} -``` - -其中: - -1. `attachLegacyConverter(converter)` 负责向 store 注入转换器; -2. `fromLegacy(num, legacy)` 负责调用当前转换器完成单个 legacy 图块定义的转换,并将结果写入 store; -3. 导入整个旧引擎 `blocksInfo` 时,由外层自行遍历并多次调用 `fromLegacy`。 - -当前更推荐的使用形式是: - -```ts -tileStore.attachLegacyConverter(converter); - -for (const [key, value] of Object.entries(core.maps.blocksInfo)) { - tileStore.fromLegacy(Number(key), value); -} -``` - -其职责包括: - -1. 用户层定义 legacy -> `ITileRawData` 的转换规则; -2. 显式处理“trigger 字段优先”与“按 cls 推导”并存的旧设计; -3. 保证 `ITileStore` 本体只负责存储与查询,不负责耦合旧样板细节。 - ---- - -# 实现思路 - -## 1. 先建立独立的 store 模块 - -因为 `ITileStore` 属于 Layer 0,且不依赖地图、怪物、勇士等更高层模块,所以更适合作为 `@user/data-base/src/store` 下的首个 Store 类接口存在。 - -当前不建议单独再开 `tile` 文件夹,而是直接放到 [packages-user/data-base/src/store](packages-user/data-base/src/store) 中。这样后续若还有其他内容迁移为新的 Store 类接口,也可以继续并列放在 `store` 目录下,而不是再拆出多个平行顶层目录。 - -## 2. 对外暴露方法,对内仍可继续使用双映射 - -`idNumberMap` 与 `numberIdMap` 迁移到 `ITileStore` 后,对外不再暴露原始 `Map`,而改成方法: - -1. `idToNumber(id)` -2. `numberToId(num)` - -但在内部实现上,仍然完全可以保留: - -1. `Map` -2. `Map` -3. `Map` - -也就是说,这次重构的重点是**收拢职责和稳定接口**,不是刻意放弃现有映射结构。 - -## 3. getType 直接从原始数据读取 - -按照当前结论,`ITileRawData` 包含: - -1. `num` -2. `id` -3. `trigger` -4. `type` - -这意味着 `getType(num)` 不需要再依赖额外并行映射,而可以直接读取 `getData(num)?.type`。这样做的好处是: - -1. `addTile` 的输入结构完整且闭合; -2. 不会再出现“raw data 一套、type 映射又一套”的双数据源; -3. legacy converter 转换出的结果可以直接完整写入 store。 - -## 4. addTile 只负责录入定义,不负责存档 - -`addTile(data)` 的职责应当收敛到“向 store 录入一个图块定义”,而不是承担任何运行时逻辑。 - -因为这部分数据不会动态变更,也不参与存档,所以其主要使用时机只有: - -1. 初次初始化 -2. 旧引擎数据迁移 -3. 极少量的测试或工具链注入 - -当前你已经给出了 number 冲突时“警告并覆盖”的语义,这一点应该直接保留。 - -此外,`id` 冲突但 `num` 不冲突时,也应当采用同样的警告并覆盖策略;并且警告内容需要明确指出冲突来源到底是 `num` 还是 `id`。 - -## 5. 全局状态从两张 Map 改为一个 store - -当前 [packages-user/data-base/src/types.ts](packages-user/data-base/src/types.ts) 里的 `IStateBase` 仍直接暴露: - -1. `idNumberMap` -2. `numberIdMap` - -如果 `ITileStore` 建立起来,那么全局状态更合理的暴露方式应当改为: - -```ts -readonly tileStore: ITileStore; -``` - -后续所有调用点统一改成: - -1. `state.tileStore.idToNumber(id)` -2. `state.tileStore.numberToId(num)` -3. `state.tileStore.getTrigger(num)` -4. `state.tileStore.getType(num)` - -这样图块定义相关职责就不会再散落在全局状态根节点上。 - -## 6. 旧引擎迁移的职责边界 - -当前 [packages-user/data-state/src/index.ts](packages-user/data-state/src/index.ts) 在初始化阶段直接遍历 `core.maps.blocksInfo`,并手动填充 `state.idNumberMap` / `state.numberIdMap`。 - -引入 `ITileStore` 后,这段初始化逻辑更适合拆成两段: - -1. 先在用户层实现并挂载 legacy converter; -2. 再遍历 `core.maps.blocksInfo`,逐个调用 `tileStore.fromLegacy(num, block)`; -3. 其他真正依赖图块定义的初始化逻辑,例如朝向绑定,再从 `tileStore` 继续读取数据。 - -这样旧引擎兼容逻辑就不会和全局状态初始化逻辑搅在一起。 - -同时,因为旧样板里的触发器来源并不统一,这种“用户层 converter + store 只负责存储”的边界也更合理: - -1. store 不需要知道 `trigger` 究竟来自字段、`cls`,还是更特殊的 legacy 规则; -2. 用户层可以按当前项目的具体兼容策略自由决定优先级; -3. 将来若 legacy 来源变化,只需要替换 converter,不必改 `ITileStore` 本体。 - ---- - -# 涉及文件 - -## 需要引用的文件 - -- [packages-user/data-base/src/types.ts](packages-user/data-base/src/types.ts):当前 `IStateBase` 仍直接暴露 `idNumberMap` 与 `numberIdMap` -- [packages-user/data-state/src/index.ts](packages-user/data-state/src/index.ts):当前旧引擎图块定义导入逻辑的主要入口 -- [packages-user/data-state/src/core.ts](packages-user/data-state/src/core.ts):当前 `CoreState` 中两张映射的实际持有位置 -- [packages-user/client-modules/src/fallback/load.ts](packages-user/client-modules/src/fallback/load.ts):当前旧引擎图块 `cls` 分类的实际使用点,可作为 `TileType` 映射参考 -- [docs/dev/map/tile-info.md](docs/dev/map/tile-info.md):后续地图触发器设计将直接依赖 `ITileStore` - -## 需要修改的文件 - -### `@user/data-base/src/store/types.ts` - -- [ ] 新增 `ITileRawData` 接口:定义图块最小原始定义,当前包含 `num`、`id`、`trigger`、`type` -- [ ] 新增 `TileType` 枚举:定义统一的图块逻辑分类 -- [ ] 新增 `ITileLegacyConverter` 接口:定义 legacy 图块定义到 `ITileRawData` 的转换规则 -- [ ] 新增 `ITileStore` 接口:提供图块定义的统一查询与写入入口 -- [ ] 为 `ITileStore` 新增 `attachLegacyConverter` 与 `fromLegacy` - -### `@user/data-base/src/store/tileStore.ts` - -- [ ] 实现 `ITileStore` -- [ ] 内部维护 `num -> raw data`、`id -> num` 与 `num -> id` 的映射 -- [ ] 实现 `addTile` 的警告覆盖逻辑,并区分 `num` 冲突与 `id` 冲突 -- [ ] 实现 `attachLegacyConverter` 与 `fromLegacy` - -### `@user/data-base/src/store/index.ts` - -- [ ] 导出 tile 模块公共接口与实现 - -### `@user/data-base/src/types.ts` - -- [ ] 从 `IStateBase` 中移除 `idNumberMap` 与 `numberIdMap` -- [ ] 新增 `readonly tileStore: ITileStore` - -### `@user/data-base/src/index.ts` - -- [ ] 补齐 tile 模块的公共导出 - -### `@user/data-state/src/core.ts` - -- [ ] 移除 `CoreState` 对两张映射的直接持有 -- [ ] 改为持有 `tileStore` - -### `@user/data-state/src/index.ts` - -- [ ] 将旧引擎 `blocksInfo` 的初始化逻辑迁移到“挂载 converter 后逐个调用 `fromLegacy`” -- [ ] 后续朝向绑定等逻辑改为通过 `tileStore.idToNumber` 读取图块数字 - -### `docs/dev/map/tile-info.md` - -- [ ] 在 `ITileStore` 定稿后,再继续补齐地图触发器设计文档中对图块默认触发器来源的描述 - ---- - -# 当前结论 - -1. `TileType` 应当直接包含进 `ITileRawData`,不再单独拆成并行数据源。 -2. legacy 导入不再使用顶层工厂函数,而改为 `attachLegacyConverter + fromLegacy` 组合;由用户层自行提供 converter,再逐个执行转换。 -3. `addTile` 在 `num` 冲突与 `id` 冲突两种场景下都应警告并覆盖,且警告信息必须明确指出冲突来源。 -4. `getTrigger(num)` 在图块不存在或无触发器时统一返回 `-1`。 -5. 旧引擎里的默认触发器来源是混合式的:有时来自显式 `trigger` 字段,有时来自 `cls` 或其他规则;这一差异应当由用户层 converter 消化,而不是让 `ITileStore` 本体直接耦合旧样板细节。 diff --git a/docs/dev/system/save-system.md b/docs/dev/system/save-system.md deleted file mode 100644 index 21e9e13..0000000 --- a/docs/dev/system/save-system.md +++ /dev/null @@ -1,215 +0,0 @@ -# 需求综述 - -实现游戏引擎存档系统(`SaveSystem`)及全局事务(`GlobalTransaction`)两个类的完整逻辑。 -存档系统分为两部分: - -- **存档内容**:按 slot id 保存/读取游戏当前状态(`Map>`), - 使用 Dexie 将数据存入 IndexedDB。 -- **全局存储**:跨存档的 key-value 存储,用于存放存档 meta data、全局设置等。 - 支持事务处理,事务中的写入操作在发生错误时全部回滚。 - -此外,系统提供基于内存的自动存档,并支持 undo/redo 操作。 -自动存档不主动写入 IndexedDB,只有显式调用 `saveAutosaveToDB` 时才将 undo -栈顶存档写入数据库。 -存档操作使用 `performance` 接口监控耗时,超过配置阈值时通过 logger 发出警告。 - ---- - -# 实现思路 - -## 1. Dexie 数据库 Schema 设计 - -`SaveSystem` 构造函数接收数据库名称 `name`,在其中创建如下两张表: - -| 表名 | 主键 | 说明 | -| -------- | --------------- | --------------------------------------------------------- | -| `saves` | `id`(number) | 按 slot id 存储存档数据;`id = -1` 固定用于持久化自动存档 | -| `global` | `key`(string) | 全局 key-value 存储 | - -- `saves` 表中每条记录结构为 `{ id: number, compression: SaveCompression, data: Map }`, - 与 `ISaveRead` 对应,直接利用 IndexedDB 的结构化克隆存储,不进行 JSON 序列化。 -- `global` 表中每条记录结构为 `{ key: string, value: unknown }`, - 同样直接存储,不进行 JSON 序列化。 - -## 2. 内部状态 - -`SaveSystem` 需要维护如下私有成员: - -- `private undoStack: ISaveRead[]`:undo 栈,存储 `ISaveRead` 快照 -- `private redoStack: ISaveRead[]`:redo 栈,存储 `ISaveRead` 快照 -- `private stackSize: number`:undo/redo 栈最大容量(默认 `20`) -- `private autosaveLevel: SaveCompression`:默认 `SaveCompression.LowCompression` -- `private commonSaveLevel: SaveCompression`:默认 `SaveCompression.HighCompressoin` -- `private saveTimeTolerance: number`:默认 `100`(ms) -- `private autosaveTimeTolerance: number`:默认 `50`(ms) - -## 3. ISaveRead 数据结构 - -栈与数据库读写均使用新接口 `ISaveRead`: - -```ts -interface ISaveRead { - readonly compression: SaveCompression; - readonly data: Map; -} -``` - -- `compression`:存档时使用的压缩等级,读档时传回给 `loadState`,使接收方能够正确解压。 -- `data`:key 到每个可存档对象序列化数据的 Map,key 与 `ISaveableContent` 注册时的 id 对应。 - -内存栈和数据库均直接存储 `ISaveRead`,不需要引入辅助包装层。 -存档系统本身不负责将数据写回游戏对象,调用方拿到 `ISaveRead` 后自行遍历并调用 -各可存档对象的 `loadState(data, compression)` 完成状态恢复。 - -## 4. 各方法实现 - -### `config(config)` - -将 config 各字段写入对应私有成员,使用传入的值覆盖默认值。 - -### `setAutosaveStackSize(size)` - -将 `stackSize` 更新为 `size`。如果当前 undo 栈超过新的 `size`, -从栈底移除多余条目(保留最新的);redo 栈同理。 - -### `autosave(state)` - -1. 遍历 `state`,对每个 `(key, content)` 调用 - `content.saveState(this.autosaveLevel)` 获取序列化数据, - 汇总为 `Map`,构建 `ISaveRead { compression: autosaveLevel, data }` 并压入 `undoStack`; -2. **清空 `redoStack`**(执行新的自动存档后无法再 redo); -3. 若 `undoStack.length > stackSize`,从栈底(`[0]`)移除多余条目。 - -> IndexedDB 支持结构化克隆,Map、Set、TypedArray 等均可直接存储,无需 JSON 序列化。 - -### `undoAutosave(current)` - -1. 若 `undoStack` 为空,返回 `null`; -2. 将 `current` 序列化为 `ISaveRead { compression: autosaveLevel, data: Map }`, - 压入 `redoStack`;检查 `redoStack.length > stackSize`,超长时从栈底移除多余条目; -3. 弹出 `undoStack` 栈顶(`pop()`),返回弹出的 `ISaveRead`; -4. 调用方拿到返回的 `ISaveRead` 后,自行遍历并对各游戏对象调用 `loadState` 完成恢复。 - -### `redoAutosave(current)` - -与 `undoAutosave` 逻辑对称:将 `current` 序列化压入 `undoStack`, -弹出 `redoStack` 栈顶并返回,调用方自行恢复状态。 - -### `getUndoStack()` / `getRedoStack()` - -使用 `slice()` 返回栈数组的浅拷贝快照,防止外部意外修改栈结构。 - -### `saveAutosaveToDB()` - -1. 若 `undoStack` 为空,直接返回(无需写入); -2. 记录 `t0 = performance.now()`; -3. 取 `undoStack` 栈顶(`ISaveRead`),将其连同 `id = -1` 一起写入 `saves` 表; -4. 记录 `t1 = performance.now()`;若 `t1 - t0 > autosaveTimeTolerance`, - 调用 `logger.warn(115, (t1 - t0).toFixed(0), this.autosaveTimeTolerance.toString())`。 - -### `save(id, state)` - -1. 记录 `t0 = performance.now()`; -2. 遍历 `state`,对每个 `(key, content)` 调用 - `content.saveState(this.commonSaveLevel)` 汇总为 `Map`, - 构建 `{ id, compression: commonSaveLevel, data }` 写入 `saves` 表; -3. 将 `id` 写入全局存储 `'lastSlot'` 键(用于 `getLastSlot()`); -4. 记录 `t1 = performance.now()`;若 `t1 - t0 > saveTimeTolerance`, - 调用 `logger.warn(114, (t1 - t0).toFixed(0), this.saveTimeTolerance.toString())`。 - -### `load(id)` - -1. 从 Dexie `saves` 表查询 `id`; -2. 若不存在返回 `null`; -3. 将读取到的记录中的 `compression` 和 `data` 字段组装成 `ISaveRead` 返回。 - 调用方自行遍历 `data` 并对各游戏对象调用 `loadState` 完成恢复。 - -> `load(-1)` 可用于读取持久化的自动存档。 - -### `deleteSave(id)` - -直接从 Dexie `saves` 表删除对应记录。 - -### `getLastSlot()` - -从全局存储读取 `'lastSlot'` 键对应的值并返回;若不存在则返回 `0`。 - -### `getGlobal(key)` / `setGlobal(key, value)` - -- `getGlobal`:从 Dexie `global` 表读取 `key` 对应的 `value` 字段并返回,类型断言为 `T`; -- `setGlobal`:将 `{ key, value }` 直接写入 Dexie `global` 表,无需 JSON 序列化。 - -### `startGlobalTransaction(handle)` - -使用 `Dexie.transaction('rw', this.db.table('global'), ...)` 包裹 `handle` 调用, -传入 `GlobalTransaction` 实例,出错时自动回滚。 - -### `GlobalTransaction.get(key)` / `GlobalTransaction.set(key, value)` - -在事务上下文中直接读写 `table`(即全局 `global` 表的引用),无需 JSON 序列化。 - -## 5. logger.json 新增 warn 代码 - -当前最大 warn 代码为 `113`,新增如下两条(写入 `packages/common/src/logger.json` -的 `warn` 对象,置于 `113` 之后): - -| 代码 | 消息 | -| ----- | ----------------------------------------------------------------------------------------------------- | -| `114` | `Save operation took $1ms, exceeding the tolerance of $2ms. Consider reducing compression level.` | -| `115` | `Autosave operation took $1ms, exceeding the tolerance of $2ms. Consider reducing compression level.` | - ---- - -# 涉及文件 - -## 需要引用的文件 - -- `dexie`:Dexie / Table 类型,用于创建和操作 IndexedDB 数据库 -- `@motajs/common`:`logger`,用于输出存档耗时超限警告 -- `@user/data-base`:`ISaveableContent`、`SaveCompression`,存档接口与压缩枚举 -- `./types`:`ISaveRead`、`IGlobalTrasaction`、`ISaveSystem`、`ISaveSystemConfig` - -## 需要修改的文件 - -### `packages/common/src/logger.json` - -- [x] 在 `warn` 对象中新增代码 `114`:普通存档耗时超限警告 -- [x] 在 `warn` 对象中新增代码 `115`:自动存档耗时超限警告 - -### `packages-user/data-state/src/save/system.ts` - -- [x] 新增 `private undoStack: ISaveRead[]` 成员:存储 undo 历史快照 -- [x] 新增 `private redoStack: ISaveRead[]` 成员:存储 redo 历史快照 -- [x] 新增 `private stackSize: number` 成员:undo/redo 栈容量上限,默认 `20` -- [x] 新增 `private autosaveLevel: SaveCompression` 成员: - 默认 `SaveCompression.LowCompression` -- [x] 新增 `private commonSaveLevel: SaveCompression` 成员: - 默认 `SaveCompression.HighCompressoin` -- [x] 新增 `private saveTimeTolerance: number` 成员:普通存档耗时阈值,默认 `100` -- [x] 新增 `private autosaveTimeTolerance: number` 成员:自动存档耗时阈值,默认 `50` -- [x] 编写构造函数:初始化 Dexie 实例,定义 `saves`(主键 `id`)和 - `global`(主键 `key`)两张表的 schema -- [x] 编写 `config` 方法:将配置项写入私有成员 -- [x] 编写 `setAutosaveStackSize` 方法:更新 stackSize,修剪超长的 undo/redo 栈 -- [x] 编写 `autosave` 方法:遍历 state 序列化为 `ISaveRead` 压入 undoStack, - 清空 redoStack,超长时修剪栈底 -- [x] 编写 `undoAutosave` 方法:将 current 序列化为 `ISaveRead` 压入 redoStack, - 弹出 undoStack 栈顶返回 `ISaveRead`(或 null) -- [x] 编写 `redoAutosave` 方法:与 undoAutosave 对称 -- [x] 编写 `getUndoStack` / `getRedoStack` 方法:使用 `slice()` 返回栈的浅拷贝快照 -- [x] 编写 `saveAutosaveToDB` 方法:取 undoStack 栈顶以 `id = -1` 写入 `saves` 表, - performance 监控,超限时调用 `logger.warn(115, ...)` -- [x] 编写 `save` 方法:遍历 state 序列化为 `ISaveRead` 写入 `saves` 表, - 更新 `lastSlot`,performance 监控,超限时调用 `logger.warn(114, ...)` -- [x] 编写 `load` 方法:从 Dexie `saves` 表读取记录组装为 `ISaveRead` 返回 - (不存在返回 null);`load(-1)` 可读取持久化的自动存档 -- [x] 编写 `deleteSave` 方法:从 Dexie `saves` 表删除指定记录 -- [x] 编写 `getLastSlot` 方法:从全局存储读取 `'lastSlot'`,不存在时返回 `0` -- [x] 编写 `getGlobal` / `setGlobal` 方法:直接读写 Dexie `global` 表,不进行 JSON 序列化 -- [x] 编写 `startGlobalTransaction` 方法: - 使用 Dexie 事务包裹 handle,传入 GlobalTransaction 实例 - -### `packages-user/data-state/src/save/system.ts`(GlobalTransaction 部分) - -- [x] 编写 `GlobalTransaction.get` 方法:在事务上下文中直接读取 table 中 key 对应的 value -- [x] 编写 `GlobalTransaction.set` 方法:在事务上下文中直接写入 key-value,不进行 JSON 序列化 diff --git a/docs/dev/system/trigger-refactor.md b/docs/dev/system/trigger-refactor.md deleted file mode 100644 index 759f99b..0000000 --- a/docs/dev/system/trigger-refactor.md +++ /dev/null @@ -1,259 +0,0 @@ -# 需求综述 - -当前触发器系统中,进入图块与撞击图块被合并为同一个触发器,通过 `eventPass` 标志位区分。这种合并设计导致两个触发条件无法拥有各自独立的触发器,同时也缺失了离开图块与无法进入图块的触发器支持。本次重构将四类触发条件分离,使触发器系统各司其职。 - -# 需求理解 - -## 明确需求 - -1. 四类触发条件分离。进入图块(`onEnter`)、离开图块(`onLeave`)、撞击图块(`onHit`)、无法移动至图块(`onCannotEnter`)必须各自独立,图块可为每种条件分别指定触发器类型。 -2. 撞击由 `eventPass` 决定。当 `eventPass` 为 `false` 时走撞击逻辑,应触发 `onHit`,而非复用进入图块的触发器。 -3. 无法移动由 `inPass` / `outPass` 决定。当地形通行性检查失败时走无法移动逻辑,应触发 `onCannotEnter`。 -4. 撞击与无法移动互斥。一次移动中只会发生其中一种,前者由 `eventPass` 控制,后者由 `inPass` / `outPass` 控制,二者不会同时触发。 -5. `onLeave` 的触发条件。仅在未发生撞击且未发生无法移动时(即移动成功,`HeroMoveCode.Step`),英雄将要离开当前图块时触发。 -6. `onEnter` 的触发时机。英雄到达目标图块且位置数据已切换后触发,通过 `ObjectMover` 的 `onStepEnd` 钩子实现。 -7. 触发器不区分触发条件。一个触发器同时包含四个触发方法(`onEnter`、`onLeave`、`onHit`、`onCannotEnter`),而非为四种条件分别注册四个触发器。触发器注册表仍按数字类型注册。 - -## 隐含需求 - -1. 同一个触发器类型可被不同条件复用。由于一个触发器实例同时包含四个方法,同一种触发器类型既可被指定为进入触发器也可被指定为撞击触发器——取决于调用方调用哪个方法。 -2. 离开图块的触发作用于出发图块。英雄从 A 移动到 B,`onLeave` 作用于 A 的图块,需要使用 `currLoc` 作为收集坐标。 -3. 图块可同时拥有多种条件的触发器。图块属性可能在游戏中变化,点级覆盖可与底层图块不同,因此同一坐标可为不同条件指定不同的触发器类型。 -4. 动态图块与静态图块拥有一致的触发器能力,`IDynamicTile` 也需要支持按条件读写触发器类型。 - -# 设计前提 - -1. 触发条件由游戏逻辑判定,不由触发器系统决定。`eventPass`、`inPass` / `outPass` 属于通行性判定范畴,判定结果决定走哪条触发路径。触发器系统只负责在给定条件后收集并执行对应触发器。 -2. 触发器类型仍以数字标识。`ITriggerRegistry` 基于数字管理触发器工厂创建,不感知触发条件。注册一个触发器类型后,该类型即拥有四个条件方法。 -3. 收集器不判定条件。`ITriggerCollector` 仅根据调用者给定的条件类型,从指定图块中收集该条件对应的触发器。条件选择由调用者(英雄移动系统)负责。 -4. 收集必定执行,不做预检查。触发器数量极少,收集一个空集合而后自然跳过的开销可忽略不计。不提供 `hasTrigger` 等预检查方法。 - -# 核心概念定义 - -## 触发条件 - -一种由英雄移动行为引发的交互类型,决定应当调用图块触发器上的哪个方法。触发条件共有四种,分别对应四类英雄与图块的交互方式。一次移动中只会产生一种触发条件(Step → enter/leave、Hit → hit、CannotMove → cannotEnter),因此四种条件不是并行触发而是排他的。 - -## 进入触发 - -当英雄成功移动到某个图块上且位置数据已切换后执行。判定条件为 `canPass` 为 `true` 且 `shouldHit` 为 `false`,此时英雄成功进入图块,应调用目标图块对应触发器实例上的 `onEnter` 方法。由于触发时机在位置切换之后,使用 `ObjectMover` 的 `onStepEnd` 钩子——在该钩子中 `tile` 的坐标已更新为新位置。 - -## 离开触发 - -当英雄将要离开当前图块,且该步移动属于成功移动(未发生撞击、未发生无法移动)时执行。离开触发作用于出发图块,使用英雄当前位置坐标(`currLoc`)收集触发器,应调用对应触发器实例上的 `onLeave` 方法。由于触发时机是"将要离开",在 `HeroMover.onStepEnd` 方法中、`setPos` 执行前调用 `leave`,此时英雄位置尚未变更。 - -## 撞击触发 - -当英雄尝试进入某个图块但被 `eventPass` 阻挡时执行。判定由 `eventPass` 完成:当 `eventPass` 为 `false` 时,英雄撞击目标图块并停止移动,应调用目标图块对应触发器实例上的 `onHit` 方法。 - -## 无法进入触发 - -当英雄尝试进入某个图块但被地形通行性(`inPass` / `outPass`)阻挡时执行。判定由 `canPass` 完成:当 `canPass` 为 `false` 时,英雄无法进入目标图块,应调用目标图块对应触发器实例上的 `onCannotEnter` 方法。在移动判定中 `canPass` 检查先于 `shouldHit` 检查,因此无法进入触发与撞击触发互斥。 - -# 接口设计分析 - -核心改动分为三个层面:触发器实例从单一方法变为四个方法,收集器增加条件参数以读取正确的触发器类型,集合执行时根据条件分发到对应方法。 - -## ITrigger - -### 设计思路 - -当前 `ITrigger` 仅有一个 `trigger(handler)` 方法,所有触发条件共用这一个人口。由于四种触发条件需要各自独立的行为,触发器实例需要为每种条件提供独立的方法入口。 - -因此将 `trigger` 方法替换为四个条件方法:`onEnter`、`onLeave`、`onHit`、`onCannotEnter`。这四个方法各自接收 `ITriggerHandler` 上下文对象,独立实现对应逻辑。这样同一个触发器类型被指定为进入触发时调用 `onEnter`,被指定为撞击触发时调用 `onHit`,互不干扰。 - -由于触发器注册表仍按数字类型注册,同一个触发器工厂产出的实例自动具备四种方法。不需要为四种条件注册四个不同触发器——条件的选择由调用方在运行时决定调用哪个方法。 - -### 接口分析 - -- 方法 `onEnter(handler: ITriggerHandler)`:预期频率**高频**。绝大多数的移动目的地都是可以进入的图块,每次进入都会调用。 -- 方法 `onLeave(handler: ITriggerHandler)`:预期频率**高频**。与 `onEnter` 对称,每次成功移动都会离开出发图块。 -- 方法 `onHit(handler: ITriggerHandler)`:预期频率**中频**。仅 `eventPass` 为 `false` 的图块会产生撞击,不如进入频繁。 -- 方法 `onCannotEnter(handler: ITriggerHandler)`:预期频率**低频**。仅地形阻挡时触发,场景相对最少。 - -### 预期体量 - -`ITrigger` 接口改动预期 5 行:四个方法替换原有的一个 `trigger` 方法。 - ---- - -## ITriggerCollection - -### 设计思路 - -当前 `ITriggerCollection.trigger(handler)` 遍历所有触发器调用各自的 `trigger` 方法。由于 `ITrigger` 不再有 `trigger` 方法而变为四个条件方法,集合在执行时需要知道应当调用哪个方法。因此 `trigger` 方法需要增加 `condition` 参数,在遍历时根据条件分发给每个触发器的对应方法。 - -`triggerIter` 同理,也需要增加 `condition` 参数,以便在迭代分步执行时知道分发到哪个方法。 - -由于集合中存储的仍然是 `ITrigger` 实例,同一个集合可以被重复用于不同条件(虽然实际场景中不会这样使用)。集合不记录自身是"为哪个条件创建的",条件信息仅在触发执行时传入。 - -### 接口分析 - -- 方法 `trigger(condition: TriggerCondition, handler: ITriggerHandler)`:预期频率**高频**。每次触发执行都要通过此方法,是集合的核心入口。修改仅增加 `condition` 参数,调用方式变化不大。 -- 方法 `triggerIter(condition: TriggerCondition, handler: ITriggerHandler)`:预期频率**中频**。用于需要逐步控制触发流程的高级场景,使用频率低于普通 `trigger`。 - -### 预期体量 - -预期改动 10 行。`trigger` 方法中的遍历循环,将 `trigger.trigger(handler)` 替换为对 `condition` 的 `switch` 分发(或等价的条件方法调用)。逻辑简单,仅增加分发层。 - ---- - -## ITriggerCollector - -### 设计思路 - -当前 `ITriggerCollector.collect(x, y, layer)` 不区分触发条件,统一读取图块上的单一触发器类型。由于四种条件可能有各自不同的触发器类型,收集器必须知道当前条件,才能从图块数据中读取该条件对应的触发器类型。 - -因此 `collect` 方法新增 `condition` 参数,收集器根据条件调用图块上对应的方法(如 `layer.getEnterTrigger(x, y)`)获取触发器类型,经由注册表创建 `ITrigger` 实例。收集到的触发器实例本身不绑定条件——条件信息仅用于"读取哪个触发器类型",不传递给集合或触发器。 - -### 接口分析 - -- 方法 `collect(condition: TriggerCondition, x: number, y: number, layer: IMapLayer)`:预期频率**高频**。英雄每移动一格都要至少调用一次,是触发系统的核心入口。新增参数不影响复杂度。 - -### 预期体量 - -预期代码体量为 90-120 行。分析如下: - -- 静态与动态图块的收集逻辑预期 90 行:保留当前 `collect` 方法的优先级逻辑(单动态优先值比较、多动态排序去重),差异在于读取触发器类型时需根据 `condition` 分发到不同字段,每个分支代码量较小但分支较多。 - ---- - -## IMapLayer / IDynamicTile - -### 设计思路 - -当前 `IMapLayer` 有 `getTriggerType(x, y)` 和 `setTriggerType(type, x, y)` 两个方法,`IDynamicTile` 有 `triggerType` 字段和 `setTriggerType` 方法。由于四种触发条件各自独立,这些单一入口需要扩展为按条件读写。 - -对于 `IMapLayer`,需要四个 `getXxxTrigger(x, y)` 和四个 `setXxxTrigger(type, x, y)` 方法(`xxx` 为 Enter / Leave / Hit / CannotEnter)。读取时先查点级覆盖映射,若未设置则回退到 `ITileStore` 的对应条件默认值。 - -对于 `IDynamicTile`,需要为每种条件提供字段及 setter。 - -`ITileRawData` 的设计不在此文档范围内,由项目负责人单独处理。 - -### 接口分析 - -- 方法 `IMapLayer.getEnterTrigger(x, y)` 等四个:预期频率**高频**。收集器每次收集都需调用其中之一,与进入触发等条件频率一致。 -- 方法 `IMapLayer.setEnterTrigger(type, x, y)` 等四个:预期频率**中频**。运行时修改触发器类型有一定场景(事件修改图块行为等),但远低于读取频率。 -- 字段 `IDynamicTile.enterTrigger` 等四个字段:预期频率**高频**。收集动态图块触发器时需要读取对应条件字段。 -- 方法 `IDynamicTile.setEnterTrigger(type)` 等四个 setter:预期频率**中频**。同 `IMapLayer` 的 setter 频率。 - -### 预期体量 - -不在此文档范围内展开——`ITileRawData` 的内部实现由项目负责人处理。接口层面的方法签名改动预期约 20 行(四个 getter + 四个 setter 的方法声明)。 - ---- - -## IHeroMoveTopImpl - -### 设计思路 - -当前 `IHeroMoveTopImpl` 仅有 `hit` 方法处理撞击触发,进入触发和无法进入触发均未覆盖。四种条件分离后,需要补齐缺失的触发入口:`enter`、`leave`、`cannotEnter`。 - -`onLeave` 的触发目标是出发图块,使用 `handler.currLoc` 坐标收集,触发时机是离开前(在 `onStepEnd` 方法中调用)。`onEnter` 的触发目标是目标图块,使用 `handler.nextLoc` 坐标收集,触发时机是位置切换后,通过 `ObjectMover` 的 `onStepEnd` 钩子实现——钩子中的 `tile` 坐标已更新为新位置。`onHit` 和 `onCannotEnter` 的触发目标都是目标图块,使用 `handler.nextLoc` 坐标收集。 - -在英雄移动流程中各条件的分发: -- `Step`:`onStepEnd` 方法中调用 `leave(handler)` 触发离开,`onStepEnd` 钩子中调用 `enter(handler)` 触发进入。 -- `Hit`:`onStepEnd` 方法中调用 `hit(handler)` 触发撞击(原逻辑,内部改为收集时指定 `Hit` 条件)。 -- `CannotMove`:`onStepEnd` 方法中调用 `cannotEnter(handler)` 触发无法进入。 - -### 接口分析 - -- 方法 `enter(handler: IHeroMoveTopHandler)`:预期频率**高频**。绝大多数移动成功后会调用。 -- 方法 `leave(handler: IHeroMoveTopHandler)`:预期频率**高频**。与 `enter` 对称,每次成功移动前调用。 -- 方法 `cannotEnter(handler: IHeroMoveTopHandler)`:预期频率**低频**。仅地形阻挡时触发。 -- 方法 `hit(handler: IHeroMoveTopHandler)`:现有方法,内部逻辑修改为收集时指定 `TriggerCondition.Hit`。预期频率不变。 - -### 预期体量 - -预期代码体量为 50 行。分析如下: - -- `enter` 方法预期 12 行:与当前 `hit` 类似——获取 `floorId`、获取 `ILayerState`、取 `eventLayer`、收集指定条件的触发器、执行。 -- `leave` 方法预期 12 行:与 `enter` 对称,差异在于收集时坐标使用 `currLoc`。 -- `cannotEnter` 方法预期 12 行:与 `hit` 对称,条件替换为 `CannotEnter`。 -- `hit` 方法修改预期 14 行:在现有骨架基础上透传条件参数。 - ---- - -# 实现思路 - -## ITrigger 方法拆分 - -1. 移除 `ITrigger.trigger(handler)` 方法,新增 `onEnter`、`onLeave`、`onHit`、`onCannotEnter` 四个方法,签名均为 `(handler: ITriggerHandler): Promise`。 -2. `BaseTrigger` 抽象类中四个方法均标记为 `abstract`,子类必须实现。 -3. `BaseTrigger.collection()` 方法不变。 - -## 集合执行分发 - -1. `ITriggerCollection.trigger` 和 `triggerIter` 增加 `condition: TriggerCondition` 参数。 -2. 遍历触发器的循环中,根据 `condition` 调用对应方法,替代原来的 `trigger.trigger(handler)`。 - -## 收集器改造 - -1. `collect` 方法增加 `condition` 参数。 -2. 静态触发器读取根据 `condition` 选择调用 `layer.getEnterTrigger` 等对应方法。 -3. 动态触发器读取根据 `condition` 选择读取 `tile.enterTrigger` 等对应字段。 -4. 优先级排序逻辑不变。 - -## 英雄移动系统适配 - -1. `IHeroMoveTopImpl` 新增 `enter`、`leave`、`cannotEnter` 三个方法,直接调用收集器收集对应条件的触发器并执行。 -2. `HeroMover.onStepEnd` 中 `Step` 分支:调用 `leave(handler)` 触发离开后再返回 `nextLoc`。 -3. `HeroMover` 注册 `onStepEnd` 钩子:当 `code === HeroMoveCode.Step` 时,调用 `enter(handler)`。此时钩子中的 `tile` 坐标已通过 `setPos` 更新为新位置,可直接从 `tile` 获取当前坐标构造 handler。 -4. `HeroMover.onStepEnd` 中 `CannotMove` 分支:调用 `cannotEnter(handler)`。 -5. `HeroMoveCode.Hit` 注释更新为"不能移动,并触发目标格撞击触发器"。 - -# 涉及文件 - -## `@user/data-system/trigger/types.ts` - -- [ ] 新增 `TriggerCondition` 枚举:定义 `Enter`、`Leave`、`Hit`、`CannotEnter` 四个成员。 -- [ ] 修改 `ITrigger` 接口:移除 `trigger` 方法,新增 `onEnter`、`onLeave`、`onHit`、`onCannotEnter` 四个方法。 -- [ ] 修改 `ITriggerCollection.trigger` 和 `triggerIter` 方法签名:新增 `condition: TriggerCondition` 参数。 -- [ ] 修改 `ITriggerCollector.collect` 方法签名:新增 `condition: TriggerCondition` 参数。 - -## `@user/data-system/trigger/trigger.ts` - -- [ ] 修改 `BaseTrigger` 抽象类:四个条件方法标记为 `abstract`,移除 `trigger`。 - -## `@user/data-system/trigger/collection.ts` - -- [ ] 修改 `TriggerCollection.trigger`:遍历中根据 `condition` 分发给对应条件方法。 -- [ ] 修改 `TriggerCollection.triggerIter`:同上。 - -## `@user/data-system/trigger/collector.ts` - -- [ ] 修改 `TriggerCollector.collect`:根据 `condition` 读取图块对应字段。 - -## `@user/data-base/map/types.ts` - -- [ ] 修改 `IMapLayer` 接口:新增四个 `getXxxTrigger` 和四个 `setXxxTrigger` 方法,替换原有的 `getTriggerType` / `setTriggerType`。 -- [ ] 修改 `IDynamicTile` 接口:新增四个条件字段及对应的 setter,替换原有的 `triggerType` / `setTriggerType`。 - -## `@user/data-base/map/mapLayer.ts` - -- [ ] 实现新增的八个方法,内部映射管理适配。 - -## `@user/data-base/map/dynamicTile.ts` - -- [ ] 拆分 `triggerType` 为四个条件字段,实现四个 setter。 - -## `@user/data-base/map/dynamicLayer.ts` - -- [ ] 修改 `createDynamic` 和 `transferToDynamic` 中触发器同步逻辑:同步四个条件字段。 - -## `@user/data-base/hero/types.ts` - -- [ ] 新增 `IHeroMoveTopImpl.enter` 方法。 -- [ ] 新增 `IHeroMoveTopImpl.leave` 方法。 -- [ ] 新增 `IHeroMoveTopImpl.cannotEnter` 方法。 -- [ ] 更新 `HeroMoveCode` 枚举中 `Hit` 和 `CannotMove` 的注释。 - -## `@user/data-state/hero/moverImpl.ts` - -- [ ] 实现新增的 `enter`、`leave`、`cannotEnter` 方法。 -- [ ] 修改 `hit` 方法:调用收集器时传入 `TriggerCondition.Hit`。 - -## `@user/data-base/hero/mover.ts` - -- [ ] 修改 `onStepEnd`:`Step` 分支调用 `leave`;`CannotMove` 分支调用 `cannotEnter`。 -- [ ] 注册 `onStepEnd` 钩子:`Step` 时调用 `enter`,利用钩子中 `tile` 坐标已更新的时机。 diff --git a/docs/dev/template.md b/docs/dev/template.md deleted file mode 100644 index 9be2552..0000000 --- a/docs/dev/template.md +++ /dev/null @@ -1,106 +0,0 @@ -> 本示例文档以物体移动为例,代码参考 `packages-user/data-common/src/common/mover.ts`。文档较为简略,实际编写的时候可以更丰富一些。 - -# 需求综述 - -为提供更便捷的物体移动接口,故进行本设计。本设计提供统一的移动接口 `IObjectMover`,并完成其抽象实现 `abstract class ObjectMover`,以方便具体拓展。 - -# 需求理解 - -## 明确需求 - -1. 移动与物体分离。移动与物体是独立的两个系统,二者之间不应存在任何耦合,移动单方面调用物体。 -2. 移动计划。移动器需要包含移动计划及其执行两个阶段,先形成计划,再进行执行。 -3. 移动流程控制。移动时需要提供相应的接口干预移动本身。 - -## 隐含需求 - -1. 异步执行。移动自然需要动画,因此移动本身必然是异步的。 -2. 移动物体。不是所有的物体都能够移动,只有某些可以,这些应该被称为可移动物体。 - -## 未确定需求 - -1. 一个物体能不能绑定两个移动器? - -# 设计前提 - -1. 网格移动。所有移动均基于离散网格,不支持连续空间移动。 -2. 允许自定义拓展。允许自定义移动策略,不是所有的移动都由某一个类统一实现。 - -# 核心概念定义 - -> 这里是一个示例,实际情况中可以更丰富一些。 - -## 移动 - -一个物体的坐标从一点改变至另一点的行为称为移动。 - -## 可移动物体 - -所有可以被移动器移动的物体都可以被称为可移动物体,它们的核心特征是包含坐标信息,并拥有设置其坐标的接口。最常见的可移动物体是地图上的动态图块以及玩家操控的勇士。 - -## 移动器 - -一种对移动的抽象接口,可以通过调用可移动物体的坐标接口来操控可移动物体,但其提供的接口往往更加丰富,且拥有计划机制,允许进行连续移动。 - -# 接口设计分析 - -默认场景下,移动主要发生在两种物体上:地图动态图块及玩家操控的勇士。二者的主要差别在于后者由玩家操控,需要进行移动判定,例如需要进行通行性判定,事件触发判定等,而前者通常由系统代码控制,且常用于演出,并不需要进行判定。 - -## IObjectMover - -### 设计思路 - -> 这部分应当分析需求,并写明接口是如何设计出来的,而不是简单地阐述接口内容。第一句必须写为什么需要设计这个接口本身,这里是 `IObjectMover` 接口,后面才能写接口的具体内容。 - -为了实现通用的物体移动功能,因此设计 `IObjectMover` 接口,包含所有物体移动最基本的接口支持。 - -对于玩家操控,判定往往发生在移动前,因此为统一接口,设计出 `onStepStart` 与 `onStepEnd` 两个接口,前者进行判定,后者根据判定结果进行相应的操作。为了让后者知道判定结果,将 `onStepStart` 的返回值设计为 `Promise`,`onStepEnd` 接收之并执行操作。 - -在实际情况中,常见的使用场景是先规划好线路,然后让物体移动,因此接口需要设计成计划式,计划完成后通过 `start` 接口开始移动,故需要两个接口 `onMoveStart` 和 `onMoveEnd` 分别在移动开始前和结束后执行某些操作。 - -为了能够在开始移动后对移动进行干预及监听,`start` 应返回 `IMoveController` 作为返回值,其中应包含临时在当前步后插入一步、在当前移动队列后插入一步、下一步结束后立刻停止移动、当移动结束后兑现的 `Promise` 等内容。 - -接下来考虑移动计划,最通用的移动方法是指定移动方向与物体的面朝朝向,然后物体面朝指定朝向,向移动方向移动一格,故需要 `stepFace` 接口。在绝大多数情况下,移动方向与朝向是一致的,故提供 `step` 接口。除了这种情况,还会包含前进一格的需求,故提供 `forward` 接口;与之对应,还提供 `backward` 后退接口,而且在多数情况下,后退往往代表面朝前向后走,这里也使用这一语义。在后退时,移动动画的播放进度应该相反,故提供 `animDir` 接口。在移动时,移动速度是一个重要参数,故提供 `speed` 接口。在某些情况下,物体可能会需要瞬移,故提供 `tp` 接口。除了这些,还需要提供转向接口 `face`,调整面朝朝向;以及一个相对比较常见的 `jump` 接口。 - -对于移动对象,仅需要 `x` `y` `setPos` `getCurrentFaceDirection` 接口即可完成所有上述行为,故将这几个接口包装为 `IObjectMovable`,同时 `interface IObjectMover` 允许自定义拓展。 - -### 接口分析 - -> 此处仅列出部分。 - -- `IObjectMover.forward(count?: number)`:预期频率**高频**。向前移动一格是地图行走、动画演出等场景的核心需求,在逻辑与演出中都会频繁出现,故为高频。 -- `IObjectMover.speed(speed: number)`:预期频率**中频**。移动中修改移速有一定使用场景,但远不及 `forward`、`step` 等移动接口的频率,通常只在特殊演出或逻辑中出现,故为中频。 -- `IObjectMover.stepFace(move: FaceDirection, face: FaceDirection, count?: number)`:预期频率**低频**。移动方向与朝向不同的常见场景(后退)已由 `backward` 覆盖,只有极特殊情况才需要此接口(如角色朝向固定但沿垂直方向平移),相当罕见,故为低频。 - -### 预期体量 - -预期代码体量为 200-300 行。分析如下: - -- 计划功能预期 100 行:`IObjectMover` 首先需要完成计划存储与计划的定义,这些接口基本大致就是向数组中添加元素,每个方法内容都不多。 -- 移动流程预期 200 行:`IObjectMover` 还需要完成移动流程的编写工作,需要根据每个移动步按照流程执行不同的行为,这一过程较为复杂。 - -## IMoveController - -... - -# 实现思路 - -> 仅列出实现思路较为复杂的内容,例如像计划存储等属于非常直观简单的内容,不需要列出实现思路。 - -## 移动流程控制 - -核心难点在于如何进行移动控制,以下为核心思路。 - -1. 为实现移动流程,首先需要写一个大循环来处理每一个移动步。 -2. 对于每一个移动步,首先调用 `onStepStart` 接口,获取移动 `code`,然后触发钩子,然后进行 `onStepEnd` 的执行,其返回值作为移动后的目标位置,最后触发钩子。 - -# 涉及文件 - -## `@user/data-common/src/common/mover.ts` - -- [ ] 编写 `IObjectMover` 接口:按照本文描述完成设计 -- [ ] 编写 `IObjectMovable` 接口:按照本文描述完成设计 -- [ ] 编写 `IMoveController` 接口:按照本文描述完成设计 -- [ ] 完成每个移动步的接口设计:每个移动步都需要一个接口对象,本文未明确提及,需包含每种移动计划 -- [ ] 完成移动枚举的编写:每种移动计划都需要一个枚举类型来标注 -- [ ] 编写 `ObjectMover` 抽象类:实现 `IObjectMover` 接口,作为移动基类 diff --git a/docs/guide/audio.md b/docs/guide/audio.md deleted file mode 100644 index e7f5254..0000000 --- a/docs/guide/audio.md +++ /dev/null @@ -1,393 +0,0 @@ -# 音频系统 - -2.B 有了与 2.A 完全不同的音频系统,新的音频系统更加自由,功能更加丰富,可以创建多种自定义效果器。本文将讲解如何使用音频系统。 - -:::tip -多数情况下,你应该不需要使用本文所介绍的内容,因为样板已经将音效、背景音乐等处理完善。如果你想实现高级效果,例如混响效果等,才需要阅读本文。 -::: - -## 获取音频播放器 - -音频播放器在 `@user/client-modules` 模块中,直接引入即可: - -```ts -// 在其他模块中使用模块化语法引入 -import { audioPlayer } from '@user/client-modules'; -// 在 client-modules 模块中使用模块化语法引入 -import { audioPlayer } from '../audio'; // 改为你自己的相对路径 - -// 使用 Mota 全局变量引入 -const { audioPlayer } = Mota.require('@user/client-modules'); -``` - -## 音频系统工作流程 - -音频播放流程如下: - -```mermaid -graph LR; - A(音频源) --> B(效果器) --> C(目的地(扬声器、耳机)) -``` - -## 创建音频源 - -:::tip -本小节的内容极大概率用不到,如果不是需要非常底层的音频接口,可以不看本小节。 -::: - -样板内置了几种音频源,它们包括: - -| 类型 | 适用场景 | 创建方法 | -| --------------- | ----------------------- | ----------------------- | -| `BufferSource` | 预加载的完整音频文件 | `createBufferSource()` | -| `ElementSource` | 通过 `