QQ个性网:专注于分享免费的QQ个性内容

关于我们| 网站公告| 广告服务| 联系我们| 网站地图

搜索
编程 JavaScript Java C++ Python SQL C Io ML COBOL Racket APL OCaml ABC Sed Bash Visual Basic Modula-2 Logo Delphi IDL Groovy Julia REXX Chapel X10 Forth Eiffel C# Go Rust PHP Swift Kotlin R Dart Perl Ruby TypeScript MATLAB Shell Lua Scala Objective-C F# Haskell Elixir Lisp Prolog Ada Fortran Erlang Scheme Smalltalk ABAP D ActionScript Tcl AWK IDL J PostScript IDL PL/SQL PowerShell

Python写游戏自动化辅助?按键没有效果?其实内幕是这样的……

日期:2025/04/05 05:55来源:未知 人气:51

导读:斌哥说大家好,我是斌哥。今天要说的是用Python开发游戏辅助,这里指的是Python自动化实现的自动点击,自动打怪等操作。而非嵌入到游戏内的辅助,如果是这类辅助,请使用lua,如果游戏允许lua寄生的话。现代网络游戏对键盘处理,大致分为以下2种:对键盘响应要求不高,采用普通按键方式对键盘响应速度要求很高,采用高速按键键盘方面,如果是纯鼠标或者键盘操作较少的游戏,那么基本上用p......

斌哥说

大家好,我是斌哥。

今天要说的是用Python开发游戏辅助,这里指的是Python自动化实现的自动点击,自动打怪等操作。

而非嵌入到游戏内的辅助,如果是这类辅助,请使用lua,如果游戏允许lua寄生的话。

现代网络游戏对键盘处理,大致分为以下2种:

对键盘响应要求不高,采用普通按键方式

对键盘响应速度要求很高,采用高速按键

键盘方面,如果是纯鼠标或者键盘操作较少的游戏,那么基本上用pyautogui这类模块或者自己封装WindowsAPI中的keybd_event即可搞定。

而对于那种鼠标键盘互动的游戏,例如3D射击、moba、赛车等游戏,对键盘要求极高的游戏。我们用pyautogui或者是API提供的keybd_event,再者通过windows消息发送按键消息,都会发生这样的一幕:无效。

这种无效,不是代码有问题,也不是运行过程发生问题,而是仅仅的因为一个问题:那就是Windows上对键盘要求高的网络游戏,都采用的DirectX框架中的DirectInput接口。

按键无效(游戏采用DirectInput接口)的解决办法

解决办法只有两种:

1、通过winio这类驱动级的按键库(这个过程是汇编出真实的键盘事件),再者如果汇编水平不错,可以考虑自己写。Python也有winio的模块,叫rabird.winio,不过相当不友好,只能在windows调试模式中运行。

2、响应DirectInput支持的按键代码

为什么pyautogui和keybd_event在游戏中无效?

开始之前,首先要清楚,为什么pyautogui和keybd_event在游戏中无效?

keybd_event是WindowsAPI的一员,它的作用是模拟出windows环境中的按键。

其原型为:VOID keybd_event(BYTE bVk,BYTE bScan,DWORD dwFlags,DWORD dwExtralnfo);

参数:

bVk:定义一个虚拟键码。键码值必须在1~254之间。bScan:定义该键的硬件扫描码。dwFlags:定义函数操作的各个方面的一个标志位集。应用程序可使用如下一些预定义常数的组合设置标志位。 KEYEVENTF_EXTENDEDKEY:若指定该值,则扫描码前一个值为OXEO(224)的前缀字节。 KEYEVENTF_KEYUP:若指定该值,该键将被释放;若未指定该值,该键将被按下。dwExtralnfo:定义与击键相关的附加的32位值。

要知道,任何操作系统和键盘交互过程都是这样的:

用户按下按键 -> 键盘驱动 -> 操作系统产生键盘事件 -> 响应

而我们的keybd_event只是提供了keybd_event接口规定的虚拟键盘码,例如虚拟键盘码F1定义为:#define VK_F1 0x70,而游戏的键盘接口却是用的DirectInput,DirectInput中虚拟键盘码则是这样定义的:#define DIK_F1 0x3B

一个是0x70 一个是0x3b

假设这样一个场景,Python程序需要在某个游戏中按下F1按键,Python调用keybd_event产生键盘事件,0x70被传到DirectInput中,这时DirectInput会以为用户按下了日文键盘中的KANA键,因为DirectInput键盘码宏就有这样的定义:#define DIK_KANA 0x70

至于pyautogui这类模块为什么一样无效,其实很简单,因为他们就是在方法内调用了keybd_event

解决办法——SendInput在呼唤你

事实上,无论是keybd_event还是mouse_event(用于处理鼠标的winAPI)都是SendInput的简单封装。我们用SendInput可以解决更复杂的操作,就比如模拟DirectX的DirectInput键盘事件。DX键盘码:http://www.gamespp.com/directx/directInputKeyboardScanCodes.html

DX键盘码宏定义

SendInput原形:

UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize );

Parameters

nInputs

Specifies how many structures pInputspoints to.

pInputs

Pointer to an array of INPUT structures.

cbSize

Specifies the size of an INPUTstructure. If cbSize is not the INPUT structure, the function will fail.

Return Values

The number of events that the function inserted into the keyboard or mouse

简单粗暴的说:nInputs指定了发送pInputs事件次数,pInputs则是键盘或鼠标事件,cbSize是pInputs事件结构体的大小。Specifies the size of an INPUT structure

INPUT结构:

typedef struct tagINPUT {

DWORD type;

union{

MOUSEINPUT mi; //鼠标结构

KEYBDINPUT ki;//键盘结构

HARDWAREINPUT hi; //硬件事件结构

};

} INPUT, PINPUT, FAR LPINPUT;

鼠标事件结构体:MOUSEINPUT

typedef struct tagMOUSEINPUT {

LONG dx;

LONG dy;

DWORD mouseData;

DWORD dwFlags;

DWORD time;

ULONG_PTR dwExtraInfo;

} MOUSEINPUT, *PMOUSEINPUT;

键盘事件结构体KEYBDINPUT

typedef struct tagKEYBDINPUT {

WORD wVk;

WORD wScan;

DWORD dwFlags;

DWORD time;

ULONG_PTR dwExtraInfo;

} KEYBDINPUT, *PKEYBDINPUT;

硬件事件结构体

typedef struct tagHARDWAREINPUT {

DWORD uMsg;

WORD wParamL;

WORD wParamH;

} HARDWAREINPUT, *PHARDWAREINPUT;

构造SendInput环境

import ctypes

import time

复制DX键盘码的宏,改成Python版,这个不用我说了吧——#define xx 0xff 改成xx = 0xff

DIK_1 = 0x02

DIK_2 = 0x03

.....

SendInput = ctypes.windll.user32.SendInput

PL = ctypes.POINTER(ctypes.c_ulong) #unsigned long类型指针

定义一个KeyBdInput的结构体,相当于定义了一个KEYBDINPUT结构体

class KeyBdInput(ctypes.Structure):

fields = [("wVk", ctypes.c_ushort),

("wScan", ctypes.c_ushort),

("dwFlags", ctypes.c_ulong),

("time", ctypes.c_ulong),

("dwExtraInfo", PL)]

定义一个HardwareInput结构体,相当于定义了一个HARDWAREINPUT结构体

class HardwareInput(ctypes.Structure):

fields = [("uMsg", ctypes.c_ulong),

("wParamL", ctypes.c_short),

("wParamH", ctypes.c_ushort)]

定义一个MouseInput结构体,相当于定义了一个MOUSEINPUT鼠标事件结构体

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", PL)]

定义一个共用体/联合体 Union 相当于定义了INPUT结构中的匿名共用体,union { MOUSEINPUT mi;

class Input_I(ctypes.Union):

fields = [("ki", KeyBdInput),

("mi", MouseInput),

("hi", HardwareInput)]

定义一个完整的INPUT结构体,把上面的共用体加进来就是完整的了

class Input(ctypes.Structure):

fields = [("type", ctypes.c_ulong),

("ii", Input_I)]

DX_PRESS_DOWN = 0x0008 #键盘按下

DX_PRESS_UP = DX_PRESS_DOWN | 0x0002 #键盘松开

INFO_MOUSE_TYPE = 0 #鼠标类型

INFO_KEY_TYPE = 1 #键盘类型

INFO_HARDWARE_TYPE = 2 #硬件类型

开始定义我们的函数,定义一个按下键盘函数,hexKeyCode参数是DX键盘码

def PressDown(hexKeyCode):

定义额外的数据为0

extra = ctypes.c_ulong(0)

初始化一个共用体 对应INPUT结构体内的匿名共用体

ii_ = Input_I()

给共用体赋值为KEYBDINPUT结构dwExtraInfo是一个指针啊,我们一定要传指针

ii_.ki = KeyBdInput( 0, hexKeyCode, DX_PRESS_DOWN, 0, ctypes.pointer(extra) )

初始化完整的INPUT结构体

x = Input( ctypes.c_ulong(INFO_KEYTYPE), ii )

发送一次键盘事件

return SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

定义松开按键也是一样,把按键DX_PRESS_DOWN改成DX_PRESS_UP松开即可

def PressUp(hexKeyCode):

extra = ctypes.c_ulong(0)

ii_ = Input_I()

ii_.ki = KeyBdInput( 0, hexKeyCode, DX_PRESS_UP, 0, ctypes.pointer(extra) )

x = Input( ctypes.c_ulong(INFO_KEYTYPE), ii )

return SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

组合起来,按下松开

def Press(hexKeyCode):

PressDown(hexKeyCode)

time.sleep(0.1)

PressUp(hexKeyCode)

按下一连串按键,例如解决在游戏中发布消息的问题,如果不熟悉IME输入法编程的小伙伴可以用这个办法解决(前提是必须训练过的现代输入法)

def PressStr(string):

定义一个字典,用于后面递归,通过键值输入,简单粗暴

dxkeys = {

"a" : DIK_A,\

"b" : DIK_B,\

"c" : DIK_C,\

"d" : DIK_D,\

"e" : DIK_E,\

"f" : DIK_F,\

"g" : DIK_G,\

...#定义a-z的按键

"0" : DIK_0,\

"1" : DIK_1,\

...#定义0-9的按键

}

if(type(string) == str):

mys = string.lower() #转换成小写,为了对应字段键名

for i in range(len(string)):

Press(dxkeys[mys[i]]) #按下对应的值

例如我们要在游戏中输入“斌哥说python”

第一步,唤起游戏内的输入状态,例如按下Enter为唤起游戏内的聊天输入框

Press(DIK_RETURN)

time.sleep(1)

第二步,切换输入法

PressDown(DIK_LCONTROL) #按下左Ctrl

Press(DIK_DIK_SPACE) #按空格

PressUp(DIK_LCONTROL) #松开左Ctrl

这时就设置了当前输入法为输入法管理器内的第一个输入法

第三步,输入“斌哥说”对应的拼音是:bingeshuo(前提是输入法必须是训练过的,现代输入法都有记忆功能)

PressStr("bingeshuo")

Press(DIK_DIK_SPACE) #按空格 输入法特性,按下空格传递当前字符到编辑控件

第四步,输入python

切换为英文输入状态

Press(DIK_LSHIFT) #按下左Shift 现代输入法几乎都支持

PressStr("python")

第五步,发送聊天消息,即可按下回车Press(DIK_RETURN)

很久之前,有个人找到我,跟我要Python版本的winio,他的目的就是开发一个自动化游戏辅助,那时候还没有rabird.winio这种东西,他跟我说愿意给我钱,那时,我感到很悲哀。

尽管现在有winio for Python,但是太不友好了,希望不久的将来,更多热爱Python的小伙伴可以开发出winio这种驱动级的模块......

我是斌哥,喜欢请点击关注。

斌哥说Python,只专注于Python技术!

关于我们|网站公告|广告服务|联系我们| 网站地图

Copyright © 2002-2023 某某QQ个性网 版权所有 | 备案号:粤ICP备xxxxxxxx号

声明: 本站非腾讯QQ官方网站 所有软件和文章来自互联网 如有异议 请与本站联系 本站为非赢利性网站 不接受任何赞助和广告