From 0a2cdbee79480a51c9ddfa7a44e089f3c5a5f489 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Tue, 15 Apr 2025 20:24:11 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20@user/client-modules=20&=20fix:=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20client-modules=20=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/motajs-legacy-common/Patch.md | 2 +- docs/api/user-client-modules/AudioDecoder.md | 142 ++++++ docs/api/user-client-modules/AudioEffect.md | 207 +++++++++ docs/api/user-client-modules/AudioPlayer.md | 172 ++++++++ docs/api/user-client-modules/AudioRoute.md | 221 ++++++++++ docs/api/user-client-modules/AudioSource.md | 184 ++++++++ docs/api/user-client-modules/BgmController.md | 158 +++++++ docs/api/user-client-modules/HeroKeyMover.md | 147 +++++++ docs/api/user-client-modules/SoundPlayer.md | 197 +++++++++ docs/api/user-client-modules/StreamLoader.md | 208 +++++++++ .../user-client-modules/TextContentParser.md | 157 +++++++ .../user-client-modules/TextContentTyper.md | 245 +++++++++++ docs/api/user-client-modules/TextboxStore.md | 166 +++++++ docs/api/user-client-modules/TipStore.md | 147 +++++++ .../user-client-modules/WeatherController.md | 217 +++++++++ docs/api/user-client-modules/functions.md | 410 ++++++++++++++++++ docs/api/user-client-modules/图标组件.md | 37 ++ docs/api/user-client-modules/组件 Arrow.md | 50 +++ .../user-client-modules/组件 Background.md | 78 ++++ docs/api/user-client-modules/组件 Choices.md | 159 +++++++ .../user-client-modules/组件 ConfirmBox.md | 124 ++++++ docs/api/user-client-modules/组件 Page.md | 192 ++++++++ docs/api/user-client-modules/组件 Progress.md | 107 +++++ docs/api/user-client-modules/组件 Scroll.md | 118 +++++ .../user-client-modules/组件 ScrollText.md | 160 +++++++ .../api/user-client-modules/组件 Selection.md | 77 ++++ .../user-client-modules/组件 TextContent.md | 184 ++++++++ docs/api/user-client-modules/组件 Textbox.md | 173 ++++++++ docs/api/user-client-modules/组件 Tip.md | 65 +++ docs/api/user-client-modules/组件 Waitbox.md | 104 +++++ .../client-modules/src/action/move.ts | 4 +- .../client-modules/src/audio/decoder.ts | 6 +- .../client-modules/src/audio/player.ts | 10 +- .../client-modules/src/audio/sound.ts | 1 + .../client-modules/src/audio/source.ts | 2 +- .../src/render/components/choices.tsx | 3 +- .../src/render/components/misc.tsx | 9 +- .../src/render/components/page.tsx | 2 +- .../src/render/components/textbox.tsx | 18 +- .../src/render/components/textboxTyper.ts | 2 +- .../client-modules/src/render/use.ts | 42 +- .../client-modules/src/utils/index.ts | 1 + packages-user/client-modules/src/utils/use.ts | 14 + .../client-modules/src/weather/weather.ts | 1 + 44 files changed, 4703 insertions(+), 20 deletions(-) create mode 100644 docs/api/user-client-modules/AudioDecoder.md create mode 100644 docs/api/user-client-modules/AudioEffect.md create mode 100644 docs/api/user-client-modules/AudioPlayer.md create mode 100644 docs/api/user-client-modules/AudioRoute.md create mode 100644 docs/api/user-client-modules/AudioSource.md create mode 100644 docs/api/user-client-modules/BgmController.md create mode 100644 docs/api/user-client-modules/HeroKeyMover.md create mode 100644 docs/api/user-client-modules/SoundPlayer.md create mode 100644 docs/api/user-client-modules/StreamLoader.md create mode 100644 docs/api/user-client-modules/TextContentParser.md create mode 100644 docs/api/user-client-modules/TextContentTyper.md create mode 100644 docs/api/user-client-modules/TextboxStore.md create mode 100644 docs/api/user-client-modules/TipStore.md create mode 100644 docs/api/user-client-modules/WeatherController.md create mode 100644 docs/api/user-client-modules/functions.md create mode 100644 docs/api/user-client-modules/图标组件.md create mode 100644 docs/api/user-client-modules/组件 Arrow.md create mode 100644 docs/api/user-client-modules/组件 Background.md create mode 100644 docs/api/user-client-modules/组件 Choices.md create mode 100644 docs/api/user-client-modules/组件 ConfirmBox.md create mode 100644 docs/api/user-client-modules/组件 Page.md create mode 100644 docs/api/user-client-modules/组件 Progress.md create mode 100644 docs/api/user-client-modules/组件 Scroll.md create mode 100644 docs/api/user-client-modules/组件 ScrollText.md create mode 100644 docs/api/user-client-modules/组件 Selection.md create mode 100644 docs/api/user-client-modules/组件 TextContent.md create mode 100644 docs/api/user-client-modules/组件 Textbox.md create mode 100644 docs/api/user-client-modules/组件 Tip.md create mode 100644 docs/api/user-client-modules/组件 Waitbox.md create mode 100644 packages-user/client-modules/src/utils/use.ts diff --git a/docs/api/motajs-legacy-common/Patch.md b/docs/api/motajs-legacy-common/Patch.md index 1fedbbf..214153d 100644 --- a/docs/api/motajs-legacy-common/Patch.md +++ b/docs/api/motajs-legacy-common/Patch.md @@ -25,7 +25,7 @@ ## 构造方法 ```typescript -function constructor(patchClass: T): T; +function constructor(patchClass: T): Patch; ``` - **参数** diff --git a/docs/api/user-client-modules/AudioDecoder.md b/docs/api/user-client-modules/AudioDecoder.md new file mode 100644 index 0000000..249c8aa --- /dev/null +++ b/docs/api/user-client-modules/AudioDecoder.md @@ -0,0 +1,142 @@ +# 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 new file mode 100644 index 0000000..0853131 --- /dev/null +++ b/docs/api/user-client-modules/AudioEffect.md @@ -0,0 +1,207 @@ +# 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 new file mode 100644 index 0000000..0a5125b --- /dev/null +++ b/docs/api/user-client-modules/AudioPlayer.md @@ -0,0 +1,172 @@ +# 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 new file mode 100644 index 0000000..681f00f --- /dev/null +++ b/docs/api/user-client-modules/AudioRoute.md @@ -0,0 +1,221 @@ +# 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 new file mode 100644 index 0000000..e3c0e20 --- /dev/null +++ b/docs/api/user-client-modules/AudioSource.md @@ -0,0 +1,184 @@ +# 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 new file mode 100644 index 0000000..0930c54 --- /dev/null +++ b/docs/api/user-client-modules/BgmController.md @@ -0,0 +1,158 @@ +# 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 new file mode 100644 index 0000000..a5e5350 --- /dev/null +++ b/docs/api/user-client-modules/HeroKeyMover.md @@ -0,0 +1,147 @@ +# 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 new file mode 100644 index 0000000..28f36da --- /dev/null +++ b/docs/api/user-client-modules/SoundPlayer.md @@ -0,0 +1,197 @@ +# 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 new file mode 100644 index 0000000..f07ea57 --- /dev/null +++ b/docs/api/user-client-modules/StreamLoader.md @@ -0,0 +1,208 @@ +# 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 new file mode 100644 index 0000000..b01cbae --- /dev/null +++ b/docs/api/user-client-modules/TextContentParser.md @@ -0,0 +1,157 @@ +# 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 new file mode 100644 index 0000000..9f76150 --- /dev/null +++ b/docs/api/user-client-modules/TextContentTyper.md @@ -0,0 +1,245 @@ +# 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 new file mode 100644 index 0000000..c6a8451 --- /dev/null +++ b/docs/api/user-client-modules/TextboxStore.md @@ -0,0 +1,166 @@ +# 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 new file mode 100644 index 0000000..d8b7882 --- /dev/null +++ b/docs/api/user-client-modules/TipStore.md @@ -0,0 +1,147 @@ +# 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 new file mode 100644 index 0000000..c8addd9 --- /dev/null +++ b/docs/api/user-client-modules/WeatherController.md @@ -0,0 +1,217 @@ +# 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 new file mode 100644 index 0000000..b2de6dd --- /dev/null +++ b/docs/api/user-client-modules/functions.md @@ -0,0 +1,410 @@ +# 模块函数 API 文档 + +本文档由 `DeepSeek R1` 模型生成并微调。 + +## 钩子 + +### `onOrientationChange` + +```typescript +function onOrientationChange(hook: OrientationHook): void; +``` + +监听屏幕方向变化事件。 +**参数** + +- `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/图标组件.md b/docs/api/user-client-modules/图标组件.md new file mode 100644 index 0000000..6da0135 --- /dev/null +++ b/docs/api/user-client-modules/图标组件.md @@ -0,0 +1,37 @@ +# 图标组件 API + +## Props 属性说明 + +| 属性名 | 类型 | 必填 | 描述 | +| ------ | ---------------- | ---- | ---------- | +| `loc` | `ElementLocator` | 是 | 图标定位符 | + +图标比例固定,会自动根据传入的长宽缩放。 + +## 图标列表 + +- `RollbackIcon`: 回退图标 +- `RetweenIcon`: 回收图标 +- `ViewMapIcon`: 浏览地图图标 +- `DanmakuIcon`: 弹幕图标 +- `ReplayIcon`: 回放图标 +- `numpadIcon`: 数字键盘图标 +- `PlayIcon`: 开始播放图标 +- `PauseIcon`: 暂停播放图标 +- `DoubleArrow`: 双箭头图标(向右) +- `StepForward`: 单步向前图标 + +## 使用示例 + +```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 new file mode 100644 index 0000000..fca36f8 --- /dev/null +++ b/docs/api/user-client-modules/组件 Arrow.md @@ -0,0 +1,50 @@ +# 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 new file mode 100644 index 0000000..37a6985 --- /dev/null +++ b/docs/api/user-client-modules/组件 Background.md @@ -0,0 +1,78 @@ +# 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 new file mode 100644 index 0000000..0de7953 --- /dev/null +++ b/docs/api/user-client-modules/组件 Choices.md @@ -0,0 +1,159 @@ +# 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 new file mode 100644 index 0000000..31f5b4f --- /dev/null +++ b/docs/api/user-client-modules/组件 ConfirmBox.md @@ -0,0 +1,124 @@ +# 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/组件 Page.md b/docs/api/user-client-modules/组件 Page.md new file mode 100644 index 0000000..01c0b4f --- /dev/null +++ b/docs/api/user-client-modules/组件 Page.md @@ -0,0 +1,192 @@ +# 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 new file mode 100644 index 0000000..322da96 --- /dev/null +++ b/docs/api/user-client-modules/组件 Progress.md @@ -0,0 +1,107 @@ +# 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 new file mode 100644 index 0000000..29ae75f --- /dev/null +++ b/docs/api/user-client-modules/组件 Scroll.md @@ -0,0 +1,118 @@ +# 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))} + +``` diff --git a/docs/api/user-client-modules/组件 ScrollText.md b/docs/api/user-client-modules/组件 ScrollText.md new file mode 100644 index 0000000..cc9e7ea --- /dev/null +++ b/docs/api/user-client-modules/组件 ScrollText.md @@ -0,0 +1,160 @@ +# 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 new file mode 100644 index 0000000..3a86afd --- /dev/null +++ b/docs/api/user-client-modules/组件 Selection.md @@ -0,0 +1,77 @@ +# 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 new file mode 100644 index 0000000..1dbe2c4 --- /dev/null +++ b/docs/api/user-client-modules/组件 TextContent.md @@ -0,0 +1,184 @@ +# 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 new file mode 100644 index 0000000..c0a2e44 --- /dev/null +++ b/docs/api/user-client-modules/组件 Textbox.md @@ -0,0 +1,173 @@ +# 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/组件 Tip.md b/docs/api/user-client-modules/组件 Tip.md new file mode 100644 index 0000000..1eae585 --- /dev/null +++ b/docs/api/user-client-modules/组件 Tip.md @@ -0,0 +1,65 @@ +# 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 new file mode 100644 index 0000000..9cd2d94 --- /dev/null +++ b/docs/api/user-client-modules/组件 Waitbox.md @@ -0,0 +1,104 @@ +# 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/packages-user/client-modules/src/action/move.ts b/packages-user/client-modules/src/action/move.ts index 59a77bd..822042c 100644 --- a/packages-user/client-modules/src/action/move.ts +++ b/packages-user/client-modules/src/action/move.ts @@ -1,6 +1,6 @@ import { KeyCode } from '@motajs/client-base'; import { Hotkey, HotkeyData } from '@motajs/system-action'; -import type { HeroMover, IMoveController } from '@user/data-state'; +import { HeroMover, IMoveController } from '@user/data-state'; import { Ticker } from 'mutate-animate'; import { mainScope } from '@motajs/legacy-ui'; @@ -114,7 +114,7 @@ export class HeroKeyMover { this.mover.oneStep(this.moveDir); const controller = this.mover.startMove(false, false, false, true); - if (!controller) return; + if (!controller) return false; this.controller = controller; controller.onEnd.then(() => { diff --git a/packages-user/client-modules/src/audio/decoder.ts b/packages-user/client-modules/src/audio/decoder.ts index 52d3b78..28f4be6 100644 --- a/packages-user/client-modules/src/audio/decoder.ts +++ b/packages-user/client-modules/src/audio/decoder.ts @@ -106,7 +106,7 @@ export abstract class AudioDecoder { } else { const decoder = new Decoder(); await decoder.create(); - const decodedData = await decoder.decode(data); + const decodedData = await decoder.decodeAll(data); if (!decodedData) return null; const buffer = player.ac.createBuffer( decodedData.channelData.length, @@ -150,7 +150,7 @@ export abstract class AudioDecoder { abstract flush(): Promise; } -export class VorbisDecoder implements AudioDecoder { +export class VorbisDecoder extends AudioDecoder { decoder?: OggVorbisDecoderWebWorker; async create(): Promise { @@ -175,7 +175,7 @@ export class VorbisDecoder implements AudioDecoder { } } -export class OpusDecoder implements AudioDecoder { +export class OpusDecoder extends AudioDecoder { decoder?: OggOpusDecoderWebWorker; async create(): Promise { diff --git a/packages-user/client-modules/src/audio/player.ts b/packages-user/client-modules/src/audio/player.ts index d4a4166..fc7100f 100644 --- a/packages-user/client-modules/src/audio/player.ts +++ b/packages-user/client-modules/src/audio/player.ts @@ -156,7 +156,7 @@ export class AudioPlayer extends EventEmitter { * |-----------| * ``` */ - createDelay() { + createDelayEffect() { return new DelayEffect(this.ac); } @@ -209,6 +209,10 @@ export class AudioPlayer extends EventEmitter { * @param id 要移除的播放路由的名称 */ removeRoute(id: string) { + const route = this.audioRoutes.get(id); + if (route) { + route.destroy(); + } this.audioRoutes.delete(id); } @@ -565,6 +569,10 @@ export class AudioRoute this.emit('updateEffect'); } + destroy() { + this.effectRoute.forEach(v => v.disconnect()); + } + private setOutput() { const effect = this.effectRoute.at(-1); if (!effect) this.output = this.source.output; diff --git a/packages-user/client-modules/src/audio/sound.ts b/packages-user/client-modules/src/audio/sound.ts index 91148c1..0033554 100644 --- a/packages-user/client-modules/src/audio/sound.ts +++ b/packages-user/client-modules/src/audio/sound.ts @@ -131,4 +131,5 @@ export class SoundPlayer< this.playing.clear(); } } + export const soundPlayer = new SoundPlayer(audioPlayer); diff --git a/packages-user/client-modules/src/audio/source.ts b/packages-user/client-modules/src/audio/source.ts index 45c4e28..5040219 100644 --- a/packages-user/client-modules/src/audio/source.ts +++ b/packages-user/client-modules/src/audio/source.ts @@ -112,7 +112,7 @@ export class AudioStreamSource extends AudioSource implements IStreamReader { /** 音频解析器 */ private parser?: CodecParser; /** 每多长时间组成一个缓存 Float32Array */ - private bufferChunkSize = 10; + private bufferChunkSize: number = 10; /** 缓存音频数据,每 bufferChunkSize 秒钟组成一个 Float32Array,用于流式解码 */ private audioData: Float32Array[][] = []; diff --git a/packages-user/client-modules/src/render/components/choices.tsx b/packages-user/client-modules/src/render/components/choices.tsx index 8ca1ed2..195d0c5 100644 --- a/packages-user/client-modules/src/render/components/choices.tsx +++ b/packages-user/client-modules/src/render/components/choices.tsx @@ -468,7 +468,7 @@ export const Choices = defineComponent< {hasTitle.value && ( @@ -515,6 +515,7 @@ export const Choices = defineComponent< font={props.selFont} cursor="pointer" zIndex={5} + fillStyle={props.selFill} onClick={() => emit('choose', v[0])} onSetText={(_, width, height) => updateChoiceSize(i, width, height) diff --git a/packages-user/client-modules/src/render/components/misc.tsx b/packages-user/client-modules/src/render/components/misc.tsx index bedc008..94a4076 100644 --- a/packages-user/client-modules/src/render/components/misc.tsx +++ b/packages-user/client-modules/src/render/components/misc.tsx @@ -14,6 +14,7 @@ import { transitioned } from '../use'; import { hyper } from 'mutate-animate'; import { logger } from '@motajs/common'; import { GameUI, IUIMountable } from '@motajs/system-ui'; +import { clamp } from 'lodash-es'; interface ProgressProps extends DefaultProps { /** 进度条的位置 */ @@ -57,9 +58,10 @@ export const Progress = defineComponent(props => { ctx.moveTo(lineWidth, height / 2); ctx.lineTo(width - lineWidth, height / 2); ctx.stroke(); - if (!isNaN(props.progress)) { + const progress = clamp(props.progress, 0, 1); + if (!isNaN(progress)) { ctx.strokeStyle = props.success ?? 'green'; - const p = lineWidth + (width - lineWidth * 2) * props.progress; + const p = lineWidth + (width - lineWidth * 2) * progress; ctx.beginPath(); ctx.moveTo(lineWidth, height / 2); ctx.lineTo(p, height / 2); @@ -137,7 +139,7 @@ export const Arrow = defineComponent(props => { ); }, arrowProps); -export interface ScrollTextProps extends TextboxProps, ScrollProps { +export interface ScrollTextProps extends TextContentProps, ScrollProps { /** 自动滚动的速度,每秒多少像素 */ speed: number; /** 文字的最大宽度 */ @@ -232,6 +234,7 @@ export const ScrollText = defineComponent< emit('scrollEnd'); paused = true; } + lastFixedTime = now; }); const pause = () => { diff --git a/packages-user/client-modules/src/render/components/page.tsx b/packages-user/client-modules/src/render/components/page.tsx index 46a5ec0..3da2866 100644 --- a/packages-user/client-modules/src/render/components/page.tsx +++ b/packages-user/client-modules/src/render/components/page.tsx @@ -33,7 +33,7 @@ export type PageEmits = { export interface PageExpose { /** * 切换页码 - * @param page 要切换至的页码数,1 表示第一页 + * @param page 要切换至的页码数,0 表示第一页 */ changePage(page: number): void; diff --git a/packages-user/client-modules/src/render/components/textbox.tsx b/packages-user/client-modules/src/render/components/textbox.tsx index 3b5ada0..3a05521 100644 --- a/packages-user/client-modules/src/render/components/textbox.tsx +++ b/packages-user/client-modules/src/render/components/textbox.tsx @@ -41,15 +41,27 @@ export interface TextContentProps fill?: boolean; /** 是否描边 */ stroke?: boolean; - /** 是否自适应高度 */ + /** 是否自适应高度,即组件内部计算 height 的值,而非指定,可与滚动条结合 */ autoHeight?: boolean; /** 文字的最大宽度 */ width: number; } export type TextContentEmits = { + /** + * 当打字机结束时触发 + */ typeEnd: () => void; + + /** + * 当打字机开始打字时触发 + */ typeStart: () => void; + + /** + * 当文字发生变动,组件内部重新计算文字高度时触发 + * @param height 更新后的高度 + */ updateHeight: (height: number) => void; }; @@ -290,7 +302,7 @@ export const Textbox = defineComponent< TextboxEmits, keyof TextboxEmits, TextboxSlots ->((props, { slots, expose }) => { +>((props, { slots, expose, emit }) => { const contentData = shallowReactive({ width: 200 }); const data = shallowReactive({ width: 200 }); @@ -403,10 +415,12 @@ export const Textbox = defineComponent< const onTypeStart = () => { store.emitTypeStart(); + emit('typeStart'); }; const onTypeEnd = () => { store.emitTypeEnd(); + emit('typeEnd'); }; expose({ diff --git a/packages-user/client-modules/src/render/components/textboxTyper.ts b/packages-user/client-modules/src/render/components/textboxTyper.ts index 50ef18f..7c5ad66 100644 --- a/packages-user/client-modules/src/render/components/textboxTyper.ts +++ b/packages-user/client-modules/src/render/components/textboxTyper.ts @@ -669,7 +669,7 @@ export class TextContentParser { font: this.font, fontSize: this.status.fontSize, fillStyle: this.status.fillStyle, - wait, + wait: wait * this.config.interval, splitLines: [], wordBreak: [] }; diff --git a/packages-user/client-modules/src/render/use.ts b/packages-user/client-modules/src/render/use.ts index 2f33465..0ca1a53 100644 --- a/packages-user/client-modules/src/render/use.ts +++ b/packages-user/client-modules/src/render/use.ts @@ -70,7 +70,25 @@ export function onLoaded(hook: () => void) { export interface ITransitionedController { readonly ref: Ref; readonly value: T; + + /** + * 执行动画,使当前值缓慢变化至目标值 + * @param value 目标值 + * @param time 动画时长 + */ set(value: T, time?: number): void; + + /** + * 设置动画的速率曲线 + * @param timing 速率曲线 + */ + mode(timing: TimingFn): void; + + /** + * 设置动画的动画时长 + * @param time 动画时长 + */ + setTime(time: number): void; } class RenderTransition implements ITransitionedController { @@ -90,8 +108,8 @@ class RenderTransition implements ITransitionedController { constructor( value: number, public readonly transition: Transition, - public readonly time: number, - public readonly curve: TimingFn + public time: number, + public curve: TimingFn ) { this.ref = ref(value); transition.value[this.key] = value; @@ -103,6 +121,14 @@ class RenderTransition implements ITransitionedController { set(value: number, time: number = this.time): void { this.transition.time(time).mode(this.curve).transition(this.key, value); } + + mode(timing: TimingFn): void { + this.curve = timing; + } + + setTime(time: number): void { + this.time = time; + } } type ColorRGBA = [number, number, number, number]; @@ -127,8 +153,8 @@ class RenderColorTransition implements ITransitionedController { constructor( value: string, public readonly transition: Transition, - public readonly time: number, - public readonly curve: TimingFn + public time: number, + public curve: TimingFn ) { this.ref = ref(value); const [r, g, b, a] = this.decodeColor(value); @@ -145,6 +171,14 @@ class RenderColorTransition implements ITransitionedController { this.transitionColor(this.decodeColor(value), time); } + mode(timing: TimingFn): void { + this.curve = timing; + } + + setTime(time: number): void { + this.time = time; + } + private transitionColor([r, g, b, a]: ColorRGBA, time: number) { this.transition .mode(this.curve) diff --git a/packages-user/client-modules/src/utils/index.ts b/packages-user/client-modules/src/utils/index.ts index dbc735d..61bb8e4 100644 --- a/packages-user/client-modules/src/utils/index.ts +++ b/packages-user/client-modules/src/utils/index.ts @@ -1 +1,2 @@ export * from './saves'; +export * from './use'; diff --git a/packages-user/client-modules/src/utils/use.ts b/packages-user/client-modules/src/utils/use.ts new file mode 100644 index 0000000..d165e6c --- /dev/null +++ b/packages-user/client-modules/src/utils/use.ts @@ -0,0 +1,14 @@ +import { getCurrentInstance, onUnmounted } from 'vue'; +import { WeatherController } from '../weather'; + +let weatherId = 0; + +export function useWeather(): [WeatherController] { + const weather = new WeatherController(`@weather-${weatherId}`); + + onUnmounted(() => { + weather.destroy(); + }); + + return [weather]; +} diff --git a/packages-user/client-modules/src/weather/weather.ts b/packages-user/client-modules/src/weather/weather.ts index 71f7ae8..cbdcd62 100644 --- a/packages-user/client-modules/src/weather/weather.ts +++ b/packages-user/client-modules/src/weather/weather.ts @@ -117,6 +117,7 @@ export class WeatherController { */ destroy() { WeatherController.map.delete(this.id); + this.clearWeather(); } static get(id: string) {