3.1 品类分类体系

把 8 个小游戏品类按玩法核心、变现模式、单局时长、内容量级整理成一张可被程序消费的分类表,并给出读表统计的 S 层脚本:IAA 与混合各占一半、纯内购为零、单局时长中位数 85 秒,为后续商业特征矩阵与加权评分统一口径,是第 3 章品类选型模型的第一块基石。

做选型决策之前,先得有一张能对话、也能被程序消费的品类表。先把定位说清楚:站点里 小游戏创作平台专题 那 24 篇讲的是做小游戏创作平台(SaaS 创业口径:商业计划、融资、编辑器产品),而本章是为单款游戏做品类选型决策——用同一套可跑、可调参、可断言的评分模型,帮你在超休闲、合成、放置等品类之间做取舍。两者口径不同:前者面向平台方,后者面向一款具体产品的立项人。

为什么需要一张品类表

品类讨论最常见的失败,是「鸡同鸭讲」。有人说「休闲游戏留存差」,指的是超休闲;有人反驳「我这款留存很好」,指的是模拟经营——两者根本不在同一个格子里。要避免这种争论,第一步不是争论优劣,而是先把格子画出来。

一张好的品类表要满足三个条件:

  • 互斥且可判:一个产品能明确落进某个格子,而不是「像 A 又像 B」。
  • 维度正交:分类用的几个轴互不重复,避免把同一个信息数两遍。
  • 机器可读:能被脚本读进来做统计、打分、画图,而不是只躺在 PPT 里。

第 2 章的竞品拆解已经给了「怎么看一个竞品」的方法;这一章要把方法沉淀成可复用的结构。第 3.2 节给每个格子填上商业特征数字,第 3.3 节再把这些数字算成一个可比较的分数。本节先解决「格子怎么画」。

分类的三个正交轴

本书用三个轴来切分小游戏品类,它们互相正交,合起来足以定位一款产品。

轴一:玩法核心

玩法核心回答「玩家每分钟在做什么」。小游戏的主流核心循环大致收敛为几类:单点操作(点/滑,无成长)、合成(同类合并升级)、放置(离线累积、上线收取)、闯关(按关推进)、实时竞技(同场淘汰)、经营(经营—产出—扩建)、布防(塔防波次)、解谜(思考型单关)。这条轴决定了原型阶段要验证什么。

轴二:变现模式

变现模式回答「钱从哪里来」。小游戏生态里主要有三种形态:

  • IAA:以内购为辅助、以广告为主要收入;
  • 混合:广告与内购并重,通常内购提供去广告、加速、皮肤等;
  • IAP:纯内购驱动。

这里有一个值得记住的生态事实:小游戏里纯 IAP 极为罕见。原因不难理解——小游戏的用户预期是「免费点开就玩」,把核心体验锁在内购后面,会直接掐死自然量与分享链路。所以真实的品类分布里,IAA 与混合占了绝大多数。

轴三:内容量级与单局时长

内容量级回答「这套内容能撑多久」,单局时长回答「一次打开的承诺有多重」。两者常一起出现:内容量级高的品类(如闯关、经营)往往单局偏长,而内容量级低的品类(如超休闲、IO)单局更短、更依赖即时反馈。这条轴直接影响后文的包体预算与关卡产能评估。

把分类表写成可被程序消费的 config(D 层)

下面这段是 D 层静态配置,即品类分类体系本身。它用三个轴把 8 个品类落格,并附带 8 个评价维度与一组整数权重(权重供 3.3 节使用)。注意:分类属性是行业归纳的示例口径,标为「公开可观察」,不是任何平台后台实测;所有数字都是整数,无浮点。

{
  "version": 1,
  "series": "《小游戏开发实战》",
  "targetGame": "merge-island",
  "observedAt": "2026-10",
  "note": "小游戏品类分类体系与评价维度。分类属性为【示例 / 公开可观察】口径的行业归纳,不是任何平台后台实测;维度权重是整数百分比,合计 100,供第 3 章加权评分与敏感度分析使用。",
  "units": { "sessionSec": "单局时长(秒,整数)", "weight": "整数百分比,合计 100", "score": "0–100 整数有利度分" },
  "genres": [
    { "id": "hyper-casual", "cn": "超休闲", "en": "Hyper-casual", "coreLoop": "单指点击 / 滑动,无长期成长线", "monetization": "IAA", "sessionSec": 45, "contentVolume": "低", "example": "示例:单点操作类休闲产品", "note": "上手零门槛,靠买量放大;留存天花板低,内容复用弱。" },
    { "id": "merge", "cn": "合成", "en": "Merge", "coreLoop": "拖拽同类合并升级,产出资源再投入", "monetization": "混合", "sessionSec": 90, "contentVolume": "中", "example": "示例:合成 + 装扮类产品", "note": "反馈密集、易做长线;难点是合成链与资源经济。" },
    { "id": "idle", "cn": "放置", "en": "Idle", "coreLoop": "离线累积,上线收取与升级", "monetization": "混合", "sessionSec": 120, "contentVolume": "中", "example": "示例:放置养成类产品", "note": "对留存与在线时长友好;前期爽感弱,需快速给正反馈。" },
    { "id": "level", "cn": "闯关", "en": "Level-based", "coreLoop": "按关卡推进,难度逐级抬升", "monetization": "IAA", "sessionSec": 75, "contentVolume": "高", "example": "示例:三消 / 跑酷类产品", "note": "关卡即内容,产能压力大;包体与首包约束更紧。" },
    { "id": "io", "cn": "IO", "en": "IO Arena", "coreLoop": "多人同场实时竞技,单局淘汰制", "monetization": "IAA", "sessionSec": 60, "contentVolume": "低", "example": "示例:.io 竞技类产品", "note": "天然具备裂变话题;依赖实时同步与匹配,长线内容薄。" },
    { "id": "sim", "cn": "模拟经营", "en": "Simulation", "coreLoop": "经营—产出—扩建的循环", "monetization": "混合", "sessionSec": 150, "contentVolume": "高", "example": "示例:小镇经营类产品", "note": "留存与付费天花板高;系统复杂,开发与调优成本最大。" },
    { "id": "tower-defense", "cn": "塔防", "en": "Tower Defense", "coreLoop": "布防—波次—升级的防守循环", "monetization": "混合", "sessionSec": 100, "contentVolume": "中", "example": "示例:塔防闯关类产品", "note": "数值与关卡可复用;核心手感在节奏与平衡。" },
    { "id": "puzzle", "cn": "益智", "en": "Puzzle", "coreLoop": "解谜 / 推理,单关思考型", "monetization": "IAA", "sessionSec": 80, "contentVolume": "高", "example": "示例:解谜益智类产品", "note": "口碑与自然量好;关卡设计与验证成本高。" }
  ],
  "dimensions": [
    { "id": "devCost", "cn": "开发成本可控度", "direction": "higher_better", "desc": "越高表示同等团队规模下越快做出来" },
    { "id": "monetization", "cn": "变现效率", "direction": "higher_better", "desc": "越高表示人均收入潜力越大" },
    { "id": "retentionCeiling", "cn": "留存天花板", "direction": "higher_better", "desc": "越高表示长期留存的可用上限越高" },
    { "id": "uaDifficulty", "cn": "买量可控度", "direction": "higher_better", "desc": "越高表示获客越便宜、越可控" },
    { "id": "viralPotential", "cn": "裂变潜力", "direction": "higher_better", "desc": "越高表示越容易自发传播" },
    { "id": "contentReuse", "cn": "内容复用度", "direction": "higher_better", "desc": "越高表示一套内容能撑更久" },
    { "id": "platformFit", "cn": "平台适配度", "direction": "higher_better", "desc": "越高表示越契合小游戏包体与首包约束" },
    { "id": "differentiation", "cn": "差异化空间", "direction": "higher_better", "desc": "越高表示越容易做出不同" }
  ],
  "weights": { "devCost": 18, "monetization": 20, "retentionCeiling": 16, "uaDifficulty": 14, "viralPotential": 10, "contentReuse": 8, "platformFit": 8, "differentiation": 6 }
}

这张表把「分类」和「评价」分开了:genres 是分类(互斥落格),dimensions 是评价轴(每个品类都能在每条轴上给分),weights 是权重(决策偏好)。分开的好处是,3.3 节改权重时不需要动分类,改分类时也不需要动权重。

读表统计:分布与单局时长(S 层)

分类表要能回答「生态长什么样」。下面这段是 S 层仿真脚本,读 config/ch03_genres.json,对 8 个品类按变现模式、内容量级做计数,并给出单局时长的 min / max / 中位数,产出 JSON 与一张纯 Python SVG 条形图。

#!/usr/bin/env python3
# file: sim/ch03_taxonomy.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ch03_taxonomy.py --config config/ --out out/
"""品类分类体系的统计维度:读 config/ch03_genres.json,对 8 个品类按
变现模式 / 内容量级 / 单局时长做计数与分布,产出 JSON + SVG。

模型(全整数,无浮点):
- byMonetization / byContentVolume:计数分布。
- sessionSec:单局时长(秒),给出 min / max / 中位数(偶数取中间两数地板平均)。
- 图:各变现模式的品类数量条形图,零计数也画出(说明生态结构)。
纯标准库 + xml.etree 手写 SVG,零依赖。
"""

from __future__ import annotations

import argparse
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 count_by(genres: list[dict], key: str) -> dict:
    out: dict[str, int] = {}
    for g in genres:
        out[g[key]] = out.get(g[key], 0) + 1
    return out


def median_int(values: list[int]) -> int:
    vs = sorted(values)
    n = len(vs)
    if n == 0:
        return 0
    if n % 2 == 1:
        return vs[n // 2]
    return (vs[n // 2 - 1] + vs[n // 2]) // 2


def write_svg(path: str, labels: list[str], counts: list[int], title: str) -> int:
    W, H, PAD, gap = 640, 360, 60, 30
    n = len(labels)
    vmax = max(counts) or 1
    plot_h = H - 2 * PAD
    plot_w = W - 2 * PAD
    bar_w = (plot_w - gap * (n + 1)) / n
    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": str(PAD), "y": "28", "font-size": "15",
                                "font-family": "sans-serif"}).text = title
    ET.SubElement(svg, "line", {"x1": str(PAD), "y1": str(H - PAD),
                                "x2": str(W - PAD), "y2": str(H - PAD),
                                "stroke": "#9ca3af", "stroke-width": "1"})
    for i, (lab, c) in enumerate(zip(labels, counts)):
        x = PAD + gap + i * (bar_w + gap)
        h = c / vmax * plot_h
        y = H - PAD - h
        ET.SubElement(svg, "rect", {"x": f"{x:.1f}", "y": f"{y:.1f}",
                                    "width": f"{bar_w:.1f}", "height": f"{h:.1f}",
                                    "fill": "#2563eb" if c > 0 else "#d1d5db"})
        ET.SubElement(svg, "text", {"x": f"{x + bar_w / 2:.1f}", "y": f"{y - 6:.1f}",
                                    "font-size": "12", "text-anchor": "middle",
                                    "font-family": "sans-serif"}).text = str(c)
        ET.SubElement(svg, "text", {"x": f"{x + bar_w / 2:.1f}", "y": str(H - PAD + 20),
                                    "font-size": "12", "text-anchor": "middle",
                                    "font-family": "sans-serif"}).text = lab
    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()
    cfg = load_config(os.path.join(args.config, "ch03_genres.json"))
    os.makedirs(args.out, exist_ok=True)

    genres = cfg["genres"]
    by_mon = count_by(genres, "monetization")
    by_vol = count_by(genres, "contentVolume")
    sessions = [g["sessionSec"] for g in genres]

    # 固定展示顺序(含零计数),保证图表可复现并暴露生态结构
    mon_labels = ["IAA", "混合", "IAP"]
    mon_counts = [by_mon.get(m, 0) for m in mon_labels]
    vol_labels = ["低", "中", "高"]
    vol_counts = [by_vol.get(v, 0) for v in vol_labels]

    svg_bytes = write_svg(os.path.join(args.out, "ch03_taxonomy.svg"),
                          mon_labels, mon_counts,
                          "各变现模式的品类数量(8 个品类)")

    report = {
        "version": 1,
        "source": "sim/ch03_taxonomy.py",
        "genreCount": len(genres),
        "dimensionCount": len(cfg["dimensions"]),
        "byMonetization": by_mon,
        "byContentVolume": by_vol,
        "sessionSecMin": min(sessions),
        "sessionSecMax": max(sessions),
        "sessionSecMedian": median_int(sessions),
        "genreIds": [g["id"] for g in genres],
        "svg": os.path.join(args.out, "ch03_taxonomy.svg"),
        "svgBytes": svg_bytes,
    }
    with open(os.path.join(args.out, "ch03_taxonomy.json"), "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)
    print(json.dumps({k: report[k] for k in (
        "genreCount", "dimensionCount", "byMonetization", "byContentVolume",
        "sessionSecMin", "sessionSecMax", "sessionSecMedian", "svgBytes",
    )}, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()

注意脚本里 vol_labels / vol_counts 已经算好了内容量级分布,但只把变现模式画进了 SVG——这是刻意的:一张图只讲一件事,剩下的分布用文本报告承载,读者按需取用。

实跑结果

在本机工作区执行 /opt/homebrew/bin/python3 sim/ch03_taxonomy.py --config config/ --out out/,输出如下(同机相对量级,绝对数值不可移植):

{
  "genreCount": 8,
  "dimensionCount": 8,
  "byMonetization": { "IAA": 4, "混合": 4 },
  "byContentVolume": { "低": 2, "中": 3, "高": 3 },
  "sessionSecMin": 45,
  "sessionSecMax": 150,
  "sessionSecMedian": 85,
  "svgBytes": 1153
}

几个可以直接读出的事实:

  • 变现模式:IAA 4 个、混合 4 个、纯 IAP 0 个。8 个品类里没有一个落在纯内购格子里,这正是前文说的生态事实——不是巧合,是小游戏用户预期的必然结果。
  • 内容量级:低 2、中 3、高 3。高内容量级品类占了三席(闯关、模拟经营、益智),意味着它们对关卡产能与包体预算的压力最大。
  • 单局时长:最短 45 秒(超休闲),最长 150 秒(模拟经营),中位数 85 秒。中位数偏短,说明小游戏的默认预期是「碎片时间」;做模拟经营这类长单局品类,等于主动逆着平台习惯走,需要在体验上给足补偿。

这张表的用法与边界

用法上,这张表是后续两步的输入:3.2 节给每个格子的 8 个维度填数字,3.3 节把数字按权重加总、排名、做敏感度分析。分类表本身不含任何「谁更好」的判断——它只负责把产品放进可比较的格子。

边界也要说清:这里选的 8 个品类是覆盖主流、便于教学的集合,不是穷举。现实中还有大量混合形态(合成 + 塔防、放置 + 经营),它们的处理方式是拆回轴上去看:先看主循环更像谁,再看变现与内容量级落哪一格,而不是硬造一个新格子。分类的价值在于降低沟通成本,不在于穷尽所有产品。

另外,observedAt 标了 2026-10,意味着这些分布是观察时点的快照。品类热度会迁移,任何「现在流行 X」的结论都要带着时点读。这也正是把分类做成 config 而非写死在正文里的原因——更新一次数据,下游的统计、矩阵、评分会一起更新。

小结

本节把「品类」从口头概念变成了三轴结构:玩法核心 / 变现模式 / 内容量级(含单局时长),并把 8 个品类落成一张机器可读的 config。S 层脚本证明这张表能被程序消费,读出了 IAA 与混合各占一半、纯 IAP 为零、单局时长中位数 85 秒等结构性事实。下一节在这个骨架里填入商业特征数字,把它变成一张可比较的矩阵。

阅读导航:上一节:从竞品到差异化机会 · 下一节:品类的商业特征矩阵 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「minigame」更多文章

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