9.FGUI实践项目总结

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 业务数据 维护 LoginDataRegisterDataSave/LoadRegisterUserCheckInfoClearLoginData(新注册用)。
LoginData 本地登录态 用户名、密码、记住密码自动登录上次选中服务器 ID 等,供流程分支与控件回填。
RegisterData 注册表 字典 registerDic 存「用户名 → 密码」;独立二进制存档,别和 LoginData 混在同一个文件语义里。

注意: 界面类里多写 事件绑定与显示刷新;跨面板共享状态、持久化字段、和「自动登录 / 上次选服」相关的判断数据,尽量收敛在 LoginMgr 与数据类,后续改存储方式(如 PlayerPrefs 换 JSON)时改动面更小。

提示、登录、注册、选服到服务器的实现要点

  • 两套脚本分工:FGUI 发布出来的绑定类负责「字段、子控件引用」;项目里 Window 派生类写显示逻辑、OnInit/OnShown、动效与事件。发布反复覆盖也不会冲掉手写逻辑。
  • LoadComponent:按包名加载 UIPackage.AddPackage("UI/" + 包名),再遍历 package.dependencies 把依赖包一并加上;UIPackage.CreateObject 得到根 GComponent。提示窗在 OnInit 里赋给 contentPaneMakeFullScreen(),需要挡操作时设 modal = true,并可用 UIConfig.modalLayerColor 控制遮罩浓度。
  • TipWindow.ChangeInfocontentPane 转成编辑器生成的面板类型(如 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.Addwin.Show()**。
  • 登录数据流LoginData 可序列化,配合 BinaryDataMgr.Save/Load("LoginData")LoginMgr 在构造里保证非 null。OnShown 专门做「每次露脸时的控件回填」,OnInit 绑定 onClick/onChanged:记住密码取消则自动登录必关;自动登录勾选则记住密码必开。CheckInfo 走注册字典。
  • 注册RegisterUser 成功则 ShowWindow<LoginWindow> 后 **SetInfoHideWindow<RegisterWindow>**;失败弹提示并清空输入。新号注册成功后 ClearLoginData,把 frontServerID 置 0、关掉自动登录与记住密码,避免沿用上一位玩家的选服与勾选。
  • 服务器与选服串联frontServerID == 0ServerChooseWindow,否则 ServerWindow。右侧条目点击写入 id,隐藏选服、打开服务器面板;换区从服务器面板再打开选服。进游戏前 SaveLoginData,再 ClearWindow(true) 清窗口并可选清包,然后 SceneManager.LoadScene。返回登录时把 autoLogin = false,否则退出登录界面仍会被自动登录拽回服务器界面。
  • 配表与列表:Excel 工具生成 ServerInfo / ServerInfoContainerBinaryDataMgr.InitDataLoadTable<ServerInfoContainer, ServerInfo>()。选服左侧按 每段 5 条 服务数据算区间个数 serverNum / 5 + 1AddItemFromPoolUI_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.defaultFontUIPackage.AddPackage("UI/Public") 后把 btnMusic 指到 UIConfig.buttonSoundGRoot.inst.SetContentScaleFactor(1365, 768, UIContentScaler.ScreenMatchMode.MatchHeight) 把逻辑分辨率与 MatchHeight 策略定死;UIConfig.modalLayerColor 控制 Window 模态遮罩透明度;LoginBinder.BindAll() 把本项目的组件扩展注册进 FairyGUI。
  • 两类 UI 的缓存策略ShowPanel<T> 面向 GComponent 全屏面板——已创建则置 visible;新建则 AddPackage、遍历 dependencies 再 AddPackage、用 组件类名 与编辑器里组件名对齐去 CreateObjectMakeFullScreen、挂 GRootAddRelation(..., Size) 跟屏幕走,fairyBatching = true 打开合批。ShowWindow<T> 面向 Window 子类,字典里缓存实例,走 Show/Hide 的窗口栈与模态行为,适合提示框、二级窗,而不是整屏登录底。
  • 卸载路径HidePanel/HideWindowisDispose 为真时 Dispose 并移除字典项;仅隐藏时面板用 visible = false,窗口用 Hide。切大场景可 ClearPanel/ClearWindow 并在需要时 RemoveAllPackages + GC.Collect
  • LoadComponent:只按包名 + 组件名拉出子组件,不自动全屏,同样设 fairyBatching,给列表 cell、嵌套小组件复用。
  • BinaryDataMgr 两条路径:只读配表从 Application.streamingAssetsPath + "/Binary/".tao(自定义头:int 行数、string 主键名、逐行按字段类型用 BitConverter 写字段;容器类里必须有 dataDic 字段);Save/LoadApplication.persistentDataPath + "/Data/",用 BinaryFormatter 序列化业务对象,与配表物理路径分开,避免热更表覆盖存档。

实现顺序建议: 先在编辑器里定好包名、依赖和 Resources/UI 或等价加载路径,再接通 ShowPanel 一条线看到界面,最后接 BinaryDataMgr.InitData 与选服读表,减少「界面有了列表却空」时排查范围。

DrawCall 与合批验证

  • FairyGUI 侧ShowPanel 创建的整屏 GComponentLoadComponent 拉出的窗口根组件 都应设 fairyBatching = true。窗口内容若只给面板开了合批、漏了 LoadComponent 返回根,Window 子树仍可能多批、Stats 里 Batches 偏高。
  • Unity Player Settings:按课程开启 Dynamic Batching 后,用 StatisticsBatches 等随设置的变化;它是引擎对一类网格渲染的批处理策略,不等价于 Fairy 的 fairyBatching,本实践里「能明显感觉」的往往还是 FGUI 合批
  • 验收动作:改 Game 视图分辨率 或窗口大小,确认 SetContentScaleFactor + AddRelation(..., Size) 仍铺满、无错位,避免只盯着数字却漏了适配回退。

9.3 面试题精选

进阶题

1. 登录流转怎么落地,UIManager 与 LoginMgr 怎么分工

题目

结合本实践的入口流程:自动登录、手动登录、注册返回登录、登录成功后「有 / 无历史选服」等,你会如何把界面跳转规则落到代码里(管理器、状态或路由),避免满天飞的全局布尔?UIManagerLoginMgr 各自该管什么,硬塞进一个单例会怎样?

深入解析
  • 流程图上的 每条箭头 对应 条件 + 目标面板:例如自动登录成功 → 服务器面板;登录成功且无选服记录 → 选服面板。条件尽量在 数据层 判定(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 用两个字典分别缓存 GComponentWindow。若要做全屏登录底、注册页切换,以及模态错误提示,你会分别走哪条 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、何时先调基类

题目

同一套项目里,TipWindowtransition.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.Addwin.Show()。结合自动登录里会在 OnShown 立刻 HideWindow,说明原先顺序会踩什么坑。

深入解析
  • Show() 触发 OnInit(若未初始化)与 OnShown 的时序是即时的;自动登录分支往往在 OnShown 末尾 校验通过后 HideWindow<LoginWindow>
  • 字典在 Show 之后才写入HideWindow 查字典会得到 无记录,窗口实例还在但管理器以为没打开,后续再 Show 可能 重复 new 或状态不一致。
  • 本质是 「生命周期回调」与「管理器登记」的原子顺序:登记必须先于任何可能在回调里依赖字典的操作。
答题示例

OnShown 里会 Hide 自己时,字典里必须先有这一扇窗。

先 Add 再 Show,HideWindow 才能找到实例并正确 Hide;否则自动登录首次会像没隐藏一样,或重复创建窗口。

参考文章
  • 5.注册面板

深度题

1. ShowPanel 加载包与合批时要注意什么

题目

ShowPanel 里对 UIPackage.AddPackagedependencies 做了处理,并设置了 fairyBatching。若漏加依赖包或关掉 fairyBatching,运行时分别可能出现什么现象?Resources 下重复 AddPackage 为何本文说「多执行问题不大」?

深入解析
  • 依赖包:FGUI 包里引用到其他包的资源时,只 Add 主包不 Add 依赖,常见结果是 贴图粉色、组件缺失、引用断链;代码里对 package.dependencies 逐项 AddPackage 就是为了一次性拉齐依赖树。
  • fairyBatching:为 true 时 FairyGUI 在同一批内合并 DrawCall;关掉后 UI 复杂时 DC 明显上升ShowPanelLoadComponent 返回的根组件 都应打开,漏掉窗口根时子树仍可能多批。
  • 重复 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 里为何先 RemoveChildrenToPoolAddItemFromPool?若只做 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

×

喜欢就点赞,疼爱就打赏