unity游戏框架学习-UI模块
1.界⾯的加载、卸载
2.打开、关闭、隐藏、显⽰界⾯,这边隐藏是指界⾯被遮挡的意思,⼀般来说,界⾯被遮住时,应该关闭界⾯的更新
3.界⾯栈的管理,主要是⽤于场景切换时需要回到上⼀个场景打开的界⾯栈
4.需要的功能:图⽚镜像(节省资源)、滑动列表(复⽤)、模糊背景等
注意点:
1.界⾯的⽣成:class的⽣成、预制体的实例化,类和实例的关联。
业务打开⼀个界⾯需要传⼊界⾯的标识(枚举、或者字符串),如何通过这个标识找到预制体并实例化go、如何⽣成指定的界⾯view,如何绑定view和go
如何销毁⼀个界⾯,清除ab缓存、清除引⽤关系、destory go
2.界⾯的层级关系:每次打开⼀个新的UI,都将它堆⼊栈,关闭时出栈。这个栈是⼀个特殊的栈,例如
它可以实现,某个不在栈顶的UI,可以“TOP”到栈顶。
打开⼀个界⾯:1.从已打开界⾯搜索,避免重复打开界⾯。2.从缓存界⾯搜索,避免重复加载。3.隐藏栈顶界⾯。4.打开新界⾯
关闭⼀个界⾯:1.关闭界⾯并加⼊缓存。2.从已打开界⾯栈顶取出⼀个界⾯,显⽰该界⾯。3.重复步骤2直到打开⼀个全屏界⾯。
退出场景:1.所有界⾯⼊栈,当再次回到场景时可以恢复界⾯栈。2.关闭界⾯
进⼊场景:1.如果需要恢复界⾯栈,从界⾯栈取出界⾯并打开显⽰该界⾯。2.重复1步骤直到打开⼀个全屏界⾯。3.不需要恢复:打开当前场景的界⾯。
3.界⾯间的通信:最好不要有界⾯之间的通信,界⾯的更新通过数据(逻辑)类发送消息通知给界⾯。例如使⽤道具后界⾯的更新,数据类接收到服务器道具使⽤成功后,发送道具更新消息BAG_DATA_UPDATE,需要更新的界⾯监听BAG_DATA_UPDATE消息并刷新界⾯。
4.界⾯打开动画控制。统⼀使⽤Animator(Animation),且每个界⾯最多只有⼀个Animator(Animation),界⾯获取焦点时调⽤Animator的Play⽅法播放动画。
5.界⾯特效控制:游戏会有很多进⼊界⾯播放⼀次的特效,如果你的界⾯关闭不是使⽤SetActive处理的(例如设置layer、移到屏幕外等),那么在你的界⾯再次打开时特效不会再次被播放。
6.界⾯内使⽤的对象怎么获取?(例如要修改界⾯的某个text)
每个需要在脚本内加载的对象都挂载⼀个UIExportItem对象,在界⾯初始化时统⼀收集这些对象,并存储在⼀个map⾥给界⾯使⽤
7.界⾯的模糊效果怎么做?可以参考:,原理就是使⽤⼀个shader均值周围的像素。
8.弹窗适配,例如实现以下效果:弹窗显⽰与item的某条边对齐
如上所⽰,w为target的包围盒宽度,例如绿⾊框的长度,h为包围盒的⾼度,例如绿⾊框的⾼度。包围盒⼤⼩可以使⽤unity的
RectTransformUtility.CalculateRelativeRectTransformBounds接⼝获取。
那么target的坐标怎么结算呢?
target.x = anchor.x + anchor.w/2 + offect.x + target.w/2
target.y - target.h/2 - offect.y = anchor.y - anchor.h/2 即 target.y = anchor.y - anchor.h/2 + target.h/2 + offect.y
当然对齐⽅式不⼀样,计算⽅法也不⼀样,例如左上、左下等,⽽且还需要考虑界⾯布局是否会超框,超框如何处理等。
具体可以参考:
UI模块分以下⼏部分:
1.界⾯类:负责界⾯的逻辑,提供⽣命周期⽅法供业务使⽤,如OnOpen、OnClo等。负责界⾯的⽣成、销毁(以及界⾯ab包的加载、卸载)。⼦窗⼝、⼦item的管理(⽣成、缓存、销毁)
2.管理类:提供界⾯的⽣命周期管理,如打开、关闭、显⽰(获得焦点,显⽰在最上层)、隐藏(失去焦点,可以理解成被其他界⾯挡住了)⼀个界⾯、打开、关闭⼀堆界⾯(场景切⼊、切出时)。缓存界⾯。维护窗体中间的层级关系。
3.配置类:负责配置界⾯的预制体路径、类、界⾯类型等参数。
4.功能类:滑动列表、图⽚镜像、模糊、弹窗适配、通⽤的标题、通⽤的tab等。
5.item类,例如界⾯的滑动列表的⼦项。
导出的层级
详细代码如下:
⼀、ViewDefine:定义类的配置:这边主要是⼀些界⾯定义以及界⾯的配置,通过配置类名可以⽤反射实例化界⾯类,通过配置的路径可以加载并实例化预制体。using System.Collections.Generic;
using UnityEngine;
public class ViewDefine
{
public enum ViewType
{
MAIN = 1, // 主窗⼝(全屏)
POPUP = 2, // 弹窗
FIXED = 3, // 固化窗⼝
SCENE = 4, // 场景UI窗⼝
GUIDE = 5, // 引导UI窗⼝
}
public enum ViewPopModal
{
Blur = 1, // 带有模糊效果的模态弹窗
Lucency_ImPenetrable = 2, // ⽆模糊,不可穿透
Lucency_Penetrate = 3, // ⽆模糊, 可穿透
}
public enum ViewLoadStateDefine
{
NONE = 0,
LOADING = 1,
LOADED = 2,
}
public enum ViewOnLoadDefine
{
Cache = 1,
Destroy = 2,
}
public enum ViewAlignmentType
{
UpperLeft = 0,
UpperCenter = 1,
UpperRight = 2,
MiddleLeft = 3,
MiddleCenter = 4,
MiddleRight = 5,
LowerLeft = 6,
LowerCenter = 7,
LowerRight = 8,
}
public enum ViewID
{
TOAST, // 吐司界⾯
TOAST_BATTLE, // 战⽃中吐司界⾯
NETWAIT, // ⽹络等待界⾯
NETWORK_TIPS, // ⽹络异常提⽰
}
private static Dictionary<int, string> _viewConfig = new Dictionary<int, string>
{
{ (int)ViewID.TOAST, "ToastView,Prefab/Common/ToastPanel" },
{ (int)ViewID.TOAST_BATTLE, "ToastBattleView,Prefab/Common/ToastBattlePanel" },
{ (int)ViewIDWAIT, "NetwaitView,Prefab/Common/NetwaitPanel" }
};
public static string GetViewType(ViewID viewID)
{
string config = _viewConfig[(int)viewID];
if (string.IsNullOrEmpty(config))
{
Debug.LogErrorFormat("未配置界⾯路径: {0}", viewID);
return null;
}
string[] split = config.Split(',');
return split[0];
}
public static string GetViewPath(ViewID viewID)
{
string config = _viewConfig[(int)viewID];
if (string.IsNullOrEmpty(config))
{
Debug.LogErrorFormat("未配置界⾯路径: {0}", viewID);
return null;
}
string[] split = config.Split(',');
return split[1];
}
}
⼆、UIBa ,ui元素基类,主要提供go的销毁以及导⼊界⾯需要引⽤的对象并保存在_viewObj⾥,业务可以通过_viewObj["对象名"]访问对象⽽不⽤去定义参数。using System.Collections.Generic;
using UnityEngine;
///<summary>
/// item、view的基类,提供预制体的⽣成、卸载(ab包的维护),提供⼦节点的⽣成
///</summary>
public class UIBa
{
protected GameObject gameObject;
protected Transform transform;
public string Name { get; t; }
protected Dictionary<string, Object> _viewObj = new Dictionary<string, Object>();//导出的界⾯对象
public virtual void Ctor(GameObject obj, Transform parent)
{
if (obj != null)
{
gameObject = obj;
transform = ansform;
ExportHierarchy();
if (parent != null) {
transform.SetParent(parent);
transform.localPosition = ;
transform.localScale = ;
}
}
}
protected virtual void OnLoad() { }
public void SetActive(bool isShow)
{
gameObject.SetActive(isShow);
}
public virtual void Dispo()
{
if (gameObject != null) {
GameObject.Destroy(gameObject);
transform = null;
gameObject = null;
}
}
protected void ExportHierarchy()
{
if (gameObject)
{
UIHierarchy hierarchy = gameObject.GetComponent<UIHierarchy>();
if (hierarchy)
{
foreach (var item in hierarchy.widgets)
{
_viewObj.Add(item.name, item.item);
}
foreach (var item als)
{
_viewObj.Add(item.name, item.item);
}
}
}
}
}
三、UIItemBa ,item类,主要是提供OnItemOpen、OnItemClo⽅法,⽅便item在界⾯打开和关闭时监听(移除)事件///<summary>
///界⾯item
///</summary>
public class UIItemBa:UIBa
{
protected ViewBa parentView;
protected bool isItemOpen = fal;
public virtual void OnItemOpen()
{
isItemOpen = true;
}
public virtual void OnItemClo()
{
isItemOpen = fal;
}
public bool IsItemOpen()
{
return isItemOpen;
}
public void SetParentView(ViewBa parent)
{
parentView = parent;
}
}
四、PanelBa ,⼦界⾯、界⾯的基类。主要是提供⼦item的⽣成、维护。
using System.Collections.Generic;
using UnityEngine;
///<summary>
///所有界⾯的基类,包括⼦窗⼝、所有的界⾯
///这个类主要功能是:提供给⼦界⾯⽣个⽣成、维护item的接⼝
/
//</summary>
public class PanelBa:UIBa
{
protected bool isOpen = fal;
protected Dictionary<string, Queue<UIItemBa>> childItemPool = new Dictionary<string, Queue<UIItemBa>>(); //缓存item的池⼦protected List<UIBa> subItems = new List<UIBa>();//维护⼦对象,包括⼦窗⼝、⼦item
///<summary>
/// UIModule调⽤,⽤于打开⼀个界⾯。如果该界⾯还未加载,会调⽤Load加载界⾯
///</summary>
///<param name="args"></param>界⾯参数
public virtual void Open(params object[] args)
{
}
public virtual void Clo()
{
}
public bool IsOpen()
{
return isOpen;
}
///<summary>
///⽣成⼀个⼦item
/
//</summary>
///<param name="className"></param>⼦item的类名
///<param name="prefabs"></param>⼦item的预制体
///<param name="parent"></param>⼦item的⽗节点
///<returns></returns>
protected UIItemBa GenerateItem(string className, GameObject prefabs, Transform parent)
{
Queue<UIItemBa> pool = childItemPool[className];
if (pool != null && pool.Count > 0)
{
return pool.Dequeue();
}
UIItemBa item = InstantiateItem(className, prefabs, parent);
AddSubItem(item);
return item;
}
protected void GenerateItemList(string className, GameObject prefabs, Transform parent, int count, ref List<UIItemBa> container) {
while (container.Count < count)
{
UIItemBa item = GenerateItem(className, prefabs, transform);
container.Add(item);
}
while (container.Count > count)
{
UIItemBa item = container[container.Count];
container.Remove(item);
RecyleItem(item);
}
}
protected UIItemBa InstantiateItem(string className, GameObject prefabs, Transform parent)
{
GameObject obj = GameObject.Instantiate(prefabs, parent);
UIItemBa item = (UIItemBa)UIModule.Instance.CreateUIClass(className);
item.Ctor(obj, parent);
item.Name = className;
return item;
}
///<summary>
///回收⼦item
///</summary>
///<param name="item"></param>
protected void RecyleItem(UIItemBa item)
{
RemoveSubItem(item);
Queue<UIItemBa> cachePool = childItemPool[item.Name];
if (cachePool == null)
{
cachePool = new Queue<UIItemBa>();
}
cachePool.Enqueue(item);
item.SetActive(fal);
if (item.IsItemOpen())
{
item.OnItemClo();
}
}
protected void RecyleItemList(int count, ref List<UIItemBa> container)
{
while (container.Count > count)
{
UIItemBa item = container[container.Count];
RecyleItem(item);
container.Remove(item);
}
}
protected void ClearCache()
{
foreach (var pool in childItemPool.Values)
{
foreach (var item in pool)
{
item.Dispo();
}
pool.Clear();
}
childItemPool.Clear();
}
/
//<summary>
///添加⼦对象,包括item、childview
///</summary>
///<param name="item"></param>
protected void AddSubItem(UIBa item)
{
if (subItems.Contains(item))
{
Debug.Log("item已存在");
return;
}
subItems.Add(item);
}
protected void RemoveSubItem(UIBa item)
{
if (subItems.Contains(item))
{
subItems.Remove(item);
}
}
protected void RemoveAllSubItem()
{
foreach (var item in subItems)
{
item.Dispo();
}
subItems.Clear();
}
protected void OnOpenSubItem()
{
foreach (var item in subItems)
{
if (item is UIItemBa)
{
(item as UIItemBa).OnItemOpen();
}