全程用 AI 做一款商业级手游 · EP5 商城 + 内购:把经济曲线接到真实 SKU
EP4 把变现的命脉——“金币随玩家进度(最高分)缩放”——的算法做出来并验证了。但我也诚实地说了:那一集只有算法,没有商城、没有真内购。这一集(EP5)把它落地:一份可购买的 SKU 目录,一条购买发放的路径,外加 Unity IAP 的诚实接线。
先看真机跑起来的商城(这一集做完的样子,Unity 里实拍)——左边是新手(最高分 1),右边是封顶(最高分 8338),两张价格栏一模一样,金币栏整体差约 118 倍:


$1.99 那一档,新手到账 2,200 金币,封顶到账 260,000——同一个商品、同一个价格,价值随玩家成长自己往上长。下面把这套是怎么实现的拆开讲。
这一集有一个我特别想讲清楚的工程取舍——怎么在一台没配商店的开发机上,把"内购发放"这件事验证到 9 条断言全绿。
SKU 目录:数据资产,金币不写死
商城的"商品表"是一份 ScriptableObject(可服务端下发)。但注意——金币数量不在表里。表里只放一个 coef(经济系数),运行时喂给 EP4 的 EconomyConfig 按玩家当前进度(最高分)算。道具数量倒是定额(道具不随进度缩放)。
[CreateAssetMenu(menuName = "BlockBlast/ShopConfigDatabase")]
public class ShopConfigDatabase : ScriptableObject
{
[System.Serializable]
public struct Sku
{
public string id; // 内部 id
public string productId; // 商店内购 productId
public string displayName;
public float priceUsd;
public float coef; // 经济系数(喂给 EconomyConfig,不是写死的金币数)
public int grantUndo, grantBomb, grantSwap; // 定额道具
}
public Sku[] skus;
}
配了 7 个 SKU:5 个纯金币包($0.99 ~ $19.99,coef 递增)+ 2 个含道具的礼包。
商城逻辑:展示价按进度实时算
商城面板要显示"这个包能拿多少金币"。这个数不是静态的——同一个 SKU,新手看到的和老玩家看到的不一样,因为它现算:
public int DisplayCoins(string skuId)
{
if (!Shop.TryGet(skuId, out var sku)) return 0;
return Econ.CoinsFor(sku.coef, UserDataModel.Instance.curLevel.Value);
}
这就是 EP4 那条经济曲线第一次真正被"用"起来:商城的每一格价签,都是 CoinsFor(coef, 当前进度) 的实时结果(curLevel 是内部进度入参,喂的是玩家最高分)。
关键设计:发放路径与 IAP 解耦
这是这一集最重要的一个决定。真实内购要装 com.unity.purchasing、配商店后台、真机联调——一台干净的开发机上根本跑不起来。如果发放逻辑写在 IAP 的成功回调里,那它就永远无法在开发期验证。
所以我把"发放"抽成一个与 IAP 无关的口子 GrantPurchase,真商店成功回调和 mock 测试都调它:
public int GrantPurchase(string skuId)
{
if (!Shop.TryGet(skuId, out var sku)) return 0;
int coins = Econ.CoinsFor(sku.coef, UserDataModel.Instance.curLevel.Value);
if (coins > 0) CoinModel.Instance.ChangeCoin(coins, "iap:" + skuId);
if (sku.grantUndo > 0) BoosterModel.Instance.Add(BoosterType.Undo, sku.grantUndo);
if (sku.grantBomb > 0) BoosterModel.Instance.Add(BoosterType.Bomb, sku.grantBomb);
if (sku.grantSwap > 0) BoosterModel.Instance.Add(BoosterType.Swap, sku.grantSwap);
UserDataModel.Instance.lastPayTime.Value = NowUnix();
MsgController.Send(new PurchaseGrantedMsg { skuId = skuId, coins = coins });
return coins;
}
真 IAP 接线层用 #if UNITY_PURCHASING 守卫——装了 IAP 包就走 IStoreListener.ProcessPurchase,没装就是个会打警告的占位类,保证编译。而那个 ProcessPurchase 里干的唯一一件实事,就是回调 GrantPurchase(sku.id):
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
foreach (var sku in _db.skus)
if (sku.productId == args.purchasedProduct.definition.id)
{ ShopController.Instance.GrantPurchase(sku.id); break; }
return PurchaseProcessingResult.Complete;
}
收钱归 IAP 包管,发放归我管且可测。这就是为什么下面 9 条断言覆盖的,是真机上也会跑的同一段发放代码。
命脉可视化:同一份目录,自己重新定价
把 5 个金币包在新手(最高分 1)和封顶(最高分 8338)的到账金币画出来——价格一栏没动,金币栏整体放大约 118 倍:

$1.99 这一档,新手买到账 2,200 金币,打到封顶买同样的 $1.99 到账 260,000。整份目录跟着玩家成长自己重新定价——这正是 EP4 那条曲线的商业意义落到商城上的样子:玩家打得越深,同样的钱越值,越值得掏。
验证:9 条断言(覆盖真实发放路径)
coin_0199 展示金币: 最高分1=2200 最高分8338=260000
PASS A 展示价有效(>0)
PASS A 展示价随进度放大(最高分8338 > 最高分1)
PASS B 发放金币 > 0
PASS B 余额增加了发放金币
PASS B 道具 Undo +1
PASS B PurchaseGrantedMsg 触发且金币一致
PASS B lastPayTime 已记录(>0)
PASS C 无效SKU发放==0
PASS C 无效SKU余额不变
==== 9/9 PASS ====
钉死的几条:展示价随进度放大(同 SKU 老玩家看到更多)、购买后金币与道具精确到账、PurchaseGrantedMsg 广播且金币一致(UI/成就/埋点各自订阅)、lastPayTime 记录(留存与付费分层要用)、无效 SKU 不发放不改余额(防脏数据刷币)。因为发放走的是 GrantPurchase 这条真机同款路径,这 9 条绿等于把真实内购的发放逻辑钉死了。
用 MCP 把商城闭环点一遍(不是断言,是真在玩)
断言验的是逻辑,但"商城能不能点、点了金币动不动"得在真游戏里走一遍。这一步全程是 Claude 用 funplay MCP 驱动的——simulate_mouse_click 点 uGUI 按钮、capture_game_view 截图回看,一个人没碰鼠标:
- 点开商城:
simulate_mouse_click(144, 51)→ MCP 回EventSystem: clicked on 'Label',商城面板弹出,7 个 SKU 按新手(最高分 1)经济曲线显示金币(一袋金币 2,200)。就是文章开头那张新手截图。 - 把进度(最高分)调到封顶再点开:商城自己重新定价——同样 $1.99 变成 260,000 金币(×118)。价格栏一个字没改,这就是 EP4 那条曲线活在商城里的样子(开头第二张封顶截图)。
- 真买一单:金币先归到 50,000,
simulate_mouse_click(538, 758)点"一桶金币 $9.99"的购买键 → MCP 回Direct button hit: invoked 'Btn',GrantPurchase跑通,顶栏金币实时跳到 63,000(+13,000):

点击 → GrantPurchase → CoinModel.ChangeCoin → CoinChangedMsg → 顶栏订阅刷新——这条链路从 UI 一直贯到 EP4 的货币地基,全程在真 PlayMode 里被 MCP 点出来、截图证下来。这就是"测试游戏闭环"本身:不是我说它对,是它在屏幕上真的动了。
这一集的产物与诚实的话
ShopConfigDatabase(SKU 目录,数据资产)+ShopController(展示价现算 +GrantPurchase发放)+BoosterModel(道具库存)+ShopIapController(#if守卫的真 IAP 接线)。ShopConfigDatabase.asset放进 Resources(7 个 SKU),运行时加载。- 真商城面板(uGUI,7 个 SKU 卡片 + 购买键,接
GrantPurchase),上面三张截图就是它真跑起来的样子。 - 9 条断言全绿 + 一次 MCP 全程点击实测,覆盖的是真机同款发放路径。
诚实地讲:真实内购需要的"最后一公里"——开发者后台建商品、签约收款、真机 sandbox 联调、收据校验/防作弊——这些我在一台开发机上做不了,需要真实的商店账号和资质。这一集做到的是:把发放逻辑做对、做到可测、把商城界面做出来并用 MCP 点通,并把真 IAP 的接线点留好(一个 GrantPurchase 回调)。剩下的购买动效/粒子会在 EP7 表现层补。
下一篇 EP6:留存系统——离线收益、每日登录、连签、主题皮肤。把"让玩家明天还回来"这件事做出来,它和这一集的变现是一对:留存供给流量,变现把流量变成钱。
- 工具:funplay-unity-mcp
- 开源工程:本系列做出来的完整 Unity 工程已开源
- 上一篇:EP4 经济系统
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)