You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python win32 PostMessage/SendMessage后台游戏点击位置异常求助

问题描述

我正在开发一个Python程序,用于后台操控视频游戏,这样就能在同一台电脑上同时处理其他事务。使用win32库实现功能,键盘输入和鼠标点击整体运行良好,但存在问题:执行鼠标点击时,点击会落在当前鼠标指向的游戏内位置,而非我指定的坐标。

初始测试代码:

handle = win32gui.FindWindow(None, "Video Game")

coords = (10, 10)

coords = win32api.MAKELONG(coords[0], coords[1])

win32gui.SendMessage(handle, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
win32gui.SendMessage(handle, win32con.WM_MOUSEMOVE, 0, coords)
win32api.SendMessage(handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, coords)
win32api.SendMessage(handle, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, coords)

win32gui.PostMessage(handle, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
win32gui.PostMessage(handle, win32con.WM_MOUSEMOVE, 0, coords)
win32api.PostMessage(handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, coords)
win32api.PostMessage(handle, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, coords)

当前完整代码:

import win32gui
import win32api
import win32con
import time


def LeftClick(handle, coords):

          coords1 = coords
          coords2 = win32gui.ScreenToClient(handle, coords)

          # Return 0, is invalid
          # handle1 = win32gui.ChildWindowFromPoint(handle,client_coords1)

          # It's the same handle
          #handle2 = win32gui.ChildWindowFromPoint(handle,client_coords2)

          tmp1 = win32api.MAKELONG(coords1[0], coords1[1])
          tmp2 = win32api.MAKELONG(coords2[0], coords2[1])

          # find on another stackoverflow response, not work
          tmp3 = coords1[0] << 15 | coords1[1]
          tmp4 = coords2[0] << 15 | coords2[1]

          # None of them work
          print(handle, tmp1, tmp2, tmp3, tmp4)

          win32gui.SendMessage(handle, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
          win32gui.SendMessage(handle, win32con.WM_MOUSEMOVE, 0, tmp1)
          win32api.SendMessage(handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, tmp1)
          win32api.SendMessage(handle, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, tmp1)

          win32gui.PostMessage(handle, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
          win32gui.PostMessage(handle, win32con.WM_MOUSEMOVE, 0, tmp1)
          win32api.PostMessage(handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, tmp1)
          win32api.PostMessage(handle, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, tmp1)


if __name__ == '__main__':
          time.sleep(2)

          coords = (10, 10)
          handle = win32gui.FindWindow(None, "Video Game")

          # click but not at coords
          LeftClick(handle, coords)

程序输出:

  • handler : 7341658
  • tmp1 : 655370
  • tmp2 : -1572878
  • tmp3 : 327690
  • tmp4 : -25

解决方案

1. 正确转换坐标并格式化消息参数

WM_MOUSEMOVE、WM_LBUTTONDOWN这类消息的lParam参数要求使用窗口客户端坐标,且必须通过MAKELONG将x、y坐标组合成标准32位值(低16位存x,高16位存y)。你之前的核心问题是直接传递屏幕坐标,或转换后的坐标组合方式不符合Windows消息规范。

修改后的LeftClick函数:

def LeftClick(handle, screen_coords):
    # 将屏幕坐标转换为游戏窗口的客户端坐标
    client_coords = win32gui.ScreenToClient(handle, screen_coords)
    # 用MAKELONG生成符合要求的lParam参数
    lparam = win32api.MAKELONG(client_coords[0], client_coords[1])
    
    # 激活窗口(部分游戏需要此步骤)
    win32gui.SendMessage(handle, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
    
    # 先移动鼠标到目标客户端坐标
    win32gui.SendMessage(handle, win32con.WM_MOUSEMOVE, 0, lparam)
    # 发送左键按下消息
    win32api.SendMessage(handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
    # 模拟真实点击的间隔,避免游戏忽略快速消息
    time.sleep(0.05)
    # 发送左键抬起消息
    win32api.SendMessage(handle, win32con.WM_LBUTTONUP, 0, lparam)

2. 定位正确的游戏渲染子窗口

多数3D游戏的实际渲染区域是主窗口的子控件(比如DirectX/OpenGL渲染窗口),直接给主窗口发消息不会生效。可以通过遍历子窗口找到目标控件:

def find_game_render_window(parent_handle):
    render_handle = None
    def callback(child_handle, extra):
        nonlocal render_handle
        # 根据窗口类名筛选,游戏渲染窗口类名通常包含Direct3D/OpenGL关键字
        class_name = win32gui.GetClassName(child_handle)
        if "Direct3D" in class_name or "OpenGL" in class_name:
            extra.append(child_handle)
            return False  # 找到后停止遍历
        return True
    children = []
    win32gui.EnumChildWindows(parent_handle, callback, children)
    return children[0] if children else parent_handle

# 使用示例
main_handle = win32gui.FindWindow(None, "Video Game")
game_handle = find_game_render_window(main_handle)
LeftClick(game_handle, (10, 10))

3. 兼容DirectInput类游戏

部分3D游戏不响应WM系列消息,而是直接读取原始输入(DirectInput)。这种情况下可以用SendInput模拟输入,但该方法默认操作前台窗口:

def send_input_click(screen_coords):
    # 移动鼠标到目标屏幕坐标
    win32api.SetCursorPos(screen_coords)
    # 模拟左键按下
    input_down = win32api.INPUT()
    input_down.type = win32con.INPUT_MOUSE
    input_down.mi.dwFlags = win32con.MOUSEEVENTF_LEFTDOWN
    win32api.SendInput(1, [input_down], win32con.sizeof(input_down))
    time.sleep(0.05)
    # 模拟左键抬起
    input_up = win32api.INPUT()
    input_up.type = win32con.INPUT_MOUSE
    input_up.mi.dwFlags = win32con.MOUSEEVENTF_LEFTUP
    win32api.SendInput(1, [input_up], win32con.sizeof(input_up))

注意:此方法需要将游戏窗口切换到前台,否则点击会落在当前激活窗口的对应位置。


关键注意事项

  • 部分游戏有反作弊机制,会拦截模拟输入,需确认游戏允许后台操作或使用更底层的模拟方式。
  • 多显示器环境下,需确保屏幕坐标对应游戏所在显示器的绝对坐标系。
  • 消息间的微小延迟不可省略,模拟真实用户操作节奏,避免游戏过滤快速消息序列。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 15:25:19