Unity Windows 页游老板键
文章目录
介绍
老板键是一种热键或热键组合,用于快速隐藏游戏或其他无关工作的程式,并让显示器呈现正常工作时的画面,藉以欺瞒老板和同事等,达到保护您隐私的目的,使之以为上班时间进行娱乐的员工在做自己份内的工作。
需求
Unity Windows 页游需要增加老板键,基本需求:
- 全局快捷键显示隐藏游戏窗口
- 支持自定义快捷键
环境
- Unity 5.6.6f2
- Windows 7
- Visual Studio 2019
编译 DLL
参考 Unity 官方文档以及网上质量较高的博客文章,了解如何编译 DLL。
- Unity - Manual: Native Plugins
- Unity - Manual: Building Plugins for Desktop Platforms
- Unity/C++混合编程全攻略!——基础准备 - 游戏开发之路
窗口操作
显示隐藏窗口
通过下面两个方法就可以获得 Unity 窗口并进行显示与隐藏。
- ShowWindow function (winuser.h) - Win32 apps | Microsoft Docs
- GetActiveWindow function (winuser.h) - Win32 apps | Microsoft Docs
设置窗口焦点
在从后台恢复窗口后,必须让游戏窗口获得焦点,这样游戏才能响应鼠标键盘操作。
兼容不同情况
使用下面这一整套调用来兼容各种奇怪的情况
|
|
全局快捷键
原理
找到以下介绍全局快捷键的文章,了解了大概的原理
核心原理非常简单,只需要注册快捷键消息,然后在消息循环中处理快捷键消息就可以了。
例子
Visual Studio 2019 打开、升级、编译、运行,当焦点不在窗口时也可以正确响应全局快捷键。
核心代码 GlobalHotkeyTest/main.cpp at master · efevans/GlobalHotkeyTest · GitHub 与 RegisterHotKey function (winuser.h) - Win32 apps | Microsoft Docs 的例子代码一样。
消息注入
由于必须要使用消息循环来处理快捷键事件,第一时间想到的方法是使用类似钩子的方法注入到 Unity 窗口的消息循环中,截获快捷键 WM_HOTKEY 消息进行处理。
Installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. These events are associated either with a specific thread or with all threads in the same desktop as the calling thread.
参考这个回答编写钩子
必须使用 GetWindowLongPtr 与 GWLP_HINSTANCE 来同时支持 x86 与 x86_64
|
|
- How to obtain HINSTANCE using HWND?
- GetWindowLongA function (winuser.h) - Win32 apps | Microsoft Docs
- c++ - How do I call SetWindowLong() in the 64-bit versions of Windows? - Stack Overflow
SetWindowsHookEx
注册时提示以下错误,需要使用 DLL Attach 时传入的 HMODULE
ERROR_HOOK_NEEDS_HMOD 1428 (0x594) Cannot set nonlocal hook without a module handle.
但实际测试退出后发现 SetWindowsHookEx
会将 DLL 注入到所有的进程中,并不是需要的结果,需要尝试其他方案。
自定义消息循环
尝试开启线程,在线程中创建窗口并执行消息循环,捕获消息处理。
使用下面博客文章的方案成功
错误(活动) E0167 “const wchar_t *” 类型的实参与 “wchar_t *” 类型的形参不兼容
调用方法 RegisterDLLWindowClass((wchar_t* )L"XXXX");
时对参数进行强制类型转换
自定义按键
这里可以使用 UGUI 的 InputField
在获得焦点时在 Update 方法中处理自定义按键显示。
按键映射
需要将 Unity 的 KeyCode 转换为 win32 的 Virtual Key Codes 与显示用的字符串,这里需要使用查找替换以及文本编辑器的列编辑生成最后需要的字典
有几个坑:
- Unity 中 Ctrl Alt Shift 分左右两个按键,但是注册全局快捷键时并不区分
- 编辑器下已占用
Ctrl+Shift+数字键
与Ctrl+Alt+数字键
等一堆快捷键,测试时需要注意是否冲突 - Input.GetKeyDown 只能获取按下那一帧,需要改用 Input.GetKey
- Input.anyKeyDown 会响应鼠标按键,因此编写逻辑时需要额外处理
注册按键
直接调用 RegisterHotKey
方法只会将事件注册当调用的线程,通过 GetLastError
拿到错误码查到错误:
ERROR_WINDOW_OF_OTHER_THREAD 1408 (0x580) Invalid window; it belongs to other thread.
这是因为 Unity 主线程与 DLL 创建的事件线程不是同一个,因此需要将快捷键绑定通过事件的方式传给线程,在线程事件响应函数中再注册快捷键。
使用给线程发消息的 API 无效
PostThreadMessage(bossKeyThreadId, WM_USER, fsModifiers, keyUnique);
最终使用给窗口发消息的 API 成功
PostMessage(bossKeyHWND, WM_USER, fsModifiers, keyUnique);
检测冲突
要想调用,必须在 Unity 主线程才可以,否则可能会崩溃:
It is technically possible but your C++ plugin must run in the engines thread, otherwise it will crash if the callback you try to call accesses anything from the unity engine. Unity is not thread safe and you must not access its “space” from another thread.
这里可以改为 Unity 侧主动查询 C++ 侧。
核心代码
|
|
参考资料
- MessageBox function (winuser.h) - Win32 apps | Microsoft Docs
- Windows客户端开发–必须清楚HWND、HANDLE、HMODULE、HINSTANCE的区别_一蓑烟雨任平生 也无风雨也无晴-CSDN博客
- development process - What is the opposite of initialize (or init)? - Software Engineering Stack Exchange
- winapi - How can I get HINSTANCE from a DLL? - Stack Overflow
- Hooks Overview - Win32 apps | Microsoft Docs
- 如何创建消息循环? - 没事造轮子
文章作者 狂飙
初次发布 2021-04-25 23:41:13 +0800
永久链接 https://networm.me/2021/04/25/unity-windows-boss-hotkey/