摘要

在TFT 阵容顾问项目里,tft_fetch_assets.py 不仅一个“下载图片”的脚本,如果把整个项目串起来看,该文件承担的是 模板资源构建器 的角色。它上游连接 tft_data_manager.py 生成的赛季数据,下游直接服务 tft_screen_capture.py 的 OpenCV 模板匹配,是截图识别链路里非常关键的一层。

一. 从整个项目的运行流程理解tft_fetch_assets.py文件的作用

tft_data_manager.py
    ↓
生成 tft_champion_db.json / tft_item_db.json
    ↓
tft_fetch_assets.py
    ↓
下载并标准化 tft_assets/champions/*.png 与 tft_assets/items/*.png
    ↓
tft_screen_capture.py
    ↓
模板匹配识别英雄、装备、星级、站位
    ↓
tft_converter.py / tft_web_ui.py

tft_fetch_assets.py 并不是一个孤立的辅助脚本,而是整个识别系统的“资源准备层”

它的核心职责如下:

1. 从本地赛季数据库中读取英雄和装备列表

2. 从 DDragon 和 CommunityDragon 下载正确的 TFT 图片资源

3. 统一命名与尺寸,构建标准模板库

4. 输出到 tft_assets/,让后续识别模块可以直接加载

二. "数据驱动"的数据脚本

我们项目的资源脚本没有采用直接写死列表的形式,如:

CHAMPIONS = ["Draven", "Leona", "Braum"]
ITEMS = ["Deathblade", "BlueBuff"]

这种写法短期看很方便,但一到赛季更新就很难维护。tft_fetch_assets.py 采用的是更稳定的方案:所有下载目标都来自 tft_data_manager.py 生成的 JSON 数据库

核心代码如下:

def load_champion_db() -> dict:
    p = DB_DIR / "tft_champion_db.json"
    if not p.exists():
        print(f"  ✗ 找不到 {p},请先运行: python tft_data_manager.py")
        return {}
    db = json.loads(p.read_text(encoding="utf-8"))
    print(f"  英雄数据库: {len(db)} 个英雄  (来自 {p})")
    return db


def load_item_db() -> dict:
    p = DB_DIR / "tft_item_db.json"
    if not p.exists():
        print(f"  ✗ 找不到 {p},请先运行: python tft_data_manager.py")
        return {}
    db = json.loads(p.read_text(encoding="utf-8"))
    filtered = {k: v for k, v in db.items() if _item_is_valid(k)}
    print(f"  装备数据库: {len(filtered)} 个装备  (来自 {p},原始 {len(db)} 条,已过滤)")
    return filtered

我们在这段代码中采用了以下的工程思想:
脚本不负责定义资源,而负责消费标准数据。

这样做有两个直接好处:

1. 赛季更新时,只需要先更新数据库,资源下载逻辑基本不变

2. 文件命名、业务 ID、模板库 key 可以天然保持一致

三. 代码文件是如何获取上层数据的?

在 tft_data_manager.py 里,我们会从 CommunityDragon 或 DDragon 拉取赛季数据,再整理成统一格式。例如英雄和装备数据会被整理成这样的字典结构:

champ_db[api] = {
    "id"      : api,
    "short_id": short,
    "name_en" : c.get("name", short),
    "cost"    : c.get("cost", 0),
    "traits"  : traits,
    "stats"   : c.get("stats", {}),
}

item_db[api] = {
    "id"         : api,
    "name_en"    : item.get("name", api),
    "desc"       : item.get("desc", ""),
    "unique"     : item.get("unique", False),
    "composition": item.get("composition", []),
}

tft_fetch_assets.py 并不是随便抓图片,而是基于上游已经整理好的标准业务主键 apiName 来抓图。

例如:

1. 英雄模板最终会命名成 TFT16_Draven.png

2. 装备模板最终会命名成 TFT_Item_Deathblade.png

这和后面识别模块加载模板时使用的 key 是一一对应的。

四. 采取"双源回退"的下载策略

我们采用了如下的资源获取策略: 优先 DDragon,失败后回退 CommunityDragon

"""
下载策略(按优先级):
  英雄:
    1. DDragon  /img/tft-champion/{apiName}.png
    2. CommunityDragon  /game/assets/characters/{lc_id}/hud/{lc_id}_square.*.png
  装备:
    1. DDragon  /img/tft-item/{apiName}.png
    2. CommunityDragon  /game/assets/items/icons2d/{lc_id}.png
"""

这个设计考虑了两个资源源站的不同特点:

1. DDragon 路径更稳定,资源命名更规整

2. CommunityDragon 更贴近游戏内部资源结构,适合作为回退来源

项目通过这样的下载策略构建了一条稳定的模板获取链路。

核心代码如下:

def fetch_champions(ver: str, failed_log: list):
    champ_db = load_champion_db()
    if not champ_db:
        return

    out = ASSETS_DIR / "champions"
    out.mkdir(parents=True, exist_ok=True)
    cdn = f"https://ddragon.leagueoflegends.com/cdn/{ver}"

    for api_name, entry in champ_db.items():
        dest = out / f"{api_name}.png"
        if dest.exists() and dest.stat().st_size > 500:
            continue

        short_id = entry.get("short_id", api_name)
        data = None

        for dd_name in _ddragon_champ_candidates(api_name, short_id):
            data = dl(f"{cdn}/img/tft-champion/{dd_name}.png")
            if data:
                break

        if not data:
            for url in _cdragon_champ_urls(api_name, short_id):
                data = dl(url)
                if data:
                    break

        if data:
            dest.write_bytes(resize_image(data, ICON_SIZE))
        else:
            failed_log.append(("champion", api_name, str(dest)))

按数据库遍历英雄,用业务 ID 构建模板文件名,优先下载 DDragon 的 TFT 专属头像,下载不到再回退 CommunityDragon。

可以看到脚本使用的是 /img/tft-champion/,而不是普通的 /img/champion/,这是因为普通英雄立绘和 TFT 游戏内实际头像风格并不一样,模板匹配时必须保证模板和截图中的目标形态尽量一致。这个选择,直接决定了后面的 OpenCV 匹配是否稳定。

五. 对英雄名称做候选兼容

实际使用第三方资源站时,不仅需要考虑“有没有图”,还要考虑“命名是否完全一致”

这里我们写了一个候选名函数:

def _ddragon_champ_candidates(api_name: str, short_id: str) -> List[str]:
    candidates = [
        api_name,
        f"TFT_{short_id}",
    ]
    _CASE_FIXES = {
        "Chogath"   : "ChoGath",
        "Kaisa"     : "KaiSa",
        "Leblanc"   : "LeBlanc",
        "Monkeyking": "Wukong",
    }
    lc = short_id.lower()
    for dd, db in _CASE_FIXES.items():
        if lc == db.lower():
            candidates.insert(0, f"TFT16_{dd}")
    return candidates

候选名函数的作用:

1. 兼容历史命名格式

2. 兼容大小写差异

3. 兼容某些特殊英雄的资源命名

六. CommunityDragon 回退路径也做了兼容封装

除了 DDragon 候选名,脚本还把 CommunityDragon 的回退路径统一生成了出来:

def _cdragon_champ_urls(api_name: str, short_id: str) -> List[str]:
    lc_tft = f"tft16_{short_id.lower()}"
    lc_base = short_id.lower()

    urls = []
    for lc in [lc_tft, lc_base]:
        base = f"{_CDN_CHARS}/{lc}/hud/{lc}"
        urls += [
            f"{base}_square.tft_set16.png",
            f"{base}_square.png",
            f"{base}_square.tft_set15.png",
        ]
    return urls

七. 装备模板下载

相比英雄,装备数据更复杂,所以脚本专门做了一层过滤:

def _item_is_valid(api_name: str) -> bool:
    if _SKIP_RE.search(api_name):
        return False
    if api_name.startswith("TFT4_") and "Ornn" not in api_name:
        return False
    if api_name.startswith("TFT9_") and "Ornn" not in api_name:
        return False
    return api_name.startswith((
        "TFT_Item_",
        "TFT5_Item_",
        "TFT16_Item_",
        "TFT16_TheDarkin",
        "TFT4_Item_Ornn",
        "TFT9_Item_Ornn",
    ))

经过过滤,模板库只保留或许识别真正会用到的资源.

八. 完整的命令行设计

主入口部分支持这些命令:

ap.add_argument("--champs",       action="store_true", help="仅下载英雄头像")
ap.add_argument("--items",        action="store_true", help="仅下载装备图标")
ap.add_argument("--verify",       action="store_true", help="检查完整性")
ap.add_argument("--list-missing", action="store_true", help="列出缺失文件")
ap.add_argument("--version",      default=None,        help="指定 DDragon 版本(默认自动获取)")
ap.add_argument("--db-dir",       default=".",         help="JSON 数据库目录(默认当前目录)")
ap.add_argument("--assets-dir",   default=None,        help="模板输出目录(默认 ./tft_assets)")

把资源构建流程工具化,让它可以反复执行,按需执行,校验执行.

九. 总结

tft_fetch_assets.py是一条面向截图识别的 TFT 模板资源构建流水线。

具体实现了以下的功能:

1. 从标准数据库读取资源目标

2. 通过双源策略下载正确素材

3. 兼容命名差异与路径差异

4. 标准化尺寸与输出结构

5. 为 OpenCV 模板匹配准备可直接使用的模板库

tft_fetch_assets.py 是一份资源构建脚本。它把“数据、下载、过滤、兼容、标准化、落盘、校验”这几个步骤组织成了一条完整链路,让模板资源不再依赖人工维护,把模板资源构建设计成一个可重复、可维护、可直接接入识别链路的过程。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐