本文是「放置游戏策划实战」系列第 2 篇。上一篇讲了进阶路线总纲和"数值飞轮"的概念框架,这篇直接上手:用 Python 写一个可运行的数值模拟器,跑掉率、卡点、留存、敏感性四个实验,每个都有真实输出和可视化。

所有代码可以直接复制运行,参数换成你自己的项目数值就能立刻出结果。别再拍脑袋定数值了——跑一次实验,比你猜一百次都准。


目录

阅读本文需要:Python 基础(变量、函数、循环),了解 numpy 基本概念。不需要数学背景——每个公式我会解释设计意图。

运行环境:Python 3.8+,安装 numpymatplotlibpip install numpy matplotlib)。完整脚本在第八节,可直接复制运行。


一、为什么独立开发者更需要数值模拟

大厂有专职数值策划 + 数据分析师团队,可以拉几百万条真实埋点做 A/B 测试。独立开发者没有这些资源,但你有一样东西大厂没有的:当天改、当天发的灵活性。

数值模拟就是你的"穷人版 A/B 测试":

大厂做法独立开发者替代方案
拉百万级埋点分析卡点用 Python 模拟 3000 名虚拟玩家
A/B 测试不同数值跑蒙特卡洛对比多组参数
数据团队写 SQL 看 LTV自己写脚本看留存/卡点分布

核心逻辑就一句话:在发版本之前,先用代码算一遍"如果数值是这样,玩家大概会怎样"

下面四个实验,覆盖了放置类游戏最常崩的四个环节。


二、模型假设与参数定义

先统一一套"极简放置游戏"模型。这套参数参考了《剑与远征》(AFK Arena)的公开数值分析——关卡公比、Boss 倍率、日成长率都贴近行业真实水平。你只需替换成自己的数值即可。

import numpy as np
rng = np.random.default_rng(20240711)  # 固定种子,结果可复现

# ── 核心参数(参考剑与远征 / AFK Arena 数值体系)──
P0        = 1000    # 初始战力(AFK Arena 新号约 1000 战力)
LEVEL1    = 1000    # 第 1 关需求(新号起步能直接打)
R         = 1.06    # 关卡公比(每关 +6%,每 10 关跨度 ≈1.79x)
BOSS_MULT = 1.40    # Boss 墙倍率(AFK Arena 约 1.3~1.5x)
M_BASE    = 1.15    # 日乘区基准(每天战力成长 +15%)
M_SIGMA   = 0.05    # 玩家间成长差异(不同在线时长 / 玩法风格)
DAYS      = 7       # 模拟时长(天)
N_PLAYERS = 3000    # 模拟玩家数

两个关键函数:

def level_req(n):
    """第 n 关的战力需求(n 从 1 开始)
    Boss 墙采用"台阶式"设计:
    - 每 10 关一道 Boss,Boss 关 = 上一关 × BOSS_MULT
    - 过了 Boss 后,后续关卡从 Boss 关需求继续等比增长,不会回跌
    - 实现方式:基础等比 × BOSS_MULT^(已过Boss数)
    """
    boss_count = n // 10                    # 已经过的 Boss 数
    base = LEVEL1 * (R ** (n - 1))          # 纯等比基础
    return base * (BOSS_MULT ** boss_count) # 台阶式 Boss 加成

def player_power(m, day):
    """玩家在第 day 天的战力(指数成长 = 独立乘区乘法的结果)"""
    return P0 * (m ** day)                   # 初始 × 日乘区^天数

参数来源说明

  • 关卡公比 R=1.06:AFK Arena 每章(约 5 关)战力需求增长约 20%,换算每关约 4%。我们取 6%(略高),让 7 天内有明显的卡点分化效果,方便观察。
  • Boss 倍率 1.40:AFK Arena 的 Boss 关约为同章普通关的 1.3~1.5 倍。1.4 倍的 Boss 墙意味着玩家需要额外积累约 0.5~1 天的挂机收益才能突破——这恰好是 AFK Arena 设计的"12~24 小时等待窗口"。
  • 日乘区 M=1.15:AFK Arena 活跃 F2P 玩家日均战力增长约 5~8%(纯挂机 3~5% + 主动玩法 2~3%)。我们取 15%,叠加了新手期福利(首抽、升级赠送等),前 7 天成长速度比长期更快。
  • 台阶式 Boss:保证关卡需求单调递增,不会出现"过了 Boss 下一关反而更简单"的不自然曲线。

数据参考:Alexandre Macmillan, “Idle mechanics and monetizing progression in AFK Arena”; Reddit r/afkarena 社区数据汇总。


三、实验一:掉率蒙特卡洛——脸黑玩家的体验下限

问题

你的某个稀有装备掉落率设成了 1%。直觉上觉得"100 次差不多能掉一次"。但脸黑玩家的实际体验是什么?

方法

蒙特卡洛模拟:对每个玩家反复"掷骰子"直到第一次成功,记录需要多少次。这在概率论中叫几何分布(Geometric Distribution)——每次独立试验成功概率为 rate,记录"第几次才第一次成功"。

rates = [0.01, 0.05, 0.10]   # 分别测试 1%、5%、10% 三种掉率
trials = 5000                # 每种掉率模拟 5000 名玩家

for rate in rates:
    # 几何分布:直接生成"首次成功所需次数",一行替代手动循环
    attempts = rng.geometric(p=rate, size=trials)
    print(f"掉率 {rate*100:.0f}%:")
    print(f"  平均 {attempts.mean():.1f} 次")
    print(f"  50% 的玩家 ≤ {int(np.percentile(attempts, 50))} 次")
    print(f"  10% 的脸黑玩家 ≥ {int(np.percentile(attempts, 90))} 次")

结果

掉率平均所需次数50% 玩家 ≤10% 脸黑玩家 ≥
1%~100 次69 次225 次
5%~20 次14 次45 次
10%~10 次7 次23 次

在这里插入图片描述

关键发现

  • 1% 掉率的陷阱:平均 100 次听起来还行,但 10% 的玩家要刷 225 次以上才第一次掉。如果每次打怪 30 秒,225 次就是 112 分钟纯战斗——这对休闲玩家来说已经接近弃游阈值了。
  • 5% 是个分水岭:10% 脸黑玩家从 225 次降到 45 次,体验断崖式改善。
  • 10% 以上基本安全:最黑的 10% 也只需 23 次,大多数人在 7 次以内就能拿到。

实操建议

如果你的稀有掉率 ≤ 3%,一定要加保底机制(如"累计 80 次未掉必出"),否则 10% 的玩家会因为"脸太黑"而流失——他们不是不玩你的游戏,是被随机性劝退了。


四、实验二:推图卡点分布——多数玩家卡在哪

问题

你的游戏上线 7 天了,TapTap 后台显示 DAU 在缓慢下降。你想知道:大多数玩家到底卡在哪一关?是不是某堵 Boss 墙把人拦住了?

方法

模拟 3000 名玩家 7 天的成长轨迹,每人每天根据自身战力计算"最高到达关卡"。

def sim_players(m_base=M_BASE, n=N_PLAYERS, days=DAYS, seed=20240711):
    rng = np.random.default_rng(seed)
    ms = np.clip(rng.normal(m_base, M_SIGMA, size=n), 1.03, 1.30)
    max_level = 80
    reqs = np.array([level_req(i) for i in range(1, max_level + 1)])
    reached = np.zeros((n, days + 1), dtype=int)

    for d in range(days + 1):
        power = player_power(ms, d)                    # 当天战力
        reached[:, d] = np.searchsorted(reqs, power,
                         side="right")                  # 战力够到第几关

    return reached, ms

reached, ms = sim_players()
final = reached[:, DAYS]                               # 第 7 天各人的最高关

结果

在这里插入图片描述

统计指标数值
中位数第 12 关
卡在第 10 关(第一道 Boss 墙)前的比例35.3%
卡在第 20 关(第二道 Boss 墙)前的比例99.7%
P90 到达关卡第 18 关
最高到达第 20 关

图表解读

这张直方图揭示了三个关键信号:

  1. 第 9~10 关出现第一个明显卡点——第一道 Boss 墙(BOSS_MULT=1.40)拦住了 35% 的玩家。在真实游戏中,Boss 墙的"12~24 小时等待窗口"是刻意设计的心流断点——让玩家产生"差一点就过了"的动机,从而第二天再来。
  2. 第 11~12 关有小高峰——突破 Boss 后的释放感。过了第 10 关的玩家在 11~14 关快速推进,说明 Boss 后的难度梯度是合理的(没有回跌)。
  3. 第 20 关是 7 天内的极限——几乎所有玩家都停在了第二道 Boss 墙前。这符合真实节奏:AFK Arena 的 F2P 玩家前 7 天大约推到第 5~7 章(25~35 关),而我们的模型每 10 关对应约 2 章,12 关 ≈ Ch.2~3,考虑到模型更简洁所以偏保守。

实操建议

35% 的玩家卡在第一道 Boss 前 = 正常现象,但如果超过 50% 就需要调整。真实放置游戏会通过以下手段缓解:

  • 策略深度:AFK Arena 允许通过阵营加成(+20%)和属性克制(+30%+)弥补战力差距,让"不是只能靠数值碾压"
  • 过渡奖励:卡住期间仍然有挂机收益、日常任务等正向反馈
  • 关卡回顾:回去刷已通关关卡获取资源,不会"无事可做"
  • 飞升/转生:长期来看,引入重置机制让数值永远有成长空间

五、实验三:留存曲线——推关节奏决定留存

问题

玩家每天上线,如果连续几天都没推过新关,他还会继续玩吗?推关节奏如何影响留存? 这里我们模拟 30 天的留存曲线,并引入"自然流失"层——即使推了新关,也有部分玩家因为"下载了没养成习惯"而离开。这更贴近真实放置游戏的留存特征。

方法

基于上面的玩家模拟,给每位玩家加一个分层流失概率模型:

alive = np.ones(n, dtype=bool)          # 是否仍在玩
no_adv_streak = np.zeros(n)             # 连续未推关天数
retained = []

for d in range(1, 30 + 1):             # 从第 1 天到第 30 天
    advanced = reached[:, d] > reached[:, d - 1]  # 当天是否推了新关
    no_adv_streak = np.where(advanced, 0, no_adv_streak + 1)

    # 分层流失模型:
    # Day 1: 首日 35% 自然流失(下载了但没玩下去)
    # Day 2-3: 过渡期,流失率逐渐降低
    # Day 4+: 稳定期
    # 推了新关 → 低流失;连续卡住 → 流失概率逐日上升
    if d == 1:
        base_churn, adv_churn = 0.35, 0.08
    elif d == 2:
        base_churn, adv_churn = 0.12, 0.03
    elif d <= 7:
        base_churn, adv_churn = 0.08, 0.02
    else:
        base_churn, adv_churn = 0.05, 0.01

    churn_p = np.where(advanced, adv_churn,
                       np.minimum(base_churn + 0.025 * no_adv_streak, 0.85))
    alive = alive & (rng.random(n) > churn_p)
    retained.append(alive.mean())

结果

在这里插入图片描述

时间节点模拟留存率(首开口径)行业基准(安装口径)
第 1 天91.6%35~40%
第 7 天70.0%15~20%
第 14 天55.8%10~15%
第 30 天33.5%5~10%

行业基准来源:GameAnalytics 2024 Mobile Benchmarks, Maf.ad 2024 Retention Report

口径说明:模拟留存从"首次进入游戏"算起,行业基准从"安装"算起。安装→首开的转化率约 40~45%,所以模拟 D1 的 91.6% ≈ 安装口径 91.6%×42% ≈ 38.5%,正好在行业基准范围内。

关键发现

  • 我们的模型留存偏高——这是因为模拟中假设所有玩家都是"成功下载并进入游戏"的,没有计入"下载后未打开"的漏斗损耗。真实行业中 D1 留存 35~40% 是从"安装"算起,而我们是从"首次游戏"算起。如果扣除安装→首开的约 55% 损耗,我们的 D1 ≈ 91.6% × 42% ≈ 38.5%,正好在行业基准范围内。
  • D7 ≈ 70%(从首开算)或 ≈29%(从安装算)——接近行业中位数偏上。说明当前的推关节奏在 7 天内是健康的。
  • D30 ≈ 33.5%(从首开算)或 ≈14%(从安装算)——略高于行业均值,说明模型中卡点带来的长期流失还不够严重。在真实游戏中,30 天后的卡点往往是多因素叠加的。

实操建议

留存分析的核心不是看绝对数字,而是看"留存断崖出现在哪里"

  • 如果你的 D1→D7 断崖特别大(比如 D1=40% 但 D7 只有 5%),说明新手引导或前 3 天的体验有严重问题
  • 如果 D7→D30 的斜率很陡,说明中期卡点或内容消耗速度不对
  • 放置类游戏的正确留存曲线应该是"前 3 天快速下降,之后趋平"——因为 Boss 墙的等待机制创造了每日登录习惯

六、实验四:数值敏感性——成长速度调一点会怎样

问题

你觉得当前成长速度可能偏慢,想把日乘区 M 从 1.15 提高到 1.18 或 1.20。但具体提高多少才能把卡点率降到可接受范围?

方法

系统性地改变 M_BASE,观察两个指标的变化:中位关卡数(玩家实际推到了哪)和 卡在第 20 关前的比例(第二道 Boss 墙的拦截面)。

m_list = [1.08, 1.10, 1.12, 1.14, 1.15, 1.16, 1.18, 1.20]
stuck_rate = []

for mb in m_list:
    reached, _ = sim_players(m_base=mb, n=1500)
    stuck_rate.append((reached[:, DAYS] < 20).mean() * 100)

结果

在这里插入图片描述

战力日乘区 M日增长率7 天中位关卡卡在第 20 关前评估
1.08+8%第 9 关100.0%几乎全员卡死
1.10+10%第 9 关99.9%仍然严重
1.12+12%第 9 关99.9%偏高
1.14+14%第 10 关99.8%勉强
1.15+15%第 11 关99.7%当前值
1.16+16%第 12 关99.3%略有改善
1.18+18%第 14 关98.7%明显改善
1.20+20%第 17 关96.8%推荐

关键发现

  • 中位关卡是更好的观察指标——从第 9 关到第 17 关(M 从 1.08 到 1.20),差距明显且直观。"卡在第20关前"这个指标在所有参数下都接近 100%,区分度不够,是因为 7 天内几乎不可能推到第二道 Boss 墙——这符合真实放置游戏的节奏设计。
  • 关键观察不是"卡不卡",而是**“卡多久”**:M 从 1.15 提到 1.20,中位关卡从 11 涨到 17,意味着玩家平均多推 6 关——这在前期体感差异很大。
  • 曲线在中低段(1.08~1.14)非常平坦,说明低于日增长 15% 时,调参几乎没有效果。真正的拐点在 M ≥ 1.16 之后。

实操建议

放置游戏的敏感性分析和线性游戏不同——不是追求"不卡关",而是控制"卡关时长"。

  • 建议目标:玩家在每道 Boss 墙前平均等待 0.5~1.5 天(即 M 需要让玩家在 Boss 等待期间仍然有成长感)
  • 不要盲目调高 M——M 过高会导致玩家"一睁眼就过了 Boss",反而失去每日登录动机
  • 更好的策略是调 Boss 倍率而非 M:把 BOSS_MULT 从 1.40 降到 1.25~1.30,卡点率会显著下降且不影响整体成长节奏

七、如何把这套方法接进你的项目

上面四个实验用的都是"通用参数",真正有价值的是把它接进你自己的项目

Step 1:参数本地化

把你游戏的实际数值填进去:

# 示例:替换成你项目的真实数据
P0        = 你的初始战力
LEVEL1    =1 关实际需求
R         = 你实际的关卡公比(通常 0.03~0.08 之间)
BOSS_MULT = 你的 Boss 倍率(通常 1.2~1.5 之间)
M_BASE    = 根据你后台数据估算的日均成长(通常 1.05~1.20)
M_SIGMA   = 根据玩家在线时长分布估算的差异(通常 0.02~0.08

Step 2:接入 TapTap 埋点验证

模拟只能预测趋势,真实数据才是金标准:

import pandas as pd
import numpy as np
# 伪代码:用 TapTap 后台导出的 CSV 做对比
real_data = pd.read_csv("taptap_level_distribution.csv")
simulated_final = final  # 来自实验二

# 对比模拟 vs 真实的关卡分布
print("模拟中位数:", np.median(simulated_final))
print("真实中位数:", real_data["max_level"].median())

如果模拟分布和真实分布形状一致但位置偏移,说明模型结构是对的,只需微调参数。如果形状都不像,说明你的飞轮模型本身有问题,需要回到 P1 重审循环结构。

Step 3:版本迭代闭环

定数值假设 → 跑模拟 → 发版本 → 拉 TapTap 埋点 → 对比 → 修正参数 → 下一轮

这就是上一篇说的"每一个版本都是一次数值实验"。半年下来,你的数值敏感度会比任何培训班出来的都强。


八、完整可运行脚本(一键复刻全部实验)

把以下代码保存为 idle_sim.py,直接 python idle_sim.py 即可生成全部四张图:

# -*- coding: utf-8 -*-
"""放置游戏数值模拟器 —— 一键跑通 4 个实验
参数参考剑与远征(AFK Arena)公开数值分析
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
import os, platform

# ── 字体配置(自动适配系统)──
_sys = platform.system()
if _sys == "Darwin":
    FONT = "/System/Library/Fonts/STHeiti Medium.ttc"
elif _sys == "Windows":
    FONT = "C:/Windows/Fonts/msyh.ttc"
else:
    FONT = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
fm.fontManager.addfont(FONT)
plt.rcParams["font.family"] = fm.FontProperties(fname=FONT).get_name()
plt.rcParams["axes.unicode_minus"] = False

OUT = "./output"
os.makedirs(OUT, exist_ok=True)
SEED = 20240711

# ═══════════════════════ 参数区(改成你的数值)═══════════════════════
# 参考剑与远征(AFK Arena)数值体系
P0          = 1000     # 初始战力
LEVEL1      = 1000     # 第 1 关需求
R           = 1.06     # 关卡公比(每关 +6%)
BOSS_MULT   = 1.40     # Boss 墙倍率(台阶式,单调递增)
M_BASE      = 1.15     # 日乘区(每天 +15%)
M_SIGMA     = 0.05     # 玩家间差异
DAYS        = 7        # 模拟天数
N_PLAYERS   = 3000     # 模拟人数
# ══════════════════════════════════════════════════════════════════════

def level_req(n):
    """台阶式 Boss:基础等比 × BOSS_MULT^(已过Boss数),单调递增"""
    boss_count = n // 10
    base = LEVEL1 * (R ** (n - 1))
    return base * (BOSS_MULT ** boss_count)

def player_power(m, day):
    return P0 * (m ** day)

def sim_players(m_base=M_BASE, n=N_PLAYERS, days=DAYS, seed=SEED):
    rng = np.random.default_rng(seed)
    ms = np.clip(rng.normal(m_base, M_SIGMA, size=n), 1.03, 1.30)
    reqs = np.array([level_req(i) for i in range(1, 81)])
    reached = np.zeros((n, days + 1), dtype=int)
    for d in range(days + 1):
        reached[:, d] = np.searchsorted(reqs, player_power(ms, d),
                                        side="right")
    return reached, ms


# ── 实验 1:掉率蒙特卡洛 ──
def exp_drop():
    rng = np.random.default_rng(SEED)
    rates = [0.01, 0.05, 0.10]
    fig, ax = plt.subplots(figsize=(8, 4.2), dpi=110)
    for rate, c in zip(rates, ["#DC2626", "#F59E0B", "#16A34A"]):
        trials = 5000
        attempts = rng.geometric(p=rate, size=trials)
        ax.hist(attempts, bins=60, density=True, alpha=0.55, color=c,
                label=f"掉率{rate*100:.0f}%(期望{1/rate:.0f}次)",
                range=(0, 160))
        ax.axvline(1/rate, color=c, ls="--", lw=1.8)
    ax.set_xlabel("首次掉落所需次数"); ax.set_ylabel("玩家占比")
    ax.set_title("掉率蒙特卡洛:首次掉落次数的分布", fontsize=13, weight="bold")
    ax.legend(fontsize=9); fig.tight_layout()
    fig.savefig(f"{OUT}/01_掉率.png", dpi=110); plt.close(fig)


# ── 实验 2:推图卡点 ──
def exp_progression():
    reached, _ = sim_players()
    final = reached[:, DAYS]
    fig, ax = plt.subplots(figsize=(8, 4.2), dpi=110)
    bins = np.arange(0, int(final.max()) + 2) - 0.5
    ax.hist(final, bins=bins, color="#4F46E5", alpha=0.85, edgecolor="white")
    med = int(np.median(final))
    ax.axvline(med, color="#F59E0B", ls="--", lw=2,
               label=f"中位数 第{med}关")
    for b in [10, 20, 30]:
        if b <= final.max():
            ax.axvline(b, color="#DC2626", ls=":", lw=1.3, alpha=0.6,
                       label=f"Boss 第{b}关" if b == 10 else "")
    ax.set_xlabel("7天内到达的最高关卡"); ax.set_ylabel("玩家数")
    ax.set_title("推图进度分布:多数玩家卡在哪?", fontsize=13, weight="bold")
    ax.legend(fontsize=9); fig.tight_layout()
    fig.savefig(f"{OUT}/02_卡点.png", dpi=110); plt.close(fig)


# ── 实验 3:留存曲线 ──
def exp_retention():
    rng = np.random.default_rng(SEED)
    reached, ms = sim_players(days=30, seed=SEED)
    n = reached.shape[0]; D = 30
    alive = np.ones(n, dtype=bool)
    streak = np.zeros(n); ret = []
    for d in range(1, D + 1):
        adv = reached[:, d] > reached[:, d - 1]
        streak = np.where(adv, 0, streak + 1)
        # 分层流失:首日高流失,后续稳定
        if d == 1:
            base_churn, adv_churn = 0.35, 0.08
        elif d == 2:
            base_churn, adv_churn = 0.12, 0.03
        elif d <= 7:
            base_churn, adv_churn = 0.08, 0.02
        else:
            base_churn, adv_churn = 0.05, 0.01
        cp = np.where(adv, adv_churn,
                       np.minimum(base_churn + 0.025 * streak, 0.85))
        alive &= rng.random(n) > cp; ret.append(alive.mean())
    fig, ax = plt.subplots(figsize=(8, 4.2), dpi=110)
    ax.plot(range(1, D + 1), np.array(ret) * 100, color="#06B6D4",
            lw=2.5, marker='o', ms=3)
    # 行业基准线
    for day_idx, benchmark, label in [(0, 37, "D1 基准 37%"),
                                      (6, 17.5, "D7 基准 17.5%"),
                                      (29, 7.5, "D30 基准 7.5%")]:
        ax.axhline(benchmark, color="#DC2626", ls=":", lw=1, alpha=0.5)
        ax.text(D + 0.5, benchmark, label, fontsize=7, color="#DC2626",
                va="center")
    ax.set_xlabel("天数"); ax.set_ylabel("留存率(%)")
    ax.set_title("模拟留存曲线:推关节奏决定留存(含行业基准)",
                 fontsize=13, weight="bold")
    ax.grid(alpha=.3); fig.tight_layout()
    fig.savefig(f"{OUT}/03_留存.png", dpi=110); plt.close(fig)


# ── 实验 4:敏感性 ──
def exp_sensitivity():
    ml = [1.08, 1.10, 1.12, 1.14, 1.15, 1.16, 1.18, 1.20]
    sr_20, sr_med = [], []
    for mb in ml:
        r, _ = sim_players(m_base=mb, n=1500, days=7)
        sr_20.append((r[:, 7] < 20).mean() * 100)
        sr_med.append(int(np.median(r[:, 7])))
    fig, ax = plt.subplots(figsize=(8, 4.2), dpi=110)
    ax.plot(ml, sr_20, color="#4F46E5", lw=2.5, marker='s', ms=6,
            label="卡<第20关比例")
    ax2 = ax.twinx()
    ax2.plot(ml, sr_med, color="#16A34A", lw=2.5, marker='o', ms=6,
             label="中位关卡数")
    ax.axvline(1.15, color="#F59E0B", ls="--", lw=1.5, alpha=0.7)
    ax.set_xlabel("战力日乘区 M")
    ax.set_ylabel("7天卡在第20关前的比例(%)", color="#4F46E5")
    ax2.set_ylabel("7天中位关卡数", color="#16A34A")
    ax.set_title("数值敏感性:成长速度如何影响推图进度",
                 fontsize=13, weight="bold")
    ax.legend(loc="upper left", fontsize=9)
    ax2.legend(loc="upper right", fontsize=9)
    ax.grid(alpha=.3); fig.tight_layout()
    fig.savefig(f"{OUT}/04_敏感性.png", dpi=110); plt.close(fig)


if __name__ == "__main__":
    exp_drop(); exp_progression(); exp_retention(); exp_sensitivity()
    print("✅ 全部图表已保存至", OUT)
    print(os.listdir(OUT))

九、总结与下期预告

今天我们用 Python 跑了四个实验,覆盖放置类游戏最容易崩的四个环节:

实验解决的核心问题一句话结论
掉率蒙特卡洛掉率设多少合适1% 掉率必须加保底,否则 10% 脸黑玩家刷 200+ 次才出
推图卡点分布多数玩家卡在哪35% 卡在第一道 Boss 前 = 正常;超过 50% 就需要调整
留存曲线留存是否健康留存分析的核心不是绝对数字,而是"断崖出现在哪里"
敏感性分析成长速度调多少放置游戏不是追求不卡关,而是控制"卡关时长"

核心方法论:别拍脑袋,跑模拟。3000 行虚拟玩家 + 4 张图 = 一个版本的数值体检报告。


下一篇 我会深拆「独立乘区」——为什么战力公式必须是乘法而不是加法,以及如何在 Excel 里搭一张投放回收表来控制膨胀。如果你有自己的项目数值想代入跑一下,欢迎在评论区贴出来,我帮你看看哪里该调。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐