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

 

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

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

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

一,图像匹配和鼠标移动

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

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

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

f1715f3f9831421593916459f6934f54.png

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,截取窗口位置

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

0a1a485db96e4a7aa8f58db6e955f44c.png

0b49853f3dac4470b68b08a7c04cf25b.png 这里我用的是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),就能得出绝对坐标(相对于电脑屏幕的坐标位置)。

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

930b3e30b53f4320bf6fe6ce18983b52.png

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

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不变,所以只需要知道第一个牌的坐标就能推导出其他卡牌位置。

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

5b2419525afe4606b8f9e7bac4805c3a.png

 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])<set:  # 一牌和二牌匹配,如果小于set值(接近0),则代表是同一个人
        a.append(l[0])             # 如果不一样,保存第一张牌到a[0]中
        a.append(l[1])             # 保存第二张牌到a[1]中
        if not fenlei(l[1],l[2])<set: #如果二和三牌不一样
            a.append(l[2])         # 保存第三张牌到a[2]中,并结束判断,返回a表。
        else:                      # 如果二和三一样
            a[0]=l[1]              # 调换位置,把前面两张牌顺序调换,第三张牌依然放在a[2],结束判断,返回a表
            a[1]=l[0]
            a.append(l[2])
    else:                          # 一和二牌同一人的情况下
        if not fenlei(l[0],l[2])<set:  # 再判断一和三牌是不是一样
            a.append(l[0])             # 如果不一样,a表的排序为[一,三,二]
            a.append(l[2])
            a.append(l[1])
        else:                          # 一二三是同一个人的情况
            if not fenlei(l[0],l[3])<set:  # 判断四牌是否与前面一样
                a.append(l[0])         # 如果不一样,a表的排序为[一,四,二]
                a.append(l[3])
                a.append(l[1])
            else:                      # 一二三四都是同一个人的情况
                a.append(l[0])         # a表排序为[一,五,二]
                a.append(l[4])
                a.append(l[1])
    return a

 图片的保存,调用排序函函数,实现点击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

 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: FGO烟雾镜立绘在国内FGO玩家中拥有广泛的知名度和普遍的喜爱,其独特的设计风格和细腻的画面质感在FGO社区中产生了很高的反响。很多玩家对烟雾镜立绘的评价非常好,认为其是FGO中最具特色和最具魅力的立绘之一。当然,也有一些玩家对其有不同的看法,但总体来说烟雾镜立绘在FGO玩家中仍然有很高的口碑。 ### 回答2: 《FGO(Fate/Grand Order)》是一款热门的日本手机游戏,而其中的烟雾镜立绘也是其特色之一。对于FGO烟雾镜立绘,我觉得它的设计非常考究和独特。 烟雾镜立绘常常应用于游戏中的角色形象设计之中,通过烟雾的遮挡和折射,增强了立绘的神秘和令人向往的感觉,给予角色更多的魅力。这种设计手法追求的是轻描淡写的美感,让玩家感受到角色身上散发出来的神秘气息。 在FGO中,烟雾镜的运用非常巧妙。角色的立绘采用了烟雾镜技法,让角色周围笼罩着似有若无的雾气,给予了角色一种超凡脱俗的感觉。这样的设计让角色形象更加立体且具有层次感,使其从众多游戏角色中脱颖而出。 此外,烟雾镜的运用还能帮助游戏中的角色清晰地展示出其特点和个性。无论是英灵还是从者,都有着各自独特的能力和背景故事。烟雾镜的设计巧妙地将这些特点与角色形象相融合,令玩家更容易地辨认出他们。这种立绘设计风格不仅能够表现英灵从者的强大,更能够赋予他们更多的人格魅力。 总之,我认为FGO烟雾镜立绘是一种非常创新而且成功的设计手法。它巧妙地利用了烟雾的遮挡和折射,让角色立绘更加神秘、具有魅力和层次感。它不仅能够凸显出每个角色的特点和个性,更能够给玩家留下深刻的印象。这样的设计风格无疑为FGO增添了更多的魅力,也使游戏更加引人入胜。 ### 回答3: FGO(Fate/Grand Order)是一款备受欢迎的日本手机游戏,烟雾镜立绘是该游戏中的一种形象设计手法。对于FGO烟雾镜立绘,我个人持有以下观点。 首先,烟雾镜立绘在设计上非常精致和独特。烟雾的效果使立绘的背景变得模糊,将焦点完全集中在角色身上,使角色更加突出。这种表现手法给人一种梦幻而神秘的感觉,与FGO的魔幻故事情节相得益彰,为游戏增添了独特的韵味。 其次,烟雾镜立绘的使用也能带给玩家一种想象空间。烟雾的遮挡与角色搭配的姿势、表情以及背景,能够让人们感受到立绘中角色的神秘和未知。这样的设计激发了玩家的好奇心和探索欲望,使他们愿意深入了解角色的故事背景和性格特点。 另外,烟雾镜立绘也能很好地突出角色的个性和特点。通过混合烟雾和立绘的颜色、线条等元素,可以凸显角色的特殊气质和技能。这样一来,烟雾镜立绘不仅是游戏中角色形象的表现手法,更是一个展现角色独特魅力的平台。 总体来说,FGO烟雾镜立绘是一种精致而独特的设计手法,不仅能突出角色的特点,还能引发玩家的好奇心和想象力。它使游戏画面更加生动,营造出与众不同的氛围,为玩家提供了更丰富的游戏体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值