Python云顶之弈辅助工具开发日志(持续更新)

前言

前段时间因为朋友的原因,我重新入坑了英雄联盟,拿起了放了好几年的一区账号。我们最开始一直打的是大乱斗。但是最近双城之战2的上映,让大乱斗也改版了,感觉新版地图让我有点摸不着头脑,遂开始玩云顶,我对云顶的认知还是我退坑时的状态(那时云顶之弈才出来不久,当时我也没怎么玩这个模式),所以我还相当于云顶萌新,对羁绊、装备等机制都还不熟悉。每局基本都在678名。突然我脑海蹦出一个想法,我可不可以自己写一个程序,让它自动帮我安排阵容,或者根据对局灵活切换阵容。

软件定位

首先考虑的是这个软件的定位,我并不想称它为外挂,我也不想因为这程序破坏游戏的公平性,所以我不会用它来做自动D牌,自动站位等操作,而是用类似Wegame工具的方法来做阵容安排(在我写这段文字时,我发现Wegame确实有一个云顶助手,但是因为官方改版原因,暂时还不能使用)。

Wegame这个功能我都还没机会使用

另外,毕竟是个软件,就该有个名字,因为云顶之弈的英文缩写是TFT,所以我打算取名为TFTool。

功能结构

软件功能结构图
软件功能结构图
  • 商店提醒:作为一个辅助工具,提醒一下我的卡牌商店里有我想要的牌很合理吧。我预想的效果是:我可以先标记某个英雄,在底部栏出现这个英雄的时候软件就能提醒我。这和游戏内置的小队规划器差不多,但是可以更加灵活的标记。
  • 阵容推荐:首先我可以使用现成的推荐阵容,在各个平台上就能爬取到。还能根据其他玩家的阵容灵活地更改自己阵容。
  • 跟随版本更新:毕竟我并不是给当前这一个版本做的,每个赛季的英雄,玩法都会变,我的软件也要根据游戏版本进行更新。

开发历程

技术栈

  • 语言:其实最初是想使用游戏的接口内嵌在游戏中,就像注入一样,但是发现这个方法太危险了,不太靠谱。后来想想还是用Python方便一些,毕竟以往也用Python做过许多项目。
  • 窗口界面:前段时间接触了一个pyqt的UI库,叫做qfluentwidgets(这是官网:链接),感觉挺好看的,这次打算试试。
  • 模型训练:因为游戏没有提供内部操作的接口,自己注入又太危险了,保险起见还是用图像识别来做。遂打算使用YOLOv8来训练模型。

功能实现

商店提醒

因为商店想要做识别的话,还得单独训练一套模型出来,但是商店中的内容本来是自带文字的,所以打算直接使用OCR识别,刚好有一个ddddocr工具,用着也挺方便的。识别的大致流程如下:

截取屏幕图像
class WindowCapture:
    # constructor
    def __init__(self, window_name=None):
        """初始化,默认捕获整个屏幕"""
        if window_name is None:
            # 捕获整个屏幕
            self.hwnd = win32gui.GetDesktopWindow()
        else:
            # 捕获指定窗口
            self.hwnd = win32gui.FindWindow(None, window_name)
            if not self.hwnd:
                raise Exception(f"窗口未找到: {window_name}")

        # 获取窗口或屏幕大小
        window_rect = win32gui.GetWindowRect(self.hwnd)
        self.w = window_rect[2] - window_rect[0]
        self.h = window_rect[3] - window_rect[1]

    def get_screenshot(self):
        """获取屏幕截图"""
        # 获取窗口的设备上下文
        wDC = win32gui.GetWindowDC(self.hwnd)
        dcObj = win32ui.CreateDCFromHandle(wDC)
        cDC = dcObj.CreateCompatibleDC()
        dataBitMap = win32ui.CreateBitmap()
        dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h)
        cDC.SelectObject(dataBitMap)
        cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (0, 0), win32con.SRCCOPY)

        # 转换为 OpenCV 格式的图像
        signedIntsArray = dataBitMap.GetBitmapBits(True)
        img = np.frombuffer(signedIntsArray, dtype='uint8')
        img.shape = (self.h, self.w, 4)

        # 释放资源
        dcObj.DeleteDC()
        cDC.DeleteDC()
        win32gui.ReleaseDC(self.hwnd, wDC)
        win32gui.DeleteObject(dataBitMap.GetHandle())

        # 删除 Alpha 通道(OpenCV 不支持带 Alpha 的图像)
        img = img[..., :3]
        return np.ascontiguousarray(img)

    @staticmethod
    def list_window_names():
        """列出所有窗口名称"""
        def win_enum_handler(hwnd, ctx):
            if win32gui.IsWindowVisible(hwnd):
                print(hex(hwnd), win32gui.GetWindowText(hwnd))
        win32gui.EnumWindows(win_enum_handler, None)
裁剪商店窗口
    def by_cords(self, image, x1, y1, x2, y2):
        """根据坐标裁剪图像"""
        return image[y1:y2, x1:x2]

    def shop_window(self, image):
        """裁剪商店窗口"""
        return self.by_cords(image, 481, 952, 1476, 1070)
裁剪出每个英雄的名字
    def cells_of_shop_window(self, shop_window_image):
        """从商店窗口裁剪 5 个商品槽"""
        slot_width = 189
        gutter_width = 12
        gutter_fix = 0

        crops = []
        for i in range(5):
            if i == 3:
                gutter_fix = 1
            crops.append(self.by_cords(shop_window_image, slot_width * i + gutter_width * i + gutter_fix, 0,
                                       slot_width * (i + 1) + gutter_width * i + gutter_fix, 118))
        return crops
对每个英雄进行OCR识别
        hero_names = []
        for idx, cell in enumerate(cells):
            hero_name = self.extract_hero_name(cell)

            # 使用匹配算法来找到最接近的英雄名
            best_hero_name = self.match_hero_name(hero_name)
            hero_names.append((idx + 1, best_hero_name))

            # 保存裁剪结果(可选)
            cv2.imwrite(f"temp/cell_{idx + 1}_hero_name.png", self.crop_hero_name(cell))

        return hero_names

这里有一个小细节,OCR识别出来的名字可能会出现误差,比如“婕拉”会识别成“捷拉”,我这里的解决方法是使用difflib库的get_close_matches方法,把识别结果和现有的所有英雄名进行对比,最终挑选出差值最小的那个名字。具体代码如下:

匹配最佳识别名
    def match_hero_name(self, ocr_name):
        """根据 OCR 提供的英雄名进行匹配,找到最相似的英雄名"""
        all_heroes = self.tft_data.all_chess_name_str.split('-')  # 假设英雄名以'-'分隔

        # 使用 difflib 匹配最相似的英雄名称
        best_match = difflib.get_close_matches(ocr_name, all_heroes, n=1, cutoff=0.4)
        return best_match[0] if best_match else "-"

更多好玩意请到主页瞧瞧
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇