8.3 KiB
自定义按键
在 2.B 中新增按键很方便,且可以为你的 UI 单独配置按键信息,玩家也可以修改快捷键设置。
下面以设置一个全局的切换技能按键为例,展示如何新增一个按键。
:::warning 按键系统在未来可能会有小幅重构,但逻辑不会大幅变动。 :::
定义按键信息
我们打开 packages-user/client-modules/src/action/hotkey.ts
,找到 //#region 按键实现
分段,在分段前可以看到一个 // #endregion
,然后我们在上面的一系列 register
后面新增:
gameKey
.group(/* ... */)
.register({})
// ... 原有内容,注意原本内容最后的分号别忘了删除
//#region 主动技能
// 分组,这样既可以方便管理,也可以让玩家设置按键时直接根据分组设置
.group('skill', '主动技能')
// 注册按键信息
.register({
// 按键的 id
id: '@skill_doubleAttack',
// 按键显示的名称
name: '二倍斩',
// 默认按键,数字 1(非小键盘)
defaults: KeyCode.Digit1
});
此时我们打开游戏,按下 Esc
,选择 系统设置->操作设置->自定义按键
,就可以看到我们新增的按键信息了,不过现在按它没有任何作用,因为我们只是定义了按键,还没有编写它的触发效果。
实现按键效果
我们回到 hotkey.ts
,翻到文件最后,在最后几行的上面会有一系列 realize
,这就是实现按键操作的地方,我们在后面新增一个 @skill_doubleAttack
的实现:
gameKey
.when(/* ... */)
.realize(/* ... */)
// ... 原有内容
// 实现刚刚定义的按键
.realize('@skill_doubleAttack', () => {
// 切换技能
toggleSkill();
});
拓展-添加辅助按键
如果我们需要一个按键默认情况下需要按下 Ctrl
时才能触发,例如 Ctrl+A
,我们可以这么写:
gameKey.register({
id: '@skill_doubleAttack',
name: '二倍斩',
defaults: KeyCode.Digit1,
// 设置 ctrl 属性为 true 即可,包括 alt 和 shift 也是一样
ctrl: true // [!code ++]
});
拓展-在 UI 内实现按键
有时候,我们需要在一个 UI 界面中提供按键操作支持,样板提供了专门的接口来实现这一点。
定义按键信息
与这一节相同,直接定义按键信息即可:
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,那么可以这么写:
// 引入 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 () => <container></container>;
});
通用按键复用
我们会有一些通用按键,例如确认、关闭,这些按键我们不希望每个 UI 或场景都定义一遍,一来写代码不方便,二来玩家如果要自定义的话需要每个界面都设置一遍,很麻烦。此时我们建议按键复用。与一般的按键一致,我们直接实现 exit
confirm
等按键即可,不需额外操作:
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 () => <container></container>;
});
实际上,你甚至可以在一个 UI 中实现另一个 UI 定义的按键,虽然这么做非常离谱。
拓展-单功能多按键
在游戏中可以发现退出、确认等功能可以设定多个按键,为了实现这种按键,我们只需要在定义按键时加上 _num
后缀即可,例如:
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
});
这样,在自定义按键界面就会显示为可以自定义三个按键。而在实现时,我们不需要添加后缀:
// 这里不需要添加后缀!
gameKey.realize('@skill_doubleAttack', () => {
toggleSkill();
});
或者,添加后缀的话,会精确匹配到对应后缀的按键:
// 只有按下 @skill_doubleAttack_1 对应的按键才会触发,而 @skill_doubleAttack_2 等不会触发!
gameKey.realize('@skill_doubleAttack_1', () => {
toggleSkill();
});
拓展-按下时触发
默认情况下,我们实现的按键都是在按键抬起时触发,如果我们需要按下时触发,我们需要在调用 realize
函数时额外传入一个配置项:
:::code-group
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时单次触发
{ type: 'down' } // [!code ++]
);
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时持续触发
{ type: 'down-repeat' } // [!code ++]
);
gameKey.realize(
'@skill_doubleAttack',
() => {
toggleSkill();
},
// 按下时节流触发,节流间隔为 100ms
{ type: 'down-throttle', throttle: 100 } // [!code ++]
);
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 时,按键实现就会遵循新创建的作用域,关闭时自动回到上一层,这就实现了上述功能。