导语:「远港 Online」开服第 41 天,运营拉出一条告警曲线:全服金币总量 30 天翻了三倍,拍卖行的铜矿从 2 银涨到 9 银,新号连药水都买不起。策划连夜改 Excel,程序给了个更坏的消息——经验曲线的指数写死在 C++ 里,调参要走完整发版流程,灰度三天。那一周的复盘会只总结出一句话:数值是活的,写死常数的代码是死的。本章讲怎么把成长、掉落、经济这三台发动机全部搬进表里,以及搬进表之后,旋钮该怎么拧。
本章地图
mindmap root((第27章
多职业成长与数值经济)) 核心循环 练级 装备 团本 四环 每个环节的乐趣与疲劳 立场:一切数值先进表 成长曲线 A["L(x)=a·x^b"] a 管总时长 b 管前松后紧 1到60级时间预算表 职业平衡 机动 生存 输出 辅助 不可替代性大于相等 等效DPS统一货币 GearScore四条军规 掉落随机 方差制造愤怒少数派 保底收敛方差 防工作室收益衰减 经济系统 水龙头:任务 卖店 掉落 水槽:修理 拍卖行税 坐骑 通胀等于流入减流出 远港落地 六张数值表结构 结算器代码零常数 赛季节奏滚动
本章的顺序跟着一台 MMO 的呼吸走:先解剖核心循环这台"希望发动机",再逐个拆它的四台分发动机——成长、职业、掉落、经济——最后把「远港 Online」的六张数值表从电子表格一路接到第 22 章的 GE 管道上。读这一章不需要你会玩 MMO,但需要你接受一个立场:程序员的职责是把公式做成配置驱动的结算器——把常数写死在代码里,那是过去式。
27.1 补课·数值策划:核心循环是一台"希望发动机"
补课:本节补数值策划的世界观——把数值理解为一台发动机的转速表,比当成一堆数字有用一百倍。策划出身的读者可以直接跳到 27.2 看公式。
单机游戏的数值是一次性剧本。「回响峡谷」主角的攻击力从 10 涨到 40,通关就结束,涨错了顶多重打一个 Boss。MMO 的数值是一台要运转五年的机器:几十万玩家同时踩踏板,任何一个齿轮转速不对,三个月后都会以运营事故的形式回来找你。
《魔兽世界》二十年没换过的核心循环,拆开来只有四个齿轮:
flowchart LR
A["练级
任务与杀怪"] --> B["升级
解锁新技能"]
B --> C["换装
属性上台阶"]
C --> D["团本
协作挑战"]
D --> E["掉落
更强装备"]
E --> C
E --> F["毕业
等待新赛季"]
A2["乐趣:成长可见
疲劳:机械重复"] -.-> A
B2["乐趣:新玩具
疲劳:满级即终点"] -.-> B
D2["乐趣:协作与荣光
疲劳:组队成本高"] -.-> D
E2["乐趣:开奖瞬间
疲劳:二十连空"] -.-> E四个齿轮,每个都同时挂着乐趣来源和疲劳点。练级的乐趣是成长肉眼可见,疲劳是杀第三十只野猪时的机械重复;升级的乐趣是新技能这个"新玩具",疲劳是满级一刻的终点落差;团本的乐趣是协作与首杀荣光,疲劳是固定团考勤式的组队成本;掉落的乐趣是开奖瞬间的多巴胺,疲劳是连刷二十次颗粒无收的挫败。
数值策划的全部工作,是在这四个齿轮上调节乐趣的密度与疲劳的间隔:乐趣密度高于疲劳积累速度,玩家留下;反过来,玩家流失。满级送一把超越五人本的好武器,是把"终点落差"往后推;团本掉落加保底,是把"二十连空"的尾巴砍掉。每一条具体手法后面都会展开。
四环之外还有一圈元循环:赛季。装备总会毕业,而毕业是疲劳的终极形态——没有下一个台阶可追,乐趣密度直接归零。赛季制用一次软重置给循环重新上弦:新团本、新装等、新水槽,旧毕业装一夜变成新本的入场券。玩家嘴上骂"肝白废了",留存曲线却在每次赛季更新时准点回弹——他们要的从来都是"又有得追了",保值只是嘴上说说。
这套循环之所以值得逐环建模,是因为每一环都可以被计算:练级速度是经验曲线的积分,换装强度是等效 DPS 的台阶,团本门槛是一次属性检查,掉落是带保底的几何分布,物价是货币存量的函数。玄学部分——手感、情怀、社区情绪——真实存在,但底盘全是可算的。后面四节,每台发动机配一个模型。
程序在这条循环里的位置要说清楚。决定 60 级该花 117 小时的那个人是策划——你的工作是把"117 小时"做成策划 Excel 里可改的一行。判断标准只有一条:策划想调任何一个数字,如果需要拉上程序发版本,这个系统就已经失败了。公式留在代码里,经过评审、可单测;常数全部进表,策划自治。这个分工和数据库一模一样——程序写存储引擎,策划写数据,没有人会接受"改一行用户数据要重新编译 MySQL"。
27.2 成长曲线:a 管五年总时长,b 管前松后紧
行业里的升级经验几乎都在用幂函数族,形式干净得不像话:
是从 级升到 级所需的经验, 是基础量级, 是形状指数。整个五年运营的节奏感,就藏在这两个旋钮里。
是手感旋钮,拧大拧小是两种完全不同的游戏:
| b 的取值 | 曲线形状 | 玩家体验 | 代价 |
|---|---|---|---|
| b = 1(线性) | 每级匀速变难 | 平稳,没有节奏感 | 缺乏钩子,中规中矩 |
| b 拧大(1.8~2.2) | 前松后紧 | 新手期飞快,留存曲线好看 | 50 级后每级时长陡增,后期流失 |
| b 拧小(0.7~0.9) | 前紧后松 | 满级很快,满级内容早消费 | 升级感贬值,第 58 次金光内心毫无波澜 |
免费游戏偏爱大 b:先把人钩住再说。但 b 拧大的账会在两个月后一起结——50 级之后的流失曲线斜率和 b 正相关。b 拧小的账更隐蔽:玩家比计划早两周满级,团本还没做完,运营被迫提前放内容,整个内容排期被牵着鼻子走。
是总时长旋钮:形状不变, 翻倍等于全部升级时间翻倍。赛季末的"追赶机制",就是给回归玩家临时把 打六折。
正确的开工顺序是先定时间预算,再反推 a 和 b。预算是体验目标,公式只是实现手段。「远港 Online」的 1–60 级预算表长这样:
| 等级段 | 每级时长 | 累计时间 | 该段主要内容 |
|---|---|---|---|
| 1–10 | 约 20 分钟 | 约 3 小时 | 新手村与教学 |
| 11–20 | 约 40 分钟 | 约 10 小时 | 第一张完整地图 |
| 21–30 | 约 1.2 小时 | 约 22 小时 | 双地图 + 首个五人本 |
| 31–40 | 约 2 小时 | 约 42 小时 | 阵营冲突区 |
| 41–50 | 约 3 小时 | 约 72 小时 | 高难任务链 |
| 51–60 | 约 4.5 小时 | 约 117 小时 | 冲刺满级 + 团本开门任务链 |
117 小时满级,对标经典 MMO 的 100–150 小时区间,是"硬核但可达"的口径。有了预算表, 和 就是解方程的事:取 让曲线微微上翘,再调 让各段落在预算线上。
预算表的另一半是经验获取速率。总时长其实是这么算出来的:
是该等级段的平均经验获取速率(杀怪加任务),它同样进表。策划真正的日常战场在 上—— 和 是开服前定好的,而 曲线要光滑,持续维护全靠它。一旦出现"这一段升级突然快三倍"的断裂带,玩家会扎堆刷同一个任务,其余内容变鬼城,地图设计师的心血直接作废。
「远港 Online」二测时就踩过一次:32 级某条任务链的单次经验被错配成同级均值的三倍,一周内全服 32 级玩家挤爆同一张地图,Zone 负载(见第 25 章)跟着报警。修表、回滚溢出经验、补发全服补偿,一套流程走了两天——那串数字好在是在表里,如果写死在代码里,还得再加三天发版。
属性成长是另一道选择题:线性还是指数。指数模型(每 10 级属性翻倍)到 60 级就是 64 倍差距,旧副本瞬间变灰,新人永远追不上老人,最后只能做"属性压缩"大手术——WOW 历史上做过两次,每次都伴随社区地震。「远港 Online」的选择是:基础属性分段线性,成长感的大头交给装备。 装备是可控的台阶,还能兼任经济回收手段(见 27.5)。一句话记住分工:等级管时间,装备管强度,两条曲线别搅在一起。
属性压缩值得单独说,因为它是所有指数膨胀的终点站。当满级血量涨到六位数、伤害数字蹦出七位数,界面排版、int32 上限、玩家的读数快感会同时出问题。压缩的做法是全服数值除以一个常数再拉平曲线——听起来只是除法,实际要重算数年积累的所有怪物血量、技能系数与装备属性,任何一处漏除都会变成秒杀 bug。WOW 的两次压缩都动用以季为单位的工程量。起点选线性,终点就不用做这台手术。
27.3 职业平衡:瞄准的是"缺了谁都不行"——谁比谁数字大,从来是假目标。
新手平衡师最容易立错的目标函数,是"三个职业 DPS 相等"。真做到了,得到的是三碗同质化的面,社区照骂——玩家嘴里喊的是"我要数值公平",心里要的其实是"我选的职业有存在的意义"。
平衡的正确定义:每个职业至少拥有一个不可替代的场景。 拿「远港 Online」的三职业画一张团队需求矩阵:战士是唯一的坦克,减伤链和换坦机制只有他扛得住;法师是唯一的群体控制加爆发 AOE,ADD 小怪波次没有他就清不掉;牧师是唯一的群体治疗与复活,团队血线缺口只有她能补。缺任何一个,副本机制无解。这三个"只有我能",比任何 DPS 排行榜都值钱。
四维定位图能看清三职业各自站在哪:
quadrantChart
title "远港 Online 三职业定位矩阵"
x-axis "偏辅助" --> "偏输出"
y-axis "偏机动" --> "偏生存"
quadrant-1 "重装前线"
quadrant-2 "团队之锚"
quadrant-3 "游走支援"
quadrant-4 "玻璃大炮"
"战士": [0.62, 0.85]
"法师": [0.85, 0.25]
"牧师": [0.20, 0.55]战士蹲在高生存的"重装前线",法师站在高输出低生存的"玻璃大炮",牧师守在"团队之锚"。四维打分(机动、生存、输出、辅助)同样进表管理,总和不必相等,但每个职业必须有一格全服第一——那一格就是他的不可替代性来源。哪天战士的辅助分被调到和牧师一样,两个职业会同时失去存在意义:一个没人组,一个没人怕。
不可替代性是可以被数据验证的。每周看三职业的团本出场率:长期低于 25% 的职业,说明他的"只有我能"被机制或数值偷走了——要么别的职业也能干他的活,要么他的活在当前副本里不重要。出场率均衡不代表强度相等,它只证明每个职业都有进本的理由。这个指标比任何木桩 DPS 排行都更接近平衡的本质。
定位定性之后,强度要定量。暴击、急速、命中这些属性各有各的单位,没法直接比较,得先折算成统一货币——等效 DPS:
逐项说人话: 是裸伤除以读条时间;暴击率 、暴击倍率 (通常为 2),期望增益是 ;急速 提升施法频率,近似独立乘区 ;命中率 直接乘——未命中就是零伤害,所以满命中之前命中的边际收益通常全场最高,"先堆命中"这条铁律就是这么推出来的。
代一组法师面板进去:火球均伤 500,读条 2.25 秒,暴击 20%,急速 10%,命中 95%,得到 DPS。在这个面板上求偏导,得到当天的"汇率":1% 暴击 ≈ 2.3 DPS,1% 急速 ≈ 2.5 DPS,1% 命中 ≈ 2.9 DPS。所有属性从此可以摆进同一个天平。
装备评分(Gear Score)就是这个天平的应用:每件装备的分数等于各属性乘上权重再求和。
GS(item) = \sum_{i} w_i \, s_i, \qquad w_i = \left. \frac{\partial \, E[DPS]}{\partial \, s_i} \right|_{\text{基准面板}}权重 是该属性在基准面板上的边际收益。GS 怎么算才不蠢,四条军规:
- 权重从边际收益导出。 拍脑袋的权重会在第二周被玩家用模拟器打脸。
- 权重随面板漂移,按赛季重算。 暴击堆到 40% 之后,1% 暴击的边际收益会下降,统一货币的"汇率"是浮动的。每赛季重算一次权重表,写进运营日历。
- GS 只在同职业内部可比。 跨职业比 GS 是论坛骂战的第一弹药,官方评分体系绝不背书这种比较。
- 套装特效与主动技能单独折算一列,别揉进主分。揉进去的评分会失去解释性,玩家问"这件凭什么高 5 分"时你答不上来。
坦克和治疗不在 DPS 榜上,但同样要建模。威胁值(Threat)= 伤害 × 威胁系数 + 技能附加威胁,系数进表;OT(仇恨失控)灭团是团本经典事故,威胁系数是坦克的隐形强度——它不出现在任何排行榜上,却决定团队能不能开打。治疗则折算成"等效生存秒数":团队承伤曲线与治疗输出曲线之间的缺口,就是牧师的工作量和存在理由。
27.4 纯随机掉落为什么会被骂上热搜
先算一道小学概率题:掉率 10% 的坐骑,连刷 20 次不出的概率是 。翻译成人话:每 8 个玩家里就有 1 个,刷了 20 次颗粒无收。10 万日活的游戏,每天产出一万多个"20 连空"玩家,其中哪怕只有 1% 发帖,也是每天一百多条"这游戏暗改掉率"——热搜素材就是这么批量生产的。
玩家的概率直觉是"十次出一次",第 11 次不出就觉得被针对。说到底是人对独立随机事件的直觉天生有偏差(赌徒谬误),别往玩家数学水平上归因。你没法教育全体玩家,只能改系统。
真正的敌人是方差。纯随机的期望次数是 10 次,但几何分布的尾巴极长——44 次不出的概率仍有约 1%。保底不改期望,只砍尾巴:分布从几何分布变成"封顶 20 次"的截断分布。这个思路在业界早有先例,Dota 2 的暴击用的就是递增概率(PRD),目的相同:砍掉"连续三刀不爆"的方差尾巴。
保底还有一个白捡的心理学加成:它把赌博变成进度条。玩家的心智从"下一次会不会出"变成"再打 7 次必出",挫败感变成目标感。期望支出不变,玩家体验质变——游戏设计里少有的零成本双赢。
严格说也不是零成本,代价藏在分布形状里。两组分布摆在一起看最直观(两边都标定到约 10% 的实际掉率,模拟 20 万次击杀):
| 指标 | 纯随机 10% | 递增概率 + 20 次硬保底 |
|---|---|---|
| 期望击杀次数 | 10.0 | ≈ 9.9 |
| 中位数 | 7 | 9 |
| 90% 玩家拿到 | 22 次内 | 18 次内 |
| 99% 玩家拿到 | 44 次内 | 20 次内 |
| 最坏情况 | 理论上无穷 | 20 次 |
期望几乎不变,尾巴从 44 次砍到 20 次;代价是中位数从 7 推到 9——"三次就爆"的欧皇时刻变少了。保底削掉的是分布的两个极端:极端的幸运和极端的不幸,而社区音量永远来自后者,这笔交换稳赚。
参数标定这步别省。base 和 step 拧完必须跑蒙特卡洛验证实际掉率:拍脑袋拍一组 5% + 5%,期望掉率会被悄悄抬到约 19%,等于全服装备通胀——保底没做成,先把装备做贬值了。上面那组能用的参数是 2.5% + 每次递增 1.25%,20 次硬保底,实际掉率 10.1%。
「远港 Online」的保底结算器长这样,全部参数来自掉落表:
# 伪代码:个人掉落保底结算(服务器侧,每次击杀 Boss 调用)
# 策划只配三个数:base_chance / step / hard_pity,全部来自掉落表
class PityState:
misses: int = 0 # 连续未中次数,随角色档案持久化(见第 26 章)
def roll_boss_loot(cfg, state) -> bool:
# 概率随未中次数线性爬坡:第 n 次未中后,命中率 = base + step * n
chance = min(cfg.base_chance + cfg.step * state.misses, 1.0)
if state.misses + 1 >= cfg.hard_pity: # 硬保底:第 N 次必掉
chance = 1.0
if rand() < chance:
state.misses = 0 # 中奖清零,重新计数
grant_loot(cfg.item_id)
return True
state.misses += 1
save_dirty(state) # 标脏,等待批量回存
return False三个工程要点。一是计数器按角色持久化,跨天、跨副本都有效——玩家上周刷了 17 次没出,这周上线第 3 次必出,这个"记忆"是保底体验的根基,落库走第 26 章的脏标记回存。二是结算只在服务器,客户端上报"我出了"一律按作弊处理,权威模型见第 21 章。三是保底对打金工作室同样有效,所以要配收益衰减:同一目标当日第 50 次击杀之后掉率打 2 折,衰减起点也进表。让脚本的边际收益低于电费,比封号便宜得多。
27.5 经济系统:盯紧水龙头与水槽的差额
MMO 金币的本质是一家无限发行的央行:每只怪都是印钞机,每次交任务都在做量化宽松。通胀不通胀,不取决于某个水龙头开多大,而取决于全体产出与全体回收的差额。差额长期为正,水位就涨,物价就跟。
通胀的机理用货币数量论一句话讲清:
是全服金币存量, 是流通速度, 是可购买的商品与服务总量。新资料片上线后,玩家刷本热情高涨, 日均涨 2%;而 (消耗品、材料、服务的有效供给)只涨 0.3%。差值全部变成物价——铜矿从 2 银涨到 9 银就是这么来的。最受伤的是新玩家:他们的收入曲线没跟上物价曲线,等于被动缴纳"新人税",新手村留存应声下跌。开服第 41 天那场事故,剧本就是这么写的。
公式里还有一个容易被忽视的 (流通速度)。同样 100 亿存量,躺在大户仓库里不动,和每天在拍卖行倒手三次,对物价的影响差出数倍。囤金是"冻结的水位",扫货是"活水位"——所以物价指数要选高流通商品做篮子——全服均富是个幻觉,别用它。 的最大放大器是线下打金交易:工作室产出的金不消费,全流向买家变成即期购买力,等于把冻结水位瞬间解冻。打击打金除了封号,更釜底抽薪的做法是 27.4 的收益衰减加绑定货币——让打金产出进不了流通市场。
回收工具箱按优雅程度排序:
- 拍卖行交易税(5%):比例税,交易越活跃回收越多,随经济规模自动缩放,玩家痛感低——首选工具,没有之一。
- 修理费与传送费:与活跃行为挂钩,稳定但量级小,适合当底仓。
- 高价坐骑、幻化学费:一次性大额回收,资料片必备,效果立竿见影,代价是掏空中产玩家的积蓄,一年最多用一两次。
- 绑定货币(拾取绑定的第二种金币):直接开一条不流通的平行货币,激进但有效,WOW 的各类点数就是这个思路。
高价坐骑这类一次性水槽,定价锚在全服人均持有的 3–5 倍:定低了,大户秒买、回收量不足;定高了,中产彻底绝望,连讨论热度都换不来。它的目标很单纯——让前 5% 的囤金大户把冻结水位一次性蒸发——一匹 500 万金的坐骑,回收效率约等于拍卖行抽税半个月。
核心原则一条:回收额必须随产出自动缩放。 固定额回收(每人每天 10 金修理费)在 M2 翻倍之后约等于没有。比例税是唯一跟得上通胀的结构——这也是它排第一的原因。
监控经济不需要复杂模型,每天三个数就够:
-- 每日经济体检(离线库执行,结果推仪表盘;窗口函数语法以所用数据库方言为准)
-- ① 水位:货币总量与人均持有
SELECT SUM(gold) AS m2_total,
AVG(gold) AS per_capita
FROM character_wallet;
-- ② 物价指数:30 种基准商品(草药/矿石/附魔材料)当日成交中位价
SELECT item_id,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY unit_price) AS median_price
FROM auction_deals
WHERE deal_time >= CURRENT_DATE - INTERVAL '1 day'
AND item_id IN (SELECT item_id FROM econ_price_basket)
GROUP BY item_id;警戒线画在"M2 周环比 > 3% 且连续两周":触发即启动回收预案——拍卖行税上调 1–2 个点,或提前投放坐骑水槽。经济事故的黄金处理期是头两周,等玩家在论坛"感觉"到通胀,物价已经涨完一轮,回收成本翻倍。
这里和第 26 章的分工要划清:第 26 章保证每一笔交易原子、不丢不重,管的是微观正确;本章管总量政策,是宏观健康。拍卖行"扣款与发货"的事务边界是 26 章的事,"每笔交易抽走的 5% 税去哪"是本章的事——答案是:从系统里直接蒸发,不进任何 NPC 的口袋。 蒸发是回收与再分配的本质区别,税收一旦回流成 NPC 补贴,回收就失效了一半。
27.6 「远港 Online」数值表落地:从 Excel 到 GE
前面五节的所有数字,最后都收进同一条管线。先看全貌:
classDiagram
class SheetSource {
+策划维护的电子表格
+Git 管版本,改动要评审
LEVEL_XP / ATTR_GROWTH / SKILL_COEF 等
}
class DataTableAsset {
+UDataTable(CSV/JSON 导入)
+CI 导出,Cook 进包
+开发期支持热重载
}
class Settlement {
+结算器:服务器唯一调用点
+查表、组装数值
+代码内零常数
}
class GameplayEffect {
+SetByCaller 注入数值
+管道见第 22/23 章
}
class AttributeSet {
+MaxHealth / Attack / SpellPower
}
SheetSource --> DataTableAsset : 导出管线
DataTableAsset --> Settlement : 运行时查询
Settlement --> GameplayEffect : 数值注入
GameplayEffect --> AttributeSet : 修改属性策划维护电子表格(Excel 或 Google Sheet,Git 管版本——是的,表格也要评审,二进制冲突就用 CSV 中转),CI 导出后导入 UDataTable。UE 原生支持 CSV/JSON 行结构导入,更复杂的批量管线可以走 Dataprep;具体导入器配置在 5.8 中对应官方文档"Data-Driven Gameplay Elements"词条,以文档为准。运行时由结算器查表,数值经 SetByCaller 注入 GE——这条管道就是第 22 章搭好的那条,本章的表是它的上游配置。
表格进了 Git,评审纪律决定这条管线的寿命,三条够用。一是改表必须带理由:commit message 回答"为什么","是什么"泛泛而谈的会被打回,三个月后追查一次装备通胀时你会感谢这条。二是跨表一致性交给 CI 校验:SKILL_COEF 引用的职业必须在 ATTR_GROWTH 里存在,DROP_TABLE 的物品必须在物品主表里存在,孤儿行直接拒合。三是每次合入自动生成 diff 报告发到策划群,数值改动全员可见——暗改在透明管线里活不过一天。
「远港 Online」的电子表格一共六张,结构如下:
erDiagram
LEVEL_XP ||--o{ ATTR_GROWTH : "level 1..60"
ATTR_GROWTH ||--o{ SKILL_COEF : "class"
DROP_TABLE }o--|| BOSS_DEF : "boss_id"
LEVEL_XP {
int level PK
int xp_to_next
int time_budget_min
string unlock_note
}
ATTR_GROWTH {
string class PK
int level PK
float max_health
float attack
float spell_power
}
SKILL_COEF {
int skill_id PK
string class
float base_damage
float coef_attack
float coef_spell
float cooldown_s
}
DROP_TABLE {
int boss_id PK
int item_id PK
float base_chance
float step
int hard_pity
int daily_decay_start
}
GEAR_WEIGHT {
string class PK
string stat PK
float weight
int season
}
ECON_POLICY {
string key PK
float value
string note
}两张样例行,感受策划的日常工作界面。属性成长表(分段线性,装备承担强度大头):
| class | level | max_health | attack | spell_power |
|---|---|---|---|---|
| 战士 | 1 | 120 | 12 | 0 |
| 战士 | 60 | 3800 | 340 | 0 |
| 法师 | 1 | 80 | 4 | 18 |
| 法师 | 60 | 2200 | 60 | 620 |
| 牧师 | 1 | 95 | 6 | 14 |
| 牧师 | 60 | 2600 | 90 | 540 |
技能系数表(与第 22 章火球术解剖同构,伤害 = 基础值 + 系数 × 属性):
| skill_id | 名称 | class | base_damage | 系数 | cooldown |
|---|---|---|---|---|---|
| 1001 | 冲锋 | 战士 | 90 | 1.2 × 攻击 | 12 s |
| 2001 | 火球 | 法师 | 320 | 0.95 × 法强 | 读条 2.25 s |
| 3001 | 圣言术 | 牧师 | −450(治疗) | 1.1 × 法强 | 8 s |
第六张 ECON_POLICY 容易被漏掉:税率、修理系数、坐骑价格这些经济政策参数同样进表。把央行利率写死在服务器代码里,等于每次降息都要程序员审批——27.5 的回收预案要求的是当天生效。
结算器的代码只有薄薄一层,全部数字来自查表:
// 成长结算器:所有数字来自 DataTable,代码里没有一个常数
USTRUCT(BlueprintType)
struct FAttrGrowthRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere) int32 Level = 1;
UPROPERTY(EditAnywhere) float MaxHealth = 0.f;
UPROPERTY(EditAnywhere) float Attack = 0.f;
UPROPERTY(EditAnywhere) float SpellPower = 0.f;
};
// 服务器侧调用:等级变化时重算基础属性,经 GE 落地
void UGrowthSettlement::ApplyLevel(UAbilitySystemComponent* ASC, int32 NewLevel) const
{
const FString RowName = FString::Printf(TEXT("%s_%d"), *ClassId, NewLevel);
const FAttrGrowthRow* Row =
GrowthTable->FindRow<FAttrGrowthRow>(*RowName, TEXT("Growth"));
// 表缺行是内容事故:直接报错,绝不给默认值——默认值等于替策划掩盖事故
checkf(Row, TEXT("成长表缺行 %s"), *RowName);
FGameplayEffectSpecHandle Spec =
ASC->MakeOutgoingSpec(LevelAttrGE, NewLevel, ASC->MakeEffectContext());
// SetByCaller 的 Tag 在数值登记表里统一分配(见第 23 章命名宪法)
Spec.Data->SetSetByCallerMagnitude(TAG_Data_MaxHealth, Row->MaxHealth);
Spec.Data->SetSetByCallerMagnitude(TAG_Data_Attack, Row->Attack);
Spec.Data->SetSetByCallerMagnitude(TAG_Data_SpellPower, Row->SpellPower);
ASC->ApplyGameplayEffectSpecToSelf(*Spec.Data.Get());
}checkf 那一行值得展开:表缺行时给默认值,等于帮策划把内容事故藏起来,它会在线上以更贵的方式爆——某个 37 级战士突然裸奔,客服工单查三天才查到一张表。报错要趁早、要刺耳。
还有一个工程细节决定这套管线在线上好不好用:表怎么到达运行中的服务器。开发期靠编辑器热重载,改完即生效;线上不能重启进程,要走配置下发——服务器记录当前表版本号,运维后台推送新版本,结算器在安全点(副本结算间隙)整体切换,切换前后各落一条审计日志。是整体切换版本,不是热改单行:同一时刻全服只能存在一份生效的表,否则两个玩家在同一秒按不同版本的系数结算,投诉工单会比通胀来得更快。
数值表跟着赛季一起滚,不是一次性交差。节奏大致是这样:
gantt
title 远港 Online 第一赛季数值节奏
dateFormat YYYY-MM-DD
section 练级期
开服冲级窗口(盯 r(x) 断裂带) :a1, 2026-03-01, 14d
section 装备期
五人本毕业潮(盯掉落保底投诉) :a2, after a1, 21d
团本一区开荒(盯职业出场率) :a3, after a2, 28d
section 回收期
团本二区 + 新坐骑水槽投放 :a4, after a3, 35d
赛季结算:GEAR_WEIGHT 重算与压缩评估 :a5, after a4, 14d每个阶段盯的指标不同:冲级窗口盯 曲线有没有断裂带,毕业潮盯保底投诉率,开荒期盯三职业出场率——出场率长期低于 25% 的职业,就是不可替代性被动了刀,下赛季的数值表要还回来。赛季末固定做两件事:按当前玩家面板重算 GEAR_WEIGHT 权重,评估属性膨胀是否逼近压缩红线。
一家运营五年的 MMO,数值团队九成的工作是在这六张表里拧旋钮;程序的那一成,是保证拧旋钮永远不需要重新编译。
小结
一切数值先进表:公式在代码里(评审、可单测),常数在表里(策划自治);策划调一个数字需要程序发版本,这个系统就已经失败了。成长曲线先定时间预算再反推参数: 管总时长, 管前松后紧;等级管时间、装备管强度,属性走分段线性,把指数膨胀挡在装备层。平衡瞄准的是不可替代性而非数字相等;等效 DPS 把暴击、急速、命中折成统一货币,Gear Score 权重从边际收益导出并按赛季重算。纯随机掉落的方差每天批量制造"20 连空"的愤怒少数派;保底不改期望、只砍尾巴,把赌博变成进度条,计数器必须按角色持久化。经济盯住产出与回收的差额:回收要随产出自动缩放(比例税是唯一结构),监控 M2、人均持有、物价指数三个数,税从系统里直接蒸发,不回流到任何 NPC 口袋。
上手任务
- 打通表格管线:把 1–60 级经验表与三职业属性表做成 CSV 导入
UDataTable,实现 27.6 的成长结算器。验收:修改表里某一级经验后,不改一行代码、不重新编译,重导表即在 PIE 生效;删掉某一行,启动时checkf直接报错并打出缺失行名。 - 保底模拟器:用 27.4 的伪代码跑 20 万次击杀模拟,对照纯随机 10%。先自己标定一组 base / step 参数,再做对照。验收:保底组"超过 20 次未中"的比例为 0%,纯随机组约 7%;两组实际掉率相差不超过 1 个百分点(提示:5% + 5% 的拍脑袋参数会把实际掉率抬到 19% 左右,标定不合格)。
- 算一张权重表:按 27.3 的等效 DPS 公式,为法师基准面板(火球 500 均伤、读条 2.25 秒、暴击 20%、急速 10%、命中 95%)计算各属性边际收益,给两件"装等相同、属性不同"的装备打分。验收:评分排序与训练木桩实测 DPS 排序一致;把暴击面板抬到 40% 后重算,指出哪件装备的排名发生了翻转。
- 伪造一周经济数据:在测试库造 7 天产出大于回收的钱包与拍卖行数据,跑 27.5 的体检 SQL。验收:仪表盘在"M2 周环比 > 3%"时标红,并能用一条
UPDATE econ_policy把拍卖行税从 5% 调到 7%,无需重启服务器。
下一章
数值与经济都进了表,最后剩下的问题是有人想"改表"——作弊者不只盯客户端内存,还盯协议、盯结算器、盯掉落随机数。第 28 章上线与运营:反作弊分层、CI/CD 与热更新,把游戏从"能玩"推到"敢运营"。
延伸阅读
- 官方文档:Unreal Engine → Data-Driven Gameplay Elements(
UDataTable、行结构与 CSV/JSON 导入的权威词条) - 官方文档:Unreal Engine → Gameplay Ability System → GameplayEffect 词条(SetByCaller 与 Modifier 管道,配合第 22 章)
- 源码:
Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h(FindRow的实现与上下文字符串的用途) - 书目:Richard Bartle《Designing Virtual Worlds》(虚拟世界经济与玩家类型的奠基之作,水龙头-水槽模型的思想源头之一)
- GDC Vault 主题方向:魔兽世界团队关于属性膨胀与"item squish"(属性压缩)的分享,以及暗黑破坏神 III 拍卖行经济失败的复盘(检索 economy / itemization 关键词,看真实事故报告)