模拟Python按键以控制游戏

41

我正在尝试使用Kinect和Python控制游戏(我的两个测试游戏是《半条命2》和《我的世界》)。 除了一件事情外,一切都正常。 游戏将响应模拟的鼠标事件和模拟的鼠标移动(鼠标事件通过ctypes完成,使用pywin32完成鼠标移动)。 然而问题在于游戏忽略模拟的按键。 它们都能在聊天窗口(Minecraft)或开发人员控制台(Half Life 2)中捕捉到模拟的按键,但在玩实际游戏时却不能。

我已经尝试了几种发送按键的方法:

import win32com.client as client
wsh = client.Dispatch('WScript.Shell')
wsh.AppActivate(gameName)
wsh.SendKeys(key)

并且:

import win32api
win32api.keybd_event(keyHexCode, 0, 0)

并且:

import ctypes
import time

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))

我应该指出,最后一个代码不是我写的,而是来自于 Stack Overflow 上另一个问题的答案。

有人知道为什么这些代码都不起作用,正确的做法是什么吗?


1
如果没有适当的回应,我可以建议检查Minecraft的源代码,看看在聊天和游戏玩法之间键盘事件的收集和处理方式是否有区别。当我回家后,我会自己去看一下。 - Logan
是的,它仍然没有工作。 - user573949
Blacklight和Tribes都是虚幻引擎,因此我认为您可以在所有虚幻游戏中获得完全兼容性。我找不到有关Source如何处理键盘事件的任何信息,也没有使用过它。Minecraft是“开源”的,您只需要反混淆.jar并查看它们如何处理鼠标点击即可。 - Logan
你尝试过我下面发布的DirectInput方法吗? - Robin
我现在会尝试一下,之前我已经做好了永远无法解决这个问题的准备,并停止查看这篇帖子。 - user573949
显示剩余4条评论
2个回答

50

我刚刚尝试在《半条命2》中模拟按键时遇到了同样的问题。正如Robin所说,解决方案是使用ScanCodes而不是VKs。

我编辑了你最后的代码示例,以便它使用ScanCodes。我在《半条命2》中尝试过,它完全可以正常工作:

import ctypes
import time

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( 0, hexKeyCode, 0x0008, 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( 0, hexKeyCode, 0x0008 | 0x0002, 0, ctypes.pointer(extra) )
    x = Input( ctypes.c_ulong(1), ii_ )
    ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

# directx scan codes http://www.gamespp.com/directx/directInputKeyboardScanCodes.html
while (True):
    PressKey(0x11)
    time.sleep(1)
    ReleaseKey(0x11)
    time.sleep(1)

1
我知道这是一个非常老的答案,但值得一试。首先非常感谢您提供如此清晰的答案,但我无法弄清楚如何将其定制为鼠标移动并单击游戏中的特定位置。非常感谢任何帮助。 - sword1st
1
有人可以指导我如何使用 MouseInput 类吗? - Sayok Majumder
1
如果可能的话,我还想看一个包含MouseInput示例的例子。 - jmoreno

7
很可能游戏正在使用DirectInput设备。因此,游戏期望DirectInput按键操作。根据这个论坛帖子的最后一篇文章,DirectInput响应ScanCodes而不是VKs。您可以尝试使用这个工具发送DirectInput按键操作。该开发者还提供了源代码和详细说明。
如果这样做有效,您只需尝试发送适当的ScanCodes而不是VKs (Scancodes列表)
还有一个名为DirectPython的旧项目,它允许您与DirectX/DirectInput进行交互。

1
那个工具的下载链接已经失效了,你知道如何使用DirectPython实际发送DirectInput按键吗?它似乎只设置为监听它们,而不是派发它们。 - user573949

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接