按键映射

按键映射

按键映射(key mapping,也称 key binding)定义与鼠标点击、按键等输入关联的特定操作。只要客户端能够接收输入,就可以检查映射定义的操作。用户还可以通过控制选项菜单将每个映射分配给任意输入。

注册 KeyMapping

仅在物理客户端上监听 Mod 事件总线的 RegisterKeyMappingsEvent,再调用 #register,即可注册 KeyMapping。

// In some physical client only class

// Key mapping is lazily initialized so it doesn't exist until it is registered
public static final Lazy<KeyMapping> EXAMPLE_MAPPING = Lazy.of(() -> /*...*/);

@SubscribeEvent // on the mod event bus only on the physical client
public static void registerBindings(RegisterKeyMappingsEvent event) {
    event.register(EXAMPLE_MAPPING.get());
}

创建 KeyMapping

使用构造函数创建 KeyMapping。它接收定义映射名称的翻译键、默认输入,以及决定映射在控制选项菜单中所属分类的 KeyMapping.Category。

建议 要使用自定义分类,可以用 Identifier 创建新的 KeyMapping.Category,并仅在物理客户端的 Mod 事件总线上,通过 RegisterKeyMappingsEvent#registerCategory 注册。分类对应的翻译键为 key.category.<namespace>.<path>。

public static final KeyMapping.Category EXAMPLE_CATEGORY = new KeyMapping.Category(Identifier.fromNamespaceAndPath("examplemod", "category"));

@SubscribeEvent // on the mod event bus only on the physical client
public static void registerBindings(RegisterKeyMappingsEvent event) {
    // Register category
    event.registerCategory(EXAMPLE_CATEGORY);

    // Register binding with category used
    event.register(EXAMPLE_MAPPING.get());
}

默认输入

每个按键映射都有一个通过 InputConstants.Key 提供的默认输入。每种输入由 InputConstants.Type 和整数构成:前者定义提供输入的设备,后者定义设备上对应输入的标识。

原版提供三种输入类型:KEYSYM 使用 GLFW 按键常量表示键盘;SCANCODE 使用平台相关扫描码表示键盘;MOUSE 表示鼠标。

提示 强烈建议键盘使用 KEYSYM 而不是 SCANCODE,因为 GLFW 按键常量不依赖具体系统。更多信息请参阅 GLFW 文档。

整数的含义取决于提供的类型。所有输入代码均在 GLFW 中定义:KEYSYM 常量以 GLFW_KEY_* 为前缀,MOUSE 代码以 GLFW_MOUSE_* 为前缀。

new KeyMapping(
    "key.examplemod.example1", // Will be localized using this translation key
    InputConstants.Type.KEYSYM, // Default mapping is on the keyboard
    GLFW.GLFW_KEY_P, // Default key is P
    KeyMapping.Category.MISC // Mapping will be in the misc category
)

提示 如果不希望映射具有默认输入,应将其设为 InputConstants#UNKNOWN。原版构造函数要求通过 InputConstants$Key#getValue 提取输入代码,而 NeoForge 构造函数可以直接接收原始输入字段。

IKeyConflictContext

并非所有映射都在所有场景使用。有些映射仅用于 GUI,另一些只用于游戏内。为避免相同按键在不同场景的映射相互冲突,可以指定 IKeyConflictContext。

每个冲突上下文都包含两个方法:#isActive 判断当前游戏状态下能否使用该映射,#conflicts 判断此映射是否与相同或不同上下文中的另一个按键冲突。

NeoForge 目前通过 KeyConflictContext 定义三个基本上下文:默认的 UNIVERSAL 表示所有场景;GUI 表示仅当 Screen 打开时可用;IN_GAME 表示仅当 Screen 未打开时可用。实现 IKeyConflictContext 可以创建新的冲突上下文。

new KeyMapping(
    "key.examplemod.example2",
    KeyConflictContext.GUI, // Mapping can only be used when a screen is open
    InputConstants.Type.MOUSE, // Default mapping is on the mouse
    GLFW.GLFW_MOUSE_BUTTON_LEFT, // Default mouse input is the left mouse button
    EXAMPLE_CATEGORY // Mapping will be in the new example category
)

KeyModifier

开发者可能希望按住修饰键时映射具有不同的行为,例如 G 和 CTRL + G。为此,NeoForge 在构造函数中添加了 KeyModifier 参数,可以为任何输入附加 Control(KeyModifier#CONTROL)、Shift(KeyModifier#SHIFT)或 Alt(KeyModifier#ALT)。默认的 KeyModifier#NONE 表示不使用修饰键。

在控制选项菜单中,同时按住修饰键和相应输入,就可以添加修饰键。

new KeyMapping(
    "key.examplemod.example3",
    KeyConflictContext.UNIVERSAL,
    KeyModifier.SHIFT, // Default mapping requires shift to be held down
    InputConstants.Type.KEYSYM, // Default mapping is on the keyboard
    GLFW.GLFW_KEY_G, // Default key is G
    KeyMapping.Category.MISC
)

检查 KeyMapping

可以检查 KeyMapping 是否被点击,并根据检查发生的时机,在条件判断中执行相应逻辑。

游戏内

在游戏内,应监听事件总线上的 ClientTickEvent.Post,并在 while 循环中检查 KeyMapping#consumeClick。该方法仅会对尚未处理的输入次数返回 true,因此不会无限循环使游戏停滞。

@SubscribeEvent // on the game event bus only on the physical client
public static void onClientTick(ClientTickEvent.Post event) {
    while (EXAMPLE_MAPPING.get().consumeClick()) {
        // Execute logic to perform on click here
    }
}

注意 不要用 InputEvent 替代 ClientTickEvent.Post。这些事件只分别处理键盘和鼠标输入,无法涵盖其他输入。

GUI 内

在 GUI 中,可以在某个 GuiEventListener 方法中,通过 IKeyMappingExtension#isActiveAndMatches 检查映射。最常用的方法是 #keyPressed 和 #mouseClicked。

#keyPressed 接收 KeyEvent,其中包含 GLFW 按键常量、平台相关扫描码以及当前按住的修饰键位字段。通过 InputConstants#getKey 创建输入后,就可以与映射比较。映射方法内部已经检查修饰键。

// In some Screen subclass
@Override
public boolean keyPressed(KeyEvent event) {
    if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(event))) {
        // Execute logic to perform on key press here
        return true;
    }
    return super.keyPressed(event);
} 

提示 如果要检查的键盘按键属于并非由你控制的界面,可以改为监听游戏事件总线上的 ScreenEvent.KeyPressed 的 Pre 或 Post 事件。

#mouseClicked 接收 MouseButtonEvent(包含鼠标 x、y 位置及被点击的 MouseButtonInfo),以及一个表示是否双击的 boolean。通过 InputConstants.Type#getOrCreate 和 MOUSE 输入类型创建输入后,就可以与映射比较。

// In some Screen subclass
@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
    if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.Type.MOUSE.getOrCreate(event.button()))) {
        // Execute logic to perform on mouse click here
        return true;
    }
    return super.mouseClicked(event, doubleClick);
} 

提示 如果要检查的鼠标按键属于并非由你控制的界面,可以改为监听游戏事件总线上的 ScreenEvent.MouseButtonPressed 的 Pre 或 Post 事件。


来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。