为残障人士开发游戏语音控制软件,如何用Python实现游戏内按键长按?
实现Windows游戏中生效的键盘/鼠标长按功能
针对你开发残障人士游戏语音辅助软件的需求,核心解决方案是使用Windows原生的SendInput API——这是系统级的输入模拟接口,相比pynput、pyautogui等高层库,它能模拟更接近真实硬件的输入信号,更容易被DirectX/Unity等游戏引擎识别。
核心原理
多数游戏不会通过Windows消息队列读取输入,而是直接读取键盘/鼠标的硬件状态(比如DirectInput)。pynput这类库本质是发送WM_KEYDOWN/WM_KEYUP消息,无法被这类游戏识别;而SendInput会直接将输入事件注入到系统的输入流中,和真实按键/鼠标操作的逻辑一致,兼容性更强。
代码实现(Python + ctypes)
1. 键盘长按模拟
通过ctypes调用user32.dll中的SendInput,先定义所需的结构体和常量:
import ctypes import time # 加载user32.dll user32 = ctypes.WinDLL('user32', use_last_error=True) # 定义常量 INPUT_KEYBOARD = 1 KEYEVENTF_SCANCODE = 0x0008 KEYEVENTF_KEYUP = 0x0002 # 定义结构体 class INPUT(ctypes.Structure): _fields_ = [("type", ctypes.c_uint), ("ki", ctypes.c_void_p)] # 共用体简化处理 class KEYBDINPUT(ctypes.Structure): _fields_ = [("wVk", ctypes.c_ushort), ("wScan", ctypes.c_ushort), ("dwFlags", ctypes.c_uint), ("time", ctypes.c_uint), ("dwExtraInfo", ctypes.c_void_p)] # 辅助函数:转换虚拟键码到扫描码 def vk_to_scan(vk_code): return user32.MapVirtualKeyW(vk_code, 0) # 按住按键 def hold_key(vk_code): scan_code = vk_to_scan(vk_code) ki = KEYBDINPUT(0, scan_code, KEYEVENTF_SCANCODE, 0, None) input_obj = INPUT(INPUT_KEYBOARD, ctypes.byref(ki)) user32.SendInput(1, ctypes.byref(input_obj), ctypes.sizeof(INPUT)) # 松开按键 def release_key(vk_code): scan_code = vk_to_scan(vk_code) ki = KEYBDINPUT(0, scan_code, KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, 0, None) input_obj = INPUT(INPUT_KEYBOARD, ctypes.byref(ki)) user32.SendInput(1, ctypes.byref(input_obj), ctypes.sizeof(INPUT)) # 示例:按住W键2秒 if __name__ == "__main__": VK_W = 0x57 hold_key(VK_W) time.sleep(2) release_key(VK_W)
2. 鼠标长按模拟
同样用SendInput实现鼠标按键的长按:
# 补充鼠标相关常量和结构体 INPUT_MOUSE = 0 MOUSEEVENTF_LEFTDOWN = 0x0002 MOUSEEVENTF_LEFTUP = 0x0004 MOUSEEVENTF_RIGHTDOWN = 0x0008 MOUSEEVENTF_RIGHTUP = 0x0010 class MOUSEINPUT(ctypes.Structure): _fields_ = [("dx", ctypes.c_long), ("dy", ctypes.c_long), ("mouseData", ctypes.c_uint), ("dwFlags", ctypes.c_uint), ("time", ctypes.c_uint), ("dwExtraInfo", ctypes.c_void_p)] # 按住鼠标左键 def hold_left_mouse(): mi = MOUSEINPUT(0, 0, 0, MOUSEEVENTF_LEFTDOWN, 0, None) input_obj = INPUT(INPUT_MOUSE, ctypes.byref(mi)) user32.SendInput(1, ctypes.byref(input_obj), ctypes.sizeof(INPUT)) # 松开鼠标左键 def release_left_mouse(): mi = MOUSEINPUT(0, 0, 0, MOUSEEVENTF_LEFTUP, 0, None) input_obj = INPUT(INPUT_MOUSE, ctypes.byref(mi)) user32.SendInput(1, ctypes.byref(input_obj), ctypes.sizeof(INPUT)) # 示例:长按鼠标左键1秒 if __name__ == "__main__": hold_left_mouse() time.sleep(1) release_left_mouse()
关键注意事项
- 管理员权限:部分游戏会限制非管理员程序的输入模拟,运行脚本时右键选择“以管理员身份运行”。
- 扫描码兼容性:使用扫描码(而非虚拟键码)能更好兼容不同键盘布局和游戏,若
vk_to_scan转换有问题,可直接使用对应按键的扫描码(比如W键的扫描码是0x11)。 - 反作弊检测:多数主流反作弊系统(如Easy Anti-Cheat)不会拦截
SendInput,但部分硬核反作弊可能会,此时需考虑使用驱动级模拟,但门槛较高。 - 延迟控制:不要在短时间内频繁发送输入事件,给系统留足够的处理时间(比如用
time.sleep控制长按时长)。 - 测试顺序:先在记事本、画图等简单程序验证功能,再迁移到游戏中测试。
内容的提问来源于stack exchange,提问作者ViperEx76
相关产品推荐
相关产品推荐

