全程用 AI 做一款商业级手游 · EP1 地基:先搭框架层,不急着写玩法

EP0 立项时给商业游戏下了个判断:它是围绕一个机制搭起来的"留存+变现机器",机制只占 5% 。如果这话当真,那第一集就不该急着写"方块怎么消",而该先搭那层 95% 都要往上插的地基

这一集(EP1)就做地基:框架层 + 启动流程。做完不会有任何可玩的东西------但它是后面经济、商城、留存、关卡能干净地长上去的根。全程在一个干净的 Unity 6 + URP 工程里,Claude 写代码、funplay MCP 编译 + 进 PlayMode 验证。

为什么先搭框架,而不是先做好玩

原型可以把关卡 3 的数据硬编码在代码里;商业游戏有 200 关,硬编码第 200 关就是灾难。原型的金币加减直接 coin += 100;商业游戏的金币要存档、要通知 UI、要记录变更来源做数据分析。这些"非功能"需求,改造比内建贵得多 。所以框架层从第一天就要立起来,核心就两件事:解耦存档

整体分层是这样(启动流程把控制器按固定顺序拉起,控制器持有 Model(状态全是会自动存档的 Property),驱动 Panel(视图);系统之间只通过事件总线通信,不直接互调):

框架层的脊梁:Property<T>

整个存档体系的基石。每个面向玩家的值都是一个 Property<T>:它持有值、被赋值时通知所有观察者、并自动写盘。这就是为什么"存档不用专门写"、"UI 永远和数据同步"。

csharp 复制代码
public class Property<T>
{
    readonly string _key; readonly bool _persist;
    T _value;
    readonly List<Action<T>> _observers = new();

    public Property(string key, T def = default, bool persist = true)
    {
        _key = key; _persist = persist;
        _value = persist ? Storage.Get(key, def) : def;   // 构造即从盘加载
    }

    public T Value
    {
        get => _value;
        set
        {
            if (EqualityComparer<T>.Default.Equals(_value, value)) return;
            _value = value;
            if (_persist) Storage.Set(_key, value);        // 改了就写盘
            for (int i = 0; i < _observers.Count; i++) _observers[i]?.Invoke(_value);  // 通知
        }
    }

    public void Observe(Action<T> cb, bool fireImmediately = true)
    {
        _observers.Add(cb);
        if (fireImmediately) cb(_value);    // UI 初始化时立刻拿当前值刷一次
    }
}

后端 Storage 现在用 PlayerPrefs(基础类型)+ JsonUtility([Serializable] 对象),不引第三方依赖;以后整体换成加密文件 / 云存档,调用方一行都不用改。

玩家档案就长这样------清一色 Property:

csharp 复制代码
public class UserDataModel : Singleton<UserDataModel>
{
    public readonly Property<int>  curLevel        = new("user.curLevel", 1);
    public readonly Property<int>  bestScore       = new("user.bestScore", 0);
    public readonly Property<int>  launchCount     = new("user.launchCount", 0);
    public readonly Property<long> lastQuitTime    = new("user.lastQuitTime", 0L);
    public readonly Property<long> firstLaunchTime = new("user.firstLaunchTime", 0L);
    public bool IsFirstLaunch => launchCount.Value == 0;
}

解耦的关键:事件总线

系统之间不准直接相互调用,而是广播 / 订阅消息。金币变了,MsgController.Send(new CoinChangedMsg{...}),UI、成就、商城各自 Register 接收。这样加一个新系统不用去改别人的代码------这是项目从 5 个系统涨到 30 个系统还不烂的前提。

csharp 复制代码
public interface IMsg { }
public static class MsgController
{
    public static void Register<T>(Action<T> h) where T : IMsg { /* ... */ }
    public static void Send<T>(T msg) where T : IMsg { /* 拷贝快照再遍历,允许回调里增删订阅 */ }
}

其余框架件:Singleton<T> / SingletonMono<T>(逻辑单例 / 带 Mono 的单例)、ResManager(资源加载,现 Resources,EP8 换 YooAsset 热更)、PoolManager(对象池,方块/粒子/飞金币这类高频对象走池)。

启动流程:固定初始化顺序

GameInit 挂在 Boot 场景,按依赖顺序拉起控制器------被依赖的(玩家档案、关卡、货币)在前,消费方(商城、UI)在后。顺序错了就会有人拿到半初始化的依赖。

csharp 复制代码
IEnumerator Boot()
{
    Application.targetFrameRate = 60;
    Application.runInBackground = true;
    yield return null;                 // (EP8: 这里接 YooAsset 热更)
    InitControllers();                 // 固定顺序,各 EP 往里加系统
    // 路由: 首次启动走教程, 否则进首页(带最高分)
    BootDone = true;
}

验证:往上盖东西之前,先确认地基不塌

这是整个系列贯穿的原则------不靠"看着没报错",靠可断言的自检。地基的自检就两条:存档层对不对、跨会话持久化成不成立。

① 存档层自检 (编辑器里直接跑):建一个 Property<int>、挂观察者、改值、看是否写盘、再建一个同 key 的新实例看是否从盘加载到改后的值。

复制代码
存档自检: 观察者触发2次(期望2)  末值42(期望42)  写盘=True  重载持久化=True
RESULT = PASS ✅

② 跨会话持久化 (PlayMode 验证):launchCount 每次启动 +1。进 PlayMode → launchCount=1;退出;再进 → launchCount=2,而 firstLaunchTime 不变。

复制代码
第一次启动: BootDone=True launchCount=1 curLevel=1 firstLaunchTime=1780568724
第二次启动: BootDone=True launchCount=2 curLevel=1 firstLaunchTime=1780568724

launchCount 从 1 涨到 2 且 firstLaunchTime 没变------说明 Property 的值真的跨了两次独立的运行会话落盘又读回。地基稳了。

(一个真实小坑:第二次进 PlayMode 一开始读到 BootDone=False------因为编辑器失焦时 Update/协程被节流,启动协程没跑完。在 GameInit 里加 Application.runInBackground = true 后正常。这种"失焦节流"在用 MCP 在编辑器外驱动时很常见,记一笔。)

这一集的产物与诚实的话

  • 6 个框架文件(Property / Storage / MsgController / Singleton / ResManager / PoolManager)+ UserDataModel + GameInit,约 350 行,编译零错误。
  • Boot 场景 + 固定初始化顺序的启动流程。
  • 两条自检:存档读写 + 跨会话持久化,PASS。

诚实地讲:这一集没有一个像素是"游戏"。屏幕上只有一个空相机。但这层水管,正是商业代码库和"硬编码一切的原型"的分水岭------后面每一集(经济、商城、关卡、留存)都会干净地插在这层上,而不是相互缠成一团。

剧透一下这层地基最终撑起的样子------下面这张是后面几集一块块盖上去、真能上手玩的成品(顶栏那个会自动存档、跨会话不丢的金币,就是这一集的 Property 在撑):

下一篇 EP2:核心玩法------方块拖放进网格、消行/列、计分、死局判定,并在 PlayMode 里自动验证消除逻辑。从这一集起,屏幕上开始有能玩的东西。

相关推荐
春风野草5 小时前
第五章 记忆系统不是假装记住——3层记忆架构的坑与遗忘的艺术
人工智能·ai编程
咖啡星人k6 小时前
MonkeyCode 多模型路由机制:AI编程工具如何智能选择最优模型
ai编程
小贺儿开发6 小时前
Unity VideoPlayer 播放控制器
unity·编辑器·播放器·视频·工具·videoplayer·互动
小橙讲编程7 小时前
一键给 AI Agent 装上「互联网眼睛」:Agent Reach 深度解析与实战指南
人工智能·开源·github·ai编程
runnerdancer7 小时前
从能用到可靠:AI Agent 规范治理的工程实践
ai编程
youcans_8 小时前
【跟我学 AI 编程】(6) Claude Code 与 IDE 的集成
ide·人工智能·ai编程·claude code
码途漫谈9 小时前
Compound Engineering:让每一次开发都给下一次铺路
开源·ai编程
TFHoney9 小时前
当 AI 真正走进你的终端:Claude Code 使用指南
java·人工智能·ai编程