3.1 节画好了格子,这一节要往格子里填数字。先把定位对齐:站点里 小游戏创作平台专题 那 24 篇是做小游戏创作平台(SaaS 创业口径),关心的是编辑器与平台商业模式;本节则是为单款游戏做品类选型决策,关心的是「这个品类值不值得做」。同一批品类,两种完全不同的提问方式。
从分类到打分
分类解决「是什么」,打分解决「值不值」。把两者分开,是这一章的核心方法。3.1 的 genres 是互斥落格,本节要新建一套评价轴,让每个品类都能在每条轴上得到一个分数。评价轴选得对不对,直接决定后面的加权评分有没有意义。
选轴的原则有三条:
- 与决策相关:每条轴都要能改变「做不做、怎么做」的答案。不相关的轴只会稀释信号。
- 方向一致:统一成「分越高越有利」,避免有的轴越高越好、有的越低越好,导致加总时符号混乱。
- 可估可争:每条轴都能给一个 0–100 的量级判断,且不同人给出的分差在可讨论范围内。
八条商业特征维度
本书用 8 条维度刻画品类的商业特征,全部统一为「越高越有利」:
- 开发成本可控度:越高表示同等团队规模下越快做出来。
- 变现效率:越高表示人均收入潜力越大。
- 留存天花板:越高表示长期留存的可用上限越高。
- 买量可控度:越高表示获客越便宜、越可控。
- 裂变潜力:越高表示越容易自发传播。
- 内容复用度:越高表示一套内容能撑更久。
- 平台适配度:越高表示越契合小游戏的包体与首包约束。
- 差异化空间:越高表示越容易做出不同。
注意每条维度都带了「可控度 / 天花板 / 潜力」这类限定词。这不是文字游戏——它提醒打分者:我们打的是结构性的上限与难易,不是某一款具体产品的成败。一款做得好的超休闲,留存也可能很高;但超休闲这个品类的留存天花板确实低。矩阵描述的是品类,不是个案。
商业特征矩阵(D 层)
下面这段是 D 层静态配置,即 8 个品类 × 8 条维度的完整打分矩阵。每个单元格是 0–100 的整数有利度分,全部标为示例 / 公开可观察口径的行业归纳,不是任何平台后台实测;绝对数值不可移植。
{
"version": 1,
"note": "品类 × 商业特征矩阵:每个单元格是 0–100 的【示例 / 公开可观察】有利度分(越高越有利)。行业归纳的建模输入,不是任何平台后台实测;用于第 3 章加权评分与敏感度分析,绝对数值不可移植。",
"scale": 100,
"dimensions": ["devCost", "monetization", "retentionCeiling", "uaDifficulty", "viralPotential", "contentReuse", "platformFit", "differentiation"],
"scores": {
"hyper-casual": { "devCost": 95, "monetization": 52, "retentionCeiling": 34, "uaDifficulty": 40, "viralPotential": 45, "contentReuse": 30, "platformFit": 92, "differentiation": 34 },
"merge": { "devCost": 70, "monetization": 71, "retentionCeiling": 62, "uaDifficulty": 58, "viralPotential": 55, "contentReuse": 74, "platformFit": 85, "differentiation": 60 },
"idle": { "devCost": 65, "monetization": 73, "retentionCeiling": 72, "uaDifficulty": 60, "viralPotential": 50, "contentReuse": 78, "platformFit": 84, "differentiation": 56 },
"level": { "devCost": 50, "monetization": 68, "retentionCeiling": 58, "uaDifficulty": 62, "viralPotential": 64, "contentReuse": 50, "platformFit": 88, "differentiation": 62 },
"io": { "devCost": 64, "monetization": 60, "retentionCeiling": 45, "uaDifficulty": 55, "viralPotential": 82, "contentReuse": 40, "platformFit": 90, "differentiation": 48 },
"sim": { "devCost": 30, "monetization": 74, "retentionCeiling": 76, "uaDifficulty": 52, "viralPotential": 42, "contentReuse": 76, "platformFit": 62, "differentiation": 68 },
"tower-defense": { "devCost": 50, "monetization": 68, "retentionCeiling": 65, "uaDifficulty": 58, "viralPotential": 45, "contentReuse": 75, "platformFit": 88, "differentiation": 58 },
"puzzle": { "devCost": 58, "monetization": 62, "retentionCeiling": 60, "uaDifficulty": 60, "viralPotential": 52, "contentReuse": 56, "platformFit": 90, "differentiation": 54 }
}
}
矩阵是「宽表」:行是品类,列是维度。宽表的好处是一眼能看出每个品类的形状——超休闲在 devCost(95)和 platformFit(92)上极高,在 retentionCeiling(34)和 contentReuse(30)上极低,形状非常「尖」;模拟经营在 devCost(30)上极低,在 retentionCeiling(76)上高,形状更「平」。形状本身就是信息。
把矩阵算成可比较的分数(S 层)
宽表好看但不好比。下面这段是 S 层仿真脚本,读 config/ch03_matrix.json 与 config/ch03_genres.json,对每个品类求 8 条维度的算术平均(整数地板除)作为「综合强度分」,同时算出极差 spread 作为「偏科度」,产出 JSON + CSV + SVG。
#!/usr/bin/env python3
# file: sim/ch03_matrix.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ch03_matrix.py --config config/ --out out/
"""把品类 × 商业特征矩阵算成可比较的分数:读 config/ch03_matrix.json(分数)
与 config/ch03_genres.json(品类中文名),对每个品类求:
- mean:8 个维度的算术平均(整数地板除)——可横向比较的「综合强度分」。
- min / max / spread:最弱项、最强项与极差——spread 越大越「偏科」。
- argmin / argmax:最弱与最强的维度 id。
产出 JSON + CSV + SVG(各品类综合强度分条形图)。全整数,无浮点。
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import xml.etree.ElementTree as ET
def load_json(path: str) -> dict:
with open(path, encoding="utf-8") as f:
return json.load(f)
def score_rows(genres: list[dict], dims: list[str], scores: dict) -> list[dict]:
rows = []
for g in genres:
gid = g["id"]
row = scores[gid]
vals = [row[d] for d in dims]
total = sum(vals)
argmin = min(dims, key=lambda d: row[d])
argmax = max(dims, key=lambda d: row[d])
rows.append({
"genre": gid,
"cn": g["cn"],
"mean": total // len(dims),
"sum": total,
"min": min(vals),
"max": max(vals),
"spread": max(vals) - min(vals),
"argmin": argmin,
"argmax": argmax,
})
rows.sort(key=lambda r: (-r["mean"], r["genre"]))
for i, r in enumerate(rows, 1):
r["meanRank"] = i
return rows
def write_csv(path: str, rows: list[dict], dims: list[str]) -> None:
with open(path, "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["genre", "cn"] + dims + ["mean", "spread", "meanRank"])
for r in rows:
w.writerow([r["genre"], r["cn"]] + [r["_scores"][d] for d in dims]
+ [r["mean"], r["spread"], r["meanRank"]])
def write_svg(path: str, rows: list[dict], title: str) -> int:
W, H, PAD, gap = 640, 400, 150, 10
n = len(rows)
plot_h = H - 2 * 40
plot_w = W - PAD - 60
bar_h = (plot_h - gap * (n + 1)) / n
vmax = max(r["mean"] for r in rows) or 1
svg = ET.Element("svg", {"xmlns": "http://www.w3.org/2000/svg",
"width": str(W), "height": str(H),
"viewBox": f"0 0 {W} {H}"})
ET.SubElement(svg, "rect", {"width": str(W), "height": str(H), "fill": "#ffffff"})
ET.SubElement(svg, "text", {"x": "40", "y": "26", "font-size": "15",
"font-family": "sans-serif"}).text = title
for i, r in enumerate(rows):
y = 40 + gap + i * (bar_h + gap)
w = r["mean"] / vmax * plot_w
ET.SubElement(svg, "rect", {"x": str(PAD), "y": f"{y:.1f}",
"width": f"{w:.1f}", "height": f"{bar_h:.1f}",
"fill": "#0ea5e9"})
ET.SubElement(svg, "text", {"x": str(PAD - 8), "y": f"{y + bar_h / 2 + 4:.1f}",
"font-size": "12", "text-anchor": "end",
"font-family": "sans-serif"}).text = r["cn"]
ET.SubElement(svg, "text", {"x": f"{PAD + w + 8:.1f}", "y": f"{y + bar_h / 2 + 4:.1f}",
"font-size": "12", "font-family": "sans-serif"}).text = str(r["mean"])
ET.ElementTree(svg).write(path, encoding="utf-8", xml_declaration=True)
return os.path.getsize(path)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--config", default="config/")
ap.add_argument("--out", default="out/")
args = ap.parse_args()
os.makedirs(args.out, exist_ok=True)
gcfg = load_json(os.path.join(args.config, "ch03_genres.json"))
mcfg = load_json(os.path.join(args.config, "ch03_matrix.json"))
dims = mcfg["dimensions"]
rows = score_rows(gcfg["genres"], dims, mcfg["scores"])
for r in rows:
r["_scores"] = mcfg["scores"][r["genre"]]
write_csv(os.path.join(args.out, "ch03_matrix.csv"), rows, dims)
svg_bytes = write_svg(os.path.join(args.out, "ch03_matrix.svg"), rows,
"各品类综合强度分(8 维度算术平均,越高越有利)")
for r in rows:
del r["_scores"]
report = {
"version": 1,
"source": "sim/ch03_matrix.py",
"scale": mcfg["scale"],
"dimensionCount": len(dims),
"rows": rows,
"topByMean": rows[0]["genre"],
"mostPolarized": max(rows, key=lambda r: r["spread"])["genre"],
"csv": os.path.join(args.out, "ch03_matrix.csv"),
"svg": os.path.join(args.out, "ch03_matrix.svg"),
"svgBytes": svg_bytes,
}
with open(os.path.join(args.out, "ch03_matrix.json"), "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(json.dumps({
"meanRank": [(r["cn"], r["mean"], r["spread"]) for r in rows],
"topByMean": report["topByMean"],
"mostPolarized": report["mostPolarized"],
}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
这个脚本刻意不做加权:算术平均是「所有维度同等重要」的假设,它给出的是「中性视角」下的强度排序。加权要等 3.3 节,因为权重体现的是你的决策偏好,而偏好是下一节的主角。先把中性结果做出来,才能看清加权改变了什么。
实跑结果
执行 /opt/homebrew/bin/python3 sim/ch03_matrix.py --config config/ --out out/,得到的综合强度分(降序,同机相对量级,绝对数值不可移植):
[
["放置", 67, 34],
["合成", 66, 30],
["塔防", 63, 43],
["闯关", 62, 38],
["益智", 61, 38],
["IO", 60, 50],
["模拟经营", 60, 46],
["超休闲", 52, 65]
]
同时写出 out/ch03_matrix.csv(含每个维度原始分与 mean / spread / meanRank)与 out/ch03_matrix.svg(综合强度分条形图)。CSV 头部如下:
genre,cn,devCost,monetization,retentionCeiling,uaDifficulty,viralPotential,contentReuse,platformFit,differentiation,mean,spread,meanRank
idle,放置,65,73,72,60,50,78,84,56,67,34,1
merge,合成,70,71,62,58,55,74,85,60,66,30,2
矩阵读法:均值与偏科
结果里有三个值得展开的观察。
第一,中性视角下「放置」综合最强(67),「超休闲」垫底(52)。 这不是说超休闲不好,而是说:如果 8 条维度同等重要,超休闲在 retentionCeiling / contentReuse / differentiation 三条上的低分,把它在 devCost / platformFit 上的高分拉平了。均值是一种「不看偏好」的视角,它天然惩罚偏科。
第二,偏科度是另一种信息。 spread 最大的两个品类是超休闲(65)和 IO(50),最小的两个是合成(30)和放置(34)。超休闲的 spread 高达 65——它的最强项 95(开发成本)与最弱项 30(内容复用)差了 65 分,是典型的「一招鲜、短板硬」。合成和放置的 spread 在 30 出头,形状更均衡。
第三,均值并列会暴露口径问题。 益智(61)与 IO(60)、模拟经营(60)靠得很近,IO 与模拟经营甚至同为 60。均值相同不代表品类相同——IO 的 60 来自「高裂变 + 低留存」,模拟经营的 60 来自「高留存 + 低开发可控度」,两者形状完全相反。这正是下一节要引入加权的原因:一旦你明确「我更看重留存还是裂变」,这两个并列的品类会立刻分开。
边界与陷阱
用矩阵时有三个常见陷阱:
- 把示例分当事实:矩阵里的分数是建模输入,标为公开可观察口径的示例值。它们的作用是让讨论有结构,不是让讨论有定论。不同团队完全可以给出不同的分数,只要把打分口径写下来,就是可复现的。
- 用均值替代决策:均值假设「维度同等重要」,这只在你还不知道自己要什么时成立。一旦有了明确的团队优势与目标,就该上权重,而不是继续看均值。
- 忽略维度的相关性:8 条维度并不完全独立(例如
contentReuse与retentionCeiling常常同向)。加权时若给两条高度相关的维度都压重注,会不知不觉放大同一个信号。这一点在 3.3 节的敏感度分析里会更清楚。
小结
本节把 3.1 的品类骨架填成了 8×8 商业特征矩阵,并用 S 层脚本算出了中性的综合强度分与偏科极差。核心结论是:均值能排序,但不能替代决策——它把「放置 67、超休闲 52」这样的强弱读出来,却读不出「你更看重什么」。下一节引入可调权重与敏感度分析,把这个缺口补上。
阅读导航:上一节:品类分类体系 · 下一节:品类选择加权评分与敏感度分析 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。