平台支持
Control Flex 通过 libgamepadjni + SDL3 读取手柄。桌面三平台的原生库与 SDL3 都随 JAR 提供,开箱即用;Android 上 SDL3 必须来自启动器 APK,因此不同启动器的行为并不完全一样。
支持总览
| 平台 | 架构 | SDL3 来源 | 状态 |
|---|---|---|---|
| Windows | x86_64 | JAR 内置(SDL3.dll) |
支持 |
| macOS | x86_64 · Apple Silicon (aarch64) | JAR 内置(libSDL3.0.dylib) |
支持 |
| Linux | x86_64 · aarch64 | JAR 内置(libSDL3.so) |
支持 |
| Android 启动器里的游戏 JVM |
arm64-v8a · armeabi-v7a · x86_64 | 启动器 APK 提供,JAR 不打包 | 支持 |
| iOS | — | — | 尚未支持 |
平台判定只看 os.version:以 Android- 开头即视为 Android,其余按桌面处理。os.name(Android 启动器同样写 Linux)只用于诊断日志,不参与判定。
桌面平台
- 无需安装 SDL3、驱动或额外运行库 —— 模组固定使用 JAR 内打包的 SDL3,不与系统里已有的 SDL 冲突,也不会去搜索系统路径。
- Windows 只随包提供 x86_64 的原生库(
gamepadjni.dll+SDL3.dll),没有 ARM64 版本 —— 在 ARM64 Windows(如骁龙 X 机型)上要用 x64 版 Java 运行,用 arm64 的 JVM 会报UnsatisfiedLinkError。 - macOS 同时提供 Intel 与 Apple Silicon 两份
libgamepadjni.dylib,Rosetta 与否都能用。 - Linux 提供 x86_64 与 aarch64 两份
libgamepadjni.so;Steam Deck 之类的 aarch64 设备同样在列。 - 手柄热插拔、多手柄识别、按手柄身份(名称 / 类型 / 序列号)记忆配置,在三个桌面平台上行为一致。
Android(启动器环境)
Android 版 Minecraft: Java Edition 由启动器提供的 JVM 运行,手柄链路因此分成两段:SDL3 来自启动器 APK(启动器已经在 ART 侧装好 SDL 的 glue),libgamepadjni.so 由模组自带并按 os.arch 解压加载。两者顺序固定 —— 先 SDL3,再 JNI 桥,否则动态链接器绑不到同一份库。
先改两项启动器设置
这两项都在启动器里改,不在模组里;换启动器、或实例目录被重置之后要重新设一次。不改也能进游戏,只是看起来像坏了:屏幕上多出一个乱飘的指针,或者手柄在设置里根本不出现。
1 · 隐藏屏幕上的虚拟鼠标指针
启动器画的指针是它自己的覆盖层,和模组的虚拟光标没有关系,两者不会互相同步 —— 结果就是屏幕上出现两个指针,或者指针停在上一次触屏的位置、和真正的点击热点差一截。
三种做法效果一样,看你用的启动器提供哪一种,任选其一:
- 把指针显示关掉(隐藏虚拟鼠标光标)
- 把指针大小设为
0 - 把指针不透明度设为
0(即全透明)
之后屏幕上只剩模组自己的光标,位置和点击判定一致。样式与缩放在游戏内的「光标样式」里改:System / Bedrock / Hand,缩放 0.1–1.0。
2 · 手柄输入模式改成 SDL 直通(ZL2 / FCL)
ZL2 与 FCL 的「手柄输入模式」默认是「映射(虚拟按键)」:手柄按键与摇杆先被翻译成虚拟键鼠事件,再交给游戏。这样 ControlFlex 拿不到原始设备 —— 设置里看不到手柄,死区、扳机行程、震动也全由启动器说了算。
改成 SDL 直通。ZL2 的路径是 设置 → 手柄设置 → 手柄输入模式。选项自己的说明就写着「手柄输入经 SDL/GLFW gamepad API 原样交给游戏,不经过按键映射」—— 正是要的状态:SDL3 直接打开设备,模组经 libgamepadjni 读到原始轴与按键,多手柄、热插拔、震动全靠这一步才能生效。
- 先确认同页顶部的手柄控制总开关是开着的 —— 只有在别的手柄或外设被误识别成手柄时才需要关掉它。
- 校验:进游戏后 设置 → 手柄选择 里能按名字看到设备,按任意键有反应。
- 代价:切到 SDL 直通后,「映射(虚拟按键)」对这个手柄不再生效。这是预期行为,映射全部交给模组的键位面板。
- 改完退出游戏重新启动一次,让启动器重新枚举设备。
已验证的启动器
| 启动器 | 如何找到启动器的 libSDL3.so |
|---|---|
| Fold Craft Launcher (FCL) | 设置 POJAV_NATIVEDIR 为 APK native 目录,走绝对路径 |
| Zalith Launcher 2 (ZL2) | 同上 |
| Mojo Launcher v3_openjdk |
不设 POJAV_NATIVEDIR;APK native 目录在 LD_LIBRARY_PATH 里,通过 System.loadLibrary("SDL3") 命中 |
| Amethyst Android | 设置 POJAV_NATIVEDIR,走绝对路径 |
这是兼容性说明,不是运行时白名单:只要启动器把 os.version 写成 Android-*,未列出的启动器同样会走 Android 路径。
ABI 与原生库
os.arch |
JAR 内目录 |
|---|---|
aarch64 / arm64 | native/android-arm64-v8a/ |
arm / armv7l / armv8l | native/android-armeabi-v7a/ |
x86_64 / amd64 | native/android-x86_64/ |
i386 及其他 |
不支持 —— 初始化失败,游戏仍可正常启动(只是没有手柄) |
Android 上的注意事项
不要覆盖 os.version。部分启动器的自定义 JVM 参数或版本 JSON 会把它改成普通内核号(例如 5.0)或干脆不写 Android- 前缀,这时模组会把环境当成桌面 Linux,去加载桌面的 SDL3,结果是手柄在设置里始终不可见。确认启动器注入 -Dos.version=Android-<版本> 没有被你的自定义参数覆盖。
- JAR 里没有 Android 版
libSDL3.so;不要在 mods 目录旁边再塞一份 SDL3,加载到没有启动器 glue 的 SDL 会让手柄永远不可见。 - 需要自带
libgamepadjni.so时,把CFX_LIB_PATH指向包含该文件的目录(系统属性优先于环境变量)。 - Android 上 SDL 与启动器共用同一个进程实例,所以退出游戏时模组不会
SDL_Quit整个库,避免拆掉启动器的 video / glue。 - 配置目录仍是
config/controlflex/,相对启动器为实例指定的游戏目录;换启动器(或换实例目录)时,配置不会自动跟过去。 - 震动、光标、分屏这类功能的实际可用度取决于启动器窗口模型与设备:分屏为桌面多实例场景设计,Android 上一般无法同时跑两个实例。
平台与依赖对照
| 平台 | 需要额外安装 | 配置文件位置 |
|---|---|---|
| Windows / macOS / Linux | 无(SDL3 内置) | config/controlflex/ |
| Android | 无(SDL3 由启动器提供;启动器需被本页列出的机制覆盖)—— 另外还要改上文那两项启动器设置 | 启动器实例目录下的 config/controlflex/ |
排障速查
| 现象 | 先检查什么 |
|---|---|
| 设置 → 手柄选择里完全没有设备(桌面) | JAR 是否与 MC 版本、加载器匹配(见 MC 版本支持);手柄是否被其他程序(Steam Input、远程串流等)独占 |
| Android 上游戏正常但手柄始终不可见 | os.version 是否被覆盖;启动器是否属于已验证的那几种;日志里 POJAV_NATIVEDIR 与 SDL3 路径是否打印出来 |
| 屏幕上出现两个指针,或指针位置和实际点击位置对不上 | 启动器的虚拟鼠标指针还开着 —— 隐藏它,或把大小 / 不透明度设为 0 |
| 手柄在启动器自带的按键映射里能用,但「设置 → 手柄选择」里始终没有它 | 手柄输入模式选的是「映射(虚拟按键)」而不是 SDL 直通 —— 见上文那两项设置 |
日志出现 UnsatisfiedLinkError |
Android 上要么是不支持的 ABI(如 32 位 x86),要么启动器 APK 没带 SDL3;桌面上是原生库与系统架构不匹配 —— 注意 Windows 只有 x86_64 版本,ARM64 机器上要换 x64 版 Java。 |
| iOS 设备上找不到入口 | iOS 尚未接入(平台层已预留判定位,但没有原生库) |