AI 写代码时代,游戏 UI 架构为什么停在 MVP?

AI 写代码时代,游戏 UI 架构为什么停在 MVP?

游戏周边 UI 本质上是套着游戏皮肤的 App。过去十年,App 开发靠 MVP 解决了"业务逻辑和视图纠缠"的问题,但游戏团队普遍没跟上来------不是不知道,是落地成本太高。现在 AI 改变了这个等式。**本文论证三件事:为什么游戏 UI 需要架构、为什么是 MVP 而不是更激进的方案、为什么现在做第一次变得划算。**

一、问题:游戏 UI 的可测试性赤字

先放下架构模式,看一个更根本的问题。

假设你有一个商城面板,里面有价格判断、库存校验、购买冷却、限购逻辑。你改了其中一条规则------比如"VIP 用户购买冷却减半"。你怎么确认改动没有破坏其他规则?

选项 A:启动游戏,点进商城,买一次,看结果。

选项 B:跑一条单元测试,0.1 秒出结果。

大多数游戏团队选 A,因为代码结构不支持 B。业务逻辑和 Cocos Component 的生命周期、节点树、事件系统绑在一起------脱离引擎根本跑不起来,更别说写测试。

这不是某个程序员的错。Cocos Creator 的教程、示例、社区最佳实践全部是 Component 直写。引擎的 @property + @ccclass 模式天然鼓励你把逻辑和视图写在同一个类里。你顺着引擎的设计走,自然走到"没法测试"的终点。

这就是游戏 UI 的"可测试性赤字"------不是不想测,是结构让你测不了。 而可测试性不只是测试本身的问题:不可测试的代码必然也是不可维护的代码,因为每次改动你都无法确认影响范围。


二、前例:Android 十年踩过的坑

移动 App 开发遇到过完全一样的问题,而且花十年走完了从坑到解的全过程。

Android 早期的 Activity 里塞满了网络请求、数据库操作、UI 更新、业务判断。一个 Activity 上千行是常态。改一行怕影响十处,测试靠手动点。这和今天游戏 Component 的状态一模一样。

推着 Android 走出这个泥潭的驱动力不是审美,是可测试性。Google 推出了单元测试框架,但 Activity 里的代码依赖 Android 运行时,无法脱离设备跑快速测试------于是必须把逻辑抽出来。MVP 是第一个能落地的方案:把逻辑抽到 Presenter,View 退化为接口。MVVM 后来走得更远(响应式绑定、数据流单向化),但那是在基础设施成熟之后的事。

模式 谁持有业务逻辑 View 能脱离逻辑测试吗 需要什么基础设施
MVC(早期 Android) Activity 自己 ❌ 不能
MVP Presenter(脱离 Activity) ✅ 能------View 退化为接口 接口定义 + 依赖注入
MVVM ViewModel(脱离 Activity) ✅ 能------View 只订阅 响应式框架 + 数据绑定 + LiveData/Flow

关键洞察:Android 的每一步架构升级,都是被"先要能测,才想怎么拆"这个需求倒逼出来的。 可测试性不是架构的副产品,而是架构的驱动力。这个经验不需要依赖 Android 的具体实现------任何事件驱动、状态稠密的 UI 系统,面临的可测试性问题是同构的。


三、游戏 UI 的特殊性:为什么选了 MVP 而不是 MVVM

如果 Android 最终走到了 MVVM,我们为什么不一步到位?

因为游戏 UI 有一个 App UI 没有的维度:表现层的时序控制 。App 的数据变化通常直接映射到 UI------价格变了,Label 更新。但游戏 UI 经常需要:"数据变了 → 先播一段 tween 动画 → 动画播完再更新 UI → 同时触发粒子特效"。这不是简单的"数据变了就刷新",而是有时序编排的渲染指令序列

在 MVVM 的声明式框架里做时序控制,要在数据绑定之外不断加副作用------Observable 变化触发动画、动画完成回调再改另一个 Observable。能做,但别扭,维护成本高。

MVP 的命令式接口天然适配这个场景。Presenter 调用 view.setPrice("已购买"),View 的实现里可以自由决定:是直接设置 Label.string,还是先播 scale 动画再设。Presenter 只说"展示什么",View 自己决定"怎么展示"------这个分工恰好对应了游戏 UI 的复杂度分布。
**MVP 不是"MVVM 的简化版",而是两种不同的适配策略。** MVVM 适合"数据变化频繁、映射关系稳定"的场景(如列表刷新);MVP 适合"单次交互触发多步渲染指令"的场景(如购买反馈、升级动画、奖励展示)------后者恰好是游戏 UI 的主流。


四、现实:大多数团队停在 MVP 之前

理论上的最优解是一回事,现实中大多数团队的代码是另一回事。真实代码长这样:

typescript 复制代码
@ccclass('ShopPanel')
export class ShopPanel extends Component {
    onBuyClicked() {
        if (PlayerData.gold >= this._currentItem.price) {
            PlayerData.gold -= this._currentItem.price;
            Inventory.addItem(this._currentItem);
            this.priceLabel.string = '已购买';
            // 还有 50 行业务逻辑直接写在 View...
        }
    }
}
typescript 复制代码
export class ShopController {
    private _view: ShopView; // 具体 View 类,不是接口

    onBuyClicked() {
        if (this._model.gold >= this._model.currentItem.price) {
            this._model.spendGold(this._model.currentItem.price);
            this._view.refresh(this._model); // 直接引用具体 View 类
        }
    }
}
typescript 复制代码
interface IShopView {
    setPrice(text: string): void;
    setBuyEnabled(enabled: boolean): void;
    showTip(msg: string): void;
}

export class ShopPresenter {
    constructor(private view: IShopView, private model: ShopModel) {}

    onBuyClicked() {
        if (this.model.gold < this.model.currentItem.price) {
            this.view.showTip('金币不足');
            return;
        }
        this.model.spendGold(this.model.currentItem.price);
        this.view.setPrice('已购买');
    }
}
text 复制代码
无架构
  ┌──────────────┐
  │  View + 逻辑  │   逻辑和视图混在一起,拆不开、测不了
  └──────────────┘

  引入分层:把逻辑从 View 里抽出来

MVC  ── 双向依赖,紧耦合 ──
  ┌──────┐  事件      ┌──────────┐
  │ View │ ────────▶ │Controller│
  │      │ ◀──────── │          │──▶ ┌──────────┐
  └──────┘  持有具体类 └──────────┘    │  Model   │
            ShopView,换不了          └──────────┘

  引入接口:把「持有具体类」变成「依赖接口」

MVP  ── 单向依赖,松耦合 ──
  ┌──────┐  事件      ┌───────────┐
  │ View │ ────────▶ │ Presenter │──▶ Model
  │      │            │ 定义 IView │
  └──┬───┘            └─────┬─────┘
     │ 实现                  │ 依赖接口
     ▼                       ▼
  ┌────────────────────────────┐
  │          IView             │
  │  Presenter 定义契约        │
  │  View 实现契约             │
  │  Presenter → IView(依赖接口)│
  │  View → IView(实现接口)     │
  │  View 不知道 Presenter       │
  └────────────────────────────┘

  引入绑定:不再手动调用 View

MVVM  ── 声明式,完全解耦 ──
  ┌──────┐  双向绑定  ┌───────────┐
  │ View │ ◀────────▶ │ViewModel │──▶ Model
  └──────┘  (无引用)   └───────────┘

区别不在代码量,在依赖方向:

  • MVC :Controller 持有具体 View 类(ShopView),换不了 View,也测不了 Controller------因为构造 ShopView 需要 Cocos 节点树
  • MVP :Presenter 指向 View 接口(IShopView),随便换 View,Presenter 可脱离引擎独立测试------传个 mock 对象即可
  • MVVM:ViewModel 通过数据绑定通知 View,不需要持有 View 引用,但需要响应式框架支撑

形态一和形态二的共同点:都不可测试。形态一完全没分层,形态二分了层但 Controller 持有具体 View 类的引用------你要测 Controller 就必须构造一个真实的 ShopView,而 ShopView 又依赖 Cocos 节点树。测试的入口被堵死了。

形态三之所以不同,不是因为"更干净",而是因为Presenter 只依赖接口,不依赖具体类。测 Presenter 时,你传入一个 mock 的 IShopView------一个普通对象,不需要 Cocos、不需要节点树、不需要场景。这就是依赖反转原则(DIP)的核心:高层模块不依赖低层模块,两者都依赖抽象。

那为什么大多数团队没到这个状态?

架构化的驱动力 App 开发 游戏开发
框架/教程引导分层 ✅ Android SDK 原生引导 MVP/MVVM ❌ Cocos 教程全是 Component 直写
测试工具链推动架构 ✅ JUnit 倒逼分层 ❌ 无标准测试框架,脱离引擎跑不起来
多人协作要求接口隔离 ✅ 团队并行开发需要接口契约 ❌ 一个面板一个人写,不需要接口
需求频繁变更 ✅ 不分层改不动,倒逼架构 ❌ 改不动就重写------活动面板用完即弃

核心矛盾:App 开发中推动架构演进的每一股力量,在游戏 UI 中要么不存在,要么方向相反。 引擎鼓励不分层、没有测试压力、协作不要求接口、面板用完即弃。

所以游戏 UI 架构的真实分布不是有序演进,而是:
**游戏 UI 架构的真实分布** - 无架构(一部分团队) - 两层 / MVC(不少团队)← **大多数人停在这一带** - MVP(极少数) - MVVM(极少)


五、拐点:AI 改变了成本等式

前面说了三个事实:①游戏 UI 需要架构来获得可测试性;②MVP 是当前阶段的最优解;③大多数团队没做是因为成本太高。

AI 改变了第三个。不是让 MVP"更正确"------正确性从来没被质疑过。是让**"做正确的事"第一次变得划算**。

MVP 的落地成本集中在四个环节。每个环节恰好是 AI 擅长的:

  • 接口定义和 View 绑定层:把 20-50 个 @property 映射到 IView 接口方法。纯体力活,模式高度固定,零创造性------AI 直出率极高。
  • Presenter 控制流:状态计算、分支判断、前置校验。脱离引擎依赖后是纯逻辑代码------Prompt 描述清楚业务规则即可,AI 擅长穷举分支。
  • 契约同步:策划改一个字段(比如新增一个消耗类型),需要同步改 proto、Presenter 判断逻辑、View 绑定、测试用例------四个文件联动,人工最容易遗漏。AI 可以同时修改关联文件,大幅降低遗漏率。
  • 单元测试:基于 Presenter 的显式控制流穷举边界条件------正常路径、异常路径、边界值、并发冲突。AI 的测试覆盖率天然超过人工。

更重要的是维护成本的结构性变化。泥球代码的维护成本随代码量加速攀升------每次改动要在 Component 里追踪散落的逻辑,改一处可能影响三处。MVP 代码的维护成本随代码量增长平缓------业务规则集中在 Presenter,改动位置可预测,测试告诉你有没有破坏其他规则。当代码存活超过一个版本周期,这个差异就决定了"改不动就重写"还是"改完跑测试就上线"。
**MVP 不是完美的方案。但它在"测试覆盖率 × 落地方便程度 × 游戏 UI 适配度"这三个维度的乘积上,是当前阶段的最优解。** 不是做不到更好,而是这一步的 ROI 已经到极致了。


六、结语

游戏 UI 的可测试性赤字不是一个技术问题------它是在引擎引导、团队习惯、成本约束三方合力下形成的均衡态。打破这个均衡需要的不是一个更好的架构理论,而是一个让架构成本低于混乱成本的拐点。

AI 就是那个拐点。

有了 AI,工程师的核心价值从"写代码"转移到"设计契约"------定义什么是对的,让 AI 在框架内生成。模式跟随契约,不是反过来。

相关推荐
春天花会开1311 小时前
影像上传前置机网络架构设计模板(含VPN)
后端·架构
英勇无比的消炎药1 小时前
前端提效神器全新AI组件库TinyRobot改写日常开发模式
前端·vue.js
GuWenyue1 小时前
10分钟搞定TodoList实战!从0搭建Bun+TS的RESTful接口服务
前端·typescript·bun
有什么事1 小时前
云手机多开哪个强?ARM架构:云手机多开的信任基石与性能核心
arm开发·智能手机·架构
IMPYLH1 小时前
HTML 的 <a>元素
前端·javascript·html
PedroQue991 小时前
uni-router:uni-app路由管理新选择
前端·uni-app
YIN_尹1 小时前
探测+检测+缓解(PDM):让云租户自主防御微架构攻击
安全·缓存·架构
Cerrda2 小时前
一行指令搞定复制:Vue 3 vCopy 实现解析
前端·代码规范
SL_staff2 小时前
《如何用规则引擎替代if-else?JVS-Rules可视化编排比硬编码强在哪里?》
java·低代码·架构