MyFramework:Unity 自动生成 UI 代码怎么避免覆盖手写逻辑 项目地址https://github.com/ZHOURUIH/MyFrameworkUnity 项目里UI 界面通常会有大量节点绑定代码。比如按钮、文本、图片、列表、输入框protected myUGUIButton mButtonClose; protected myUGUIText mTextTitle; protected myUGUIImage mImageIcon;然后在初始化时找到这些节点newObject(out mButtonClose, ButtonClose); newObject(out mTextTitle, TextTitle); newObject(out mImageIcon, ImageIcon);这种代码手写很麻烦。Prefab 改一次脚本也要跟着改。所以很多项目都会做 UI 代码生成工具。但 UI 自动生成有一个很现实的问题生成代码确实方便 但不能把手写业务逻辑覆盖掉否则工具就不是提高效率而是在制造风险。一、最危险的做法最简单的 UI 代码生成方式是根据 Prefab 扫描节点 生成一个完整 C# 文件 覆盖原来的脚本文件这种方式实现最简单。但问题也最大。因为一个界面脚本里通常不只有节点绑定。还会有业务逻辑protected override void init() { base.init(); mButtonClose.setClickCallback(onCloseClick); mButtonBuy.setClickCallback(onBuyClick); } protected void onCloseClick() { close(); } protected void onBuyClick() { // 购买逻辑 }如果生成器每次都覆盖整个文件那这些手写代码也会被覆盖。结果就是Prefab 一改 重新生成代码 业务逻辑没了这种工具不敢频繁用。开发者会越来越不信任它。二、真正需要自动生成的部分UI 脚本里并不是所有代码都需要自动生成。真正适合自动生成的通常只有两类成员变量声明 节点绑定代码例如成员变量protected myUGUIButton mButtonClose; protected myUGUIText mTextTitle; protected myUGUIImage mImageIcon;以及节点绑定newObject(out mButtonClose, ButtonClose); newObject(out mTextTitle, TextTitle); newObject(out mImageIcon, ImageIcon);这些代码和 Prefab 结构强相关。Prefab 节点变了它们就应该跟着变。但点击回调、数据刷新、界面逻辑不应该由生成器覆盖。这些是业务代码。所以 MyFramework 的做法是自动生成结构代码 手写业务逻辑保留三、自动生成区域要做到这一点生成器不能直接覆盖整个文件。它只能替换固定的自动生成区域。示例结构类似这样public class UILogin : LayoutScript { //-------------------自动生成开始------------------- protected myUGUIButton mButtonLogin; protected myUGUIText mTextAccount; protected myUGUIInput mInputPassword; //-------------------自动生成结束------------------- protected override void assignWindow() { base.assignWindow(); //-------------------自动绑定开始------------------- newObject(out mButtonLogin, ButtonLogin); newObject(out mTextAccount, TextAccount); newObject(out mInputPassword, InputPassword); //-------------------自动绑定结束------------------- } protected override void init() { base.init(); mButtonLogin.setClickCallback(onLoginClick); } protected void onLoginClick() { // 手写登录逻辑 } }生成器只处理这两段自动生成开始 - 自动生成结束 自动绑定开始 - 自动绑定结束其他地方一律不动。四、重新生成时发生什么当 Prefab 改动后重新执行 UI 代码生成。生成器会做几件事读取原来的 C# 文件 找到自动生成区域 重新生成成员变量 替换成员变量区域 找到自动绑定区域 重新生成 newObject 代码 替换绑定区域 保留其他所有手写代码所以开发者可以放心写protected override void init() { base.init(); mButtonClose.setClickCallback(onCloseClick); mButtonBuy.setClickCallback(onBuyClick); }重新生成时这些代码不会被动到。生成器只更新和 Prefab 结构相关的部分。五、为什么不能只生成一个新文件也有人会选择生成一个单独文件。例如UILogin.Generated.cs UILogin.cs用 partial class 拆开。这个方案也能避免覆盖手写代码。但 MyFramework 里更偏向直接在同一个文件中保留自动区域。原因是 UI 脚本本身是一个界面类。成员变量、绑定代码、业务逻辑放在同一个文件里查看和调试更直接。只要生成器严格只替换标记区域就能同时满足两个要求代码集中 不会覆盖手写逻辑这种方式对项目成员也更直观。打开一个界面脚本就能看到这个界面的节点绑定和业务逻辑。六、生成器必须守规则只替换自动区域听起来简单但工具必须严格遵守规则。不能因为某个标记没找到就直接重建整个文件。正确做法应该是文件不存在 创建完整模板 文件存在且标记完整 只替换标记区域 文件存在但标记缺失 报错 停止生成标记缺失时直接覆盖文件是很危险的。因为这说明文件可能被手动改坏了或者不是当前生成器生成的文件。这时应该让开发者处理问题而不是由工具猜测。自动化工具最重要的不是“能生成”。而是“不会破坏已有代码”。七、节点变动时的价值UI Prefab 在开发过程中经常变。比如新增一个按钮 删除一个文本 改一个节点名 某个 Image 改成 RawImage 列表结构调整 子窗口拆分如果没有代码生成每次都要手动同步脚本。很容易出现Prefab 里节点已经改名 代码里字段还是旧名字 Prefab 里删除了节点 代码里还在绑定 Prefab 里新增了按钮 代码里忘了声明成员UI 自动生成能减少这些重复工作。但前提是重新生成足够安全。如果重新生成会覆盖业务代码那开发者就不敢频繁使用。MyFramework 的自动区域替换就是为了让重新生成变成常规操作。Prefab 改了就重新生成。不用担心手写逻辑被清掉。八、成员变量和业务逻辑分离自动生成区域里只放结构代码。例如protected myUGUIButton mButtonClose; protected myUGUIButton mButtonBuy; protected myUGUIText mTextPrice;业务逻辑放在自动区域外protected override void init() { base.init(); mButtonClose.setClickCallback(onCloseClick); mButtonBuy.setClickCallback(onBuyClick); } protected void refreshPrice() { mTextPrice.setText(getPriceText()); }这样职责很清楚。生成器负责 节点字段 节点绑定 子窗口创建 控件类型同步 开发者负责 按钮回调 数据刷新 界面打开逻辑 界面关闭逻辑 网络回包处理生成器不应该理解业务。它只应该理解 UI 结构。九、为什么这很适合 UnityUnity UI 的问题是 Prefab 和 C# 脚本天然分离。Prefab 里有节点树。C# 里有成员变量。两边必须保持同步。手动同步成本很高。尤其是复杂界面几十个按钮 几十个文本 多个列表 多个子窗口 多个动态节点如果每个节点都手写查找和声明代码量很大。而且这些代码没有多少业务价值。自动生成可以把重复劳动交给工具。开发者只关注真正的界面逻辑。十、重新生成后的代码可读性UI 代码生成不能只追求“能用”。生成出来的代码还应该可读。因为 UI 脚本是经常被打开的。MyFramework 的思路不是把所有东西都隐藏起来而是生成普通 C# 代码。例如newObject(out mButtonClose, ButtonClose); newObject(out mTextTitle, TextTitle); newObject(out mImageIcon, ImageIcon);这种代码可以直接看懂。出问题时也方便断点调试。工具生成代码不是为了让代码不可见。而是为了减少重复手写。十一、和运行时查找的区别还有一种做法是运行时按字符串查找节点getObject(ButtonClose); getObject(TextTitle);需要用时再查。这种方式减少了声明字段。但问题是运行时才知道节点是否存在 字符串散落在业务代码里 重命名不安全 重复查找也容易变乱自动生成成员变量的好处是节点集中声明 绑定集中执行 业务代码直接使用成员变量 节点缺失可以在初始化时统一报错业务代码里不需要到处写路径字符串。这更适合中大型 UI 工程。十二、设计边界自动生成区域替换也有边界。它要求开发者遵守规则不要手动修改自动生成区域 不要删除自动区域标记 业务逻辑写在标记区外 Prefab 节点改动后重新生成生成器也要遵守规则只改标记区域 标记缺失时报错 不要覆盖整个文件 不要替开发者生成业务逻辑双方规则清楚工具才稳定。十三、这个设计解决的不是代码量UI 自动生成表面上是在减少代码量。但更重要的是减少同步成本。真正的问题不是少写几行代码。而是Prefab 变了 脚本必须同步变 同步过程不能破坏手写逻辑所以 MyFramework 的 UI 自动生成重点不是“生成多少代码”。而是哪些代码可以交给工具 哪些代码必须留给开发者 工具修改范围必须可控自动区域替换就是这个边界。总结Unity UI 自动生成最大的问题不是怎么生成字段和绑定代码。这些都不难。真正的问题是重新生成时怎么不覆盖手写业务逻辑MyFramework 的做法是把 UI 脚本分成两类区域自动生成区域 成员变量声明 节点绑定代码 手写业务区域 初始化逻辑 点击回调 数据刷新 网络处理生成器每次只替换自动生成区域。自动区域之外的代码原样保留。这样 Prefab 改动后可以反复重新生成 UI 代码而不用担心业务逻辑被覆盖。这也是 UI 代码生成工具真正能在项目里长期使用的前提。