combind

combind

扩展原版按键绑定,增加组合键支持

游戏机制

本模组扩展了原版的按键绑定功能,增加了组合键支持,让你可以将任意动作绑定到和弦或按键序列上,而非单个按键。

仅客户端安装,无需服务端安装。

按键绑定界面

功能

和弦组合

  • 将动作绑定到按键组合,例如 左 Shift + E、左 Ctrl + 鼠标左键,或任意其他按住键 + 触发键的组合
  • 任意按键或鼠标按钮都可以作为组合中的触发键或按住键。录制时按下的最后一个键成为触发键,之前所有按住的键构成完整的组合
  • 无限制!你可以按住任意多个按键,创建任意复杂的组合
  • 可以后接序列组合

序列组合

  • 将动作绑定到重复按键,例如 双击 Q 或 三次按空格
  • 可配置的时间窗口决定按键之间必须多快才能被识别为序列
  • 无限制!你可以绑定任意多次点击

控制界面集成

  • 组合键录制直接嵌入原版的 控制 > 按键绑定 界面,无需单独的 UI
  • 按 Escape 可以解除按键绑定

配置

  • 允许冲突:禁用后,按下某个组合键时,不会同时触发其完全包含的子组合绑定。例如,同时绑定了 E 和 左 Shift + E,按下 左 Shift + E 通常会触发两者。禁用此选项可确保只触发 左 Shift + E。序列键同理:如果同一个键同时绑定了单次按压和多次点击(例如 Q 和 Q Q),按下该键只会触发单次按压绑定。像 左 Ctrl + E 和 左 Shift + E 这样的两个绑定不被视为冲突,因为按下其中一个绝不会意外触发另一个。只有当你故意同时按住 左 Ctrl + 左 Shift + E 时,两者才会同时触发。此设置仅适用于共享相同触发键的绑定(默认:开启)
  • 序列窗口:游戏中必须多快地重复按键才能被识别为一个序列(默认:400 毫秒)
  • 序列录制窗口:在控制界面录制序列时,录制器在两次点击之间等待的时间(默认:200 毫秒)
  • 设置保存到 config/combind.json

用例

本模组在按键稀缺、每个绑定都需要精心设计的整合包中表现出色,但在原版中同样能提供额外的灵活性。

实际例子

双击 Q(Q Q)(甚至三击 Q(Q Q Q)!)来丢弃物品,而不是单次按压。不再意外丢物。

兼容性

本模组除了 Fabric API 之外没有其他必需的依赖。它可以与以下模组配合使用:

  • ModMenu (推荐) — 在模组列表中添加一个配置按钮,作为手动编辑 config/combind.json 的替代方案
  • Controlling 和 Searchables — 使用类似 key:"Left Shift + E" 或 key:"Q Q" 的语法,按组合键搜索绑定

注意:某些特定的原版交互与特定的输入类型绑定,重新绑定后可能无法正常工作。例如,将鼠标按钮绑定到“打开物品栏”将无法像按键一样关闭它;将按键绑定到“选取方块”将无法像鼠标按钮一样在容器内支持拖拽复制方块。

注意:如果遇到任何兼容性问题,请在 GitHub Issues 页面 报告。


给模组开发者

使用 Combind API

Combind 在 net.pemiridosa.combind.api 下公开了三个 API 类,让你的模组可以注册带有完整组合键支持的按键绑定。

依赖设置

通过 Modrinth Maven 仓库将 Combind 添加为编译时依赖:

// build.gradle
repositories {
    maven { url "https://api.modrinth.com/maven" }
}

dependencies {
    compileOnly "maven.modrinth:combind:VERSION"
    localRuntime "maven.modrinth:combind:VERSION" // 可选:用于开发运行
}

在 fabric.mod.json 中声明依赖。如果你的模组需要 Combind,请使用 depends;如果是可选的,请使用 suggests:

"depends": {
    "combind": "*"
}

API 类

类 用途
CombindKeyBinding 包装了一个原版 KeyMapping 并添加了组合键支持——主要入口点
KeyCombo 描述一个组合键:单个键、和弦、序列、或和弦 + 序列
InputKey 代表一个物理输入——InputKey.keyboard(glfwCode) 或 InputKey.mouse(glfwButton)

示例

和弦——按住修饰键 + 触发键

将动作绑定到 左 Shift + E:

KeyMapping.Category category = KeyMapping.Category.register(
    Identifier.fromNamespaceAndPath("mymod", "mymod")
);

KeyMapping someActionMapping = KeyMappingHelper.registerKeyMapping(
    new KeyMapping("key.mymod.someaction", GLFW.GLFW_KEY_E, category)
);

// 左 Shift + E
CombindKeyBinding someActionBinding = CombindKeyBinding.of(someActionMapping,
    new KeyCombo(
        InputKey.keyboard(GLFW.GLFW_KEY_E),
        new InputKey[]{ InputKey.keyboard(GLFW.GLFW_KEY_LEFT_SHIFT) }
    )
);

someActionBinding
    .onPress(ctx -> LOGGER.info("按下动作 — 组合键: {}", ctx.combo().getDisplayName()))
    .onRelease(ctx -> LOGGER.info("释放动作 — 组合键: {}", ctx.combo().getDisplayName()));

带多个修饰键的和弦

将动作绑定到 左 Ctrl + 左 Shift + E:

KeyMapping someActionMapping = KeyMappingHelper.registerKeyMapping(
    new KeyMapping("key.mymod.someaction", GLFW.GLFW_KEY_E, category)
);

// 左 Ctrl + 左 Shift + E
CombindKeyBinding someActionBinding = CombindKeyBinding.of(someActionMapping,
    new KeyCombo(
        InputKey.keyboard(GLFW.GLFW_KEY_E),
        new InputKey[]{
            InputKey.keyboard(GLFW.GLFW_KEY_LEFT_CONTROL),
            InputKey.keyboard(GLFW.GLFW_KEY_LEFT_SHIFT)
        }
    )
);

someActionBinding
    .onPress(ctx -> LOGGER.info("按下动作 — 组合键: {}", ctx.combo().getDisplayName()))
    .onRelease(ctx -> LOGGER.info("释放动作 — 组合键: {}", ctx.combo().getDisplayName()));

序列——多次点击同一个键

将动作绑定到 M M(双击)或 M M M(三击):

KeyMapping someActionMapping = KeyMappingHelper.registerKeyMapping(
    new KeyMapping("key.mymod.someaction", GLFW.GLFW_KEY_M, category)
);

// 双击 M (M M)
CombindKeyBinding someActionBinding = CombindKeyBinding.of(someActionMapping,
    KeyCombo.sequence(InputKey.keyboard(GLFW.GLFW_KEY_M), 2)
);

// 三击 M (M M M) — 更安全
CombindKeyBinding someActionBinding = CombindKeyBinding.of(someActionMapping,
    KeyCombo.sequence(InputKey.keyboard(GLFW.GLFW_KEY_M), 3)
);

someActionBinding
    .onPress(ctx -> LOGGER.info("按下动作 — 组合键: {}", ctx.combo().getDisplayName()))
    .onRelease(ctx -> LOGGER.info("释放动作 — 组合键: {}", ctx.combo().getDisplayName()));

和弦 + 序列——按住修饰键的同时多次点击

将动作绑定到 左 Shift + Q Q(按住 Shift 的同时双击 Q):

// 左 Shift + Q Q
CombindKeyBinding binding = CombindKeyBinding.of(mapping,
    new KeyCombo(
        InputKey.keyboard(GLFW.GLFW_KEY_Q),
        new InputKey[]{ InputKey.keyboard(GLFW.GLFW_KEY_LEFT_SHIFT) },
        2
    )
);

鼠标按钮作为触发键

将动作绑定到 左 Ctrl + 鼠标左键:

// 左 Ctrl + 鼠标左键
CombindKeyBinding binding = CombindKeyBinding.of(mapping,
    new KeyCombo(
        InputKey.mouse(GLFW.GLFW_MOUSE_BUTTON_LEFT),
        new InputKey[]{ InputKey.keyboard(GLFW.GLFW_KEY_LEFT_CONTROL) }
    )
);

单个按键(无需显式组合键)

CombindKeyBinding.of(mapping) 会从 KeyMapping 的默认按键推断出初始组合键:

CombindKeyBinding binding = CombindKeyBinding.of(mapping); // 默认为 KeyMapping 的按键

未绑定——默认未分配按键

// 玩家必须在 控制 > 按键绑定 界面中进行配置
CombindKeyBinding binding = CombindKeyBinding.of(mapping, KeyCombo.unbound());

基于 Tick 的轮询(原版风格)

如果你更喜欢标准的 tick 轮询模式而不是回调,可以通过原版 KeyMapping 直接实现,无需改动:

ClientTickEvents.END_CLIENT_TICK.register(client -> {
    while (someActionMapping.consumeClick()) {
        // 处理每个排队的点击
    }

    if (someActionMapping.isDown()) {
        // 当前按住绑定
    }
});

更多细节,请查看 src/client/java/net/pemiridosa/combind/api 的完整实现。