9.总结
9.1 知识点

学习的主要内容

UI功能制作套路

拼面板——注意事项

写代码——注意事项

UI四部曲简单总结

9.2 核心要点速览
登录闭环与界面跳转
- 游戏入口先判 自动登录:若玩家曾勾选且账号校验通过,跳过登录面板,直接进入 服务器面板;否则先进 登录面板。
- 登录面板 与 注册面板 互转:点击注册进入注册页;注册成功 后回到登录页继续走登录流程。
- 登录成功 后再分支:若属于「已有选服记录」的老用户路径,进 服务器面板;若 从未选过服,进 选服面板 先完成选服。
- 服务器面板 上确认进入游戏后,执行 切换场景,离开整套登录 UI,进入玩法场景。
实现顺序建议: 先把上述分支条件当成状态机或路由表写清(谁会跳过谁、何时必须选服),再实现各面板和 UIManager,避免后期改跳转牵一堆依赖。
分层结构:谁管界面、谁管数据
本系列采用 UI 管理层 + 各窗口 + 数据管理层 分工,和 FGUI 里「面板类 + 外部管理器」的常见用法一致。
| 模块 | 角色 | 要点 |
|---|---|---|
UIManager |
面板调度 | 单例;用 字典 缓存已打开面板;对外 显示 / 隐藏 / 获取 面板,避免到处 Find 或重复创建。 |
TipWindow |
提示 | 对外提供 更新展示内容(错误提示、操作结果等),其它面板按需调用。 |
LoginWindow |
登录 | 除自身交互外,提供 设置账号密码 一类接口,给 注册成功回填 使用,减少面板间直接操作控件。 |
RegisterWindow |
注册 | 校验、注册、成功回到登录并 SetInfo;关闭回到登录。 |
ServerChooseWindow |
选服 | 读 ServerInfoContainer,左侧区间 + 右侧列表对象池刷新,UpdatePanel(begin,end)。 |
ServerWindow |
服务器摘要 | 展示当前服;进游戏清窗切场景;换区打开选服;返回登录并 关闭自动登录。 |
UI_ServerLeftItem / UI_ServerRightItem |
列表子项 | 左:区间与 UpdatePanel;右:持有 ServerInfo,点击写 frontServerID 并切到 ServerWindow。 |
LoginMgr |
业务数据 | 维护 LoginData、RegisterData;Save/Load、RegisterUser、CheckInfo、ClearLoginData(新注册用)。 |
LoginData |
本地登录态 | 用户名、密码、记住密码、自动登录、上次选中服务器 ID 等,供流程分支与控件回填。 |
RegisterData |
注册表 | 字典 registerDic 存「用户名 → 密码」;独立二进制存档,别和 LoginData 混在同一个文件语义里。 |
注意: 界面类里多写 事件绑定与显示刷新;跨面板共享状态、持久化字段、和「自动登录 / 上次选服」相关的判断数据,尽量收敛在 LoginMgr 与数据类,后续改存储方式(如 PlayerPrefs 换 JSON)时改动面更小。
提示、登录、注册、选服到服务器的实现要点
- 两套脚本分工:FGUI 发布出来的绑定类负责「字段、子控件引用」;项目里
Window派生类写显示逻辑、OnInit/OnShown、动效与事件。发布反复覆盖也不会冲掉手写逻辑。 LoadComponent:按包名加载UIPackage.AddPackage("UI/" + 包名),再遍历package.dependencies把依赖包一并加上;UIPackage.CreateObject得到根GComponent。提示窗在OnInit里赋给contentPane后MakeFullScreen(),需要挡操作时设modal = true,并可用UIConfig.modalLayerColor控制遮罩浓度。TipWindow.ChangeInfo:contentPane转成编辑器生成的面板类型(如UI_TipWindow),再改嵌套文本。依赖LoginBinder.BindAll()已注册扩展类型,否则as不到正确子类。- 动效与基类顺序:提示类窗口希望「过渡播完再算真正 Show/Hide」,则
GetTransition("show").Play(base.DoShowAnimation),把基类显示留到过渡回调;登录窗从注册返回时,OnShown会先根据本地LoginData填充输入框,若先播过渡再执行基类,可能盖住SetInfo的节奏——因此要base.DoShowAnimation()先跑完(触发OnShown),再GetTransition("show").Play()只负责视觉,这和提示窗是两种合法顺序,取决于你要 先改数据还是先完成窗口状态机。 - **
ShowWindow先入字典再Show**:Window.Show()会立刻进OnShown,若自动登录里马上HideWindow,而实例还没写进windowDic,隐藏会落空。做法是 **windowDic.Add→win.Show()**。 - 登录数据流:
LoginData可序列化,配合BinaryDataMgr.Save/Load("LoginData");LoginMgr在构造里保证非 null。OnShown专门做「每次露脸时的控件回填」,OnInit绑定onClick/onChanged:记住密码取消则自动登录必关;自动登录勾选则记住密码必开。CheckInfo走注册字典。 - 注册:
RegisterUser成功则ShowWindow<LoginWindow>后 **SetInfo再HideWindow<RegisterWindow>**;失败弹提示并清空输入。新号注册成功后ClearLoginData,把frontServerID置 0、关掉自动登录与记住密码,避免沿用上一位玩家的选服与勾选。 - 服务器与选服串联:
frontServerID == 0走ServerChooseWindow,否则ServerWindow。右侧条目点击写入 id,隐藏选服、打开服务器面板;换区从服务器面板再打开选服。进游戏前SaveLoginData,再ClearWindow(true)清窗口并可选清包,然后SceneManager.LoadScene。返回登录时把autoLogin = false,否则退出登录界面仍会被自动登录拽回服务器界面。 - 配表与列表:Excel 工具生成
ServerInfo/ServerInfoContainer,BinaryDataMgr.InitData里LoadTable<ServerInfoContainer, ServerInfo>()。选服左侧按 每段 5 条 服务数据算区间个数serverNum / 5 + 1,AddItemFromPool拉UI_ServerLeftItem;右下角UpdatePanel(begin,end)里RemoveChildrenToPool()回收旧子节点再AddItemFromPool创建UI_ServerRightItem,区间标题用 **SetVar+FlushVars**。左侧子项里通过UIManager.GetWindow<ServerChooseWindow>()调UpdatePanel,避免列表项直接 new 面板。
实现顺序建议: 先做 TipWindow + LoadComponent + ShowWindow 字典顺序 这一条竖切,再拼登录与注册数据流、校验,最后接 选服表 + 左右列表池化;否则动效与自动登录时序问题会混在一起难拆。
工程准备与运行时 UI 启动
- FGUI 编辑器侧:按业务拆包,通用资源集中到
Public一类包(例如Public / 通用资源下放ui_TY_*底图、品质框、返回按钮等),Login等业务包只引用,避免同一套大图复制多份、后续换皮要改几处。 - UIManager 构造阶段的全局初始化(整局游戏通常只做一次):指定
UIConfig.defaultFont;UIPackage.AddPackage("UI/Public")后把btnMusic指到UIConfig.buttonSound;GRoot.inst.SetContentScaleFactor(1365, 768, UIContentScaler.ScreenMatchMode.MatchHeight)把逻辑分辨率与 MatchHeight 策略定死;UIConfig.modalLayerColor控制 Window 模态遮罩透明度;LoginBinder.BindAll()把本项目的组件扩展注册进 FairyGUI。 - 两类 UI 的缓存策略:
ShowPanel<T>面向GComponent全屏面板——已创建则置visible;新建则AddPackage、遍历dependencies再 AddPackage、用 组件类名 与编辑器里组件名对齐去CreateObject,MakeFullScreen、挂GRoot、AddRelation(..., Size)跟屏幕走,fairyBatching = true打开合批。ShowWindow<T>面向Window子类,字典里缓存实例,走Show/Hide的窗口栈与模态行为,适合提示框、二级窗,而不是整屏登录底。 - 卸载路径:
HidePanel/HideWindow的isDispose为真时Dispose并移除字典项;仅隐藏时面板用visible = false,窗口用Hide。切大场景可ClearPanel/ClearWindow并在需要时RemoveAllPackages+GC.Collect。 - LoadComponent:只按包名 + 组件名拉出子组件,不自动全屏,同样设
fairyBatching,给列表 cell、嵌套小组件复用。 - BinaryDataMgr 两条路径:只读配表从
Application.streamingAssetsPath + "/Binary/"读.tao(自定义头:int行数、string主键名、逐行按字段类型用BitConverter写字段;容器类里必须有dataDic字段);Save/Load走Application.persistentDataPath + "/Data/",用BinaryFormatter序列化业务对象,与配表物理路径分开,避免热更表覆盖存档。
实现顺序建议: 先在编辑器里定好包名、依赖和 Resources/UI 或等价加载路径,再接通 ShowPanel 一条线看到界面,最后接 BinaryDataMgr.InitData 与选服读表,减少「界面有了列表却空」时排查范围。
DrawCall 与合批验证
- FairyGUI 侧:
ShowPanel创建的整屏GComponent与LoadComponent拉出的窗口根组件 都应设fairyBatching = true。窗口内容若只给面板开了合批、漏了LoadComponent返回根,Window子树仍可能多批、Stats 里 Batches 偏高。 - Unity Player Settings:按课程开启 Dynamic Batching 后,用 Statistics 看 Batches 等随设置的变化;它是引擎对一类网格渲染的批处理策略,不等价于 Fairy 的
fairyBatching,本实践里「能明显感觉」的往往还是 FGUI 合批。 - 验收动作:改 Game 视图分辨率 或窗口大小,确认
SetContentScaleFactor+AddRelation(..., Size)仍铺满、无错位,避免只盯着数字却漏了适配回退。
9.3 面试题精选
进阶题
1. 登录流转怎么落地,UIManager 与 LoginMgr 怎么分工
题目
结合本实践的入口流程:自动登录、手动登录、注册返回登录、登录成功后「有 / 无历史选服」等,你会如何把界面跳转规则落到代码里(管理器、状态或路由),避免满天飞的全局布尔?UIManager 与 LoginMgr 各自该管什么,硬塞进一个单例会怎样?
深入解析
- 流程图上的 每条箭头 对应 条件 + 目标面板:例如自动登录成功 → 服务器面板;登录成功且无选服记录 → 选服面板。条件尽量在 数据层 判定(
LoginData、是否已有frontServerID等),UI 只消费结论。 - 集中调度:由
UIManager(或单独流程类)统一Show/Hide;面板少互相引用具体类型;需要状态时用LoginMgr或只读接口。 UIManager:FGUI 实例生命周期——谁显示/隐藏、缓存、获取引用;不负责「记不记得密码」「上次服 ID」等业务规则。LoginMgr:领域数据与规则——LoginData/RegisterData、校验、Save/Load、注册与选服 id 相关逻辑;可换存储或对接网络,尽量不绑死在某个GComponent。- 二者混进一个类:单例 臃肿,UI 与持久化/网络 绞在一起,改存储或换 UI 难测难拆。实务上可以是 LoginMgr 给出「下一步」结论,
UIManager只负责 Show/Hide。 - 注册回到登录:用
LoginWindow.SetInfo做回填,勿让注册面板直接改登录面板私有控件。 - 扩展新流程:只加 数据字段 + 一条路由,避免各处
OnClick复制同一串if。
答题示例
先列「条件 → 目标界面」,单一入口根据 LoginData / 校验结果决定打开哪块;UIManager 只管面板实例和显隐,LoginMgr 管账号、选服 id、存档和校验。
跳转走 Show/Hide,注册成功用 LoginWindow 设值回填。职责拆开,以后改存档或接服务器不必改一堆 UI 代码。
参考文章
- 1.需求分析
2. ShowPanel 与 ShowWindow 各适合什么场景
题目
本课 UIManager 用两个字典分别缓存 GComponent 与 Window。若要做全屏登录底、注册页切换,以及模态错误提示,你会分别走哪条 API,理由是什么?
深入解析
- ShowPanel:产出的是挂到
GRoot的 全屏或大块GComponent,适合登录、选服等「占满安全区、长期存在、面板间切换」的界面;缓存后用visible切换比反复CreateObject便宜;MakeFullScreen+RelationType.Size跟分辨率。 - ShowWindow:FairyGUI 的 Window 带模态、居中、层级与
ShowModalWait等语义,适合做 提示、确认、小弹层;同一类型多次Show可走已缓存实例。 - 混用风险:把全屏页做成
Window也可以,但模态与遮挡语义会变复杂;把提示做成裸GComponent则要自己管遮罩和排序。
答题示例
全屏登录、选服这类用 ShowPanel,挂 GRoot,字典里用 visible 切换显示。
模态提示、确认框用 ShowWindow,走窗口的 Show、Hide,方便遮罩和置顶。别用错了:全屏底做成 Window 会带来多余的模态栈行为。
参考文章
- 2.准备工作
3. Window 动效里何时先播 Transition、何时先调基类
题目
同一套项目里,TipWindow 用 transition.Play(base.DoShowAnimation),而登录窗在注册回填场景下会先 base.DoShowAnimation() 再播 show 过渡。各自解决什么问题?如果写反了会有什么症状?
深入解析
Play(CompleteCallback):过渡结束再调用基类,等价于 视觉上先淡入/位移,再提交 Window 的最终可见状态,适合模态提示这种「就怕一闪而过」的窗口。- 先基类再过渡:基类的 Show 流程会先跑
OnShown,把LoginData填进输入框;若此时过渡还没开始或与SetInfo顺序打架,会出现 刚设好的账号密码被OnShown覆盖 一类问题。 - 写反时:要么 动效看不见(基类提早把 Alpha 或显隐设死),要么 UI 状态与数据不同步(显示层仍在上一次的缓存值)。
答题示例
需要过渡接在真实 Show 完成之前,就用 Play 的回调里调基类 DoShow。
需要 OnShown 先跑完再播入场动画,就先 base.DoShowAnimation,再单独 Play transition,避免和 SetInfo、本地存档回填冲突。
参考文章
- 3.提示面板
- 5.注册面板
4. 为什么 ShowWindow 要先写入 windowDic 再调用 Show
题目
文中把 ShowWindow 改成先 windowDic.Add 再 win.Show()。结合自动登录里会在 OnShown 立刻 HideWindow,说明原先顺序会踩什么坑。
深入解析
Show()触发OnInit(若未初始化)与OnShown的时序是即时的;自动登录分支往往在OnShown末尾 校验通过后HideWindow<LoginWindow>。- 若 字典在
Show之后才写入,HideWindow查字典会得到 无记录,窗口实例还在但管理器以为没打开,后续再 Show 可能 重复 new 或状态不一致。 - 本质是 「生命周期回调」与「管理器登记」的原子顺序:登记必须先于任何可能在回调里依赖字典的操作。
答题示例
OnShown 里会 Hide 自己时,字典里必须先有这一扇窗。
先 Add 再 Show,HideWindow 才能找到实例并正确 Hide;否则自动登录首次会像没隐藏一样,或重复创建窗口。
参考文章
- 5.注册面板
深度题
1. ShowPanel 加载包与合批时要注意什么
题目
ShowPanel 里对 UIPackage.AddPackage 与 dependencies 做了处理,并设置了 fairyBatching。若漏加依赖包或关掉 fairyBatching,运行时分别可能出现什么现象?Resources 下重复 AddPackage 为何本文说「多执行问题不大」?
深入解析
- 依赖包:FGUI 包里引用到其他包的资源时,只 Add 主包不 Add 依赖,常见结果是 贴图粉色、组件缺失、引用断链;代码里对
package.dependencies逐项AddPackage就是为了一次性拉齐依赖树。 - fairyBatching:为
true时 FairyGUI 在同一批内合并 DrawCall;关掉后 UI 复杂时 DC 明显上升。ShowPanel与LoadComponent返回的根组件 都应打开,漏掉窗口根时子树仍可能多批。 - 重复 AddPackage:从
StreamingAssets/Resources路径加载时 FairyGUI 侧会 避免重复载入同包,因此构造或ShowPanel多次调用通常不致内存爆炸;真正要释放应用RemovePackage/RemoveAllPackages配合生命周期。 - 类名与组件名一致:
panelName = typeof(T).Name必须与编辑器导出组件名一致,否则CreateObject(packageName, panelName)找不到对应组件。
答题示例
依赖没加全表现为资源丢失或粉红材质;fairyBatching 关掉会让 DrawCall 变多。
Resources 加载时 AddPackage 有去重,重复调一般还好,切场景再集中 Clear 和 RemoveAllPackages。组件类名要和 FGUI 里组件名一致才能 CreateObject。
参考文章
- 2.准备工作
2. 选服面板如何用 GList 做区间切换而不堆积无效节点
题目
UpdatePanel 里为何先 RemoveChildrenToPool 再 AddItemFromPool?若只做 RemoveChildren 或重复 Add 不复用会有什么问题?
深入解析
- RemoveChildrenToPool:FGUI 列表把子节点 回收到对象池,下一次
AddItemFromPool优先复用同一批UI_ServerRightItem,减少实例分配与 GC,并保持 列表项类型与默认资源 一致。 - 只 Remove 不 Pool:子项 直接销毁,频繁换区时 Alloc 压力更大;若忘记移除,旧按钮仍占位或仍挂着 过期点击事件。
- 与数据侧配合:区间变更前清列表,再按
ServerInfoContainer.dataDic在[beginIndex,endIndex]内逐条InitInfo,保证 展示与当前区间严格同步。
答题示例
换区间前先 RemoveChildrenToPool,把旧 item 还回池子,再按新区间 AddItemFromPool 取出格子填数据。
这样不会越堆越多,也能避免旧服务器按钮残留;比每次 new 再挂树更省,也符合 FGUI 列表推荐用法。
参考文章
- 7.选服面板和功能串联
3. Unity Dynamic Batching 与 FairyGUI 的 fairyBatching 是不是一回事
题目
本课在 Player Settings 里开了 Dynamic Batching,又在代码里把 fairyBatching 设为 true。若在面试里被追问「两者分别优化什么、关掉哪一个对 FGUI 登录界面影响更大」,你怎么答?
深入解析
fairyBatching:FairyGUI 在 DisplayObject 树 上做的合批与绘制提交优化,直接决定这类 UI 的常见 Batches / DrawCall 观感;本项目的窗口根来自LoadComponent,根节点没开时,子树容易打不满批。- Dynamic Batching:Unity 引擎对 符合条件 的动态网格渲染路径的合批,与具体 UI 方案是否大量走到那条路径有关;不能替代 FairyGUI 自己的 batching 开关。
- 实务判断:做 FGUI 性能时优先查
fairyBatching、图集与材质打断、遮罩与过滤器;Player Settings 项作为 全局对比实验 没问题,但别把登录 UI 的 DC 全算到动态批处理头上。
答题示例
fairyBatching 是 FairyGUI 自己的合批,挂在组件上,对这套 UI 最直接。
Dynamic Batching 是 Unity 引擎侧对一类渲染的批处理,和 FGUI 不是同一个旋钮;关掉 fairyBatching 通常登录界面 batches 会明显难看,光是 Player Settings 里动一下未必能补回来。
参考文章
- 8.DrawCall优化
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com