Files
2026-08-30 22:25:23 +08:00

5.5 KiB
Raw Permalink Blame History

UI 维护要点

汇总 SBE 项目中 UI 的代码组织、嵌套 Form 协作与常见注意事项。 相关代码:Assets/GameMain/Scripts/UI/(生成 + 手写 partial)、Assets/GameMain/Scripts/Base/Event/UIForm/(UI 事件)。

一、Form / View 的组织方式

  • 自动生成:UIPrefabBuilder(编辑器菜单 Utility/UI/Build UI Prefabs)构建 prefab;UIAssetsTools 根据 UISerializationRoot/UISerializationItem 上的引用生成 XxxForm.cs 与 XxxView.cs(文件头带 // <auto-generated />)。
  • 手写逻辑:业务代码写在同名的 .Logic.cs partial 里(如 LobbyForm.Logic.cs),与生成文件合并;不要修改自动生成文件。
  • View 字段:prefab 中需要代码访问的组件在序列化项上登记变量名(variableName),生成到 View 的 public 字段。View 只暴露组件引用,不做业务。
  • View 引用的是子 Form 组件而非子 View:如 LobbyView.warehouseForm(WarehouseForm 组件)、WarehouseView.inventoryPanelForm / itemDetailsPanelForm。通过 子Form.View 再取具体组件。

二、嵌套 Form 的能力模型

  • 一个 Form 可以嵌套子 Form(prefab 实例层级:LobbyForm → WarehouseForm → InventoryForm / ItemDetailsForm)。
  • 子 Form 只提供能力(public 方法),不直接读取全局数据源、不主动触发刷新;数据由调用方传入。例:InventoryForm.RefreshList(stacks)、ItemDetailsForm.Refresh(itemId)、WarehouseForm.Refresh(stacks)。
  • 只有顶级 Form(通过 GameEntry.UI.OpenUIForm 打开的)才有编排权,负责读数据并向下分发。例:LobbyForm.OnOpen → SwitchPage → warehouseForm.Refresh(save.mainWarehouse)。
  • 子 Form 的能力入口保持幂等、可反复调用;父 Form 切换页面时重新调用即可。

三、嵌套 Form 与 UGF 生命周期

  • 只有被 UIComponent 打开的顶级 Form 才会被框架调用 OnInit / OnOpen / OnClose / OnUpdate 等生命周期方法。嵌套 Form 的这些方法永远不会执行——不要在嵌套 Form 里依赖它们(最常见错误:把事件绑定写在 OnInit,结果永远不生效)。
  • 嵌套 Form 的事件绑定/订阅改为幂等式:bool 标志 + EnsureXxx(),在能力入口(如 Refresh)调用,保证先于任何交互执行。
    private bool _listenersBound = false;
    private void EnsureListenersBound()
    {
        if (_listenersBound) return;
        _listenersBound = true;
        View.allToggle.onValueChanged.AddListener(OnAllToggleValueChanged);
    }
    
  • UGF 表单实例会被对象池复用,幂等标志同时防止重复订阅。
  • 订阅了 GameEntry.Event 的嵌套 Form 实例被销毁后,事件中心可能残留引用:handler 开头加 if (this == null) return; 防御。

四、子 Form 向上传递数据:事件

  • 子组件(如 WarehouseSlotItem)禁止直接持有并调用父 Form(避免反向引用),需要向上层传数据时发事件:
    GameEntry.Event.Fire(this, WarehouseSlotItemClickEventArgs.Create(_slotId));
    
  • 事件参数类放在 Assets/GameMain/Scripts/Base/Event/UIForm/,命名 XxxEventArgs,继承 GameEventArgs,使用 ReferencePool(Create 静态工厂 + Clear 回收):
    public class XxxEventArgs : GameEventArgs
    {
        public static int EventId => typeof(XxxEventArgs).GetHashCode();
        public override int Id => EventId;
        public static XxxEventArgs Create(...) { var a = ReferencePool.Acquire<XxxEventArgs>(); ...; return a; }
        public override void Clear() { ... }
    }
    
  • 事件参数要携带唯一标识(如格子索引 SlotId),不要用可能重复的语义值(如 itemId 在多堆同道具时无法唯一定位)。
  • 订阅方是拥有上下文的父 Form(WarehouseForm 监听格子点击 → 分发选中高亮与详情刷新),在能力入口幂等订阅。
  • 事件从 GameEntry.Event.Subscribe(XxxEventArgs.EventId, handler) / GameEntry.Event.Fire(this, args) 走 UGF 事件总线,无需手动管理对象生命周期。

五、其它约定

  • 固定网格 + 模板格子:列表用 warehouseSlotRoot(GridLayout 容器)+ warehouseSlotTemplate(一个模板格)。刷新时隐藏模板、销毁其他子物体、按模板实例化;格子数等数值来自配表(GlobalConfig.WarehouseSlotCount),不写死。
  • 选中态:点击选中由事件驱动,SetSelectedSlot(slotId) 按唯一索引高亮;筛选后重建网格时选中自动清除。
  • 格式化文本:界面文案用 FormatTextUI(Inspector 填 key,文本从 FormatText_格式化文本.xlsx 读取,Set(params object[]) 传参),不要硬编码在代码里。
  • 导航切换:导航用 Toggle + ToggleGroup 管理选中态,代码只订阅 onValueChanged(仅在 isOn == true 时处理),不再手动维护按钮状态。
  • 动画:DOTween 移动 UI 元素时,TransformPoint 的入参是目标 rect pivot 局部空间坐标;元素锚点偏移需要换算:(anchor - pivot) * rect.size + anchoredPosition。
  • 打开流程:Form 由 UIFormType 枚举 + UIFormConfig 表(tbuiformconfig)数据驱动,GameEntry.UI.OpenUIForm(UIFormType.Xxx);Procedure 的 OnLeave 记得 GameEntry.UI.CloseAllLoadedUIForms()。
  • 布局组件注意:生成 prefab 的 Content 上挂 GridLayoutGroup + ContentSizeFitter,运行时实例化子物体即可自动排布,不要手动改尺寸。