数值:成长曲线与击杀时间
你在表格里调的是「攻击力从 10 涨到 200」。玩家在游戏里体验到的是「打死一个怪要几秒」。这两个数字之间隔着一整条敌人的成长曲线 —— 而很多游戏就是在这里翻车的:玩家辛辛苦苦升了二十级,攻击力翻了二十倍,然后发现打怪还是要那么久,于是他说:「这游戏升级有什么用?」
玩家感受不到攻击力
这是数值设计的第一条,也是最容易被忘掉的一条。
「攻击力 200」对玩家来说是一个抽象的数字。他真正能感受到的是三样东西:
- 击杀时间(TTK, Time To Kill):打死一个敌人要几秒 / 几下
- 生存时间(TTD, Time To Die):站着不动被打死要几秒
- 相对变化:这次比上次快了还是慢了
所有的数值设计,最终都要翻译成这三个。下面这个 demo 把这个翻译过程画出来了 —— 左边是你调的(攻击力),右边是玩家感受的(TTK)。
重点操作:把「敌人血量同步成长」拖到 100%,然后切换不同的成长曲线。你会看到左边那条线飞上天,而右边那条几乎是平的。
如果敌人的血量和你的攻击力同比例增长,那么击杀时间恒定 —— 玩家在数值上升了 20 倍之后,战斗体验一模一样。
这不是假设,这是大量 RPG 的真实情况。玩家的怨气来源是:他花了十小时做的事情,在体验上等于什么都没做。
更糟的是,如果敌人成长得比你快(同步 > 100%),玩家会感觉越打越吃力 —— 而他明明一直在变强。这是最伤人的一种设计。
四种成长曲线
先把常见的几种曲线摆出来,看它们各自适合什么。
| 曲线 | 公式 | 性格 | 适合 |
|---|---|---|---|
| 线性 | base + n × k |
稳定、可预测、后期无感 | 短流程游戏、次要属性 |
| 等比 / 指数 | base × r ⁿ |
前期慢、后期爆炸 | 放置类、构筑类的「起飞」感 |
| 二次方 | base + k × n² |
加速增长但不失控 | 中长流程的主属性 |
| 对数 / 收敛 | base + k × ln(n) |
前期爽、后期趋于平缓 | 大多数情况下最好用 |
因为它匹配了玩家的心理适应:
- 前期每一级都带来明显提升 → 玩家立刻感到成长,愿意继续
- 后期提升变缓 → 但此时玩家的成长感已经转移到别的地方了(新机制、新关卡、技巧提升)
而指数曲线的问题是:前期每级提升太小(玩家感觉不到,容易早期流失),后期又大到失控(你没法设计出跟得上的敌人)。
指数增长的东西,在游戏里几乎总是麻烦。唯一的例外是那些把「数字爆炸」本身当作卖点的类型(放置游戏、割草游戏)—— 那时数字变大就是玩法。
击杀时间:那个真正该被设计的数字
与其调攻击力,不如先决定 TTK 应该是多少,再反推攻击力。
这是一个思维顺序的翻转,但它会让整个数值设计变得清晰得多。
| 敌人类型 | 建议 TTK | 为什么 |
|---|---|---|
| 杂兵(会成群出现) | 0.3 – 1 秒 | 要能被快速清掉,否则清怪变成劳动 |
| 精英 | 3 – 8 秒 | 长到需要用技巧,短到不会烦 |
| Boss | 60 – 180 秒 | 要能容纳「几个阶段」的结构 |
| 玩家的 TTD | ≥ 3 次受击 | 一击必杀会让人不敢尝试(除非那就是你的设计) |
杂兵的 TTK 如果超过 2 秒,你的游戏会变得非常累。
算笔账:一关有 40 个杂兵,每个 3 秒 = 2 分钟纯粹的「敲血条」时间。而这段时间里玩家没有做任何有意思的决策 —— 他只是在等血条走完。
这是「清怪很烦」这个评价的数学来源。修法不是减少敌人数量(那样关卡会变空),而是降低单个杂兵的血量,让清怪变成一个流畅的过程而不是一系列小战斗。
《DOOM 永恒》《吸血鬼幸存者》都把杂兵 TTK 压得极低,所以尽管敌人成百上千,玩起来是爽而不是累。
反推的例子
假设你决定:杂兵 TTK = 0.6 秒,玩家攻击间隔 0.3 秒(也就是两下打死)。
已知:TTK = 0.6 s,攻击间隔 = 0.3 s → 需要 2 次命中
决定:玩家 1 级攻击力 = 10
推出:1 级杂兵血量 = 20
到 10 级时,我希望玩家能「一下打死」杂兵(成长感!)
决定:10 级攻击力 = 24(对数曲线)
推出:10 级杂兵血量 ≤ 24 → 就设 22
结果:杂兵血量从 20 涨到 22(几乎不涨)
玩家攻击力从 10 涨到 24
TTK 从 2 下变成 1 下 ← 这就是玩家能感受到的成长
注意最后那个变化:从 2 下变成 1 下。这是一个离散的、明确的、玩家一定会注意到的变化 —— 比「攻击力 +140%」这个抽象数字有效一百倍。
这是本章最重要的一句话。
玩家感受到成长的时刻,是那些离散的、可命名的时刻:
- 从「两下打死」变成「一下打死」
- 从「必须躲这个攻击」变成「可以硬吃一下」
- 从「这个坡跳不上去」变成「跳得上去了」
- 从「这个精英打不过」变成「打得过了」
所以数值设计的真正工作,是安排这些阈值什么时候被跨过 —— 而不是让曲线好看。
推论:一个好的成长曲线,应该在几个特定的等级上「刚好跨过」某个阈值。而不是每级都平滑地涨一点点(那样玩家一次阈值都跨不过,全程无感)。
数值膨胀:为什么你的数字最后会变成一串零
长期项目的经典病。第一章的怪 20 血,最后一章的怪 2000000 血。
它是怎么发生的:
设计师想让第 2 章比第 1 章「更强」 → 数值 ×2 第 3 章要比第 2 章更强 → 再 ×2 …… 第 10 章 → 512 倍 玩家看到的伤害数字: 10 → 5120 → 后面全是零
问题不只是不好看:
- 玩家失去了数字的直觉。「12500 伤害」和「13800 伤害」哪个好?大脑要算,而算不动就干脆不看了。
- 早期内容彻底作废。第 1 章的装备在第 3 章就是垃圾,你精心设计的东西只被用了半小时。
- 浮点精度和整数溢出(真的会遇到)。
- 用乘法修饰,不用加法堆叠。与其「+50 攻击力」,不如「+15% 攻击力」。前者会让数值线性膨胀,后者天然收敛。
- 设一个数值上限,然后横向扩展。攻击力封顶在 100,之后的成长改成「新能力」「新组合」「新效果」而不是更大的数。这是《杀戮尖塔》的做法 —— 它的伤害数字从头到尾都是两位数。
- 如果一定要膨胀,就换单位。1000 伤害 → 显示成「1.0k」。这是放置类游戏的标准做法。
对个人项目,第 2 条最好:数值封顶,成长靠机制。因为它同时解决了数值膨胀和「后期内容做不完」两个问题。
怎么调:从哪里开始
面对一堆需要定的数字,有个实用的顺序:
- 先定「一个基准单位」。比如:玩家 1 级攻击力 = 10。所有其他数值都相对它来定。
- 再定几个关键的 TTK。杂兵几下死、精英几秒死、Boss 几分钟。
- 反推血量。而不是先拍血量再调攻击。
- 然后定阈值时刻。「我希望玩家在第 3 关能一下打死杂兵」—— 这决定了成长曲线的形状。
- 最后才填表格。
新手常见流程:打开表格软件,建 20 行「等级」,然后开始填攻击力、血量、经验值……
这个流程的问题是你在填数字,而不是在做设计决定。填完之后你有一张漂亮的表,但你不知道玩起来是什么感觉。
正确的流程是先回答那几个体验问题(TTK 多少?什么时候跨过阈值?),表格只是把答案写下来的地方。
这是第 20 章「数据驱动」的前哨。数值一定会改很多次,把它们做成可以在编辑器里直接编辑的资源:
class_name EnemyStats
extends Resource
@export var display_name: String = "史莱姆"
@export var max_hp: int = 20
@export var damage: int = 5
@export var move_speed: float = 40.0
@export_range(0.0, 5.0) var attack_interval: float = 1.2
# 把「设计意图」也写进来,未来的你会感谢自己
@export_group("设计意图")
@export var intended_ttk: float = 0.6 # 期望的击杀时间
@export var tier: String = "杂兵"
# 敌人脚本只引用资源,不关心具体数字 extends CharacterBody2D @export var stats: EnemyStats func _ready() -> void: hp = stats.max_hp
好处:改数值不用碰代码、能建多个变体(普通史莱姆 / 精英史莱姆共用同一个脚本)、而且 intended_ttk 那一栏让你能写一个自动校验脚本:
@tool
extends EditorScript
func _run() -> void:
var player_dps := 10.0 / 0.3 # 攻击力 / 攻击间隔
for path in _all_enemy_stats():
var s: EnemyStats = load(path)
var actual := s.max_hp / player_dps
if absf(actual - s.intended_ttk) > s.intended_ttk * 0.3:
push_warning("%s:期望 TTK %.1fs,实际 %.1fs —— 血量该调了"
% [s.display_name, s.intended_ttk, actual])
让工具替你盯住设计意图和实际数值的偏离。手工核对二十个敌人的数值,你一定会漏。
本章小结
- 玩家感受不到攻击力,他感受的是 TTK、TTD、相对变化。
- 敌人血量和玩家攻击力同步增长 = 击杀时间恒定 = 升了个寂寞。
- 四种曲线里,对数 / 收敛通常最好用;指数增长在游戏里几乎总是麻烦。
- 先定 TTK,再反推血量,不要反过来。杂兵 TTK 超过 2 秒,游戏会变得很累。
- 成长感来自跨过阈值(两下变一下),不是来自数字变大。数值设计的真正工作是安排阈值时刻。
- 治数值膨胀:乘法修饰、数值封顶 + 横向扩展、换单位。个人项目首选封顶。
- 把数值做成
Resource,并把设计意图一起写进去,让工具自动校验偏离。
- 卡牌 / 肉鸽:TTK 的等价物是「几回合打完一场战斗」(通常 3–8 回合最舒服)。这类游戏是「数值封顶 + 横向扩展」的典范:伤害永远两位数,成长全靠组合。如果你的卡牌游戏后期开始出现四位数伤害,多半是设计出了问题。
- 塔防:核心数值是「DPS 缺口」 —— 玩家的总输出 vs 一波敌人的总血量。设计一关就是设计这条缺口曲线:什么时候刚好够、什么时候差一点(逼玩家升级)。先画缺口曲线,再定塔的数值。
- 模拟经营:核心数值是「产出 / 消耗比」和回本周期。这类游戏的膨胀问题最严重(复利),第 17 章会专门讲。
- 平台 / 动作:数值最少的类型,但也有 —— 跳跃高度和格子尺寸的关系是这里最重要的数值。建议:先定格子(16px),再定跳跃高度为格子的整数倍多一点(比如 2.3 格),这样关卡设计时心里有数。本书那台引擎跳 37 像素 = 2.3 格,就是这么定的。
- 叙事解谜:几乎没有数值,但有一个隐藏的:「玩家卡住的容忍时长」(通常 5–10 分钟)。超过就要给提示。这是这类游戏唯一需要认真调的「数值」。