You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity游戏前台运行时拦截Windows键失效问题排查与解决

问题原因分析

1. 钩子调用顺序倒置

WH_KEYBOARD_LL 钩子的执行逻辑是最后注册的钩子最先处理输入事件。如果 Unity(尤其是启用新 Input System 后)的内部钩子比你的钩子更早完成注册,那么游戏前台运行时,Unity 的钩子会优先接收 Win 键事件。若 Unity 的钩子返回0(允许事件继续传递),即便你的钩子后续拦截并返回1,系统已经收到了 Unity 钩子传递的事件,最终还是会触发开始菜单。而游戏后台时,Unity 的输入钩子会降低优先级或暂停处理,你的钩子成为第一个处理者,拦截逻辑才能生效。

2. Unity Input System 的输入抢占

新 Input System 会深度接管输入管线,可能在低级别钩子之前就完成了按键事件的处理,或者其注册的钩子会强制传递 Win 键事件。即便回滚到旧 Input System,项目的 Library 文件夹可能残留了新系统的配置,导致问题持续。

3. 钩子线程的消息循环失效

WH_KEYBOARD_LL 钩子依赖注册线程的消息循环来处理事件。如果你的 DLL 是在 Unity 主线程中注册钩子,前台运行时 Unity 主线程被游戏逻辑占据,钩子线程的消息循环无法及时处理输入事件,导致拦截失效。


解决办法

1. 延迟注册钩子,确保优先级

在 Unity 获得焦点后延迟注册钩子,让 Unity 的内部钩子先完成注册,你的钩子成为最后注册的那个(最先处理事件):

[DllImport("YourHookDll.dll")]
private static extern bool RegisterWinKeyHook();
[DllImport("YourHookDll.dll")]
private static extern void UnregisterWinKeyHook();

void OnApplicationFocus(bool hasFocus)
{
    if (hasFocus)
    {
        // 延迟100ms注册,确保Unity内部钩子初始化完成
        Invoke(nameof(RegisterHookDelayed), 0.1f);
    }
    else
    {
        UnregisterWinKeyHook();
    }
}

void RegisterHookDelayed()
{
    RegisterWinKeyHook();
}

2. 修改钩子逻辑,阻断后续传递

在你的 C++ 钩子函数中,拦截 Win 键时不要调用CallNextHookEx,直接返回1阻断事件传递给后续钩子(包括 Unity 的):

#include <windows.h>

HHOOK g_hook = NULL;

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0)
    {
        KBDLLHOOKSTRUCT* pKeyboard = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
        // 拦截左右Win键
        if (pKeyboard->vkCode == VK_LWIN || pKeyboard->vkCode == VK_RWIN)
        {
            // 直接返回非0值,不调用CallNextHookEx,阻断后续处理
            return 1;
        }
    }
    // 其他按键正常传递
    return CallNextHookEx(g_hook, nCode, wParam, lParam);
}

extern "C" __declspec(dllexport) bool RegisterWinKeyHook()
{
    g_hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0);
    return g_hook != NULL;
}

extern "C" __declspec(dllexport) void UnregisterWinKeyHook()
{
    if (g_hook != NULL)
    {
        UnhookWindowsHookEx(g_hook);
        g_hook = NULL;
    }
}

3. 清理Unity配置残留

  • 完全删除项目根目录下的Library文件夹,重新打开Unity让其自动生成新的配置文件,确保回滚Input System后无残留。
  • Project Settings > Player中,将Input Handling设置为Old Input System(若使用旧系统),或在新Input System的配置中移除所有Win键的绑定规则。

4. 系统级热键拦截

使用RegisterHotKey注册Win键为热键,让系统优先将事件发送到你的程序,绕过开始菜单的触发逻辑:

// 在DLL加载时注册热键
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        // 获取Unity主窗口句柄
        HWND unityWnd = FindWindowA("UnityWndClass", NULL);
        // 注册左右Win键为热键
        RegisterHotKey(unityWnd, 1, 0, VK_LWIN);
        RegisterHotKey(unityWnd, 2, 0, VK_RWIN);
        break;
    case DLL_PROCESS_DETACH:
        HWND unityWndDetach = FindWindowA("UnityWndClass", NULL);
        UnregisterHotKey(unityWndDetach, 1);
        UnregisterHotKey(unityWndDetach, 2);
        break;
    }
    return TRUE;
}

同时在Unity中通过子类化窗口过程(WndProc)监听热键消息,确保事件被正确拦截。


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

火山引擎 最新活动