• 绝地求生 压枪python版


    仅做学习交流,非盈利,侵联删(狗头保命)

    2023-06-26 测试可用

    一、概述

    1.1 效果

    总的来说,这种方式是通过图像识别来完成的,不侵入游戏,不读取内存,安全不被检测。

    1.2 前置知识

    1. 游戏中有各种不同的q械,不同的q械后坐力不一样,射速也不同。相同的q械,装上不同的配件后,后坐力也会发生变化。
    2. q械的y轴上移是固定的,x轴是随机的,因此我们程序只移动鼠标y轴。x轴游戏中手动操作。

    1.3 实现原理简述

    1. 通过python中的pynput模块监听键盘鼠标。

    监听鼠标左键按下,这个时候开始移动鼠标。左键抬起,终止移动。
    监听键盘按键,比如tab键,这时打开背包,截屏开始识别装备栏。

    1. 通过python的pyautogui模块来截屏,可以截取屏幕指定位置。

    2. 通过python的opencv模块来处理截取的图片。

    3. 通过SSIM算法来对比图片相似度,获取到装备栏的武器、配件。

    4. 通过python的pydirectinput操作鼠标移动。

    二、详解

    2.1 pynput监听键盘

    import pynput.keyboard as keyboard
    
    # 监听键盘
    def listen_keybord():
        listener = keyboard.Listener(on_press=onPressed, on_release=onRelease)
        listener.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    pynput的监听为异步事件,但是会被阻塞,所以如果事件处理事件过长,得用异步处理。

    2.2 监听事件

    创建了c_equipment类来封装武器信息。
    重点在tab键的监听,使用异步来检测装备信息。

    def onRelease(key):
        try:
            if '1' == key.char:
                c_equipment.switch = 1 #主武器1
            elif '2' == key.char:
                c_equipment.switch = 2 #主武器2
            elif '3' == key.char:
                c_equipment.switch = 3 #手q switch=3的时候不压q
            elif '4' == key.char:
                c_equipment.switch = 3 #刀具
            elif '5' == key.char:
                c_equipment.switch = 3 #手雷
        except AttributeError:
            if 'tab' == key.name:      #tab键异步操作检测
                asyncHandle()
            elif 'num_lock' == key.name:  #小键盘锁用来控制程序开关
                changeOpen()
            elif 'shift' == key.name:   
                c_contants.hold = False
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.3 pyautogui截屏

    检测装备,首先要在打开装备栏的时候,截屏。

    pyautogui.screenshot(region=[x, y, w, h])

    x,y分别表示坐标,w,h表示宽度和高度。
    截取之后,为了方便对比图片,需要将图片二值化,然后保存到本地。

    完整代码如下:

    import pyautogui
    
    def adaptive_binarization(img):
        #自适应二值化
        maxval = 255
        blockSize = 3
        C = 5
        img2 = cv2.adaptiveThreshold(img, maxval, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, blockSize, C)
        return img2
    
    # 屏幕截图
    def shotCut(x, y, w, h):
        im = pyautogui.screenshot(region=[x, y, w, h])
        screen = cv2.cvtColor(numpy.asarray(im), cv2.COLOR_BGR2GRAY)
        temp = adaptive_binarization(screen)
        return temp
        
    def saveScreen():
        screen1 = shotCut(1780, 125, 614, 570)
        cv2.imwrite("./resource/shotcut/screen.bmp", screen1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    image.png

    2.4 素材准备

    屏幕截图处理后如上,在装备识别之前,我们需要先准备很多素材图片用来对比。
    比如:武器名、q托、握把、q口

    image.png

    武器名:

    m762.bmp

    q托

    m4.bmp

    2.5 裁剪图片

    为了方便图片对比,我们需要将截取的装备栏部分的图片裁剪成和素材一样大小的图片。

    比如,我们要检测武器一的名字:

    #读取之前的截屏
    screen = cv2.imread("./resource/shotcut/screen.bmp", 0)
    #裁剪出武器1名字
    screenWepon1 = screen[0:40, 45:125]
    #拿裁剪的图片和武器素材的目录作为入参,进行对比
    w1Name = compareAndGetName(screenWepon1, "./resource/guns/")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.6 对比图片

    #对比图片获取名字
    def compareAndGetName(screenImg, dir):
        #获取目录下所有文件
        content = os.listdir(dir)
        name = 'none'
        max = 0
        #遍历文件
        for fileName in content:
            #使用opencv读取文件
            curWepone = cv2.imread(dir + fileName, 0)
            #使用SSIM算法拿到图片相似度
            res = calculate_ssim(numpy.asarray(screenImg), numpy.asarray(curWepone))
            #获取相似度最大的
            if max < res and res > 0.5:
                max = res
                name = str(fileName)[:-4]
        return name
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    SSIM算法:

    def calculate_ssim(img1, img2):
        if not img1.shape == img2.shape:
            raise ValueError('Input images must have the same dimensions.')
        if img1.ndim == 2:
            return ssim(img1, img2)
        elif img1.ndim == 3:
            if img1.shape[2] == 3:
                ssims = []
                for i in range(3):
                    ssims.append(ssim(img1, img2))
                return numpy.array(ssims).mean()
            elif img1.shape[2] == 1:
                return ssim(numpy.squeeze(img1), numpy.squeeze(img2))
        else:
            raise ValueError('Wrong input image dimensions.')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    到这,我们就能获取到装备栏1位置的武器名字了。

    2.7 操作鼠标

    知道武器名字后,同理,我们可以获取到装备的配件。
    然后,监听鼠标左键按下,然后开始下移鼠标。

    我们以m762武器为例:

    射速:86, 每一发子弹间隔86毫秒

    后坐力:
    [42, 36, 36, 36, 42, 43, 42, 43, 54, 55, 54, 55, 54, 55, 54, 55, 62, 62, 62, 62, 62, 62, 62, 62,62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 77, 78, 77, 78]

    表示每发子弹打出后,需要在y轴下移的距离,用来与后坐力对冲。

    def moveMouse(): 
        #从识别的数据中,再更具当前选择的武器,获取此刻的武器(比如按下1键,武器装备栏1为m762,那么此时武器就是m762)
        curWepone = getCurrentWepone()
        if (curWepone.name == 'none'):
            return
        #基础y轴补偿(没任何配件)
        basic = curWepone.basic
        #射速
        speed = curWepone.speed
        startTime = round(time.perf_counter(), 3) * 1000
        for i in range(curWepone.maxBullets):
            #是否可以开火,比如左键抬起,就中断。
            if not canFire():
                break
            #系数,比如按住shift屏息,就需要再原来基础上乘1.33
            holdK = 1.0
            if c_contants.hold:
                holdK = curWepone.hold
            #乘以系数后实际的移动距离
            moveSum = int(round(basic[i] * curWepone.k * holdK, 2))
            while True:
                if (moveSum > 10):
                    #移动鼠标
                    pydirectinput.move(xOffset=0, yOffset=10, relative=True)
                    moveSum -= 10
                elif (moveSum > 0):
                    pydirectinput.move(xOffset=0, yOffset=moveSum, relative=True)
                    moveSum = 0
                elapsed = (round(time.perf_counter(), 3) * 1000 - startTime)
                if not canFire() or elapsed > (i + 1) * speed + 10:
                    break
                time.sleep(0.01)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    代码中的while循环:

    其实再第一发子弹射出后,我们只需要下移42的距离,然后计算出需要等待的时间(0.086-移动鼠标的时间),然后第二发子弹射出,以此类推。

    while循环的作用是防止屏幕抖动太厉害。因为直接移动42的距离,游戏中抖的厉害,所以我们再86毫秒的间隔里分了多次来移动鼠标。

    python中的sleep函数不准确,所以我们要自己来计时,防止错过每发子弹的时间间隔。
    不准确还有个好处,随机,正好不用自己来随机防止检测了。

    三、最麻烦的部分

    每个q的后坐力都不一样,我们需要自己去游戏的训练场,一发发子弹的调试,获取准确的补偿数据。

    四、环境安装

    https://blog.csdn.net/LookOutThe/article/details/131398983

    五、最后

    代码上传到gitee,感兴趣的一起交流。
    https://gitee.com/lookoutthebush/PUBG

  • 相关阅读:
    【Flink入门修炼】2-3 Flink Checkpoint 原理机制
    HCIP-Datacom-ARST自选题库_10_多种协议多选【24道题】
    【业务功能篇112】Springboot + Spring Security 权限管理-登录模块开发实战
    java培训技术自定义视图介绍
    【面试题精讲】Mysql如何实现乐观锁
    使用chat-GPT接口提取合同中关键信息
    java日志框架之Log4j
    嵌入式实时操作系统的设计与开发(概述学习)
    如何创建一个自己的sphinx文档网站
    Nginx中proxy_pass的使用介绍
  • 原文地址:https://blog.csdn.net/LookOutThe/article/details/130639025