11.总结
11.1 知识点

学习的主要内容

UI功能制作套路

拼面板——注意事项

写代码——注意事项

思考

11.2 核心要点速览
登录与选服流程
- 总入口:玩家从「进入游戏」进入;是否走登录界面,首先看自动登录是否开启且账号校验是否成功。
- 自动登录成功:可直接进入服务器面板,跳过登录界面,适合「记住账号 + 服务端认可令牌」的回头客路径。
- 登录面板:手动输入账号密码;可点「注册」进注册面板,注册成功再回到登录;登录成功后按「是否曾经选过服」分流。
- 老用户且已有选服记录:登录成功后进服务器面板(展示当前/上次所选区服)。
- 新用户或未选过服:登录成功后先进选服面板选区,再进入服务器面板。
- 进游戏:在服务器面板确认后执行切换场景,进入实际玩法场景。
| 判断或玩家操作 | 下一界面或动作 |
|---|---|
| 自动登录成功 | 服务器面板 |
| 需手动登录 | 登录面板 |
| 登录面板点注册 | 注册面板;注册成功回到登录面板 |
| 登录成功且已有选服记录 | 服务器面板 |
| 登录成功且未选过服 | 选服面板 → 再回服务器面板 |
| 服务器面板点进入游戏 | 切换场景 |
分层与类型职责(需求类图)
- UI 管理层:
UIManager单例,内部用字典登记各面板实例,Show/Hide/Get三类操作;所有业务界面都通过它拿实例,避免场景里到处Find。 - 面板基类:
BasePanel负责各面板通用的淡入淡出、显示/隐藏自己的节奏;具体面板只关心业务控件与数据绑定。 - 功能面板层:
TipPanel、LoginPanel、RegisterPanel、ChooseServerPanel、ServerPanel等继承BasePanel;各自注册控件事件、刷新显示;选服、服列表再通过列表项脚本拆细。 - 列表项脚本:
ServerLeftItem(左侧区间按钮)、ServerRightItem(右侧单服格子);按区间或ServerInfo刷新显示、响应点击,挂在 ScrollView 的预制体上。 - 纯数据类型:
LoginData、RegisterData;全服列表为List<ServerInfo>(如ServerInfo.json反序列化),ServerInfo表示单服(id、name、state、isNew)。与界面解耦,便于 JSON 与 UI 分工。 - 登录数据入口:
LoginMgr单例持有登录/注册存档与服务端列表,对外CheckInfo、RegisterUser、SaveLoginData等;面板只调管理器,不互相掏私有字段。
实现主线建议
- 按类图先把
LoginData/RegisterData/LoginMgr定下来,保证「记住密码 / 自动登录 / 上次服务器」有统一存取入口。 - 实现 **
UIManager+BasePanel**,把面板生命周期和 Resources 路径约定写死,后面所有面板只派生、不重复写淡入淡出框架。 - 按流程图从 提示、登录、注册、选服、服务器主界面 逐块预制体 + 脚本实现,列表项单独做成可复用预制体。
- 最后再把 界面跳转条件(自动登录、是否选过服)接进各面板按钮与回调,场景切换挂在服务器面板「进入游戏」上。
工程准备与 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若对整棵Canvas做DontDestroyOnLoad,输入模块和相机可以一起到下个场景,避免只带了 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 |
当前走淡入还是淡出 | ShowMe 置 true 并 alpha=0;HideMe 置 false 并 alpha=1。 |
hideCallBack |
淡出结束通知 | HideMe 里赋值,alpha 减到 0 后调用;一般由 UIManager 里 Destroy 和字典移除。 |
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();
}
}
}
实现时注意:!isShow 且 alpha 已钳在 0 时,每帧仍会进 <= 0 分支,hideCallBack 会反复触发。课内回调里一般会 Destroy 面板,物体销毁后不再跑 Update;若改成淡出后仍留在场景,要在回调里置空委托或做一次性防护,避免每帧刷关面板/销毁逻辑。
UI 管理器 UIManager
类图里三件事写死:单例、面板字典、对外三个接口 显示 / 隐藏 / 获取。Hierarchy 上保证 Canvas 根节点名字就是 Canvas,下面挂着 EventSystem 和 Camera,这样 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> 流程(正文顺序)
- 算出
panelName,若字典已有该键,直接as T返回,不会再Instantiate第二份——业务上把同名面板当成「单例窗口」用时要清楚这一点。 - 否则
Resources.Load<GameObject>再Instantiate,SetParent(canvasTrans, false)挂到 DontDestroy 的 Canvas 下;false表示用 RectTransform 本地姿态贴合父节点,而不是保持世界坐标乱飘。 GetComponent<T>()→panelDic.Add→panel.ShowMe(),最后把实例返回给调用方。
HidePanel<T>(bool isFade = true)
isFade == true:取字典里的面板调HideMe,在传入的UnityAction回调里Destroy该 GameObject 并Remove字典项——淡出的最后几帧仍由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 btnSure、Text txtInfo;Init里确定键HidePanel<TipPanel>关自己。对外ChangeInfo(string)只改txtInfo.text,方便登录/注册里弹出同预制体、改字即用。 - 测试流程:
Main里ShowPanel<TipPanel>()再ChangeInfo,验证管理器路径与淡入是否正常。
登录与注册
数据
| 类型 | 作用 |
|---|---|
LoginData |
存 userName、passWord、rememberPw、autoLogin,以及 frontServerID(≤ 0 表示还没选过服,与后文选服串联一致)。 |
RegisterData |
Dictionary<string,string> 存本地「账号 → 密码」,配合 JsonMgr 持久化。 |
LoginMgr
- 与
UIManager类似的常驻单例思路;构造里LoadData<LoginData>("LoginData")、LoadData<RegisterData>("RegisterData"),后续课再LoadData<List<ServerInfo>>("ServerInfo")。 SaveLoginData/SaveRegisterData:把当前内存里的数据写回 JSON。RegisterUser:ContainsKey则失败;否则Add再保存。CheckInfo:有用户且密码相等才true,当成本地假「登录校验」。ClearLoginData:注册新号成功时把frontServerID、自动登录、记住密码等清回默认,避免沿用上一位玩家的选服状态(正文强调残留问题)。
LoginPanel
- 控件:
btnRegister、btnSure、inputUN/inputPW、togPW、togAuto。 - Toggle 互锁:关掉「记住密码」时强制
togAuto.isOn = false;勾选「自动登录」时若没记住密码则 **强制togPW.isOn = true**——否则会出现「不存密码却自动登」的矛盾状态。 ShowMe:base.ShowMe()后从LoginMgr.Instance.LoginData刷多选框与账号;仅当记住密码为真才把存档里的密码填回输入框。- 登录按钮:正文约定账号、密码长度都要大于 6,否则
TipPanel提示「账号和密码都必须大于6位」;通过CheckInfo后把输入写回LoginData并SaveLoginData(),再按frontServerID <= 0打开ChooseServerPanel否则ServerPanel,最后HidePanel<LoginPanel>。 - 自动登录(后文补全):
ShowMe里若togAuto为真,直接CheckInfo;成功则同样按frontServerID分流;为少一层淡出,可对登录面板HidePanel<LoginPanel>(false)直接撕掉实例。 - 注册入口:
ShowPanel<RegisterPanel>+HidePanel<LoginPanel>。
RegisterPanel
- 取消:隐藏自己、
ShowPanel<LoginPanel>。 - 确定:长度 ≤ 6 则
TipPanel提示;否则RegisterUser,失败则提示「用户名已存在」并清空输入。成功则ClearLoginData→ShowPanel<LoginPanel>并SetInfo填好新账号 →HidePanel<RegisterPanel>。
LoginPanel.SetInfo:给注册成功回调用,避免用户再手打一遍号。
服务器面板与场景切换
btnBack:显示LoginPanel、隐藏自己;若当前是自动登录回路,正文会让LoginData.autoLogin = false,防止退回登录页后又瞬间被自动登录抬走。btnStart:**先HidePanel<ServerPanel>**(必要时再隐藏LoginBKPanel全屏底图),再SaveLoginData,最后SceneManager.LoadScene("GameScene")——Canvas 根被DontDestroyOnLoad后子面板不随旧场景销毁,必须主动关 UI,否则会带到新场景。btnChange:ShowPanel<ChooseServerPanel>+HidePanel<ServerPanel>。ShowMe:用frontServerID在ServerData[id - 1]取服,刷新txtName(无选服时显示「无选择」一类占位)。
选服面板 ChooseServerPanel 与列表项
配置链路
- Excel 配 id / name / state / isNew,转 JSON 时注意不要多余空行;放到 **
StreamingAssets的ServerInfo.json**(或课内等价路径),JsonMgr反序列化成List<ServerInfo>,字段名与 C# 类一致。 ServerInfo:id、name、state(0~4 五种展示)、isNew。
列表项
| 脚本 | 职责 |
|---|---|
ServerLeftItem |
记录 beginIndex / endIndex,文案 beginIndex + " - " + endIndex + "区";点击里 GetPanel<ChooseServerPanel>() 调 UpdatePanel(begin, end) 刷右侧。 |
ServerRightItem |
持有 ServerInfo nowServerInfo;InitInfo 里拼 id + "区 " + name、新服角标、state 用 Resources.Load<SpriteAtlas>("Login") 取 ui_DL_* 几张状态图,0 则关状态图。点击:写 LoginData.frontServerID → HidePanel<ChooseServerPanel> → ShowPanel<ServerPanel>。 |
ChooseServerPanel
Init:按服务器总数 每 5 个一组算左侧区间个数infoList.Count / 5 + 1,循环Instantiate(Resources.Load<GameObject>("UI/ServerLeftItem"))挂到svLeft.content,InitInfo里修正最后一组endIndex不超过 Count。ShowMe:根据frontServerID <= 0决定「上次登录」区文案与状态图;默认UpdatePanel(1, min(5, Count))刷出第一组右侧按钮。UpdatePanel(begin, end):改区间标题;先遍历itemListDestroy旧右侧格子,再Clear;再for (i = begin; i <= end)从ServerData[i-1]取数据InstantiateUI/ServerRightItem,塞进svRight.content并入表。左侧 ScrollRect 用 垂直布局 + Content Size Fitter,右侧用 网格布局,删掉滚动条后记得 把 ScrollRect 的滚动条引用置空(正文提醒)。
全屏底图 LoginBKPanel
- 继承
BasePanel,可空Init;Main里与LoginPanel一并Show,进入主流程后再按需要HidePanel。
图集整理与批次自测
- 资源入口:把登录注册相关散图目录(课内如
ArtRes/登录注册)拖进Resources/UI/Login这张 Sprite Atlas 的打包对象列表,必要时再并入少量公共 UI 图;改列表后在 Inspector 里执行 Pack / 生成,让多张 Sprite 共用一张贴图采样。 - 与代码对齐:运行时仍用
Resources.Load<SpriteAtlas>("Login"),再GetSprite("子图名")赋给Image.sprite,和ServerRightItem、ChooseServerPanel里状态图写法一致;子图名要和图集里资源名对得上。 - Inspector 常见项(与课内截图一致):Include in Build 开启;Padding 约 4;Allow Rotation、Tight Packing 保持关,减轻缝边洇色;Max Texture Size 如 2048;UI 通常不生成 Mip Maps,Filter 用 Bilinear,平台压缩 Normal Quality。
- BK 提示:正文「不要打开 BK」指课上标出的、与背景图或打包策略冲突的误勾项;按原课演示勾选,避免整屏底图和控件图混打包规则搞乱。
- 验收:Play 模式下 Game → Stats,看 Batches、SetPass calls;全屏登录在图集与层级整理到位时可维持在很低批次(课内示例约个位数 batches)。若飙升,优先查:是否仍在用散图而非图集、材质是否被打成多份、
Mask/ 特效 / 不同 Shader 是否打断同一批。
实现顺序建议
- 建好工程与目录,导入
JsonMgr、美术与预制体。 - Canvas / UI 相机 / Scaler、
BasePanel、UIManager。 LoginData/RegisterData/LoginMgr,再TipPanel→LoginPanel→RegisterPanel预制体与跳转。ServerInfo与 JSON、LoginMgr.ServerData,再ServerPanel与ServerLeftItem/ServerRightItem预制体。ChooseServerPanel:左右动态 Instantiate、UpdatePanel与左侧联动;图集Login打进Resources。- 串联:登录成功分流、注册成功 Clear、自动登录、进游戏存盘与
LoadScene、返回登录关自动登录;GameScene加入 Build Settings。 - 收尾:按第 10 课把散图收紧
LoginSprite Atlas 并 Pack,进 Stats 看 Batches,各面板过一遍确认没有无故涨 DrawCall。
11.3 面试题精选
基础题
1. 面板基类为什么用 CanvasGroup 渐变而不是直接 SetActive
题目
BasePanel 用 CanvasGroup.alpha 做淡入淡出。若改成显示时 SetActive(true)、隐藏时 SetActive(false),会和课内写法差在哪?
深入解析
- 过渡:
alpha可以按帧插值,玩家看到的是渐变;SetActive是瞬时切换,没有课内要的淡出过程。 - 生命周期:关掉 GameObject 会走到
OnDisable/OnEnable,子物体协程、监听也随激活态抖动;只用CanvasGroup改透明度,物体仍是激活的,逻辑是否继续跑要自己在业务里约束,但不会因为一帧SetActive把整套初始化又打一遍。 - 扩展:
CanvasGroup还能顺带关interactable、blocksRaycasts,隐藏未完时挡住点击;课内只用了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的根;EventSystem、UI 相机挂在该根子级,才能和整块 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的回调一般由UIManager里Destroy收尾,物体销毁后不再跑Update。若业务里淡出结束不销毁且不把hideCallBack置空,则alpha钳在 0 后仍满足<= 0,存在 每帧重复Invoke的风险,属于实现细节要防的坑。 - 可改方向:只对「正在过渡」的面板开驱动,例如协程、
DOTween、或集中式动效 tick;与课内「每个面板自带Update」相比,更易控峰值。
答题示例
多块面板同时淡入淡出时,各自
Update会叠在一起;一般淡出完就Destroy,不会一直跑。
若淡出结束不销毁,要小心回调被每帧触发;工程上可用协程或 Tween,只驱动正在动效的那几块。
参考文章
- 3.面板基类
2. 选服面板右侧列表刷新为什么要先 Destroy 再 Clear
题目
ChooseServerPanel.UpdatePanel 里对 itemList 里的实例逐个 Destroy,然后 Clear,再重新 Instantiate 一排 ServerRightItem。为什么不只改数据不删物体,或直接 Clear 不 Destroy?
深入解析
- 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