You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中基于LWJGL实现类似KeyListener的手柄按键输入显示?

在LWJGL中实现手柄按键的监听式输入(替代轮询)

核心思路

利用GLFW的手柄状态回调机制,封装成类似KeyListener的自定义监听器接口,在按键状态变化时自动触发回调方法,彻底替代轮询循环方案。

实现步骤

1. 定义自定义监听器接口

先创建一个规范按键回调的接口,和AWT的KeyListener逻辑对齐:

public interface GamepadKeyListener {
    void onGamepadKeyPressed(int gamepadId, int key);
    void onGamepadKeyReleased(int gamepadId, int key);
}

2. 封装GLFW手柄回调逻辑

通过GLFW的全局手柄回调,监听手柄连接、断开及按键状态变化,对比前后按键状态来触发自定义监听器的方法:

import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GamepadInputHandler {
    // 存储每个手柄的上一次按键状态
    private static final Map<Integer, boolean[]> previousButtonStates = new HashMap<>();
    // 存储所有注册的监听器
    private static final List<GamepadKeyListener> listeners = new ArrayList<>();

    public static void init() {
        // 初始化GLFW
        if (!GLFW.glfwInit()) {
            throw new IllegalStateException("无法初始化GLFW");
        }

        // 注册手柄状态回调
        GLFW.glfwSetJoystickCallback((joy, event) -> {
            if (event == GLFW.GLFW_CONNECTED) {
                // 手柄连接时初始化状态
                boolean[] initialButtons = GLFW.glfwGetJoystickButtons(joy);
                if (initialButtons != null) {
                    previousButtonStates.put(joy, initialButtons);
                }
            } else if (event == GLFW.GLFW_DISCONNECTED) {
                // 手柄断开时清理状态
                previousButtonStates.remove(joy);
            } else {
                // 处理按键状态变化事件
                boolean[] currentButtons = GLFW.glfwGetJoystickButtons(joy);
                if (currentButtons == null) return;

                boolean[] previousButtons = previousButtonStates.getOrDefault(joy, new boolean[currentButtons.length]);

                // 遍历所有按键,对比状态差异
                for (int i = 0; i < currentButtons.length; i++) {
                    if (currentButtons[i] != previousButtons[i]) {
                        if (currentButtons[i]) {
                            // 触发按键按下事件
                            listeners.forEach(listener -> listener.onGamepadKeyPressed(joy, i));
                        } else {
                            // 触发按键释放事件
                            listeners.forEach(listener -> listener.onGamepadKeyReleased(joy, i));
                        }
                    }
                }

                // 更新存储的按键状态
                previousButtonStates.put(joy, currentButtons);
            }
        });
    }

    // 添加监听器
    public static void addGamepadKeyListener(GamepadKeyListener listener) {
        listeners.add(listener);
    }

    // 移除监听器(可选)
    public static void removeGamepadKeyListener(GamepadKeyListener listener) {
        listeners.remove(listener);
    }

    // 销毁资源
    public static void destroy() {
        GLFW.glfwTerminate();
    }
}

3. 注册监听器实现显示功能

在主程序中初始化输入处理器,注册监听器并实现按键显示逻辑:

import org.lwjgl.glfw.GLFW;

public class Main {
    public static void main(String[] args) {
        // 初始化手柄输入处理器
        GamepadInputHandler.init();

        // 注册监听器,实现按键显示
        GamepadInputHandler.addGamepadKeyListener(new GamepadKeyListener() {
            @Override
            public void onGamepadKeyPressed(int gamepadId, int key) {
                // 把GLFW按键ID转为可读名称
                String keyName = switch (key) {
                    case GLFW.GLFW_GAMEPAD_BUTTON_A -> "A";
                    case GLFW.GLFW_GAMEPAD_BUTTON_B -> "B";
                    case GLFW.GLFW_GAMEPAD_BUTTON_X -> "X";
                    case GLFW.GLFW_GAMEPAD_BUTTON_Y -> "Y";
                    case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> "左肩键";
                    case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> "右肩键";
                    case GLFW.GLFW_GAMEPAD_BUTTON_START -> "开始键";
                    case GLFW.GLFW_GAMEPAD_BUTTON_BACK -> "返回键";
                    default -> String.valueOf(key);
                };
                System.out.printf("手柄%d 按键【%s】被按下%n", gamepadId, keyName);
                // 替换为你的UI显示逻辑,比如更新文本框、渲染按键图标等
            }

            @Override
            public void onGamepadKeyReleased(int gamepadId, int key) {
                String keyName = switch (key) {
                    case GLFW.GLFW_GAMEPAD_BUTTON_A -> "A";
                    case GLFW.GLFW_GAMEPAD_BUTTON_B -> "B";
                    case GLFW.GLFW_GAMEPAD_BUTTON_X -> "X";
                    case GLFW.GLFW_GAMEPAD_BUTTON_Y -> "Y";
                    case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> "左肩键";
                    case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> "右肩键";
                    case GLFW.GLFW_GAMEPAD_BUTTON_START -> "开始键";
                    case GLFW.GLFW_GAMEPAD_BUTTON_BACK -> "返回键";
                    default -> String.valueOf(key);
                };
                System.out.printf("手柄%d 按键【%s】被释放%n", gamepadId, keyName);
                // 对应更新显示的释放状态
            }
        });

        // 创建最小窗口(GLFW需要事件循环,部分平台必须有窗口)
        long window = GLFW.glfwCreateWindow(1, 1, "Gamepad Input", 0, 0);
        if (window == 0) {
            throw new RuntimeException("无法创建GLFW窗口");
        }

        // 主事件循环,必须调用glfwPollEvents触发回调
        while (!GLFW.glfwWindowShouldClose(window)) {
            GLFW.glfwPollEvents();
            // 这里可以添加游戏渲染/逻辑代码
        }

        // 清理资源
        GamepadInputHandler.destroy();
    }
}

关键注意事项

  • 事件循环必须调用glfwPollEvents():GLFW的回调只有在处理事件队列时才会触发,主循环中需定期调用该方法(或glfwWaitEvents(),后者会阻塞直到有事件)。
  • 按键常量映射:GLFW提供GLFW_GAMEPAD_BUTTON_*系列常量,直接使用可让代码更易读,避免硬编码数字ID。
  • 多手柄支持:通过gamepadId区分不同手柄,天然支持多手柄同时连接的场景。
  • 窗口依赖:部分平台要求GLFW必须有至少一个窗口才能运行事件循环,建议创建最小化或隐藏窗口。

内容的提问来源于stack exchange,提问作者s0ju

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:50:31