全程用 AI 做一款商业级手游 · EP3 内容数据化:让数值和代码分家
EP2 把核心玩法跑通了,但有个"原型味"很重的地方:发牌是写死的均匀随机——PieceLibrary.Random 等概率抽一个形状。这在原型里没问题,在商业游戏里是大问题。
EP0 讲过:成熟商业游戏的数值(经济曲线、难度参数等)通常都是服务端可下发的配置,策划改表、运营调参,都不用动代码、不用发版。这一集(EP3)就立这个规矩——让数值和代码分家,拿"难度曲线"开刀。
为什么数值必须数据化
写死 Random() 意味着:想让"越往后大块越多、越难"就得改代码、重编译、重新出包。等到有 200 关、要按留存数据反复调难度时,这是灾难。商业做法是把"可调的东西"抽成数据资产,代码只读它。
Block Blast 的难度主要由发什么方块决定:开局多给小块(好放、轻松上手),后期多给大块和难形状(制造压力)。把这条曲线做成数据。
难度配置:一个 ScriptableObject
[CreateAssetMenu(menuName = "BlockBlast/DifficultyConfig")]
public class DifficultyConfig : ScriptableObject
{
[System.Serializable]
public struct Tier { public int scoreThreshold; public float wSmall, wMed, wLarge; }
public int colorCount = 6;
public Tier[] tiers; // 按 scoreThreshold 升序
// 取 ≤ score 的最高档权重
public void WeightsFor(int score, out float s, out float m, out float l)
{
s = 1f; m = 1f; l = 0.4f;
if (tiers == null || tiers.Length == 0) return;
for (int i = 0; i < tiers.Length; i++)
{
if (score >= tiers[i].scoreThreshold) { s = tiers[i].wSmall; m = tiers[i].wMed; l = tiers[i].wLarge; }
else break;
}
}
}
配的 4 档(资产放在 Assets/Resources/DifficultyConfig.asset):
| 分数档 | 小块权重 | 中块权重 | 大块权重 |
|---|---|---|---|
| 0(开局) | 3.0 | 2.0 | 0.3 |
| 300 | 2.0 | 2.5 | 0.8 |
| 1000 | 1.2 | 2.0 | 2.0 |
| 3000(后期) | 0.8 | 1.8 | 3.0 |

光看配置表没意思,直接在真游戏里验:开局(分数低)托盘给的是小块——好放、轻松上手:

然后我用 MCP 把分数顶到 5000、按当前分数重发一手托盘(execute_code 调控制器的 DealTray),同一套发牌代码,托盘立刻变成清一色大块(两个 5 格 L + 一个 L 角)——压力一下就上来了:

代码一行没改,改的只是"当前分数"喂给配置——这就是数据驱动在真机上的样子。下面把它怎么实现的拆开。
方块按尺寸分类 + 加权抽取
PieceLibrary 把形状按格子数分三档(小 ≤2 / 中 3~4 / 大 ≥5),按权重抽:
public static int SizeClass(in Piece p) => p.CellCount <= 2 ? 0 : (p.CellCount <= 4 ? 1 : 2);
public static Piece WeightedRandom(System.Random rng, float wSmall, float wMed, float wLarge, int colorCount)
{
float total = wSmall + wMed + wLarge;
int cls = total <= 0 ? rng.Next(3) : Pick(rng, wSmall, wMed, wLarge); // 按权重选档
var bucket = _byClass[cls];
var src = bucket[rng.Next(bucket.Count)];
return new Piece { name = src.name, colorId = 1 + rng.Next(colorCount), cells = src.cells };
}
控制器发牌时,从配置按当前分数取权重——代码不关心曲线长什么样,只问配置:
void DealTray()
{
for (int i = 0; i < TraySize; i++)
{
_diff.WeightsFor(Score.Value, out float s, out float m, out float l);
Tray.Add(PieceLibrary.WeightedRandom(_rng, s, m, l, _diff.colorCount));
}
}
代码里没有任何一个难度数字——全在资产里。想让游戏更难/更易,改资产,不动代码。这就是"分家"。
验证:11 条断言,重点是分布真的漂移了
PASS A dot→小 / d2h→小 / T→中 / d5h→大 / sq3→大 (尺寸分类)
PASS B 分0→档0(wLarge=0.3) / 分700→档1(=0.8... 实为1) / 分5000→档2/3 (tier 选择)
大块占比: 早期=5.8 % 后期=50.8 %
PASS C 后期大块占比 > 早期
PASS C 后期大块占比明显更高(>2x)
PASS D NewGame 后 tray==3 (控制器集成)
==== 11/11 PASS ====
最有说服力的是 C:用早期权重抽 4000 次,大块只占 5.8%;用后期权重抽 4000 次,大块占 50.8%。同一套抽取代码,只换了配置里的几个数字,难度分布就从"几乎全是小块"漂移到"一半是大块"。这正是数据驱动的意义——行为由数据决定,不由代码决定。
这一集的产物与诚实的话
DifficultyConfig(ScriptableObject)+ 方块尺寸分类 + 加权抽取 + 控制器集成。DifficultyConfig.asset(4 档曲线)放进 Resources,运行时加载。- 11 条断言全绿,含分布漂移实测。
诚实地讲:这一集只把"难度"这一类数值数据化了,而且是"按尺寸权重"这种粗粒度的难度(不是精确编排每一关的布局)。一个完整商业游戏还要把关卡布局、商城 SKU、主题、各种平衡曲线全部数据化——那是后面几集(经济、商城、主题)会继续铺的。但"数值/内容与代码分家"的这条缝,从这一集起立住了,后面所有可调的东西都顺着它走。
一个该数据化但还没做的:现在配置放在
Resources里、随包走。真正的商业项目会让它服务端可下发(远程拉一份覆盖本地),运营不发版就能调难度——这个留到 EP8 远程配置一起做。
下一篇 EP4:经济系统——货币、道具,以及那条"金币随玩家进度缩放"的经济曲线(就是 EP0 拆解时讲的 系数 × milestone(进度))。开始从"玩法"走进"商业化"。
- 工具:funplay-unity-mcp
- 开源工程:本系列做出来的完整 Unity 工程已开源
- 上一篇:EP2 核心玩法
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)