博文目录

文章目录

  • 环境准备
    • 操纵键鼠
      • 驱动安装 链接库加载 代码准备和游戏外测试
        • toolkit.py
      • 游戏内测试
    • 键鼠监听
    • 武器识别
      • 如何简单且高效判断是否在游戏内
      • 如何简单且高效判断背包状态 无武器/1号武器/2号武器
      • 如何简单且高效判断武器子弹类别
      • 如何简单且高效判断武器名称
      • 如何简单且高效判断武器模式 全自动/连发/单发
      • 何时触发识别
    • 压枪思路
    • 组织数据
  • 第一阶段实现 能自动识别出所有武器
    • cfg.py
    • toolkit.py
    • apex.py
  • 第二阶段实现 能自动识别出所有武器并采用对应压枪参数执行压枪
  • 第三阶段实现 放弃抖枪术 转常规后座抵消法

本文为下面参考文章的学习与实践

[原文] FPS游戏自动枪械识别+压枪(以PUBG为例)
[转载] FPS游戏自动枪械识别+压枪(以PUBG为例)

环境准备

Python Windows 开发环境搭建

conda create -n apex python=3.9

操纵键鼠

由于绝地求生屏蔽了硬件驱动外的其他鼠标输入,因此我们无法直接通过py脚本来控制游戏内鼠标操作。为了实现游戏内的鼠标下移,我使用了罗技鼠标的驱动(ghub),而py通过调用ghub的链接库文件,将指令操作传递给ghub,最终实现使用硬件驱动的鼠标指令输入给游戏,从而绕过游戏的鼠标输入限制。值得一提的是,我们只是通过py代码调用链接库的接口将指令传递给罗技驱动的,跟实际使用的是何种鼠标没有关系,所以即便用户使用的是雷蛇、卓威、双飞燕等鼠标,对下面的代码并无任何影响。

驱动安装 链接库加载 代码准备和游戏外测试

罗技驱动使用 LGS_9.02.65_X64(请自行找资源安装,官网新版罗技驱动没找到对应的链接库文件),链接库文件在项目链接里面可以找到。下面是载入链接库的代码。

罗技驱动分LGS(老)和GHub(新), 必须装指定版本的LGS驱动(如已安装GHub可能需要卸载), 不然要么报未安装, 要么初始化成功但调用无效

LGS_9.02.65_x64_Logitech.exe, 网盘下载
其他地址1
其他地址2

try:    gm = CDLL(r'./ghub_device.dll')    gmok = gm.device_open() == 1    if not gmok:        print('未安装ghub或者lgs驱动!!!')    else:        print('初始化成功!')except FileNotFoundError:    print('缺少文件')

装了该驱动后, 无需重启电脑, 当下就生效了. 遗憾的是, 没有对应的文档, 只能猜测参数了

toolkit.py

import timefrom ctypes import CDLLimport win32api  # conda install pywin32try:    driver = CDLL(r'mouse.device.lgs.dll')  # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'    ok = driver.device_open() == 1    if not ok:        print('初始化失败, 未安装lgs/ghub驱动')except FileNotFoundError:    print('初始化失败, 缺少文件')class Mouse:    @staticmethod    def move(x, y, absolute=False):        if ok:            mx, my = x, y            if absolute:                ox, oy = win32api.GetCursorPos()                mx = x - ox                my = y - oy            driver.moveR(mx, my, True)    @staticmethod    def down(code):        if ok:            driver.mouse_down(code)    @staticmethod    def up(code):        if ok:            driver.mouse_up(code)    @staticmethod    def click(code):        """        :param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键        :return:        """        if ok:            driver.mouse_down(code)            driver.mouse_up(code)class Keyboard:    @staticmethod    def press(code):        if ok:            driver.key_down(code)    @staticmethod    def release(code):        if ok:            driver.key_up(code)    @staticmethod    def click(code):        """        :param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来        :return:        """        if ok:            driver.key_down(code)            driver.key_up(code)

游戏内测试

在游戏里面试过后, 管用, 但是不准, 猜测可能和游戏内鼠标灵敏度/FOV等有关系

from toolkit import Mouseimport pynput  # conda install pynputdef onClick(x, y, button, pressed):    if not pressed:        if pynput.mouse.Button.x2 == button:            Mouse.move(100, 100)mouseListener = pynput.mouse.Listener(on_click=onClick)mouseListener.start()mouseListener.join()

键鼠监听

前面说到,要实现压枪就要对各种配件、状态做出识别。那么在写识别的函数之前,我们先要解决的是何时识别的问题。如果识别使用多线程\多进程的一直持续检测,无疑是一种巨大的开销,因此就需要对键盘、鼠标的状态进行监听。只有按下特定按键时,才触发特定相应的识别请求。

这里我使用的钩子是Pynput,其他可使用的库还有Pyhook3

Pynput 说明

def onClick(x, y, button, pressed):    print(f'button {button} {"pressed" if pressed else "released"} at ({x},{y})')    if pynput.mouse.Button.left == button:        return False  # 正常不要返回False, 这样会结束监听并停止监听线程, 在关闭程序前返回False就好了listener = pynput.mouse.Listener(on_click=onClick)listener.start()def onRelease(key):    print(f'{key} released')    if key == pynput.keyboard.Key.end:        return False  # 正常不要返回False, 这样会结束监听并停止监听线程, 在关闭程序前返回False就好了listener = pynput.keyboard.Listener(on_release=onRelease)listener.start()

Listener中绑定on_press和on_release的函数( on_key_press、on_key_release),它们返回False的时候是结束监听,下文鼠标监听的函数同理,所以不要随便返回False

键盘的特殊按键采用keyboard.Key.tab这种写法,普通按键用keyboard.KeyCode.from_char(‘c’)这种写法

这里有一点非常坑,on_press和on_release的参数只能有一个key,这个key就是对应键盘按下的哪颗按键。但这是不足以满足我们的需求的,因为我们应该在钩子函数内部,在按下指定按键时对信号量做出修改,但因为参数的限制,我们无法把信号量传进函数内部,这里我也是想了很久,最后才想到用嵌套函数的写法解决这个问题。

另外,钩子函数本身是阻塞的。也就是说钩子函数在执行的过程中,用户正常的键盘/鼠标操作是无法输入的。所以在钩子函数里面必须写成有限的操作(即O(1)时间复杂度的代码),也就是说像背包内配件及枪械识别,还有下文会讲到的鼠标压枪这类时间开销比较大或者持续时间长的操作,都不适合写在钩子函数里面。这也解释了为什么在检测到Tab(打开背包)、鼠标左键按下时,为什么只是改变信号量,然后把这些任务丢给别的进程去做的原因。

武器识别

如何简单且高效判断是否在游戏内

找几个特征点取色判断, 血条左上角和生存物品框左下角

一般能用于取色的点, 它的颜色RGB都是相同的, 这种点的颜色非常稳定

我原本以为屏幕点取色应该不会超过1ms的耗时, 结果万万没想到, 取个色居然要1-10ms, 效率奇低, 暂无其他优雅方法

如何简单且高效判断背包状态 无武器/1号武器/2号武器

看武器边框上红色圈住的部分颜色, 灰色说明没有武器, 上下不同色, 说明使用2号武器, 上下同色说明使用1号武器

如何简单且高效判断武器子弹类别

可以和上面的放在一起, 同一个点直接判断出背包状态和武器子弹类别

如何简单且高效判断武器名称

在分类后的基础上, 通过 背包状态 确定要检查颜色的位置(1号位/2号位), 通过 武器子弹类别 缩小判断范围, 在每个武器的名字上找一个纯白色的点, 确保这个点只有这把武器是纯白色, 然后逐个对比

如何简单且高效判断武器模式 全自动/连发/单发


需要压枪的只有全自动和半自动两种模式的武器, 单发不需要压枪(后面有可能做自动单发, 到时候在考虑), 喷子和狙不需要压枪

所以需要找一个能区分三种模式的点(不同模式这个点的颜色不同但是稳定), 且这个点不能受和平和三重的特殊标记影响

收起武器, 部分武器可以通过[V]标判断, 放弃

何时触发识别

键盘 1/2/3/E/V 释放, 鼠标 右键 按下, 这个如果不影响开枪就这个了, 影响的话就改成侧下键. 键位和键在游戏内的功能不冲突的

压枪思路

apex 的压枪有两个思路, 因为 apex 不同武器的弹道貌似是固定的, 其他游戏也是” />

第二阶段实现 能自动识别出所有武器并采用对应压枪参数执行压枪

第三阶段实现 放弃抖枪术 转常规后座抵消法