按键映射
按键映射(或按键绑定)定义与输入关联的特定操作,例如鼠标点击或按键。客户端能够接收输入时,可以检查映射定义的操作。每个映射还可通过控制选项菜单分配给任意输入。
原创说明图,依据 Forge 1.21.x 官方文档 绘制。
注册 KeyMapping
仅在物理客户端的模组事件总线上监听 RegisterKeyMappingsEvent,调用 #register 注册 KeyMapping。
// 在仅物理客户端使用的类中;延迟初始化,直到注册时才创建
public static final Lazy<KeyMapping> EXAMPLE_MAPPING = Lazy.of(() -> /*...*/);
// 仅物理客户端的模组事件总线
@SubscribeEvent
public void registerBindings(RegisterKeyMappingsEvent event) {
event.register(EXAMPLE_MAPPING.get());
}
创建 KeyMapping
通过构造函数创建 KeyMapping。它接收定义映射名称的翻译键、默认输入,以及定义控制选项菜单中所属分类的翻译键。
提示:提供原版没有的分类翻译键,即可将映射加入自定义分类。自定义分类翻译键应包含模组 ID,例如
key.categories.examplemod.examplecategory。
默认输入
每个映射都有通过 InputConstants$Key 提供的默认输入。输入由定义设备的 InputConstants$Type 和定义设备上对应输入标识符的整数构成。
原版提供三种输入类型:KEYSYM 通过 GLFW 按键标记定义键盘;SCANCODE 通过平台特定扫描码定义键盘;MOUSE 定义鼠标。
注意:强烈建议键盘使用
KEYSYM而不是SCANCODE,因为 GLFW 按键标记不绑定特定系统。详情见 GLFW 文档。
整数取决于输入类型。所有输入代码都在 GLFW 中定义:KEYSYM 标记以 GLFW_KEY_* 开头,MOUSE 代码以 GLFW_MOUSE_* 开头。
new KeyMapping(
"key.examplemod.example1", // 用此翻译键本地化
InputConstants.Type.KEYSYM, // 默认使用键盘
GLFW.GLFW_KEY_P, // 默认按键 P
"key.categories.misc" // 位于 misc 分类
)
注意:如果不应有默认绑定,将输入设为
InputConstants#UNKNOWN。原版构造函数要求通过InputConstants$Key#getValue提取输入代码;Forge 构造函数可以直接接收输入字段。
IKeyConflictContext
并非所有映射都用于所有上下文:有的仅用于 GUI,有的仅用于游戏中。可分配 IKeyConflictContext,避免不同上下文中同一按键的映射互相冲突。
冲突上下文有两个方法:#isActive 定义当前游戏状态下映射能否使用;#conflicts 定义它是否与相同或不同冲突上下文中的按键冲突。
Forge 通过 KeyConflictContext 定义三种基本上下文:默认的 UNIVERSAL 表示所有上下文均可使用;GUI 表示仅打开 Screen 时可用;IN_GAME 表示仅未打开 Screen 时可用。实现 IKeyConflictContext 可以创建新上下文。
new KeyMapping(
"key.examplemod.example2",
KeyConflictContext.GUI, // 仅屏幕打开时可用
InputConstants.Type.MOUSE, // 默认使用鼠标
GLFW.GLFW_MOUSE_BUTTON_LEFT, // 默认鼠标左键
"key.categories.examplemod.examplecategory" // 自定义分类
)
KeyModifier
开发者可能不希望按住修饰键时映射行为相同,例如 G 与 CTRL + G。Forge 构造函数增加 KeyModifier 参数,可为任意输入添加 Control(KeyModifier#CONTROL)、Shift(#SHIFT)或 Alt(#ALT)。默认 KeyModifier#NONE 不添加修饰键。
可在控制选项菜单中同时按住修饰键和对应输入,添加修饰键。
new KeyMapping(
"key.examplemod.example3",
KeyConflictContext.UNIVERSAL,
KeyModifier.SHIFT, // 默认需要按住 Shift
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_G, // 默认 G
"key.categories.misc"
)
检查 KeyMapping
可检查映射是否被点击,并在适当时机通过条件语句执行相应逻辑。
游戏中
在Forge 事件总线上监听 ClientTickEvent,在 while 循环中检查 KeyMapping#consumeClick。该方法只会按尚未处理的输入次数返回 true,因此不会无限阻塞游戏。
// 仅在物理客户端的 Forge 事件总线上
public void onClientTick(ClientTickEvent event) {
if (event.phase == TickEvent.Phase.END) { // tick 事件每刻触发两次,只执行一次
while (EXAMPLE_MAPPING.get().consumeClick()) {
// 在此执行点击逻辑
}
}
}
警告:不要用
InputEvent替代ClientTickEvent。键盘和鼠标有各自的事件,不能处理其他输入。
GUI 内
在 GUI 中,可在 GuiEventListener 方法中通过 IForgeKeyMapping#isActiveAndMatches 检查映射。最常用的是 #keyPressed 和 #mouseClicked。
#keyPressed 接收 GLFW 按键标记、平台特定扫描码和按住的修饰键位字段。通过 InputConstants#getKey 创建输入后可与映射比较,映射方法本身已检查修饰键。
@Override
public boolean keyPressed(int key, int scancode, int mods) {
if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) {
// 执行按键逻辑
return true;
}
return super.keyPressed(key, scancode, mods);
}
原文示例的父类调用使用了未声明的
x, y, button;译文已改为该方法的key, scancode, mods参数。
注意:如果屏幕不属于你的代码,可在 Forge 事件总线上监听
ScreenEvent$KeyPressed的Pre或Post事件。
#mouseClicked 接收鼠标 x/y 坐标和点击的按钮。使用 InputConstants$Type#getOrCreate 与 MOUSE 输入创建输入后,可与映射比较。
@Override
public boolean mouseClicked(double x, double y, int button) {
if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.Type.MOUSE.getOrCreate(button))) {
// 执行鼠标点击逻辑
return true;
}
return super.mouseClicked(x, y, button);
}
原文示例使用
InputConstants.TYPE,与上文所示的嵌套类型InputConstants.Type大小写不一致;译文采用后者。
注意:如果屏幕不属于你的代码,可在 Forge 事件总线上监听
ScreenEvent$MouseButtonPressed的Pre或Post事件。
原文:Key Mappings。