11.UGUI实践项目总结

11.总结


11.1 知识点

学习的主要内容

UI功能制作套路

拼面板——注意事项

写代码——注意事项

思考


11.2 核心要点速览

登录与选服流程

  • 总入口:玩家从「进入游戏」进入;是否走登录界面,首先看自动登录是否开启且账号校验是否成功。
  • 自动登录成功:可直接进入服务器面板,跳过登录界面,适合「记住账号 + 服务端认可令牌」的回头客路径。
  • 登录面板:手动输入账号密码;可点「注册」进注册面板,注册成功再回到登录;登录成功后按「是否曾经选过服」分流。
  • 老用户且已有选服记录:登录成功后进服务器面板(展示当前/上次所选区服)。
  • 新用户或未选过服:登录成功后先进选服面板选区,再进入服务器面板
  • 进游戏:在服务器面板确认后执行切换场景,进入实际玩法场景。
判断或玩家操作 下一界面或动作
自动登录成功 服务器面板
需手动登录 登录面板
登录面板点注册 注册面板;注册成功回到登录面板
登录成功且已有选服记录 服务器面板
登录成功且未选过服 选服面板 → 再回服务器面板
服务器面板点进入游戏 切换场景

分层与类型职责(需求类图)

  • UI 管理层UIManager 单例,内部用字典登记各面板实例,Show / Hide / Get 三类操作;所有业务界面都通过它拿实例,避免场景里到处 Find
  • 面板基类BasePanel 负责各面板通用的淡入淡出、显示/隐藏自己的节奏;具体面板只关心业务控件与数据绑定。
  • 功能面板层TipPanelLoginPanelRegisterPanelChooseServerPanelServerPanel继承 BasePanel;各自注册控件事件、刷新显示;选服、服列表再通过列表项脚本拆细。
  • 列表项脚本ServerLeftItem(左侧区间按钮)、ServerRightItem(右侧单服格子);按区间或 ServerInfo 刷新显示、响应点击,挂在 ScrollView 的预制体上。
  • 纯数据类型LoginDataRegisterData;全服列表为 List<ServerInfo>(如 ServerInfo.json 反序列化),ServerInfo 表示单服(id、name、state、isNew)。与界面解耦,便于 JSON 与 UI 分工。
  • 登录数据入口LoginMgr 单例持有登录/注册存档与服务端列表,对外 CheckInfoRegisterUserSaveLoginData 等;面板只调管理器,不互相掏私有字段。

实现主线建议

  1. 按类图先把 LoginData / RegisterData / LoginMgr 定下来,保证「记住密码 / 自动登录 / 上次服务器」有统一存取入口。
  2. 实现 **UIManager + BasePanel**,把面板生命周期和 Resources 路径约定写死,后面所有面板只派生、不重复写淡入淡出框架。
  3. 按流程图从 提示、登录、注册、选服、服务器主界面 逐块预制体 + 脚本实现,列表项单独做成可复用预制体。
  4. 最后再把 界面跳转条件(自动登录、是否选过服)接进各面板按钮与回调,场景切换挂在服务器面板「进入游戏」上。

工程准备与 Canvas

  • 工程节奏:新建工程 → 导入课用的 JSON 数据管理 相关脚本 → 导入美术与 UI 预制资源 → 搭文件夹与 Canvas。前面几步正文以操作的标题列出,具体点按钮以课堂录屏为准;资源进工程后,下面几项是后面写 Resources.Load 和拼面板时一定会踩到的硬性约定。

目录约定

文件夹 用途
Resources 下面再放 UI 等子目录,供 Resources.Load 按路径取预制体;名字必须是 Unity 识别的 Resources,不要乱改大小写或层级。
Scripts 业务与 UI 脚本集中放,避免和美术资源搅在一起。
StreamingAssets 课内与 Resources 等并列保留的工程目录,后续若有包内配置或外部数据读取方案会用到。
ArtRes 美术提供的图集、散图等静态资源入口,和脚本、预制体区分清楚,后期做图集合并也好看管。

Canvas 渲染模式(摄像机模式)

  • Inspector 里把 Render Mode 设成 Screen Space - Camera,与课内「UI 与场景分相机」目标一致;长期用默认 Overlay 不利于叠模型和分层调试。
  • Render Camera 指定专门渲染 UI 的那台相机;课内层级里在 Canvas 下挂 UI Camera(或等价的专用相机对象),和场景里照 3D 的 Main Camera 拆开。
  • 动机:面板上可能要叠 3D 模型、或希望 UI 与场景深度、后处理拆开时,Overlay 很难细调层次;主摄像机不渲 UI 层、UI 单独一台相机,后期调前后关系、裁剪平面会清晰很多。
  • 层级习惯EventSystem 也挂在 Canvas 下,和 UI 相机同属一棵 UI 子树;后面 UIManager 若对整棵 CanvasDontDestroyOnLoad,输入模块和相机可以一起到下个场景,避免只带了 Canvas 没带 EventSystem 的半截 UI。

Canvas Scaler(本课截图上的配法)

课内取值 含义
UI Scale Mode Scale With Screen Size 按屏幕尺寸整体缩放,而不是死像素。
Reference Resolution 1365 × 768 美工按这个比例摆的分辨率;Scaler 以此为基准算缩放系数。
Screen Match Mode Match Width Or Height 在「按宽」和「按高」之间取折中。
Match 偏向 Height(1) 更跟屏幕高度走,竖条、顶底栏一类布局在高度变化的设备上相对稳定;宽向可能被拉伸或裁切,要靠 锚点 收口。
Reference Pixels Per Unit 100 Sprite 像素与世界单位换算,用默认即可,除非你改过导入设置。

这套 Scaler 配完,再在不同分辨率下跑一遍登录场景,能提前发现「字糊了还是边被切了」,比写完所有面板再改锚点省力。

面板基类 BasePanel

需求类图里把基类收成三件事:显示自己隐藏自己淡入淡出,代码上都落在 CanvasGroup.alpha 和下面几个入口上,子类只补业务。

成员 / 入口 作用 课内写法要点
canvasGroup 整页淡入淡出 Awake 取组件,没有就 AddComponent<CanvasGroup>
alphaSpeed 渐变快慢 私有浮点,默认 10,乘 Time.deltaTime 加/减 alpha
isShow 当前走淡入还是淡出 ShowMetruealpha=0HideMefalsealpha=1
hideCallBack 淡出结束通知 HideMe 里赋值,alpha 减到 0 后调用;一般由 UIManagerDestroy 和字典移除。
Init() 按钮、数据绑定 抽象Start 里统一调,避免和 Awake 抢顺序时子类还没准备好引用。
ShowMe / HideMe 对外切换显隐节奏 虚拟方法;课内主要由管理器调用。

Update 里两段分支(与正文一致):

void Update()
{
    if (isShow && canvasGroup.alpha != 1)
    {
        canvasGroup.alpha += alphaSpeed * Time.deltaTime;
        if (canvasGroup.alpha >= 1)
            canvasGroup.alpha = 1;
    }
    else if (!isShow)
    {
        canvasGroup.alpha -= alphaSpeed * Time.deltaTime;
        if (canvasGroup.alpha <= 0)
        {
            canvasGroup.alpha = 0;
            hideCallBack?.Invoke();
        }
    }
}

实现时注意!isShowalpha 已钳在 0 时,每帧仍会进 <= 0 分支hideCallBack反复触发。课内回调里一般会 Destroy 面板,物体销毁后不再跑 Update;若改成淡出后仍留在场景,要在回调里置空委托或做一次性防护,避免每帧刷关面板/销毁逻辑。

UI 管理器 UIManager

类图里三件事写死:单例面板字典、对外三个接口 显示 / 隐藏 / 获取。Hierarchy 上保证 Canvas 根节点名字就是 Canvas,下面挂着 EventSystemCamera,这样 DontDestroyOnLoad(canvasRoot) 一次,输入和 UI 相机跟着根走,不会出现「场景没了 Canvas、EventSystem 还在别的根上」的半截状态。

单例与引用来源

  • 静态字段 instance + 只读属性 Instance私有构造GameObject.Find("Canvas").transform 赋给 canvasTrans,立刻 DontDestroyOnLoad(canvasTrans.gameObject)
  • 首次访问 Instance 时才会跑完这套初始化;场景里必须已经有一个叫 Canvas 的根物体,否则这里会空引用。正式项目里往往改成序列化引用、或由启动流程显式 Init(Transform root),减少字符串查找和改名风险。

字典与命名约定

键 / 路径 规则
panelDic 的 key typeof(T).Name,如 LoginPanel
Resources.Load 路径 "UI/" + panelName,即 Resources/UI/LoginPanel 这类预制。(无扩展名)
预制体上的脚本 必须带泛型要求的 T 组件,否则 GetComponent<T>() 拿不到实例。

ShowPanel<T> 流程(正文顺序)

  1. 算出 panelName,若字典已有该键,直接 as T 返回,不会再 Instantiate 第二份——业务上把同名面板当成「单例窗口」用时要清楚这一点。
  2. 否则 Resources.Load<GameObject>InstantiateSetParent(canvasTrans, false) 挂到 DontDestroy 的 Canvas 下;false 表示用 RectTransform 本地姿态贴合父节点,而不是保持世界坐标乱飘。
  3. GetComponent<T>()panelDic.Addpanel.ShowMe(),最后把实例返回给调用方。

HidePanel<T>(bool isFade = true)

  • isFade == true:取字典里的面板调 HideMe,在传入的 UnityAction 回调Destroy 该 GameObjectRemove 字典项——淡出的最后几帧仍由 BasePanel.Update 驱动,毁掉物体后不再进更新。
  • isFade == false走淡出,直接 Destroy + Remove,相当于立刻拆面板。

GetPanel<T>

  • 有键则 as T,没有则 null。业务里要先判空再刷新控件,别把「从未 Show 过」当成已创建。
// 类名与 Resources 下文件名一致:LoginPanel → Resources/UI/LoginPanel.prefab
public T ShowPanel<T>() where T : BasePanel { /* ... */ }
public void HidePanel<T>(bool isFade = true) where T : BasePanel { /* ... */ }

提示面板 TipPanel

  • 根节点同样要有 CanvasGroup(与 BasePanel 淡入淡出一致)。拼板时注意 九宫格切图、文字叠在更高 Sort Order,减少无意义合批断裂。
  • 脚本里拖 Button btnSureText txtInfoInit 里确定键 HidePanel<TipPanel> 关自己。对外 ChangeInfo(string) 只改 txtInfo.text,方便登录/注册里弹出同预制体、改字即用。
  • 测试流程:MainShowPanel<TipPanel>()ChangeInfo,验证管理器路径与淡入是否正常。

登录与注册

数据

类型 作用
LoginData userNamepassWordrememberPwautoLogin,以及 frontServerID≤ 0 表示还没选过服,与后文选服串联一致)。
RegisterData Dictionary<string,string> 存本地「账号 → 密码」,配合 JsonMgr 持久化。

LoginMgr

  • UIManager 类似的常驻单例思路;构造里 LoadData<LoginData>("LoginData")LoadData<RegisterData>("RegisterData"),后续课再 LoadData<List<ServerInfo>>("ServerInfo")
  • SaveLoginData / SaveRegisterData:把当前内存里的数据写回 JSON。
  • RegisterUserContainsKey 则失败;否则 Add 再保存。
  • CheckInfo:有用户且密码相等才 true,当成本地假「登录校验」。
  • ClearLoginData:注册新号成功时把 frontServerID、自动登录、记住密码等清回默认,避免沿用上一位玩家的选服状态(正文强调残留问题)。

LoginPanel

  • 控件:btnRegisterbtnSureinputUN / inputPWtogPWtogAuto
  • Toggle 互锁:关掉「记住密码」时强制 togAuto.isOn = false;勾选「自动登录」时若没记住密码则 **强制 togPW.isOn = true**——否则会出现「不存密码却自动登」的矛盾状态。
  • ShowMebase.ShowMe() 后从 LoginMgr.Instance.LoginData 刷多选框与账号;仅当记住密码为真才把存档里的密码填回输入框。
  • 登录按钮:正文约定账号、密码长度都要大于 6,否则 TipPanel 提示「账号和密码都必须大于6位」;通过 CheckInfo 后把输入写回 LoginDataSaveLoginData(),再按 frontServerID <= 0 打开 ChooseServerPanel 否则 ServerPanel,最后 HidePanel<LoginPanel>
  • 自动登录(后文补全):ShowMe 里若 togAuto 为真,直接 CheckInfo;成功则同样按 frontServerID 分流;为少一层淡出,可对登录面板 HidePanel<LoginPanel>(false) 直接撕掉实例。
  • 注册入口ShowPanel<RegisterPanel> + HidePanel<LoginPanel>

RegisterPanel

  • 取消:隐藏自己、ShowPanel<LoginPanel>
  • 确定:长度 ≤ 6 则 TipPanel 提示;否则 RegisterUser,失败则提示「用户名已存在」并清空输入。成功则 ClearLoginDataShowPanel<LoginPanel>SetInfo 填好新账号 → HidePanel<RegisterPanel>

LoginPanel.SetInfo:给注册成功回调用,避免用户再手打一遍号。

服务器面板与场景切换

  • btnBack:显示 LoginPanel、隐藏自己;若当前是自动登录回路,正文会让 LoginData.autoLogin = false,防止退回登录页后又瞬间被自动登录抬走。
  • btnStart:**先 HidePanel<ServerPanel>**(必要时再隐藏 LoginBKPanel 全屏底图),再 SaveLoginData,最后 SceneManager.LoadScene("GameScene")——Canvas 根被 DontDestroyOnLoad 后子面板不随旧场景销毁,必须主动关 UI,否则会带到新场景。
  • btnChangeShowPanel<ChooseServerPanel> + HidePanel<ServerPanel>
  • ShowMe:用 frontServerIDServerData[id - 1] 取服,刷新 txtName(无选服时显示「无选择」一类占位)。

选服面板 ChooseServerPanel 与列表项

配置链路

  • Excel 配 id / name / state / isNew,转 JSON 时注意不要多余空行;放到 **StreamingAssetsServerInfo.json**(或课内等价路径),JsonMgr 反序列化成 List<ServerInfo>,字段名与 C# 类一致。
  • ServerInfoidnamestate(0~4 五种展示)、isNew

列表项

脚本 职责
ServerLeftItem 记录 beginIndex / endIndex,文案 beginIndex + " - " + endIndex + "区";点击里 GetPanel<ChooseServerPanel>()UpdatePanel(begin, end) 刷右侧。
ServerRightItem 持有 ServerInfo nowServerInfoInitInfo 里拼 id + "区 " + name、新服角标、stateResources.Load<SpriteAtlas>("Login")ui_DL_* 几张状态图,0 则关状态图。点击:LoginData.frontServerIDHidePanel<ChooseServerPanel>ShowPanel<ServerPanel>

ChooseServerPanel

  • Init:按服务器总数 每 5 个一组算左侧区间个数 infoList.Count / 5 + 1,循环 Instantiate(Resources.Load<GameObject>("UI/ServerLeftItem")) 挂到 svLeft.contentInitInfo 里修正最后一组 endIndex 不超过 Count
  • ShowMe:根据 frontServerID <= 0 决定「上次登录」区文案与状态图;默认 UpdatePanel(1, min(5, Count)) 刷出第一组右侧按钮。
  • UpdatePanel(begin, end):改区间标题;先遍历 itemList Destroy 旧右侧格子,再 Clear;再 for (i = begin; i <= end)ServerData[i-1] 取数据 Instantiate UI/ServerRightItem,塞进 svRight.content 并入表。左侧 ScrollRect 用 垂直布局 + Content Size Fitter,右侧用 网格布局,删掉滚动条后记得 把 ScrollRect 的滚动条引用置空(正文提醒)。

全屏底图 LoginBKPanel

  • 继承 BasePanel,可空 InitMain 里与 LoginPanel 一并 Show,进入主流程后再按需要 HidePanel

图集整理与批次自测

  • 资源入口:把登录注册相关散图目录(课内如 ArtRes/登录注册)拖进 Resources/UI/Login 这张 Sprite Atlas 的打包对象列表,必要时再并入少量公共 UI 图;改列表后在 Inspector 里执行 Pack / 生成,让多张 Sprite 共用一张贴图采样。
  • 与代码对齐:运行时仍用 Resources.Load<SpriteAtlas>("Login"),再 GetSprite("子图名") 赋给 Image.sprite,和 ServerRightItemChooseServerPanel 里状态图写法一致;子图名要和图集里资源名对得上。
  • Inspector 常见项(与课内截图一致):Include in Build 开启;Padding4Allow RotationTight Packing 保持关,减轻缝边洇色;Max Texture Size2048;UI 通常不生成 Mip MapsFilterBilinear,平台压缩 Normal Quality
  • BK 提示:正文「不要打开 BK」指课上标出的、与背景图或打包策略冲突的误勾项;按原课演示勾选,避免整屏底图和控件图混打包规则搞乱。
  • 验收Play 模式下 Game → Stats,看 BatchesSetPass calls;全屏登录在图集与层级整理到位时可维持在很低批次(课内示例约个位数 batches)。若飙升,优先查:是否仍在用散图而非图集材质是否被打成多份Mask / 特效 / 不同 Shader 是否打断同一批。

实现顺序建议

  1. 建好工程与目录,导入 JsonMgr、美术与预制体。
  2. Canvas / UI 相机 / Scaler、BasePanelUIManager
  3. LoginData / RegisterData / LoginMgr,再 TipPanelLoginPanelRegisterPanel 预制体与跳转。
  4. ServerInfo 与 JSON、LoginMgr.ServerData,再 ServerPanelServerLeftItem / ServerRightItem 预制体
  5. ChooseServerPanel:左右动态 Instantiate、UpdatePanel 与左侧联动;图集 Login 打进 Resources
  6. 串联:登录成功分流注册成功 Clear自动登录进游戏存盘与 LoadScene返回登录关自动登录GameScene 加入 Build Settings
  7. 收尾:按第 10 课把散图收紧 Login Sprite AtlasPack,进 StatsBatches,各面板过一遍确认没有无故涨 DrawCall。

11.3 面试题精选

基础题

1. 面板基类为什么用 CanvasGroup 渐变而不是直接 SetActive

题目

BasePanelCanvasGroup.alpha 做淡入淡出。若改成显示时 SetActive(true)、隐藏时 SetActive(false),会和课内写法差在哪?

深入解析
  • 过渡alpha 可以按帧插值,玩家看到的是渐变;SetActive 是瞬时切换,没有课内要的淡出过程。
  • 生命周期:关掉 GameObject 会走到 OnDisable / OnEnable,子物体协程、监听也随激活态抖动;只用 CanvasGroup 改透明度,物体仍是激活的,逻辑是否继续跑要自己在业务里约束,但不会因为一帧 SetActive 把整套初始化又打一遍。
  • 扩展CanvasGroup 还能顺带关 interactableblocksRaycasts,隐藏未完时挡住点击;课内只用了 alpha,面试可以顺带提这一嘴。
答题示例

课内要淡入淡出,CanvasGroup 能每帧改透明度;SetActive 一闪就没了,也没有渐变。
频繁 SetActive 还会牵扯 OnEnable/OnDisable,和单纯改透明度不是一回事。

参考文章
  • 3.面板基类

2. Sprite Atlas 和登录页「少 Batches」有什么关系

题目

第 10 课让把登录注册散图打进 Login 图集,再在 Game Stats 里看 Batches。从 UGUI 合批角度,这样做主要解决什么问题?

深入解析
  • 贴图来源统一:多张 Image 若各自引用不同 Texture,往往要多次采样、多次提交;打进同一张图集后,共用同一贴图与同一套默认 UI 材质,更容易被 CanvasRenderer 合成更少的批次。
  • 仍可能打断批不同材质球Mask 嵌套文本网格与图标交替等仍会把批撕开;图集只是消灭「一堆小图各自一张纹理」里最显眼的那类浪费。
  • 和课内代码的关系SpriteAtlas.GetSprite 取的是图集子图,最终仍落在同一图集纹理上;Stats 里 batches 低,说明当前层级 + 图集策略基本是健康的。
答题示例

散图太多,每个 Image 一张纹理,GPU 切换频繁,batches 高。
打进 Sprite Atlas 后多张 UI 共用一张大图采样,UGUI 更容易合批,Stats 里 batches 会下来。
还要注意材质、Mask 别乱打断合批。

参考文章
  • 10.图集整理

进阶题

1. 泛型面板名和 Resources 路径绑在一起意味着什么

题目

ShowPanel<T> 里用 typeof(T).Name 去拼 Resources.Load("UI/" + panelName),项目里要注意什么?

深入解析
  • 约定是 C# 类名 = 预制体在 Resources/UI 下的文件名(无扩展名),改其一就要同步改另一处,否则 Load 返回 null 或挂错脚本。
  • ShowPanel 对已存在键直接返回旧实例:同名面板在业务上应是「单例 UI」,重复打开前要想好是要复用还是强制关旧再开。
  • Resources 打进包、路径硬编码,体量一大不利维护;正文教学这样写够直观,工程上可再演进到 Addressables、对象池等,但那是另一套约定。
  • UIManager 构造联动:课内若在私有构造里 GameObject.Find("Canvas")DontDestroyOnLoad,首场景必须已有名为 Canvas 的根;EventSystemUI 相机挂在该根子级,才能和整块 UI 一起进常驻场景,避免过场景只剩空壳或输入丢失。
答题示例

类名和预制体文件名必须一致,ShowPanel<LoginPanel> 就对应 UI/LoginPanel
改名要两边一起改,否则加载失败;字典里已有同名实例会直接返回,不会新建第二个。

参考文章
  • 4.UI管理器

2. 记住密码和自动登录两个 Toggle 为什么要互相牵制

题目

LoginPanel 里:取消「记住密码」会把 「自动登录」关掉;勾选「自动登录」时若 「记住密码」没开,会被代码强行打开。这两条规则各自在防什么?

深入解析
  • 自动登录依赖可恢复的凭据:不记住密码却仍自动登录,下次启动没有密码可填,CheckInfo 必然失败或与策划预期矛盾;所以关记住密码时一并关掉自动登录。
  • 勾自动登录必须能存密码:自动登录要走完整账号密码校验,课内用「自动登录为真则强制记住密码为真」把状态锁到合法组合,避免 UI 勾选与存档字段矛盾。
答题示例

不记住密码就不能自动登录,否则没有密码可验证。
勾了自动登录就必须记住密码,否则逻辑上对不上。
课内用两个 Toggle 事件互相改 isOn 把非法组合挡住。

参考文章
  • 6.登录面板

3. 注册成功后为什么要 ClearLoginData

题目

RegisterPanel 注册成功后会调 LoginMgr.ClearLoginData,再打开登录界面并 SetInfo。如果不清理,会出现什么 串号、串服 类问题?

深入解析
  • LoginData 是全局单例里的一份引用:上一账号如果写过 frontServerID、开过自动登录、记住密码,字典里 RegisterUser 换新人后这些字段仍指向旧玩家的选服与选项
  • Clear 做的事(正文):把 frontServerID 拉回默认,自动登录、记住密码关掉,让新号从「干净登录态」起步,再凭 SetInfo 只带新账号密码,避免一进游戏按旧服 ID 直进 ServerPanel 或误触自动登录。
  • 工程习惯:更稳的做法还包括 登出时统一重置 Model、或 分账号命名空间存档;课内用一次 Clear 把问题说透即可。
答题示例

上一份登录数据里的选服 ID、自动登录还在,新号会沿用,界面分流就错了。
Clear 把登录相关字段恢复默认,再 SetInfo 填新账号,避免旧号残留。

参考文章
  • 7.注册面板
  • 9.选服面板和功能串联

深度题

1. 用 Update 做全局淡入淡出的开销与改法

题目

每个 BasePanel 都在 Update 里改 CanvasGroup.alpha,面板多的时候有什么问题?可以怎么优化思路?

深入解析
  • 过渡叠加:多块面板同时在做淡入或淡出时,各自 Update 都在改 alpha,数量上去后这部分开销会叠起来;静止且 isShow && alpha==1 时两个分支都不进,几乎无额外计算。
  • 回调与生命周期:正文里 HideMe 的回调一般由 UIManagerDestroy 收尾,物体销毁后不再跑 Update。若业务里淡出结束不销毁且不把 hideCallBack 置空,则 alpha 钳在 0 后仍满足 <= 0,存在 每帧重复 Invoke 的风险,属于实现细节要防的坑。
  • 可改方向:只对「正在过渡」的面板开驱动,例如协程、DOTween、或集中式动效 tick;与课内「每个面板自带 Update」相比,更易控峰值。
答题示例

多块面板同时淡入淡出时,各自 Update 会叠在一起;一般淡出完就 Destroy,不会一直跑。
若淡出结束不销毁,要小心回调被每帧触发;工程上可用协程或 Tween,只驱动正在动效的那几块。

参考文章
  • 3.面板基类

2. 选服面板右侧列表刷新为什么要先 Destroy 再 Clear

题目

ChooseServerPanel.UpdatePanel 里对 itemList 里的实例逐个 Destroy,然后 Clear,再重新 Instantiate 一排 ServerRightItem。为什么不只改数据不删物体,或直接 ClearDestroy

深入解析
  • GameObject 与字典外引用:只清空列表不 Destroy,旧按钮仍在 svRight.content 下,场景里会越堆越多,交互和 DrawCall 都炸;正文做法是 列表 = 当前区间右侧格子的唯一权威集合,换区间就整批换掉。
  • Destroy 的时序:Unity 的 Destroy 默认排到帧末真正销毁;立刻 Clear 丢掉 C# 引用即可避免复用野引用,下一帧旧对象由引擎回收。若改用对象池,则应 SetActive(false) 回收而不是裸 Destroy,那是另一种维护 itemList 的约定。
  • ServerLeftItem 的配合:左侧点分段时必须 GetPanel<ChooseServerPanel>() 非 null,即选服面板已 Show 过并在字典里;否则要先保证打开顺序,这是动态列表 UI 常见「谁在驱动谁」的时序坑。
答题示例

不 Destroy 只 Clear,子物体还挂在 ScrollView 下,越积越多。
课内换区间就销毁旧格子再 Instantiate 新的,列表只保留当前这一组引用。
Destroy 帧末才真销毁,所以要先清列表避免逻辑上还拿着旧引用。

参考文章
  • 9.选服面板和功能串联


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏