1.2 目标用户与竞品拆解方法

定位不能只朝内看画像,还要朝外看竞品。本篇给一套只依赖公开可观察机制的竞品拆解方法:把「某机制是否存在」做成 0/1 矩阵,算出品类采用率与自研《王朝纪元》的差异向量,识别惯例轴与分水岭轴,再用 B 基准把背离强惯例的轴挑成软告警,全程不碰任何竞品后台数据。

上一节朝内画了品类图谱,这一节朝外:我们要服务的到底是谁,别人已经在服务谁,我们打算和谁不一样。 这三问听起来像市场部的事,其实是产品架构的前置输入——目标用户决定核心循环的节奏,竞品拆解决定哪些系统可以借鉴、哪些必须绕开。

需要先立一条纪律。本节涉及的《率土之滨》《三国志·战略版》《万国觉醒》《COK》都是真实产品,但它们只作「公开可观察机制」引用:观察时点 2026-10,来源为官方公告、商店页描述与公开试玩。任何竞品后台的留存、流水、付费率都属 X——需真实业务环境,命令行不可复现,本篇一个数字都不编。下面所有可跑的结论,输入都只是「某机制是否存在」这类公开事实。

目标用户不是「喜欢策略的人」

「25–40 岁男性、喜欢策略、有一定付费能力」——这句话对产品设计没有任何约束力,因为它同时命中三个互不兼容的方向。真正能驱动架构的用户定义,必须绑定可观察的行为约束:

用户类型可观察行为对架构的硬约束
碎片时间中度玩家单次在线 10–20 分钟一次有效决策必须在一次会话内完成
长线经营玩家连续 3 个月每日登录需要长线目标与赛季承接
联盟社交玩家主动加入并参与联盟活动联盟必须是长期目标的唯一路径
冲突驱动玩家主动进攻其他玩家需要可控的冲突出口(但未必是掠夺)

关键:用户定义里每一条都必须能翻译成一条系统约束。 翻译不了的,就是营销话术,删掉。这四类不互斥,《王朝纪元》选择的是前三类为主、第四类受控——这个选择会在 1.3 变成可判定的设计约束。

竞品拆解的三条纪律

拆竞品最容易犯的错,是「我听说某产品次留 45%,所以我们要做到 40%」——这个 45% 从哪来?多半是编的。可复现的拆解只做三件事:

  1. 只记公开可观察机制。「有没有赛季重置」「地图是不是格子」「有没有英雄抽卡」,这些看商店页和试玩就能确认;「次留多少」确认不了。
  2. 标观察时点。机制会变,2026-10 的结论不能当 2028 的结论用。
  3. 不可复现处标 X。竞品后台数据一律 X,正文显式写明「需真实业务环境,命令行不可复现」,绝不伪装成实测。

下面这段是 D 层配置:把四个公开可观察的竞品与自研《王朝纪元》放在 8 个机制轴上,值只有 0/1,表示机制缺失/存在。

{
  "version": 1,
  "observation": "2026-10",
  "source": "公开可观察机制(官方公告、商店页描述、公开评测与试玩录像)归纳",
  "disclaimer": "本表只记录「某机制是否存在」这类公开可观察事实,不含任何竞品后台留存、流水、付费率数据;后者属 X,需真实业务环境,命令行不可复现。数值 0/1 仅表示机制缺失/存在。",
  "axes": [
    { "id": "persistentWorld", "name": "持续世界" },
    { "id": "tileGrid", "name": "格子地块" },
    { "id": "realtimeMarch", "name": "实时行军" },
    { "id": "heroGacha", "name": "英雄卡牌抽取" },
    { "id": "seasonReset", "name": "赛季重置" },
    { "id": "allianceWar", "name": "联盟战" },
    { "id": "territoryOccupy", "name": "领地占领" },
    { "id": "resourceRaid", "name": "资源掠夺" }
  ],
  "titles": [
    { "id": "shuaitu", "name": "率土之滨", "values": [1, 1, 1, 0, 1, 1, 1, 1] },
    { "id": "sanzhan", "name": "三国志·战略版", "values": [1, 1, 1, 1, 1, 1, 1, 1] },
    { "id": "wgjx", "name": "万国觉醒", "values": [1, 0, 1, 1, 1, 1, 0, 1] },
    { "id": "cok", "name": "COK", "values": [1, 0, 1, 0, 0, 1, 0, 1] }
  ],
  "target": {
    "id": "dynasty-era",
    "name": "王朝纪元",
    "values": [1, 1, 1, 0, 1, 1, 1, 0]
  }
}

品类采用率与差异向量(S 层)

有了矩阵,两个问题又能用算术回答:哪些机制是这个品类的「公共惯例」?《王朝纪元》在哪些轴上背离了主流? 下面这段是 S 层仿真脚本,算每轴的采用率与自研产品的差异向量,产出 JSON + CSV + SVG。

# file: sim/ch01_competitor_scan.py
# tier: S
# verify: python3 sim/ch01_competitor_scan.py --config config/ --out out/
"""竞品拆解(公开可观察机制口径):读 config/ch01_competitor_axes.json,
算每个机制的「品类采用率」,再算自研《王朝纪元》相对品类主流的差异向量,产出 JSON + CSV + SVG。

纪律:输入只有「某机制是否存在」这类公开可观察事实,观察时点写在配置里。
不含任何竞品后台留存/流水/付费率(那属 X,命令行不可复现),也不做「实测」表述。
"""

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 adoption(titles: list[dict], n_axes: int) -> list[dict]:
    rows = []
    for k in range(n_axes):
        vals = [t["values"][k] for t in titles]
        rows.append({"values": vals, "adoption": round(sum(vals) / len(vals), 4),
                     "count": sum(vals), "total": len(vals)})
    return rows


def differentiation(target: dict, rows: list[dict]) -> list[dict]:
    out = [{"axis": r["axis"], "adoption": r["adoption"], "target": target["values"][k],
            "deviation": round(abs(target["values"][k] - r["adoption"]), 4)} for k, r in enumerate(rows)]
    out.sort(key=lambda r: (-r["deviation"], r["axis"]))
    return out


def write_csv(path: str, axes: list[dict], titles: list[dict], rows: list[dict], target: dict) -> None:
    with open(path, "w", encoding="utf-8", newline="") as f:
        w = csv.writer(f)
        w.writerow(["title"] + [a["id"] for a in axes])
        for t in titles:
            w.writerow([t["id"]] + t["values"])
        w.writerow(["__adoption__"] + [r["adoption"] for r in rows])
        w.writerow([target["id"]] + target["values"])


def write_svg(path: str, axes: list[dict], rows: list[dict], target: dict) -> None:
    bar, left, top, gap = 40, 150, 60, 46
    width, height = left + 360, top + gap * len(axes) + 30
    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": "18",
                                "font-family": "sans-serif"}).text = "品类采用率 vs 《王朝纪元》选择(公开可观察机制)"
    for i, (ax, row) in enumerate(zip(axes, rows)):
        y = top + i * gap
        ET.SubElement(svg, "text", {"x": str(left - 10), "y": str(y + 16), "font-size": "12",
                                    "text-anchor": "end", "font-family": "sans-serif"}).text = ax["name"]
        ET.SubElement(svg, "rect", {"x": str(left), "y": str(y), "width": str(int(300 * row["adoption"])),
                                    "height": str(bar - 14), "fill": "#4a7ebb"})
        mx = left + int(300 * target["values"][i])
        ET.SubElement(svg, "line", {"x1": str(mx), "y1": str(y - 4), "x2": str(mx), "y2": str(y + bar - 10),
                                    "stroke": "#c0392b", "stroke-width": "3"})
        ET.SubElement(svg, "text", {"x": str(left + 310), "y": str(y + 16), "font-size": "12",
                                    "font-family": "sans-serif"}).text = f"{row['adoption']:.2f} / 目标 {target['values'][i]}"
    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, "ch01_competitor_axes.json"))
    axes, titles, target = cfg["axes"], cfg["titles"], cfg["target"]
    os.makedirs(args.out, exist_ok=True)
    rows = adoption(titles, len(axes))
    for k, row in enumerate(rows):
        row["axis"], row["name"] = axes[k]["id"], axes[k]["name"]
    diff = differentiation(target, rows)
    summary = {"version": 1, "observation": cfg["observation"], "source": cfg["source"], "nTitles": len(titles),
               "adoption": rows, "differentiation": diff,
               "conventions": [r["axis"] for r in rows if r["adoption"] >= 0.75],
               "splits": [r["axis"] for r in rows if 0.25 < r["adoption"] < 0.75]}
    with open(os.path.join(args.out, "ch01_competitor_scan.json"), "w", encoding="utf-8") as f:
        json.dump(summary, f, ensure_ascii=False, indent=2)
    write_csv(os.path.join(args.out, "ch01_competitor_scan.csv"), axes, titles, rows, target)
    write_svg(os.path.join(args.out, "ch01_competitor_scan.svg"), axes, rows, target)
    print(json.dumps(summary, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()

同机实跑(4 个竞品,节选):

conventions(采用率>=0.75): persistentWorld, realtimeMarch, seasonReset, allianceWar, resourceRaid
splits(0.25<采用率<0.75):  tileGrid, heroGacha, territoryOccupy
differentiation(偏差降序): resourceRaid 1.0 > heroGacha/tileGrid/territoryOccupy 0.5
                            > seasonReset 0.25 > allianceWar/persistentWorld/realtimeMarch 0.0

读法:持续世界、实时行军、联盟战、资源掠夺的采用率都是 1.0——这是「不做就不像 SLG」的公共惯例;格子地块、英雄抽卡、领地占领是 0.5 的分水岭轴,正是上一节图谱里的高方差轴;赛季重置 0.75 接近惯例。《王朝纪元》只在 resourceRaid(放弃掠夺)上偏离了强惯例整整 1.0,其余偏离都在分水岭轴上——这是有意的定位,不是漏做。

B 基准:把「背离惯例」变成软告警

偏离强惯例不一定是错,但必须被看见。下面这段是 B 层基准脚本:把品类采用率当期望、把《王朝纪元》的选择当观测,凡是在强惯例轴上偏离超过阈值就出软告警——脚本永远 exit 0,只提醒,不判决。

# file: sim/ch01_competitor_baseline.py
# tier: B
# verify: python3 sim/ch01_competitor_baseline.py --config config/ --out out/
"""B 基准:把「品类主流采用率」当期望,把《王朝纪元》的定位选择当观测,算偏差并出软告警。

软告警不硬失败:脚本永远 exit 0,只把「背离强惯例」的轴列进 warnings 供策划决策,不作对错判决。
产出 out/ch01_competitor_baseline.json。
"""

from __future__ import annotations

import argparse
import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ch01_competitor_scan import load_config  # noqa: E402

STRONG_CONVENTION, WARN_BAND = 0.75, 0.5


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, "ch01_competitor_axes.json"))
    axes, titles, target = cfg["axes"], cfg["titles"], cfg["target"]
    os.makedirs(args.out, exist_ok=True)
    n = len(titles)
    warnings, rows = [], []
    for k, ax in enumerate(axes):
        expected = sum(t["values"][k] for t in titles) / n
        observed = target["values"][k]
        dev = abs(observed - expected)
        rows.append({"axis": ax["id"], "name": ax["name"], "expectedAdoption": round(expected, 4),
                     "observedChoice": observed, "deviation": round(dev, 4)})
        if dev >= WARN_BAND and expected >= STRONG_CONVENTION:
            warnings.append(ax["id"])
    report = {"version": 1, "kind": "benchmark", "severity": "soft",
              "note": "同机相对量级,绝对数值不可移植;这是设计对照,不是对错判决。",
              "observation": cfg["observation"], "rows": rows, "softWarnings": warnings,
              "acceptedDeviations": [r["axis"] for r in rows
                                     if r["deviation"] >= WARN_BAND and r["axis"] not in warnings]}
    with open(os.path.join(args.out, "ch01_competitor_baseline.json"), "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)
    print(json.dumps(report, ensure_ascii=False, indent=2))
    sys.exit(0)  # B 层软告警:永远成功退出


if __name__ == "__main__":
    main()

同机实跑(节选):

severity: soft
softWarnings: [resourceRaid]        # 采用率 1.0 的强惯例,被《王朝纪元》整轴放弃
acceptedDeviations: [tileGrid, heroGacha, territoryOccupy]   # 分水岭轴上的有意差异

这条软告警才是本节最有用的产物:放弃资源掠夺,意味着《王朝纪元》少了一个最直接的冲突出口和资源再分配手段。它不违法、也不代表错,但产品必须为「玩家之间如何产生冲突」准备替代方案——这个待办会被带到第 3 章的核心循环与第 14 章的交易与市场去兑现。软告警的价值在于:把「我们和别人不一样」从一个卖点,变成一张必须回应的待办清单。

拆解方法的操作步骤

把上面这套沉淀成可重复的流程:

  1. 圈定 3–5 个公开可观察的竞品,只选能玩到/能看到商店页描述的,避免道听途说。
  2. 列 6–10 个机制轴,每个轴必须能用 0/1 或序数回答,拒绝「品质高低」这类主观轴。
  3. 填矩阵,标观察时点,任何后台数字写 X 并注明不可复现。
  4. 跑采用率与差异向量,识别惯例轴与分水岭轴。
  5. 对每个背离强惯例的轴出一张待办,写清「放弃它,玩家失去什么、用什么补」。

这套流程的产物不是一份 PPT,而是 config/ch01_competitor_axes.json 加两个可重跑脚本——换一批竞品、改一个观察时点,结论立刻可复算。

小结

  • 目标用户必须翻译成系统约束;翻译不了的表述直接删。
  • 竞品拆解只碰公开可观察机制,标观察时点,后台数据一律 X,绝不写「实测」。
  • 采用率分出惯例轴与分水岭轴;分水岭轴才是可以做差异化的地方。
  • B 基准把「背离强惯例」变成软告警,逼产品为每一次「不一样」准备替代方案。
  • 下一节把用户与竞品的结论收成一句话定位,并翻译成可判定的设计约束。

阅读导航:上一节:1.1 SLG 品类图谱与细分(4X / 城战 / 卡牌 SLG) · 下一节:1.3 差异化定位与设计约束 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「slg」更多文章

  1. 12.3 留存与付费的联合分析
  2. 12.2 流失预警与召回
  3. 附录 C:全书公式、参数与数据集索引