2.2 竞品拆解方法

竞品拆解不是抄,是建坐标系。本篇给一套只依赖公开可观察机制与示例数据的拆解方法:把每个竞品在「核心循环 / 变现 / 素材」三维度上按准则打 1~5 分,聚成 0~100 的维度分与加权总分,产出可重跑的 CSV 与 SVG,全程不碰任何真实竞品后台数据。

上一节把榜单读成了结构,这一节把镜头拉近到单个竞品:榜单告诉你「市场长什么样」,竞品拆解告诉你「别人具体怎么做、做到什么程度」。这里同样要先划清分工:/h5游戏产业全景/ 那篇是 H5 小游戏的产业综述,讲的是格局与趋势;本章给的是可量化的竞品拆解方法——把「感觉这个游戏做得好」翻译成可打分、可排序、可复算的维度分。/posts/products/ 下的 PRD 系列是产品需求文档,写的是「我们要做什么」;本章给的是可跑的数据分析与机会评估,回答的是「别人做了什么、我们差在哪」。一个是交付物,一个是分析工具,不要混为一谈。

拆解不是抄,是建坐标系

新手拆竞品最常见的两个极端:一是「抄界面」——把别人的 UI 像素级复刻,却说不清为什么这么设计;二是「看感觉」——玩两把,写一句「体验不错,值得学习」。两种都产不出可用的决策。

可复现的拆解要做的是建坐标系:先定义有限个可判定的维度,再把每个竞品放进这个坐标系里打分。打分是主观的(因为判断本身主观),但坐标系是公开的、准则是写死的、聚合是确定的——换一个人来打,可能会给出不同的分,但只要按同一套准则打,结论就能互相比较、能被复核。这正是它比「凭感觉」强的地方。

本节只做三件事,与后续章节严格分工:

  1. 拆维度——把竞品拆成「核心循环 / 变现 / 素材」三块,每块下面挂若干可判定的准则。
  2. 打分聚合——按准则打 15 分,聚成 0100 的维度分与加权总分,产出 CSV。
  3. 读结论——看自研产品在哪个维度领先、哪个维度落后,落后项进待办。

明确不做:这里不选型、不排序决定做哪个品类——那是第 3 章品类选择模型的事;这里也不讲玩法怎么实现——那是第 6 章核心循环的事。本节只到「把竞品量化成一张可比较的表」为止。

三维度拆解模板(核心循环 / 变现 / 素材)

为什么是这三个维度?因为它们对应一款小游戏能否成立的三个基本面:好不好玩(核心循环)、能不能赚钱(变现)、能不能被看到(素材/买量)。三者缺一,产品都很难跑起来。每个维度下再挂 4 条可判定的准则,准则都写成 1~5 分的锚点,避免「好坏」这种无法打分的描述。

下面这段是 D 层配置:一份竞品拆解模板,含三个维度、各自的准则与评分锚点,以及 6 个示例竞品(含自研《合成小岛》)的示例评分。

{
  "version": 1,
  "observation": "示例数据(非真实竞品评测)",
  "scale": 5,
  "disclaimer": "本表的竞品名称、评分均为作者构造的示例,用于演示「三维度拆解 + 加权打分」的可复现流程,不代表对任何真实产品的评测结论。真实竞品的留存、流水、付费率、买量成本均需真实业务环境,命令行不可复现(X)。",
  "dimensions": [
    {
      "id": "coreLoop",
      "name": "核心循环",
      "weight": 40,
      "criteria": [
        { "id": "loopLen", "name": "单局循环时长合理性", "rubric": "5=30~90 秒一局,节奏与碎片时间匹配" },
        { "id": "decisionDensity", "name": "单位时间有效决策数", "rubric": "5=每分钟多次有意义选择" },
        { "id": "feedbackDensity", "name": "正反馈密度", "rubric": "5=每次操作都有明确反馈" },
        { "id": "firstMinuteHook", "name": "首分钟钩子强度", "rubric": "5=开局 60 秒内出现第一个爽点" }
      ]
    },
    {
      "id": "monetization",
      "name": "变现",
      "weight": 35,
      "criteria": [
        { "id": "adFormFit", "name": "广告形态与玩法契合", "rubric": "5=激励视频嵌进核心循环而非硬插" },
        { "id": "adPlacement", "name": "广告点位自然度", "rubric": "5=点位出现在玩家主动想要收益处" },
        { "id": "iapDepth", "name": "内购点深度", "rubric": "5=有清晰的付费阶梯与高价值锚点" },
        { "id": "hybridBalance", "name": "混合变现平衡", "rubric": "5=IAA 与 IAP 互不打架、互为补充" }
      ]
    },
    {
      "id": "creative",
      "name": "素材",
      "weight": 25,
      "criteria": [
        { "id": "hookVisual", "name": "前 3 秒视觉钩子", "rubric": "5=静音也能看懂卖点" },
        { "id": "gameplayShowcase", "name": "玩法可展示性", "rubric": "5=核心循环可被 15 秒素材讲清" },
        { "id": "styleConsistency", "name": "风格一致性", "rubric": "5=素材与游戏内画面不割裂" },
        { "id": "iterationCadence", "name": "素材迭代产能", "rubric": "5=能稳定周更一批可测素材" }
      ]
    }
  ],
  "competitors": [
    {
      "id": "jelly-merge",
      "name": "果冻合成",
      "note": "示例竞品",
      "scores": { "loopLen": 4, "decisionDensity": 4, "feedbackDensity": 5, "firstMinuteHook": 4,
                  "adFormFit": 4, "adPlacement": 4, "iapDepth": 3, "hybridBalance": 3,
                  "hookVisual": 4, "gameplayShowcase": 5, "styleConsistency": 4, "iterationCadence": 3 }
    },
    {
      "id": "big-watermelon",
      "name": "合成大西瓜",
      "note": "示例竞品",
      "scores": { "loopLen": 5, "decisionDensity": 3, "feedbackDensity": 5, "firstMinuteHook": 5,
                  "adFormFit": 3, "adPlacement": 3, "iapDepth": 2, "hybridBalance": 2,
                  "hookVisual": 5, "gameplayShowcase": 5, "styleConsistency": 4, "iterationCadence": 2 }
    },
    {
      "id": "sweet-match3",
      "name": "甜甜消消乐",
      "note": "示例竞品",
      "scores": { "loopLen": 4, "decisionDensity": 4, "feedbackDensity": 4, "firstMinuteHook": 3,
                  "adFormFit": 5, "adPlacement": 5, "iapDepth": 5, "hybridBalance": 5,
                  "hookVisual": 4, "gameplayShowcase": 4, "styleConsistency": 5, "iterationCadence": 5 }
    },
    {
      "id": "idle-heroes",
      "name": "放置奇兵",
      "note": "示例竞品",
      "scores": { "loopLen": 3, "decisionDensity": 2, "feedbackDensity": 3, "firstMinuteHook": 3,
                  "adFormFit": 4, "adPlacement": 4, "iapDepth": 4, "hybridBalance": 4,
                  "hookVisual": 3, "gameplayShowcase": 3, "styleConsistency": 3, "iterationCadence": 3 }
    },
    {
      "id": "my-cafe",
      "name": "我的咖啡店",
      "note": "示例竞品",
      "scores": { "loopLen": 3, "decisionDensity": 3, "feedbackDensity": 3, "firstMinuteHook": 2,
                  "adFormFit": 3, "adPlacement": 3, "iapDepth": 4, "hybridBalance": 3,
                  "hookVisual": 3, "gameplayShowcase": 2, "styleConsistency": 4, "iterationCadence": 2 }
    },
    {
      "id": "merge-island",
      "name": "合成小岛",
      "note": "自研目标(示例评分)",
      "scores": { "loopLen": 5, "decisionDensity": 4, "feedbackDensity": 5, "firstMinuteHook": 5,
                  "adFormFit": 5, "adPlacement": 4, "iapDepth": 3, "hybridBalance": 4,
                  "hookVisual": 5, "gameplayShowcase": 5, "styleConsistency": 5, "iterationCadence": 4 }
    }
  ]
}

模板里有三个刻意的设计:一是准则必须可判定——「单局循环时长合理性」能对着锚点打,「美术好不好」不能;二是维度带权重——核心循环 40、变现 35、素材 25,权重是产品定位的直接表达;三是自研与竞品用同一套准则——只有同一把尺子,分数才可比。

打分与聚合(S 层)

打出来的原始分是 15 的整数,直接看不好比。下面这段是 S 层仿真脚本,读 config/ch02_competitor_teardown.json,把每个竞品在各维度下的准则分聚成 0100 的维度分,再按维度权重算总分,产出 CSV + JSON + 一个对照条形 SVG。聚合全用整数算术:维度分 = sum(scores) * 100 // (scale * nCriteria),总分 = sum(dimScore * weight) // 100。

# file: sim/ch02_competitor_score.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ch02_competitor_score.py --config config/ --out out/
"""竞品三维度拆解打分(示例数据口径):读 config/ch02_competitor_teardown.json,
把每个竞品在「核心循环 / 变现 / 素材」三个维度上的 1~5 分聚成 0~100 的维度分,
再按维度权重算总分,产出 CSV + JSON + 一个纯 Python SVG 雷达式对照条形图。

纪律:输入是示例竞品与示例评分,不是对真实产品的评测。真实竞品的留存/流水/付费率/买量成本属 X。
评分聚合用整数算术:维度分 = sum(scores) * 100 // (scale * nCriteria),总分 = sum(dimScore * weight) // 100。
"""

from __future__ import annotations

import argparse
import csv
import json
import os
import xml.etree.ElementTree as ET


def load_config(path: str) -> dict:
    with open(path, encoding="utf-8") as f:
        return json.load(f)


def dimension_score(dim: dict, scores: dict, scale: int) -> int:
    """维度分(0~100 整数):把该维度下各准则的 1~scale 分求和后归一化,地板除。"""
    n = len(dim["criteria"])
    got = sum(scores[c["id"]] for c in dim["criteria"])
    return got * 100 // (scale * n)


def score_all(cfg: dict) -> list[dict]:
    scale = cfg["scale"]
    rows = []
    for comp in cfg["competitors"]:
        dims = {d["id"]: dimension_score(d, comp["scores"], scale) for d in cfg["dimensions"]}
        total = sum(dims[d["id"]] * d["weight"] for d in cfg["dimensions"]) // 100
        rows.append({"id": comp["id"], "name": comp["name"], "note": comp["note"],
                     "dims": dims, "total": total})
    rows.sort(key=lambda r: (-r["total"], r["id"]))
    return rows


def write_csv(path: str, cfg: dict, rows: list[dict]) -> None:
    with open(path, "w", encoding="utf-8", newline="") as f:
        w = csv.writer(f)
        dim_ids = [d["id"] for d in cfg["dimensions"]]
        w.writerow(["id", "name"] + dim_ids + ["total"])
        for r in rows:
            w.writerow([r["id"], r["name"]] + [r["dims"][d] for d in dim_ids] + [r["total"]])


def write_svg(path: str, cfg: dict, rows: list[dict]) -> None:
    left, top, group_h, bar_h, gap = 120, 60, 66, 16, 26
    width, height = left + 420, top + gap * len(rows) + 24
    palette = {"coreLoop": "#2563eb", "monetization": "#db2777", "creative": "#16a34a"}
    svg = ET.Element("svg", {"xmlns": "http://www.w3.org/2000/svg", "width": str(width),
                             "height": str(height), "viewBox": f"0 0 {width} {height}"})
    ET.SubElement(svg, "text", {"x": str(left), "y": "30", "font-size": "16",
                                "font-family": "sans-serif"}).text = "示例竞品三维度得分(示例数据,0~100)"
    for i, r in enumerate(rows):
        y = top + i * gap
        ET.SubElement(svg, "text", {"x": str(left - 10), "y": str(y + 34), "font-size": "12",
                                    "text-anchor": "end", "font-family": "sans-serif"}).text = r["name"]
        for j, d in enumerate(cfg["dimensions"]):
            val = r["dims"][d["id"]]
            by = y + j * bar_h
            ET.SubElement(svg, "rect", {"x": str(left), "y": str(by), "width": str(int(3.4 * val)),
                                        "height": str(bar_h - 4), "fill": palette[d["id"]]})
        ET.SubElement(svg, "text", {"x": str(left + 352), "y": str(y + 34), "font-size": "12",
                                    "font-family": "sans-serif"}).text = f"总分 {r['total']}"
    ET.ElementTree(svg).write(path, encoding="utf-8", xml_declaration=True)


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--config", default="config/")
    ap.add_argument("--out", default="out/")
    args = ap.parse_args()

    cfg = load_config(os.path.join(args.config, "ch02_competitor_teardown.json"))
    os.makedirs(args.out, exist_ok=True)
    rows = score_all(cfg)
    write_csv(os.path.join(args.out, "ch02_competitor_score.csv"), cfg, rows)
    write_svg(os.path.join(args.out, "ch02_competitor_score.svg"), cfg, rows)

    report = {"source": "sim/ch02_competitor_score.py", "observation": cfg["observation"],
              "disclaimer": cfg["disclaimer"], "scale": cfg["scale"],
              "dimensions": [{"id": d["id"], "name": d["name"], "weight": d["weight"]} for d in cfg["dimensions"]],
              "ranking": rows, "artifacts": ["out/ch02_competitor_score.csv", "out/ch02_competitor_score.svg"]}
    with open(os.path.join(args.out, "ch02_competitor_score.json"), "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)
    print(json.dumps({"ranking": rows}, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()

同机实跑(6 个示例竞品,节选输出):

ranking(总分降序,dims = 核心循环 / 变现 / 素材):
  合成小岛    coreLoop=95  monetization=80  creative=95  总分=89   ← 自研目标
  甜甜消消乐  coreLoop=75  monetization=100 creative=90  总分=87
  果冻合成    coreLoop=85  monetization=70  creative=80  总分=78
  合成大西瓜  coreLoop=90  monetization=50  creative=80  总分=73
  放置奇兵    coreLoop=55  monetization=80  creative=60  总分=65
  我的咖啡店  coreLoop=55  monetization=65  creative=55  总分=58
产物: out/ch02_competitor_score.csv  out/ch02_competitor_score.svg

读法:自研《合成小岛》总分 89,靠的是核心循环 95 与素材 95 双高,但变现只有 80,是明显的短板——尤其 iapDepth(内购点深度)只打了 3 分。而排第二的甜甜消消乐恰好相反:变现满分 100,靠的是成熟的付费阶梯与混合变现。这两条放在一起看,结论就很清楚:《合成小岛》在「好玩」和「能被看到」上有优势,在「怎么收钱」上是短板——这张表的价值不在于排名,而在于把短板定位到了一个具体维度、甚至具体准则上。

拆解的纪律

把方法沉淀成可重复的流程,同时守住三条纪律:

  1. 只碰公开可观察机制。核心循环、广告点位、素材风格,玩一遍、看商店页就能判断;留存、流水、付费率、买量成本确认不了。
  2. 标观察时点。竞品会改版,2026-10 的判断不能当 2028 的结论。
  3. 不可复现处标 X。竞品后台数据一律 X,正文显式写明「需真实业务环境,命令行不可复现」,绝不伪装成实测。

打分流程本身也要诚实:分数是主观的,坐标系必须是客观的。准则要写死在配置里,权重要有理由,聚合要能复算。换一个人来打,分可能不同——但只要按同一套准则打,两张表就能放在一起比较,差异会指向「我们对某准则的理解不同」,而不是「谁在拍脑袋」。

一个常见追问:这 6 个示例竞品凭什么打这些分? 诚实回答:这些分是示例数据,用来演示算法;真实场景里,分数应当由团队按准则逐个讨论后填入配置,并把打分依据(试玩录像时间点、商店页截图)附在文档里。本节演示的是流程与工具,不是这 6 款游戏的评测结论。

小结

  • 竞品拆解的目标是建坐标系,不是抄界面、更不是凭感觉。
  • 三维度——核心循环 / 变现 / 素材——对应「好不好玩 / 能不能赚钱 / 能不能被看到」。
  • 准则必须可判定;维度带权重;自研与竞品用同一把尺子。
  • 聚合全用整数算术,换一套示例评分,结论立刻可复算。
  • 示例跑出:自研《合成小岛》核心循环与素材双高、变现偏弱,短板被定位到具体准则。
  • 真实竞品后台数据属 X;本节全部为示例数据。

阅读导航:上一节:2.1 榜单结构与品类分布 · 下一节:2.3 从竞品到差异化机会 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「minigame」更多文章

  1. 附录 F:全书公式、参数与数据集索引
  2. 9.3 同意管理与数据出境
  3. 9.2 COPPA 与儿童向游戏的限制