Roguelite 的随机:表驱动、可学习性与「公平翻车」

By pocaster

Roguelite 里最迷人的句子往往是「这局完全不一样」。最烦人的句子则是「这局纯粹搞我」。两者都可能来自同一套 RNG,差别通常不在随机函数本身,而在随机被放在系统的哪一层、有没有给玩家可学习的规律

我后来把随机问题拆成三件事:池子是否干净、权重是否可解释、失败是否公平可追溯。做不到第三点,玩家会把「方差」听成「恶意」。

Roguelite randomness

1. 表驱动:让策划能「看见」分布

与其在代码里散落 if (rand() < 0.3),我更愿意把掉落、商店、事件门收进表:

  • 条目 id基础权重解锁条件互斥标签
  • 最小间隔(同一负面事件多久内不重复);
  • 保底与封顶(硬上限防脸黑,软下限防无聊)。

表的价值不是「方便改数」,而是分布可被讨论。团队能指着一行说:这条在第三层出现是否太早。

2. 池化:全池随机常常是设计偷懒

「从所有道具里抽」听起来公平,实际上会破坏构筑学习曲线:玩家学不到「这一局在鼓励什么玩法」。我倾向:

  • 分层池:早期池、中期池、Boss 前池;
  • 构筑池:与当前已持有道具标签相关的子池加权;
  • 污染控制:强力组合的出现需要前置条件,而不是纯运气叠乘。

随机在这里服务的是叙事节奏,不是彩票机。

3. 可学习性:玩家需要「翻车理由」

真正的挫败来自不可归因:不知道为什么死、不知道为什么穷、不知道为什么永远缺那一块拼图。我会检查:

  • 危险是否有可读预警(动画、音效、UI);
  • 资源曲线是否在若干局内可对比(同一策略为何有时暴毙);
  • 是否有「教科书局」让核心机制被自然复习。

随机可以残酷,但要残酷得可复盘。否则社区只会留下一句「运气游戏」。

flowchart LR R[RNG draw] --> P[Pool filter] P --> W[Weight table] W --> C[Cooldown & mutex] C --> O[Offer to player] O --> L[Player learning loop]

4. 真随机 vs 可控方差:我如何选

  • 真随机适合:装饰性掉落、次要奖励、无关紧要的事件调味。
  • 可控方差适合:核心资源、决定构筑方向的升级、可能导致软锁的钥匙道具。

可控不等于固定,而是把方差关进规则:例如「三局内必出现一次恢复手段」这类不破坏惊喜、但防止连续绝育的设计。

5. 加权抽样的实现:别在热路径里做「重复扫描」

表驱动之后,运行时要把权重变成一次抽取。朴素做法是:

roll = random_uniform(0, sum(weights))
acc = 0
for each item:
  acc += item.weight
  if roll < acc: pick item

条目少时完全够用。条目多、每帧多次抽取时,我会考虑:

  • 前缀和 + 二分:预处理 cumsum,每次 O(log n)
  • Alias Method:预处理 O(n),每次抽取 O(1)(实现略繁,但商店刷新、批掉落很香)。

实现细节里还有两个坑:

  • 浮点累加误差:用整数权重(例如放大 1000 倍)有时更稳;
  • 动态权重:解锁、标签过滤后池子变化,要定义缓存失效时机,避免拿着过期前缀和抽。

6. 保底、怜悯与「伪独立」:玩家感受到的是序列,不是分布

数学上每次独立抽取可以符合「1% 传说」;体验上玩家记住的是连续三十次没出。所以我会显式维护序列层规则

  • 硬保底:第 N 次必出(或必出池内子集之一);
  • 软保底:未命中则下次权重乘倍数,命中后重置;
  • 负相关:同一 run 内某些强力事件互斥或强冷却,避免「一局把彩票兑完」。

这些都会改变边缘分布,不再是独立同分布。调试时要在工具里同时显示「单抽概率」与「整局期望」,否则策划和程序会对同一个数吵一晚上。

7. RNG 种子:要可复现,也要防作弊

单机游戏常纠结存档与读档。完全固定种子利于复现 bug(同一 seed 重放同一地图与掉落),但也会让 S/L(存档/读档)变成可控刷首抽。

我的折中通常是:

  • 世界生成战斗外事件用一层种子;
  • 战斗内暴击/掉落可与 run 内递增计数器混合,降低 S/L 粒度;
  • 开发构建强制记录 seed + decision log,发布构建再关。

联机或排行榜若涉及随机,要另写威胁模型,这里不展开。

8. 共现与「组合爆炸」:矩阵比柱状图更有用

除了单道具出现率,我会算二元共现:(P(A \cap B)) 与 (P(A)P(B)) 的比值偏离,往往暴露:

  • 同一池子里标签未互斥;
  • 权重函数不小心对某标签二次计数;
  • 关卡掉落表与全局表叠乘。

三元以上组合爆炸时,用频繁模式挖掘或只看「Top 构建标签集合」也能抓大头。

9. 与难度曲线的耦合:随机是调音台,不是噪音发生器

同一套道具池,在「第三层」与「Boss 前一层」应有不同有效强度。我会给层数乘子或给池子分段,而不是全局一张表抽到通关。

Boss 战前若需要「补资源」,与其偷偷调爆率,不如显式插入恢复事件节点——玩家能学会「节奏上有喘息」,比暗改 RNG 更不容易被骂「暗箱」。

10. 「教科书局」与信息经济学

Roguelite 的学习依赖可重复的实验。若核心机制藏在极低概率事件后面,玩家学不到因果。我会预留:

  • 教学层或首 run 的窄池
  • 或「第一次必见」的伪随机(仅一次),之后回归全局表。

这不是降低深度,是把深度从蒙眼试错迁到可读博弈

11. 遥测:随机也要被度量

上线后我会看:

  • 道具出现频率与共现矩阵
  • 关卡放弃点是否与特定事件簇相关;
  • 速通与休闲玩家的分布尾部差异;
  • 保底触发率:若长期逼近 100%,说明主表设计过苛。

没有数据,调权重只能靠直播间弹幕。

12. 收束

Roguelite 的随机是作曲,不是掷骰子。表驱动让分布可见,池化让节奏可控,可学习性让玩家愿意再来一局;实现上把加权抽样、序列规则、种子与遥测当作一等公民,才不会在「明明数学没问题」的地方输掉口碑。翻车若可复盘,玩家会骂 RNG;翻车若不可复盘,玩家会骂设计——后者难洗得多。

Tags: Game Dev