# 自定义按键
在 2.B 中新增按键很方便,且可以为你的 UI 单独配置按键信息,玩家也可以修改快捷键设置。
下面以设置一个全局的切换技能按键为例,展示如何新增一个按键。
:::warning
按键系统在未来可能会有小幅重构,但逻辑不会大幅变动。
:::
## 定义按键信息
我们打开 `packages-user/client-modules/src/action/hotkey.ts`,找到 `//#region 按键实现` 分段,在分段前可以看到一个 `// #endregion`,然后我们在上面的一系列 `register` 后面新增:
```ts {6-17}
gameKey
.group(/* ... */)
.register({})
// ... 原有内容,注意原本内容最后的分号别忘了删除
//#region 主动技能
// 分组,这样既可以方便管理,也可以让玩家设置按键时直接根据分组设置
.group('skill', '主动技能')
// 注册按键信息
.register({
// 按键的 id
id: '@skill_doubleAttack',
// 按键显示的名称
name: '二倍斩',
// 默认按键,数字 1(非小键盘)
defaults: KeyCode.Digit1
});
```
此时我们打开游戏,按下 `Esc`,选择 `系统设置->操作设置->自定义按键`,就可以看到我们新增的按键信息了,不过现在按它没有任何作用,因为我们只是定义了按键,还没有编写它的触发效果。
## 实现按键效果
我们回到 `hotkey.ts`,翻到文件最后,在最后几行的上面会有一系列 `realize`,这就是实现按键操作的地方,我们在后面新增一个 `@skill_doubleAttack` 的实现:
```ts {6-10}
gameKey
.when(/* ... */)
.realize(/* ... */)
// ... 原有内容
// 实现刚刚定义的按键
.realize('@skill_doubleAttack', () => {
// 切换技能
toggleSkill();
});
```
## 拓展-添加辅助按键
如果我们需要一个按键默认情况下需要按下 `Ctrl` 时才能触发,例如 `Ctrl+A`,我们可以这么写:
```ts
gameKey.register({
id: '@skill_doubleAttack',
name: '二倍斩',
defaults: KeyCode.Digit1,
// 设置 ctrl 属性为 true 即可,包括 alt 和 shift 也是一样
ctrl: true // [!code ++]
});
```
## 拓展-在 UI 内实现按键
有时候,我们需要在一个 UI 界面中提供按键操作支持,样板提供了专门的接口来实现这一点。
### 定义按键信息
与[这一节](#定义按键信息)相同,直接定义按键信息即可:
```ts
gameKey
//#region 自定义UI
.group('@ui_myUI', '自定义UI')
.register({
id: '@myUI_key',
name: '自定义UI',
// 默认使用 H 键
defaults: KeyCode.KeyH
});
```
### 在 UI 内实现按键操作
按键实现方式略有变动,我们需要使用 `useKey` 接口来实现按键。假设我们在 `packages-user/client-modules/src/render/ui` 文件夹下编写 UI,那么可以这么写:
```tsx {7-13}
// 引入 useKey 接口
// 文件在 packages-user/client-modules/src/render/utils/use.ts,注意路径关系
import { useKey } from '../utils/use'; // [!code ++]
// UI 模板及如何编写 UI 参考 “新增 UI” 需求指南,这里只给出必要的修改部分,模板部分不再给出
export const MyCom = defineComponent(props => {
// 调用 useKey
const [key] = useKey();
// 直接开始实现,本例按键效果为显示一个提示
key.realize('@myUI_key', () => {
// 调用 drawTip 显示提示
core.drawTip('这是一个提示');
});
return () => ;
});
```
### 通用按键复用
我们会有一些通用按键,例如确认、关闭,这些按键我们不希望每个 UI 或场景都定义一遍,一来写代码不方便,二来玩家如果要自定义的话需要每个界面都设置一遍,很麻烦。此时我们建议按键复用。与一般的按键一致,我们直接实现 `exit` `confirm` 等按键即可,不需额外操作:
```tsx {12-21}
import { useKey } from '../utils/use';
// UI 模板及如何编写 UI 参考 “新增 UI” 需求指南,这里只给出必要的修改部分,模板部分不再给出
export const MyCom = defineComponent(props => {
// 调用 useKey
const [key] = useKey();
// 直接开始实现,本例按键效果为显示一个提示
key.realize('@myUI_key', () => {
// 调用 drawTip 显示提示
core.drawTip('这是一个提示');
})
// 关闭操作
.realize('exit', () => {
// 调用关闭函数
props.controller.close(props.instance);
})
// 确认操作
.realize('confirm', () => {
// 弹出提示说明按下了确认键
core.drawTip('按下了确认键!');
});
return () => ;
});
```
实际上,你甚至可以在一个 UI 中实现另一个 UI 定义的按键,虽然这么做非常离谱。
## 拓展-单功能多按键
在游戏中可以发现退出、确认等功能可以设定多个按键,为了实现这种按键,我们只需要在定义按键时加上 `_num` 后缀即可,例如:
```ts {4,10,16}
gameKey
.register({
// 添加 _1 后缀
id: '@skill_doubleAttack_1',
name: '二倍斩',
defaults: KeyCode.Digit1
})
.register({
// 添加 _2 后缀
id: '@skill_doubleAttack_2',
name: '二倍斩',
defaults: KeyCode.Digit2
})
.register({
// 添加 _3 后缀
id: '@skill_doubleAttack_3',
name: '二倍斩',
defaults: KeyCode.Digit3
});
```
这样,在自定义按键界面就会显示为可以自定义三个按键。而在实现时,我们不需要添加后缀:
```ts {2}
// 这里不需要添加后缀!
gameKey.realize('@skill_doubleAttack', () => {
toggleSkill();
});
```
或者,添加后缀的话,会精确匹配到对应后缀的按键:
```ts {2}
// 只有按下 @skill_doubleAttack_1 对应的按键才会触发,而 @skill_doubleAttack_2 等不会触发!
gameKey.realize('@skill_doubleAttack_1', () => {
toggleSkill();
});
```
## 拓展-按下时触发
默认情况下,我们实现的按键都是在按键抬起时触发,如果我们需要按下时触发,我们需要在调用 `realize` 函数时额外传入一个配置项:
:::code-group
```ts [down]
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时单次触发
{ type: 'down' } // [!code ++]
);
```
```ts [down-repeat]
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时持续触发
{ type: 'down-repeat' } // [!code ++]
);
```
```ts [down-throttle]
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时节流触发,节流间隔为 100ms
{ type: 'down-throttle', throttle: 100 } // [!code ++]
);
```
```ts [down-timeout]
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时延迟触发,延迟 1000ms
{ type: 'down-timeout', timeout: 1000 } // [!code ++]
);
```
:::
这里的 `type` 可以填这些值:
- `up`: 抬起时触发,默认就是它。
- `down`: 按下时触发,只触发一次。
- `down-repeat`: 按下时触发,且会重复触发。这一操作可能会与键盘或系统设置有关,一般来说首次触发后会有 `500ms` 的延时,然后每帧触发一次。
- `down-throttle`: 按下时节流触发,在 `down-repeat` 的基础上,每隔一段时间才会触发一次,例如可以设定为 `100ms` 触发一次。
- `down-timeout`: 按下后延迟触发,会在按下后延迟一段时间触发。
## 拓展-样板为什么不会在 UI 中触发全局按键?
这是按键系统最实用的功能之一,这个功能允许我们在 UI 中不会触发全局按键,例如在怪物手册中不会触发打开楼传,也不会触发打开系统菜单。你可能会好奇,我们在上面的讲述中似乎并没有哪一行执行了这一操作,那么是如何实现的呢?
实际上,按键系统内部有一个栈,而我们调用 `useKey` 时就会自动创建一个新的作用域,同时在关闭 UI 时释放作用域。这样的话,我们在打开 UI 时,按键实现就会遵循新创建的作用域,关闭时自动回到上一层,这就实现了上述功能。
## 拓展-API 参考
[Hotkey API 文档](../../api/motajs-system-action/Hotkey.md)