游戏开发学习记录
初始化只是第一次实例化的时候调用,show和unshow是打开界面和关闭界面的时候,会多次调用
在一个脚本里面show是每一次打开界面的时候需要做的事情,而Init是初始化。
UIMgr里面的数据结构:
为什么我要先从数据结构入手呢?因为程序 = 数据结构 + 算法!!!!!!
UIMgr
(UI 管理器),用于管理游戏中打开、关闭、缓存 UI 页面,属于典型的 MVC(Model-View-Controller)架构中的 View 管理层。
List<PageInfo> m_Ctrls
—— 当前正在显示的页面列表
类型:
List<PageInfo>
用途:保存当前正在显示(激活)的页面。
特性:打开时添加,关闭时移除。
List<PageInfo> m_Caches
—— 已经关闭但缓存的页面
类型:
List<PageInfo>
用途:用作“对象池”,防止 UI 页面重复加载。
特性:关闭后放入缓存,下次打开优先从缓存中取。
UIMgr(UI管理器)
│
├── PageInfo // 每个页面的信息,包含 Ctrl 和 View
│ ├── m_PageType : EPageType // 页面枚举
│ ├── m_Ctrl : BaseCtrl // 控制器逻辑
│ ├── m_View : BaseView // UI 视图
│ ├── m_IsOpen : bool // 当前是否打开
│
├── BaseCtrl(控制器)
│ ├── Init() / Show() / UnShow() // 控制逻辑
│ ├── m_PageInfo : PageInfo
│
├── BaseView(UI显示层)
│ ├── Init() / Show() / UnShow() // 视觉表现
│ ├── m_PageInfo : PageInfo
│ ├── m_Panel : UIPanel // FairyGUI UI根
核心算法流程:
一.打开页面(OpenPage(EPageType type)
输入:EPageType(页面类型枚举)
算法流程:
1. 检查是否已经在打开中(m_Ctrls 中查找)
2. 如果缓存中有(m_Caches 中查找),直接复用(缓存指的是“已经关闭但没有销毁的页面实例”,用于复用,避免重复创建,提高性能。)
3. 如果没有,就创建新页面:
- 加载资源(从路径映射表中查路径)
- 挂载 PageInfo、BaseCtrl、BaseView、UIPanel
- 初始化 Ctrl 与 View
4. 添加到当前页面列表 m_Ctrls
5. 显示 Ctrl 和 View
二.关闭页面 closePage()
输入:可以是 EPageType / PageInfo / BaseCtrl / BaseView
算法流程:
1. 设置页面状态为关闭
2. 调用 Ctrl 和 View 的隐藏方法(带或不带动画)
3. 从 m_Ctrls 中移除
4. 添加到 m_Caches(复用池)
5. 广播事件通知 UI 被关闭三.重新刷新页面 RefreshCurPage()
1. 取出栈顶页面(最后一个 m_Ctrls)
2. 关闭它
3. 再打开它(重新加载或从缓存中恢复)四.同时关闭并打开新页面(CloseAndOpenPage)
1. 先关闭传入的旧页面(PageInfo 或 BaseCtrl)
2. 再打开新的页面
五.查找指定页面信息
PageInfo GetPageInfo(EPageType type)
遍历 m_Ctrls,找到匹配页面类型的 PageInfo
eg:打开背包页面时,
OpenPage(EPageType.BagPage)
会:
检查当前 UI 中是否已经有 BagPage
没有则加载资源,例如
"UI/BagPanel"
实例化组件,挂载脚本
添加到
m_Ctrls
列表展示 UI 视图
关闭时会反过来处理。
C#基础语法
语法点 示例 建议学习关键词 类继承 : Singleton<UIMgr>
泛型继承、泛型类 构造函数保护 protected override void OnInit()
虚方法、重写 泛型方法 GetPageCtrl<T>() where T : BaseCtrl
泛型约束 集合操作 List<PageInfo>.Add/Remove
集合遍历、倒序遍历 条件判断 if / else / return
分支语句 循环 for / foreach
倒序删除元素的写法 类型转换 as PlayerEvt
,as T
类型安全转换 组件操作 AddComponent<>(), GetComponent<>()
Unity / FairyGUI 组件获取 字典操作 TryGetValue()
Dictionary 用法 日志打印 MyLogger.Info()
打印调试工具类 枚举 EPageType
枚举类型使用
基础语法回顾
什么是泛型?
泛型(Generic)是为类或方法指定“类型参数”的一种机制。可以让你的代码更通用、可复用,而且类型安全。
泛型类型示例:public class Box<T> {public T Content;public void Print(){Console.WriteLine(Content);} }
使用方法:
Box<string> box1 = new Box<string>(); box1.Content = "Hello";Box<int> box2 = new Box<int>(); box2.Content = 123;
泛型方法示例:
public class Printer {public void Print<T>(T value){Console.WriteLine(value);} }
调用方式:
Printer p = new Printer(); p.Print("文字"); p.Print(100);
泛型约束:
public T GetPageCtrl<T>(EPageType ePageType) where T : BaseCtrl
这表示
T
必须继承自 BaseCtrl,否则编译报错。这叫泛型约束。
List倒序删除元素
为什么要倒序删除?
因为如果你正序删除,索引会移动,容易漏删或报错。如下写法:
for (int i = list.Count - 1; i >= 0; i--) {if (list[i] == 条件){list.RemoveAt(i);} }
C# override 和 base 的区别
override是重写父类方法public class Base {public virtual void Speak(){Console.WriteLine("Base speaking");} }public class Child : Base {public override void Speak(){Console.WriteLine("Child speaking");} }
base是调用父类方法public override void Speak() {base.Speak(); // 先调用父类逻辑Console.WriteLine("Child speaking"); }
你可以理解为:
override
: 我要覆盖父类行为
base
: 我要在子类中继续使用父类行为
什么是MVC架构
MVC是什么?
MVC 全称:Model - View - Controller
是一种软件架构模式,用于分离数据、界面与逻辑,提高可维护性与模块化。如我的UIMgr里面的MVC架构:
MVC示意图:
组件 作用 示例类 Model 数据 DataMgr
,ItemData
等View 界面显示 BaseView
,UIPanel
Controller 控制逻辑 BaseCtrl
Controller 连接用户输入与业务逻辑
View 专注展示 UI,不处理逻辑
Model 是纯数据(如道具表、角色数据)
UIPanel是什么?
在 FairyGUI 中,UIPanel
是 挂在 GameObject 上的 UI 容器组件,用于将 FGUI 的 UI 显示在 Unity 场景中。用法说明:
UIPanel panel = go.GetComponent<UIPanel>(); panel.ui = UIPackage.CreateObject("包名", "组件名") as GComponent;
UIPanel.ui
:是一个 FGUI 的GComponent
,就是 UI 根节点每个页面视图(BaseView)都挂在这个 panel 上操作
GetComponent<T>()如何用?
Unity 中常用来获取挂载在 GameObject 上的组件脚本
示例:// 获取自身挂载的 BoxCollider BoxCollider box = gameObject.GetComponent<BoxCollider>();// 获取子物体上挂载的 BaseView BaseView view = transform.Find("Child").GetComponent<BaseView>();
常见错误:// 错误:可能 GameObject 上没有这个组件 var view = go.GetComponent<BaseView>(); if (view == null) Debug.Log("没有找到组件!");
如何异步加载UI?
Unity 中 UI 面板如果太大,推荐异步加载资源文件,以防卡顿。
简单异步加载方式(基于 Addressables 或 Resources)IEnumerator LoadUI(string path, Action<GameObject> callback) {ResourceRequest request = Resources.LoadAsync<GameObject>(path);yield return request;GameObject obj = GameObject.Instantiate(request.asset as GameObject);callback?.Invoke(obj); }
调用方式:
StartCoroutine(LoadUI("UIPrefab/Bag", (ui) => {// 初始化逻辑 }));
总结
关键词 用途 重点 泛型 类与方法复用,带类型限制 T
,where T : BaseCtrl
List 倒序删除 避免删除错位 for (i = Count - 1; i--)
override/base 子类覆盖/调用父类方法 多态 MVC 分离逻辑/界面/数据 架构思想 UIPanel FairyGUI 与 Unity 桥梁 ui = GComponent
GetComponent 获取组件 类型匹配注意空检查 异步加载 防止 UI 卡顿 Resources.LoadAsync
,Addressables
缓存
缓存指的是“已经关闭但没有销毁的页面实例”,用于复用,避免重复创建,提高性能。List<PageInfo> m_Caches;
保留在内存中,放入缓存列表中,等下次打开时直接复用。
原因 说明 🚀 提高性能 打开/关闭页面频繁,如果每次都加载资源、实例化,会卡顿、浪费性能 🧠 减少 GC 避免频繁创建销毁对象带来的垃圾回收 ⏱️ 加快响应速度 从缓存中复用 UI 页面几乎是瞬间完成的 缓存逻辑是如何运作的?
1.打开页面时OpenPage(EPageType type)
:// 先检查缓存列表 PageInfo pageInfo = GetCachePage(type); if (pageInfo != null) {// 从缓存恢复m_Caches.Remove(pageInfo);m_Ctrls.Add(pageInfo);pageInfo.m_Ctrl.Show();pageInfo.m_View.Show(); }
2.关闭页面时 Close Page(Page InfopageInfo):
pageInfo.m_IsOpen = false;// 隐藏,但不销毁 pageInfo.m_Ctrl.UnShow(); pageInfo.m_View.UnShow();// 从显示列表中移除 m_Ctrls.Remove(pageInfo);// 加入缓存列表 m_Caches.Add(pageInfo);
示意图:页面状态转换
类似现实中:想象你是个餐馆老板:
顾客来了:你先看看有没有空桌子(缓存)
没有就新摆一张桌子(创建新的页面)
顾客走了,你不收桌子,只是让它暂时闲置,等下个顾客再来用(放入缓存)
缓存的注意点:
UI 页面状态要正确重置(避免残留上次数据)
如果页面太多,可能要设计LRU缓存策略(如保留最近 3 个页面,其余销毁)
缓存列表不能太大,否则内存占用也会增加
缓存是“已关闭但未销毁的页面”,用于下次快速复用,提高性能、减少加载。