python用实现FGO自动挂机战斗思路

 

主要需要用到的库有:pywin32gui,PIL,cv2(opencv-python),pywin32api,pyautogui

这篇文章是用于记录我自己的思路,当初在淘宝买了fgo的挂后,也买了商家的自动刷图脚本,但奈何老是更新用不了,我就打算用python写一个简单的战斗选牌以及重复刷图的脚本。(这个脚本是基于模拟器以及能一键秒杀的情况下,挂自己上某宝搜,基本所有商家都是一个产的(指搬运汉化外国大神卖二手国内))。

实现自动挂机,需要实现两个基本功能,一是图像匹配,二是鼠标移动定位,三是自动选牌。

一,图像匹配和鼠标移动

图像匹配自然分为截图和匹配

截图那自然就是自己预先截图保证到一个文件夹中,然后调用图片(使用PIL的Image函数,opencv的我试过不知道为什么不行,懒得想了),再使用pyautogui.locateCenterOnScreen进行对屏幕的图像匹配,im2是这个攻击按钮的图片,confidence是置信度。

然后如果匹配成功,会返回x值,这个x值是图片的中心坐标。我使用win32api.SetCursorPos进行鼠标移动定位,然后再用pyautogui.click实现鼠标单击。(我也不知道为什么我当时要用两个库实现一个功能,可能是为了学习多一个库的用法吧。)

python用实现FGO自动挂机战斗思路_第1张图片

from PIL import Image
import pyautogui
import win32api

def attack_star():
    im2 = Image.open('./fgo国服截图/开始.png')
    x = pyautogui.locateCenterOnScreen(im2, confidence=0.9)
    if x:
         win32api.SetCursorPos(x)
         pyautogui.click()
    return x

 

二,自动选牌 

这一点是脚本的核心所在!

因为fgo的5张牌是随机生成,如果按顺序直接选前3张牌,有可能不能一波结束一个回合,而我需要写一个小算法,简单实现匹配出3张不一样的牌,用列表保存3张牌的图片,在依次用图像匹配寻找坐标位置,在用鼠标点击即可实现快速结束战斗。

1,截取窗口位置

因为我使用的是逍遥模拟器,我需要先定位模拟器窗口的坐标信息

python用实现FGO自动挂机战斗思路_第2张图片 这里我用的是win32gui.FindWindow先获取窗口的权柄,然后再win32gui.GetWindowRect(handle)返回窗口的坐标信息,返回的坐标(x1,y1,x2,y2),(x1,y1)是窗口的左上角位于屏幕的坐标,而(x2,y2)是窗口右下角的坐标。

import win32gui

def get_windows_info():#获取窗口信息
    # wdname = u'Fate/GO - MuMu模拟器'
    wdname = u'逍遥模拟器'
    handle = win32gui.FindWindow(0, wdname) #注意大写
    if handle == 0:
        return None
    else:
        return win32gui.GetWindowRect(handle)#窗口坐标位置

 2,截取模拟器窗口内的卡牌位置

思路是,卡牌是永远固定再模拟器窗口里面的固定位置,而我们需要的卡牌坐标自然也是相对于模拟器的坐标(即从模拟器左上角为0点开始计算卡牌坐标位置),而用鼠标选牌的时候,再加上模拟器左上角的坐标(x1,y1),就能得出绝对坐标(相对于电脑屏幕的坐标位置)。

但要注意的第一点是,模拟器的窗口是可以变大变小的,所以我们计算坐标的时候,需要用比例关系算出卡牌位于模拟器的位置。这样一来,无论窗口变化,按照对应的比例我们都能截取到卡牌的位置。

 计算公式可能有点复杂,我们需要知道,基于逍遥模拟器,是有两个边框的(上边和右边),这两个边框的宽度是不变的,所以需要再计算比例的时候留意这个固定数值。

def abc_zh(i):  # 五张攻击卡的截图位置(i=0,1,2,3,4),基于逍遥模拟器
    s(1)
    x1, y1, x2, y2 = get_windows_info()  # 得到模拟器窗口坐标
    x_in = (x2 - x1 - 52) // 1600  # 游戏实际窗口的x坐标的比例系数 =(模拟器窗口的宽-模拟器右边框的宽)//某次游戏窗口的宽
    y_in = (y2 - y1 - 39) // 900  # 游戏实际窗口的y坐标的比例系数 =(模拟器窗口的高-模拟器上边框的高)//某次游戏窗口的高
    x_dis = 193  # 某次游戏卡牌的宽
    y_dis = 123  # 某次游戏卡牌的高
    ''''''
    xu =(64 + 320 * i) * x_in + x1  # 卡牌左上角的x绝对坐标 = (某次相对于游戏窗口的第一张卡牌左上角x坐标 + 距离第 i+1 卡牌的距离)* x比例系数 + 模拟器窗口左上角x坐标
    yu =530 * y_in + y1  # 卡牌左上角的y绝对坐标 = 某次相对于游戏窗口的第一张卡牌左上角y坐标 +y比例系数 + 模拟器左上角x坐标
    xd =xu + x_dis * x_in  # 卡牌右下角的x绝对坐标 = 左上角坐标 + 卡牌宽度 * x比例系数
    yd =yu + y_dis * y_in  # 卡牌右下角的y绝对坐标 = 左上角坐标 + 卡牌高度 * y比例系数
    return (xu,yu,xd,yd)  # 返回卡牌坐标信息

简单解读一下代码,例如x_in的注释里的

(模拟器窗口的宽-模拟器右边框的宽)//某次游戏窗口的宽

(模拟器窗口的宽-模拟器右边框的宽)= 当前游戏窗口的宽

而里面的固定数字,都是我在某次写代码的时候,游戏窗口的长宽(游戏窗口指的是除去固定的边框后,只有游戏画面的窗口。),这些数据都是用qq截图的坐标显示记录下来的,用qq截图一个一个获取对应卡牌的相对坐标。

i值为1的时候,就是第二张牌的x相对位置坐标,因为牌与牌之间的间隔是一样的,而高度y不变,所以只需要知道第一个牌的坐标就能推导出其他卡牌位置。

对了,卡牌的截取并不是整一张牌,因为我们需要的是打出不同角色的牌,而同一个角色的牌会有三种文字,匹配的时候如果匹配整张牌,会把同一个角色的红绿蓝牌匹配为不同角色的牌,所以我们需要截取的位置是卡牌的人物头像部分即可。

python用实现FGO自动挂机战斗思路_第3张图片

 3,给图片进行排位置

这是自动选牌的核心所在,思路是通过图片与图片匹配,得出置信度,当置信度高于一个阈值,则把这两张牌认为是同一个人。(这里用的是cv2.TM_SQDIFF_NORMED,返回的值越低反而代表图片越相似,如果需要正常的置信度值,可以找cv2.matchTemplate的用法。)

图片匹配:

import cv2
from PIL import ImageGrab
import time
def s(i):
    return time.sleep(i)

def fenlei(img1,img2):  # 匹配图片相似度,接近 0 为相似
    result = cv2.matchTemplate(img1, img2, cv2.TM_SQDIFF_NORMED)
    return result  # 返回一个值,范围为(0,1), 接近 0 表示相似

选牌算法:

def attack_list(l):  # 用于判断5张卡牌图片的排序,返回的a表包含3张牌
    # set = 0.55
    set = 0.4 # 设定一个阈值
    a =[]

    if not fenlei(l[0],l[1])

 图片的保存,调用排序函函数,实现点击3张不同的牌:

def attack_zh():
    s(1)
    i = 0
    for xx in range(5):
        card = ImageGrab.grab(abc_zh(i))  # 调用函数abc_zh返回坐标,用PIL截取每一张卡牌
        i += 1
        card.save('card'+str(i)+'.png')  # 保存卡牌图片

    card1 = cv2.imread("card1.png")  # 读取图片
    card2 = cv2.imread("card2.png")
    card3 = cv2.imread("card3.png")
    card4 = cv2.imread("card4.png")
    card5 = cv2.imread("card5.png")

    l = [card1,card2,card3,card4,card5]  #把图片放入列表

    a = attack_list(l)  # 把列表用选牌算法得出列表a,列表a包含了3张牌
    for i in range(3):
        x = pyautogui.locateCenterOnScreen(a[i], confidence=0.8)  # 允许误差0.9
        win32api.SetCursorPos(x)
        pyautogui.click()

三,自动挂机

if __name__=='__main__':
    pass
    s(2)
    i = 1
    while True:#战斗选卡
        # pyautogui.click()
        if pyautogui.position() == (0, 0):  # 鼠标移动到左上角,停止死循环
            print('中止程序')
            break
        if other.attack_blue():  # 是否有开始键
            print('第 ', i, ' 回合')
            attack_zh()  # 选牌攻击
            i = i + 1
        # if other.attack_over():  # 是否到了结束画面
        #     print('战斗结束')
        #     break

最后一步把所有功能整合起来到main中就行啦,本篇文章只是记录了一小部分的重要代码,其他的像判断回合结束,点击继续战斗等,都是“匹配图片,鼠标点击”,只不过麻烦点在于每个模拟器的边框、比例不一样,国服日服的图片不一样,都是些重复性的工作了。

但这种编写脚本的方式也有很多缺点,首先图片匹配,有时候放大缩小窗口,图标也跟着变小,它这个匹配反而不好使了,可能找不到。我的截取卡牌图片的公式也好像有点问题,因为我后来调整了模拟器窗口大小后,截取的位置反而偏小了,解决的办法只能是普通运行脚本的时候,不要老是变换窗口大小,默认的窗口大小默认的数据就行了。

                                                                                                                          2022.5.29

 

你可能感兴趣的:(opencv,经验分享,python,计算机视觉)