docs: @user/client-modules & fix: 修复 client-modules 中的一些错误

This commit is contained in:
unanmed 2025-04-15 20:24:11 +08:00
parent 1d62a404b7
commit 0a2cdbee79
44 changed files with 4703 additions and 20 deletions

View File

@ -25,7 +25,7 @@
## 构造方法
```typescript
function constructor(patchClass: T): T;
function constructor<T extends PatchClass>(patchClass: T): Patch<T>;
```
- **参数**

View File

@ -0,0 +1,142 @@
# AudioDecoder API 文档
本文档由 `DeepSeek R1` 模型生成并微调。
---
## 类描述
音频解码系统的核心抽象类,为不同音频格式提供统一的解码接口。主要处理浏览器原生不支持音频格式的解码任务(如 iOS 平台的 Ogg 格式)。
---
## 静态成员说明
### `decoderMap`
```typescript
declare const decoderMap: Map<AudioType, new () => 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<AudioBuffer | null>;
```
核心解码入口方法,自动选择最佳解码方案
| 参数 | 类型 | 说明 |
| ------ | ------------- | ---------------- |
| data | `Uint8Array` | 原始音频字节数据 |
| player | `AudioPlayer` | 音频播放器实例 |
**处理流程**
1. 通过文件头检测音频类型
2. 优先使用浏览器原生解码能力
3. 无原生支持时查找注册的自定义解码器
4. 返回标准 `AudioBuffer` 格式数据
---
## 抽象方法说明
### `abstract create`
```typescript
function create(): Promise<void>;
```
初始化解码器实例(需分配 WASM 内存等资源)
---
### `abstract destroy`
```typescript
function destroy(): void;
```
销毁解码器实例(需释放资源)
---
### `abstract decode`
```typescript
function decode(data: Uint8Array): Promise<IAudioDecodeData | undefined>;
```
流式解码方法(分块处理)
| 参数 | 类型 | 说明 |
| ---- | ------------ | ------------ |
| data | `Uint8Array` | 音频数据分块 |
---
### `abstract decodeAll`
```typescript
function decodeAll(data: Uint8Array): Promise<IAudioDecodeData | undefined>;
```
全量解码方法(单次处理完整文件)
---
### `abstract flush`
```typescript
function flush(): Promise<IAudioDecodeData | undefined>;
```
冲刷解码器缓冲区,获取残留数据
---
## 数据结构
### IAudioDecodeData
```typescript
interface IAudioDecodeData {
channelData: Float32Array[]; // 各声道 PCM 数据
samplesDecoded: number; // 已解码采样数
sampleRate: number; // 采样率 (Hz)
errors: IAudioDecodeError[]; // 解码错误集合
}
```
## 内置解码器
- `VorbisDecoder`: 解码 ogg vorbis 音频。
- `OpusDecoder`: 解码 ogg opus 音频。

View File

@ -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
```
- 带反馈的延迟效果
- 自动渐弱回声处理
---

View File

@ -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<string, AudioRoute>` | 已注册的音频路由表 |
| `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+ ⊙ 朝向用户
```

View File

@ -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<void>;
```
启动/恢复音频播放
| 参数 | 类型 | 说明 |
| ------ | -------- | -------------------------------------- |
| `when` | `number` | 基于 AudioContext 时间的启动时刻(秒) |
---
### `pause`
```typescript
function pause(): Promise<void>;
```
触发暂停流程(执行淡出过渡)
---
### `resume`
```typescript
function resume(): void;
```
从暂停状态恢复播放(执行淡入过渡)
---
### `stop`
```typescript
function stop(): Promise<void>;
```
完全停止播放并释放资源
---
### `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: 暂停播放
```

View File

@ -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[实时音频节点]
```
- 支持渐进式加载
- 动态缓冲管理
- 适用于浏览器自身不支持的音频类型
### AudioElementSourceHTML 音频元素源)
```mermaid
graph LR
AudioTag[audio 元素] -->|音频流| Output[媒体元素源节点]
```
- 基于 HTML5 Audio 元素
- 支持跨域资源
- 自动处理音频格式兼容
### AudioBufferSource静态音频缓冲源
```mermaid
graph LR
File[音频文件] --> Decode[解码为 AudioBuffer]
Decode --> Output[缓冲源节点]
```
- 完整音频数据预加载
- 精确播放控制
- 支持内存音频播放
---
## 注意事项
1. **时间精度**
所有时间参数均以 `AudioContext.currentTime` 为基准,精度可达 0.01 秒

View File

@ -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);
});
```

View File

@ -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. **作用域隔离**:只有当前作用域匹配时才会响应按键事件

View File

@ -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<T, AudioBuffer>` | 已加载音效缓冲存储池 |
| `playing` | `Set<number>` | 当前活跃音效 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<void>;
```
加载并缓存音效资源
| 参数 | 类型 | 说明 |
| ---- | ------------ | ---------------- |
| 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 个,可通过优先级系统管理

View File

@ -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<void>;
```
启动流传输流程(自动处理分块读取与分发)。
---
### `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<T = any> {
/**
* 接受字节流流传输的数据
* @param data 传入的字节流数据,只包含本分块的内容
* @param done 是否传输完成
*/
pump(
data: Uint8Array | undefined,
done: boolean,
response: Response
): Promise<void>;
/**
* 当前对象被传递给加载流时执行的函数
* @param controller 传输流控制对象
*/
piped(controller: IStreamController<T>): void;
/**
* 开始流传输
* @param stream 传输流对象
* @param controller 传输流控制对象
*/
start(
stream: ReadableStream,
controller: IStreamController<T>,
response: Response
): Promise<void>;
/**
* 结束流传输
* @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('视频解码错误');
```

View File

@ -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
```

View File

@ -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<TyperConfig>` | 当前文字渲染配置(包含字体、行高、对齐等参数) |
| `parser` | `TextContentParser` | 文字解析器实例(负责分词、分行等底层处理) |
---
## 核心方法说明
### `constructor`
```typescript
function constructor(config: Partial<ITextContentConfig>): TextContentTyper;
```
初始化打字机实例,接受文字配置参数:
```typescript
interface ITextContentConfig {
/** 字体 */
font: Font;
/** 是否持续上一次的文本,开启后,如果修改后的文本以修改前的文本为开头,那么会继续播放而不会从头播放(暂未实现,后续更新) */
keepLast: boolean;
/** 打字机时间间隔,即两个字出现之间相隔多长时间 */
interval: number;
/** 行高 */
lineHeight: number;
/** 分词规则 */
wordBreak: WordBreak;
/** 文字对齐方式 */
textAlign: TextAlign;
/** 行首忽略字符,即不会出现在行首的字符 */
ignoreLineStart: Iterable<string>;
/** 行尾忽略字符,即不会出现在行尾的字符 */
ignoreLineEnd: Iterable<string>;
/** 会被分词规则识别的分词字符 */
breakChars: Iterable<string>;
/** 填充样式 */
fillStyle: CanvasStyle;
/** 描边样式 */
strokeStyle: CanvasStyle;
/** 线宽 */
strokeWidth: number;
/** 文字宽度,到达这么宽之后换行 */
width: number;
}
```
---
### `setConfig`
```typescript
function setConfig(config: Partial<ITextContentConfig>): 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` 组件。如果必须使用的话,可以直接阅读源码来看一些实现细节。

View File

@ -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<TextboxProps>): 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
<Textbox id={`npc_${npcId}_dialog`} ... />
```
2. **未找到实例处理**
调用前需做空值检测:
```typescript
const store = TextboxStore.get('custom_id');
if (!store) {
console.warn('文本框未注册: custom_id');
return;
}
```
3. **生命周期匹配**
在组件卸载时自动注销实例,请勿持有长期引用
4. **批量操作优化**
同时操作多个实例时建议使用迭代器:
```typescript
// 隐藏所有对话框
TextboxStore.list.forEach(store => store.hide());
```

View File

@ -0,0 +1,147 @@
# TipStore API 文档
本文档由 `DeepSeek R1` 模型生成并微调。
---
## 类描述
`TipStore` 是提示框的集中管理器,提供全局访问和控制提示组件的能力。所有通过 `<Tip>` 组件注册的实例会自动加入静态 `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
// 在组件定义时注册实例
<Tip id="quest-tip" loc={[20, 20, 400, 40]}></Tip>;
// 在业务逻辑中调用
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); // 等待淡出动画结束
```

View File

@ -0,0 +1,217 @@
# WeatherController API 文档
本文档由 `DeepSeek R1` 模型生成并微调。
```mermaid
graph LR
WeatherController --> IWeather
```
_实现 `IWeather` 接口_
## 类描述
`WeatherController` 是天气系统的核心控制器,支持动态管理多种天气效果(如雨、雪、雾等),可将天气效果绑定到任意渲染元素上,实现多场景独立天气控制。
---
## 属性说明
| 属性名 | 类型 | 描述 |
| -------------- | -------------------------------- | ----------------------------------------------------------------- |
| `id` | `string` | 只读,控制器的唯一标识符 |
| `active` | `Set<IWeather>` | 当前激活的天气实例集合 |
| `list`(静态) | `Map<string, Weather>` | 静态属性,存储所有注册的天气类型(键为天气 ID值为天气构造函数 |
| `map`(静态) | `Map<string, WeatherController>` | 静态属性,存储所有控制器实例 |
---
## 构造方法
```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<T extends IWeather = IWeather>(
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<Container>();
onMounted(() => {
// 绑定天气的渲染元素
controller.bind(root.value);
// 激活天气效果
controller.activate('gray-filter', 5);
});
return () => <container ref={root}></container>;
});
```
:::

View File

@ -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<T> {
readonly ref: Ref<T>; // 响应式引用
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<number> | null;
```
创建数值渐变控制器(仅限组件内使用)。
**示例** - 旋转动画
```tsx
// Vue 组件内
const rotate = transitioned(0, 500, hyper('sin', 'out'));
// 触发动画
rotate.set(Math.PI, 800); // 800ms 内旋转到 180 度
// 模板中使用
<text rotate={rotate.ref.value} text="一些显示内容" />;
```
### `transitionedColor`
```typescript
function transitionedColor(
color: string, // 初始颜色(目前支持 #RGB/#RGBA/rgb()/rgba()
time: number, // 默认过渡时长ms
curve: TimingFn // 缓动函数
): ITransitionedController<string> | null;
```
创建颜色渐变控制器(仅限组件内使用)。
**示例** - 背景色过渡
```tsx
// Vue 组件内
const bgColor = transitionedColor('#fff', 300, linear());
// 触发颜色变化
bgColor.set('rgba(255, 0, 0, 0.5)'); // 渐变为半透明红色
// 模板中使用
<g-rect fillStyle={bgColor.ref.value} />;
```
---
### 注意事项
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<ConfirmBoxProps> // 扩展配置
): Promise<boolean>;
```
---
#### 参数说明
| 参数名 | 类型 | 必填 | 描述 |
| ------------ | -------------------------- | ---- | ---------------------------------------------- |
| `controller` | `IUIMountable` | 是 | UI 控制器实例(通常从组件 props 获取) |
| `text` | `string` | 是 | 需要用户确认的文本内容 |
| `loc` | `ElementLocator` | 是 | 对话框位置配置(需包含 x,y 坐标及锚点) |
| `width` | `number` | 是 | 对话框宽度(像素),高度自动计算 |
| `props` | `Partial<ConfirmBoxProps>` | 否 | 扩展配置项(支持所有 ConfirmBox 组件的 props |
---
#### 返回值
返回 `Promise<boolean>`
- `true` 表示用户点击确认
- `false` 表示用户取消或关闭
---
#### 使用示例
##### 基础用法 - 删除确认
```tsx
import { defineComponent } from 'vue';
import { DefaultProps } from '@motajs/render';
import { GameUI } from '@motajs/system-ui';
// 在业务逻辑中调用,注意,组件需要使用 UI 控制器打开,它会自动传递 controller 参数
const MyCom = defineComponent<DefaultProps>(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 () => (
<container>
{/* 假设有一个按钮在点击后触发上面的删除函数 */}
<text text="删除" onClick={() => handleDeleteItem(item.id)} />
</container>
);
});
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<T extends ChoiceKey = ChoiceKey>(
controller: IUIMountable, // UI 控制器
choices: ChoiceItem[], // 选项数组
loc: ElementLocator, // 定位配置
width: number, // 对话框宽度(像素)
props?: Partial<ChoicesProps> // 扩展配置
): Promise<T>;
```
#### 参数说明
| 参数名 | 类型 | 必填 | 描述 |
| ------------ | ----------------------- | ---- | ------------------------------------------- |
| `controller` | `IUIMountable` | 是 | UI 控制器实例(通常从组件 props 获取) |
| `choices` | `ChoiceItem[]` | 是 | 选项数组,格式为 `[key, text]` 的元组 |
| `loc` | `ElementLocator` | 是 | 对话框位置配置(需包含 x,y 坐标及锚点) |
| `width` | `number` | 是 | 对话框宽度(像素),高度自动计算 |
| `props` | `Partial<ChoicesProps>` | 否 | 扩展配置项(支持所有 Choices 组件的 props |
#### 返回值
返回 `Promise<T>`
- 解析为选中项的 `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<T>(
controller: IUIMountable,
loc: ElementLocator,
width: number,
promise: Promise<T>,
props?: Partial<WaitBoxProps<T>>
): Promise<T>;
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 描述 |
| ------------ | -------------------------- | ---- | ------ | ---------------------------------------------------- |
| `controller` | `IUIMountable` | 是 | - | UI 挂载控制器(通常传递父组件的 `props.controller` |
| `loc` | `ElementLocator` | 是 | - | 定位参数 |
| `width` | `number` | 是 | - | 内容区域宽度(像素) |
| `promise` | `Promise<T>` | 是 | - | 要监视的异步操作 |
| `props` | `Partial<WaitBoxProps<T>>` | 否 | `{}` | 扩展配置项(继承 `Background` + `TextContent` 属性) |
---
#### 返回值
| 类型 | 说明 |
| ------------ | ----------------------------------------------------------------------------------- |
| `Promise<T>` | 与传入 `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)

View File

@ -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 () => (
<container>
<RollbackIcon loc={[32, 32, 64, 64]} />
</container>
);
});
```

View File

@ -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) 的基础箭头
<Arrow arrow={[50, 100, 200, 300]} />
```
### 定制样式
```tsx
// 红色粗箭头头部尺寸12px
<Arrow arrow={[120, 80, 320, 400]} head={12} color="#FF0000" lineWidth={3} />
```
### 虚线箭头
```tsx
// 带虚线效果的箭头
<Arrow
arrow={[0, 0, 400, 300]}
lineDash={[5, 3]} // 5px实线 + 3px间隔
color="gold"
/>
```

View File

@ -0,0 +1,78 @@
# Background 背景组件 API 文档
本文档由 `DeepSeek R1` 模型生成并微调。
---
## 核心特性
- **双样式模式**:支持图片皮肤或纯色填充
- **精准定位**:像素级坐标控制
- **静态呈现**:无内置动画的稳定背景层
---
## Props 属性说明
| 属性名 | 类型 | 默认值 | 描述 |
| --------- | ---------------- | -------- | ----------------------------- |
| `loc` | `ElementLocator` | **必填** | 背景定位 |
| `winskin` | `ImageIds` | - | 皮肤图片资源 ID优先级最高 |
| `color` | `CanvasStyle` | `"#333"` | 填充颜色(无皮肤时生效) |
| `border` | `CanvasStyle` | `"#666"` | 边框颜色(无皮肤时生效) |
---
## 使用示例
### 图片皮肤模式
```tsx
// 使用预加载的UI背景图
<Background loc={[0, 0, 416, 416]} winskin="winskin.png" />
```
### 纯色模式
```tsx
// 自定义颜色背景
<Background
loc={[20, 20, 760, 560]}
color="gold"
border="rgba(255,255,255,0.2)"
/>
```
### 对话框组合
```tsx
// 对话框容器
<container loc={[200, 100, 400, 300]}>
<Background loc={[0, 0, 400, 300]} winskin="winskin.png" />
<text loc={[20, 20]} content="系统提示" font={titleFont} />
<text loc={[30, 60]} content="确认要离开吗?" font={contentFont} />
</container>
```
---
## 注意事项
1. **样式优先级**
同时指定参数时的生效顺序:
```tsx
// 以下配置仅生效 winskin
<Background winskin="bg_wood" color="#FF0000" />
```
2. **默认边框**
未指定 border 时的行为:
```tsx
// 无边框(指定为透明色)
<Background color="#222" border="transparent" />;
// 默认白色边框(当未指定任何参数时)
<Background />;
```

View File

@ -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 () => (
<Choices
choices={options}
loc={[208, 208, void 0, void 0, 0.5, 0.5]}
width={208}
title="图形质量设置"
text="请选择适合您设备的画质等级"
// key 在这里是每个选项的第一个元素,即 low, medium, high
onChoose={key => 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 () => (
<Choices
choices={characters}
loc={[208, 208, void 0, void 0, 0.5, 0.5]}
width={208}
maxHeight={400} // 高度超过 400px 自动分页
interval={12}
winskin="winskin.png"
// 粗体20px 大小的 Verdana 字体
titleFont={new Font('Verdana', 20, 'px', 700)}
selFill="#4CAF50"
/>
);
});
```
### 动态内容 + 自定义样式
```tsx
import { defineComponent } from 'vue';
import { Choices, ChoiceItem } from '@user/client-modules';
import { onTick } from '@motajs/render';
export const MyCom = defineComponent(() => {
const dynamicOptions = ref<ChoiceItem[]>([]);
onTick(() => {
// 每帧生成随机选项名称
dynamicOptions.value = Array.from(
{ length: 50 },
(_, i) =>
[
`char_${i}`,
`随机数 ${Math.random().toFixed(8)}`
] as ChoiceItem
);
});
return () => (
<Choices
choices={dynamicOptions.value}
loc={[208, 208, void 0, void 0, 0.5, 0.5]}
width={208}
color="rgba(30,30,30,0.9)"
border="#607D8B"
title="选择随机数"
titleFill="#B2EBF2"
/>
);
});
```
---
## 注意事项
1. **选项键值唯一性**
每个选项的 `key` 必须唯一,否则可能引发不可预期行为
2. **分页计算规则**
分页依据 `maxHeight` 和字体大小自动计算,需确保字体大小一致
3. **使用更方便的函数**:多数情况下,你不需要使用本组件,使用包装好的函数往往会更加方便,参考 [`getChoice`](./functions.md#getchoice)

View File

@ -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 () => (
<ConfirmBox
text="确定要保存当前进度吗?"
width={208}
loc={[208, 208, void 0, void 0, 0.5, 0.5]}
onYes={() => 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 () => (
<ConfirmBox
text="此操作不可逆,是否继续?"
width={208}
loc={[208, 208, void 0, void 0, 0.5, 0.5]}
// 背景使用 winskin
winskin="winskin.png"
yesText="确认删除"
noText="取消操作"
// 设置选项字体
selFont={new Font('Verdana')}
selFill="#f44"
/>
);
});
```
### 动态内容 + 编程控制
```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 () => (
<ConfirmBox
text={myText.value}
width={360}
loc={[208, 208, void 0, void 0, 0.5, 0.5]}
color="rgba(0,0,0,0.8)"
border="#4CAF50"
defaultYes={false}
onYes={() => void count.value++} // 每次确认次数加一
onNo={() => void count.value--} // 每次确认次数减一
/>
);
});
```
## 注意事项
1. **使用更方便的函数**:多数情况下,你不需要使用本组件,使用包装好的函数往往会更加方便,参考 [`getConfirm`](./functions.md#getconfirm)

View File

@ -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 pages={3} loc={[208, 208, 208, 208, 0.5, 0.5]}>
{page => (
<text
text={`第 ${page + 1} 页内容`}
loc={[104, 104, void 0, void 0, 0.5, 0.5]}
/>
)}
</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<PageExpose>();
// 页码变化回调
const handlePageChange = (currentPage: number) => {
// 可以使用参数获得当前页码,加一是因为页码是从 0 开始的
console.log(`当前页码:${currentPage + 1}`);
// 或者也可以使用 Page 组件的接口获得当前页码
console.log(`当前页码:${pageRef.value!.now() + 1}`);
};
return () => (
<Page
pages={pages.length}
loc={[208, 208, 208, 208, 0.5, 0.5]} // 游戏画面居中
onPageChange={handlePageChange}
ref={pageRef}
>
{page => <text text={pages[page].content} />}
</Page>
);
});
```
### 动态配置示例
```tsx
import { defineComponent, ref } from 'vue';
import { Page, PageExpose } from '@user/client-modules';
// 带统计面板的复杂分页
export const MyCom = defineComponent(() => {
const dataPages = [
/* 复杂数据结构 */
];
// 暴露方法实现翻页逻辑
const pageRef = ref<PageExpose>();
const jumpToAnalysis = () => pageRef.value?.changePage(3); // 1-based
return () => (
<container>
{/* 分页内容 */}
<Page
pages={dataPages.length}
loc={[208, 208, 208, 208, 0.5, 0.5]}
hideIfSingle // 如果只有一个页面,那么隐藏底部的页码显示
ref={pageRef}
>
{page => (
<container>
{/* 这里面可以写一些复杂的渲染内容,或者单独写成一个组件,把页码作为参数传入 */}
<text text="渲染内容" />
<g-rect loc={[50, 50, 100, 50]} stroke />
</container>
)}
</Page>
{/* 自定义跳转按钮 */}
<text
loc={[108, 180, void 0, void 0, 0.5, 1]} // 左右居中,靠下对齐
onClick={jumpToAnalysis}
text="跳转到分析页"
/>
</container>
);
});
```
### 边缘检测示例
```tsx
import { defineComponent, ref } from 'vue';
// 边界处理逻辑
export const MyCom = defineComponent(() => {
const pageRef = ref<PageExpose>();
// 自定义边界提示
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
pages={8}
ref={pageRef}
onPageChange={handleEdge}
loc={[208, 208, 208, 208, 0.5, 0.5]}
>
{page => <text text={`第${page}页`} />}
</Page>
);
});
```
---
## 注意事项
1. **自动约束**:切换页码时会自动约束在 `[0, pages-1]` 范围内

View File

@ -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 () => (
<Progress
loc={[20, 20, 200, 8]} // x=20, y=20, 宽200px, 高8px
progress={loadingProgress.value}
/>
);
});
```
### 自定义样式
```tsx
// 自定义进度条的已完成和未完成部分的样式
<Progress
loc={[50, 100, 300, 12]}
progress={0.65}
success="rgb(54, 255, 201)"
background="rgba(255,255,255,0.2)"
lineWidth={6}
/>
```
### 垂直进度条
```tsx
// 通过旋转容器实现垂直效果,注意锚点的使用
<container rotation={-Math.PI / 2} loc={[100, 200, 150, 8, 0.5, 0.5]}>
<Progress loc={[0, 0, 150, 8]} progress={0.8} success="#FF5722" />
</container>
```
---
## 动画效果实现
### 平滑过渡示例
```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 () => (
<Progress loc={[0, 0, 400, 10]} progress={progressValue.ref.value} />
);
```
---
## 注意事项
1. **坐标系统**
实际渲染高度由 `loc[3]` 参数控制,会自动上下居中:
```tsx
// 情况1显式指定高度为8px
<Progress loc={[0, 0, 200, 8]} />
```

View File

@ -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
// ✅ 正确写法
<Scroll>
<item />
<item />
<item />
</Scroll>;
// ❌ 错误写法(影响虚拟滚动计算)
<Scroll>
<container>
// 会导致整体被视为单个元素
<item />
<item />
</container>
</Scroll>;
```
---
## 使用示例
### 基础垂直滚动
```tsx
import { defineComponent } from 'vue';
export const MyCom = defineComponent(() => {
const list = Array(200).fill(0);
return () => (
<Scroll loc={[208, 208, 208, 208, 0.5, 0.5]}>
{list.map((_, index) => (
<text key={index} text={index.toString()} />
))}
</Scroll>
);
});
```
### 水平滚动 + 编程控制
```tsx
import { defineComponent, ref, onMounted } from 'vue';
export const MyCom = defineComponent(() => {
const list = Array(200).fill(0);
const scrollRef = ref<ScrollExpose>();
// 滚动水平 100 像素位置,动画时长 500 毫秒
onMounted(() => {
scrollRef.value?.scrollTo(100, 500);
});
return () => (
<Scroll hor loc={[208, 208, 208, 208, 0.5, 0.5]} ref={scrollRef}>
{list.map((_, index) => (
<text key={index} text={index.toString()} />
))}
</Scroll>
);
});
```
---
## 性能优化指南
### 1. 替代方案建议
⚠️ **当子元素数量 > 1000 时**,推荐改用分页组件:
```tsx
// 使用 Page 组件处理超大数据集
<Page pages={Math.ceil(data.length / 50)}>
{page => renderChunk(data.slice(page * 50, (page + 1) * 50))}
</Page>
```

View File

@ -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 () => (
<ScrollText
text={longText}
speed={80} // 每秒滚动80像素
width={416} // 文本区域宽度
loc={[0, 0, 416, 416]} // 容器位置和尺寸
fillStyle="#E6E6FA" // 薰衣草色文字
/>
);
});
```
### 动态控制
```tsx
import { defineComponent, ref } from 'vue';
import { ScrollText } from '@user/client-modules';
export const MyCom = defineComponent(() => {
const longText = '序幕\n'.repeat(100) + '——全剧终——';
const scrollRef = ref<ScrollTextExpose>();
// 暂停/恢复控制
const toggleScroll = () => {
if (scrollRef.value?.isPaused) {
scrollRef.value.resume();
} else {
scrollRef.value?.pause();
}
};
// 速度控制
const accelerate = () => {
scrollRef.value?.setSpeed(200);
};
return () => (
<ScrollText
ref={scrollRef}
text={longText}
speed={100}
width={416}
loc={[0, 0, 416, 416]}
onScrollEnd={() => console.log('滚动结束')}
/>
);
});
```
### 复杂排版
```tsx
const staffText =
'\\c[32]====制作人员====\\c\n\n' +
'\\r[#FFD700]总监督\\r\t\t张三\n' +
'\\r[#00FF00]美术指导\\r\\t李四\n' +
'\\i[logo]\n' +
'特别感谢:某某公司';
<ScrollText
text={staffText}
speed={120}
width={720}
loc={[40, 40, 720, 560]}
pad={40} // 顶部留白
font={new Font('黑体', 24)}
lineHeight={8} // 行间距
interval={0} // 禁用打字机效果
/>;
```
---
## 注意事项
1. **容器尺寸**
实际可滚动区域计算公式:
```
可视高度 = loc[3](容器高度)
滚动距离 = 文本总高度 + pad首行前空白
```
2. **速度控制**
推荐速度范围 50-200 像素/秒
3. **组合动画**
可与容器变换配合实现复杂效果:
```tsx
<container rotation={-5} alpha={0.9}>
<ScrollText />
</container>
```

View File

@ -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
// 使用预加载的游戏皮肤资源
<Selection
loc={[120, 80, 160, 24]}
winskin="winskin.png"
alphaRange={[0.4, 1.0]}
/>
```
---
### 纯色模式
```tsx
// 自定义颜色方案
<Selection
loc={[20, 240, 200, 32]}
color="rgba(0,128,255,0.2)" // 填充颜色
border="#00BFFF" // 边框颜色
alphaRange={[0.5, 0.9]}
/>
```
---
## 注意事项
1. **样式优先级**
同时指定 `winskin` 和颜色参数时:
```tsx
// 以下配置将忽略 color/border 参数
<Selection winskin="winskin.png" color="red" border="blue" />
```
2. **动画速度**
呼吸动画固定为 2000ms/周期,暂不支持自定义时长
3. **点击反馈**
建议配合事件系统实现点击效果:
```tsx
<container onClick={handleClick}>
<Selection />
<text />
</container>
```

View File

@ -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 () => (
<TextContent
text="这是基础文本内容,会自动换行排版"
width={200} // 最大宽度,达到这个宽度会自动换行
font={new Font('宋体', 18)}
fillStyle="#333333" // 黑色字体
onTypeEnd={() => console.log('显示完成')} // 打字机结束后执行
/>
);
});
```
### 自定义样式 + 描边效果
```tsx
import { defineComponent } from 'vue';
import { TextContent } from '@user/client-modules';
import { Font } from '@motajs/render';
export const MyCom = defineComponent(() => {
return () => (
<TextContent
text="\\r[#FFD700]金色描边文字"
width={300}
font={new Font('黑体', 24)}
stroke // 设为填充+描边格式,如果仅描边需要设置 fill={false}
strokeStyle="#8B4513" // 描边颜色
strokeWidth={2} // 描边宽度
lineHeight={8} // 行间距8 像素
interval={30} // 打字机间隔
/>
);
});
```
### 动态内容更新
```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 () => (
<TextContent
text={dynamicText.value}
width={500}
autoHeight
onUpdateHeight={h => console.log('当前高度:', h)} // 当高度发生变化时触发
/>
);
});
```
### 禁用动画效果
```tsx
import { defineComponent } from 'vue';
import { TextContent } from '@user/client-modules';
export const MyCom = defineComponent(() => {
return () => (
<TextContent
text="立即显示所有内容"
width={350}
interval={0} // 设置为0禁用逐字效果
showAll // 立即显示全部
fillStyle="rgba(0,128,0,0.8)"
/>
);
});
```
### 多语言复杂排版
```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 () => (
<TextContent
text={complexText}
width={600}
interval={25}
onTypeStart={() => console.log('开始渲染复杂文本')}
/>
);
});
```
---
## 转义字符示例
```tsx
// 颜色/字体/图标综合使用
const styledText =
'\\r[#FF0000]警告!\\g[方正粗宋]\\c[24]' +
'\\i[warning_icon]发现异常\\z[10]\\n' +
'请立即处理\\r\\g\\c';
<TextContent text={styledText} width={450} interval={40} />;
```
转义字符具体用法参考 [TextContentParser](./TextContentParser.md#转义字符语法说明)

View File

@ -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
// 例如使用一张图片作为背景
<Textbox>
<container>
<image image="myImage.png" />
</container>
</Textbox>
```
### title
标题背景插槽,自定义标题背景
```tsx
// 与 default 一起使用
<Textbox>
{{
// 背景图
default: () => <image image="myImage.png" />,
// 标题背景图
title: () => <g-rectr circle={[4]} fill />
}}
</Textbox>
```
## 使用示例
### 基础对话框
```tsx
import { defineComponent } from 'vue';
import { Textbox } from '@user/client-modules';
export const MyCom = defineComponent(() => {
return () => (
<Textbox
title="NPC对话"
winskin="winskin.png" // 背景 winskin
padding={12} // 文字内边距
width={416} // 最大宽度,超过换行,自动减去内边距
titleFont={new Font('楷体', 22)} // 标题字体
titleFill="#FFD700" // 标题填充样式
/>
);
});
```
### 纯色背景 + 复杂标题
```tsx
import { defineComponent } from 'vue';
import { Textbox } from '@user/client-modules';
export const MyCom = defineComponent(() => {
return () => (
<Textbox
title="国王的旨意" // 标题内容
backColor="rgba(0,0,0,0.8)" // 背景色,使用半透明黑色
padding={16}
width={416}
titlePadding={10} // 标题的内边距
titleFont={new Font('华文行楷', 24)}
titleStroke="#8B4513" // 标题描边样式
titleFill="#FFFFFF"
/>
);
});
```
### 动态标题交互
```tsx
import { defineComponent } from 'vue';
import { Textbox, TextbosExpose } from '@user/client-modules';
export const MyCom = defineComponent(() => {
const currentTitle = ref<string>();
// 点击按钮切换标题
const toggleTitle = () => {
currentTitle.value += 1;
};
return () => (
<container>
<Textbox
ref={dialogRef}
title={currentTitle.value.toString()}
winskin="winskin.png"
padding={10}
width={416}
titleFill="#FF4444"
/>
<text text="点击切换标题" onClick={toggleTitle} />
</container>
);
});
```
---
## 布局结构示意图
```mermaid
graph TB
Dialog[对话框] --> Background[背景层]
Dialog --> Title[标题层]
Dialog --> Content[内容层]
Background -->|winskin/backColor| 渲染背景
Title -->|title 配置| 标题文本
Content -->|padding 控制| 文字内容
```

View File

@ -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`<br>`icon?: AllIds \| AllNumbers` | `void` | 显示带图标的提示文本(图标支持字符串 ID 或数字 ID |
## 使用示例
```tsx
import { defineComponent } from 'vue';
import { Tip } from './tip';
// 在游戏界面中定义提示组件
export const MyCom = defineComponent(() => {
return () => (
<container>
<Tip
loc={[32, 32, 280, 40]}
pad={[8, 4]}
corner={8}
id="global-tip"
></Tip>
</container>
);
});
// 在业务代码中调用提示,使用 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')` 参数调整动画曲线

View File

@ -0,0 +1,104 @@
# WaitBox 等待框组件 API 文档
本文档由 `DeepSeek R1` 模型生成并微调。
---
## 核心特性
- **Promise 绑定**:自动监控 `Promise` 状态
- **复合式组件**:集成背景+文字+加载动画
- **双重控制**:支持自动/手动完成等待
- **动态布局**:根据内容自动计算高度
---
## 组件定位
> 💡 更推荐使用 `waitbox` 工具函数,该组件主要用于需要深度定制的场景。参考 [此文档](./functions.md#waitbox)。
---
## Props 属性说明
| 属性名 | 类型 | 必填 | 描述 |
| --------- | ---------------- | ---- | ----------------------- |
| `promise` | `Promise<T>` | 否 | 要监控的 `Promise` 对象 |
| `loc` | `ElementLocator` | 是 | 容器定位 |
| `width` | `number` | 是 | 内容区域宽度(像素) |
| `text` | `string` | 否 | 等待提示文字 |
| `pad` | `number` | `16` | 文字与边缘间距 |
### 继承属性
- 支持所有 `Background` 背景属性
- 支持所有 `TextContent` 文本属性
---
## 事件说明
| 事件名 | 参数 | 触发时机 |
| --------- | ---- | -------------------------- |
| `resolve` | `T` | `Promise` 完成时返回结果值 |
---
## Exposed Methods 暴露方法
| 方法名 | 参数 | 描述 |
| --------- | --------- | ---------------------------- |
| `resolve` | `data: T` | 手动完成等待(立即触发事件) |
---
## 使用示例
### 基础组件用法
```tsx
// 等待网络请求
const fetchPromise = fetchData();
<WaitBox
promise={fetchPromise}
loc={[208, 208, void 0, void 0, 0.5, 0.5]} // 居中定位
width={208}
text="加载中..."
winskin="ui/loading_bg"
font={new Font('黑体', 18)}
onResolve={data => console.log('收到数据:', data)}
/>;
```
### 手动控制示例
```tsx
const waitRef = ref<WaitBoxExpose<number>>();
// 手动结束等待
const forceComplete = () => {
waitRef.value?.resolve(Date.now());
};
return () => (
<WaitBox
ref={waitRef}
loc={[100, 100, 400, 200]}
width={360}
text="点击按钮继续"
color="rgba(0,0,0,0.7)"
></WaitBox>
);
```
---
## 注意事项
1. **推荐用法**
90% 场景应使用 `waitbox` 函数,以下情况才需要直接使用组件:
- 需要永久显示的等待界面
- 需要组合复杂子组件
- 需要复用同一个等待实例

View File

@ -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(() => {

View File

@ -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<IAudioDecodeData | undefined>;
}
export class VorbisDecoder implements AudioDecoder {
export class VorbisDecoder extends AudioDecoder {
decoder?: OggVorbisDecoderWebWorker;
async create(): Promise<void> {
@ -175,7 +175,7 @@ export class VorbisDecoder implements AudioDecoder {
}
}
export class OpusDecoder implements AudioDecoder {
export class OpusDecoder extends AudioDecoder {
decoder?: OggOpusDecoderWebWorker;
async create(): Promise<void> {

View File

@ -156,7 +156,7 @@ export class AudioPlayer extends EventEmitter<AudioPlayerEvent> {
* |-----------|
* ```
*/
createDelay() {
createDelayEffect() {
return new DelayEffect(this.ac);
}
@ -209,6 +209,10 @@ export class AudioPlayer extends EventEmitter<AudioPlayerEvent> {
* @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;

View File

@ -131,4 +131,5 @@ export class SoundPlayer<
this.playing.clear();
}
}
export const soundPlayer = new SoundPlayer<SoundIds>(audioPlayer);

View File

@ -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[][] = [];

View File

@ -468,7 +468,7 @@ export const Choices = defineComponent<
<Background
loc={[0, 0, props.width, boxHeight.value]}
winskin={props.winskin}
color={props.color}
color={props.color ?? '#333'}
border={props.border}
/>
{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)

View File

@ -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<ProgressProps>(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<ArrowProps>(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 = () => {

View File

@ -33,7 +33,7 @@ export type PageEmits = {
export interface PageExpose {
/**
*
* @param page 1
* @param page 0
*/
changePage(page: number): void;

View File

@ -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<TextContentProps>({ width: 200 });
const data = shallowReactive<TextboxProps>({ width: 200 });
@ -403,10 +415,12 @@ export const Textbox = defineComponent<
const onTypeStart = () => {
store.emitTypeStart();
emit('typeStart');
};
const onTypeEnd = () => {
store.emitTypeEnd();
emit('typeEnd');
};
expose<TextboxExpose>({

View File

@ -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: []
};

View File

@ -70,7 +70,25 @@ export function onLoaded(hook: () => void) {
export interface ITransitionedController<T> {
readonly ref: Ref<T>;
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<number> {
@ -90,8 +108,8 @@ class RenderTransition implements ITransitionedController<number> {
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<number> {
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<string> {
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<string> {
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)

View File

@ -1 +1,2 @@
export * from './saves';
export * from './use';

View File

@ -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];
}

View File

@ -117,6 +117,7 @@ export class WeatherController {
*/
destroy() {
WeatherController.map.delete(this.id);
this.clearWeather();
}
static get(id: string) {