用Python向Windows游戏发送按键?

12 投票
2 回答
10298 浏览
提问于 2025-04-18 02:26

我在Windows环境下使用Python,写了一个脚本来自动化处理一个我熟悉的游戏中的一些任务。这个任务需要频繁使用鼠标和键盘。

不过,这个脚本有一个问题:它无法向应用程序发送按键。我尝试了至少三种不同的方法,下面会列出这些方法,还有一些变种(我也看了很多类似的问题和答案,但都没有解决)。

第一种方法,使用了win32api模块:

f = 0x46 # VirtualKey Code of the letter "F", see http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx 

win32api.keybd_event(f,0,0,0) # holds the "F" key down
time.sleep(2) # waits 2 seconds
win32api.keybd_event(f,0,win32con.KEYEVENTF_KEYUP,0) # releases the key

这个方法没什么特别的,能在任何文本编辑器、浏览器中正常工作(比如能输入一个“f”)。但是,如果我打开像《反恐精英》这样的游戏,按键就“消失”了——也就是说,什么都没有发生。另一方面,如果我打开《反恐精英》的控制台,那么按键被识别(就像在记事本中一样)。我在另一个游戏《英雄联盟》中测试了,结果也是一样。在实际游戏中,没有检测到按键。不过,如果我在游戏中打开聊天框,然后重新运行脚本,按键就会被聊天框识别。

接下来是第二种方法:

shell = win32com.client.Dispatch("WScript.Shell")
shell.SendKeys("F")

结果和上面完全一样。在所有其他地方都能正常工作,但在游戏中只在聊天框中有效。

第三种方法(这个方法的来源是另一个StackOverflow帖子),更高级一些(调用了SendInput()),使用了ctypes模块。理论上,这三种方法中,这个方法最接近模拟实际的物理按键:

SendInput = ctypes.windll.user32.SendInput

# C struct redefinitions 
PUL = ctypes.POINTER(ctypes.c_ulong)
class KeyBdInput(ctypes.Structure):
    _fields_ = [("wVk", ctypes.c_ushort),
                ("wScan", ctypes.c_ushort),
                ("dwFlags", ctypes.c_ulong),
                ("time", ctypes.c_ulong),
                ("dwExtraInfo", PUL)]

class HardwareInput(ctypes.Structure):
    _fields_ = [("uMsg", ctypes.c_ulong),
                ("wParamL", ctypes.c_short),
                ("wParamH", ctypes.c_ushort)]

class MouseInput(ctypes.Structure):
    _fields_ = [("dx", ctypes.c_long),
                ("dy", ctypes.c_long),
                ("mouseData", ctypes.c_ulong),
                ("dwFlags", ctypes.c_ulong),
                ("time",ctypes.c_ulong),
                ("dwExtraInfo", PUL)]

class Input_I(ctypes.Union):
    _fields_ = [("ki", KeyBdInput),
                 ("mi", MouseInput),
                 ("hi", HardwareInput)]

class Input(ctypes.Structure):
    _fields_ = [("type", ctypes.c_ulong),
                ("ii", Input_I)]

# Actuals Functions

def PressKey(hexKeyCode):

    extra = ctypes.c_ulong(0)
    ii_ = Input_I()
    ii_.ki = KeyBdInput( hexKeyCode, 0x48, 0, 0, ctypes.pointer(extra) )
    x = Input( ctypes.c_ulong(1), ii_ )
    ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

def ReleaseKey(hexKeyCode):

    extra = ctypes.c_ulong(0)
    ii_ = Input_I()
    ii_.ki = KeyBdInput( hexKeyCode, 0x48, 0x0002, 0, ctypes.pointer(extra) )
    x = Input( ctypes.c_ulong(1), ii_ )
    ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))


def KeyPress():
    PressKey(0x46) # press F
    time.sleep(.5)
    ReleaseKey(0x46) #release F

...但它也不管用。奇怪的是,它表现出的行为和之前的三种方法完全一样:在任何文本编辑器或简单应用中都能工作,但在游戏中要么被忽略,要么只在游戏的聊天部分被识别。

如果让我猜,我觉得这些游戏是通过其他方式获取键盘事件的,而我用的这三种方法没有覆盖到,因此它们被忽略了。

我希望能得到一些帮助。如果可能的话,提供一些在《反恐精英》、《英雄联盟》或类似游戏中有效的具体代码示例,这样我就有了一个起点。

2 个回答

1

你可以试着用 EnumWindows() 来列举应用程序的所有窗口,然后直接对游戏的主窗口使用 SendMessage() 发送消息。

6

你的游戏可能在用DirectInput。可以试试用DirectInput的扫描码,看看这两种方法能不能解决问题。快速搜索一下,可以找到这个网站:http://www.gamespp.com/directx/directInputKeyboardScanCodes.html

撰写回答