最终效果
文章目录
- 最终效果
- 前言
- UI管理器
- 1、新增UI面板层枚举
- 2、初始化
- 2.1、用代码创建画布
- 2.2、用代码创建UI面板的父物体层
- 2.3、代码添加EventSystem物体
- 3、ShowPanel显示面板方法
- 4、HidePanel隐藏面板的方法
- 5、CloseUI关闭界面的方法
- 6、UI界面基类
- 测试调用
- 优化绑定按钮事件
- 新增提示框
- UI绘制
- 实现
- 使用DOTween实现动画效果
- 提示框动画
- 打开UI面板动画
- 关闭UI面板动画
- 完整代码
- 完结
前言
unity在4.6版本之后,引入了自己的界面显示系统,全称unity graphic user interface,即我们所熟知的ugui。
毕竟是unity的亲儿子,这个系统一经推出,就与其灵活快速可视化,迅速抢占用户市场,逐渐成为unity ui的主流系统,但是它也并不是完美的,对于开发人员来说,使用这套系统往往需要面对如下困境,比如缺乏跨场景的u管理器,界面的上下层关系紊乱三,界面之间的通信手段贫乏等等,上述几个问题大大影响到我们的开发效率。
针对上述问题,我们可以选择制作一套UI管理器解决。
UI管理器
1、新增UI面板层枚举
/// <summary>
/// UI面板层枚举
/// </summary>
public enum E_UIPanelLayer
{None,Rearmost,//最后方Rear,//后方Middle,//中间Front,//前方Forefront//最前方
}
2、初始化
2.1、用代码创建画布
/// <summary>
/// 创建画布
/// </summary>
void CreateCanvas()
{//改LayergameObject.layer = LayerMask.NameToLayer("UI");//添加并设置Canvas组件Canvas canvas = gameObject.AddComponent<Canvas>();canvas.renderMode = RenderMode.ScreenSpaceOverlay;canvas.sortingOrder = 30000;//添加并设置CanvasScaler组件CanvasScaler canvasScaler = gameObject.AddComponent<CanvasScaler>();canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;canvasScaler.referenceResolution = new Vector2(Screen.width, Screen.height);//横版屏幕设置为1 竖版屏幕设置为0canvasScaler.matchWidthOrHeight = Screen.width > Screen.height ? 1 : 0;//添加Graphic Raycaster组件gameObject.AddComponent<GraphicRaycaster>();
}
效果
2.2、用代码创建UI面板的父物体层
// 用于记录每个层的父物体
private Dictionary<E_UIPanelLayer, Transform> layerParents;/// <summary>
/// 创建UI面板的父物体层
/// </summary>
void CreateLayer()
{//Rearmost层的父物体Transform rearmost = new GameObject(E_UIPanelLayer.Rearmost.ToString()).transform;rearmost.SetParent(transform, false);//Rear层的父物体Transform rear = new GameObject(E_UIPanelLayer.Rear.ToString()).transform;rear.SetParent(transform, false);//Middle层的父物体Transform middle = new GameObject(E_UIPanelLayer.Middle.ToString()).transform;middle.SetParent(transform, false);//Fornt层的父物体Transform front = new GameObject(E_UIPanelLayer.Front.ToString()).transform;front.SetParent(transform, false);//Frontmost层的父物体Transform foreFront = new GameObject(E_UIPanelLayer.ForeFront.ToString()).transform;foreFront.SetParent(transform, false);//记录每个层的父物体layerParents = new Dictionary<E_UIPanelLayer, Transform>{{ E_UIPanelLayer.Rearmost, rearmost },{ E_UIPanelLayer.Rear, rear },{ E_UIPanelLayer.Middle, middle },{ E_UIPanelLayer.Front, front },{ E_UIPanelLayer.ForeFront, foreFront }};
}
效果
2.3、代码添加EventSystem物体
/// <summary>
/// 创建EventSystem
/// </summary>
void CreateEventSystem()
{//如果场景中已经有一个EventSystem了,则直接返回。if (FindObjectOfType<EventSystem>()) return;GameObject eventSystem = new GameObject("EventSystem");DontDestroyOnLoad(eventSystem);//切换场景不销毁eventSystem.AddComponent<EventSystem>();eventSystem.AddComponent<StandaloneInputModule>();
}
效果
3、ShowPanel显示面板方法
//存储加载过的界面的集合
private List<UIBase> uiList = new List<UIBase>();/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T">UI面板脚本,记得UI面板预制体名要和脚本名一样</typeparam>
/// <param name="layer">父级层</param>
/// <returns>UIBase</returns>
public UIBase ShowUI<T>(E_UIPanelLayer layer = E_UIPanelLayer.Middle) where T : UIBase
{string uiName = typeof(T).Name;//获取名称UIBase ui = Find(uiName);if (ui == null){//记录该面板要放进哪个层中来显示Transform parent = layerParents[layer];//集合中没有 需要从Resources/UI文件夹加载GameObject obj = Instantiate(Resources.Load("UI/" + uiName), parent) as GameObject;//改名字,默认实例化会加上(clone),所以得重命名obj.name = uiName;//添加需要的脚本ui = obj.AddComponent<T>();//添加到集合进行存储uiList.Add(ui);}else{//显示ui.Show();}return ui;
}
调用
4、HidePanel隐藏面板的方法
/// <summary>
/// 隐藏面板
/// </summary>
/// <param name="uiName">面板名</param>
public void HideUI(string uiName)
{UIBase ui = Find(uiName);if (ui != null){ui.Hide();}
}
调用
5、CloseUI关闭界面的方法
/// <summary>
/// 关闭某个界面
/// </summary>
/// <param name="uiName">面板名</param>
public void CloseUI(string uiName)
{UIBase ui = Find(uiName);if (ui != null){uiList.Remove(ui);Destroy(ui.gameObject);}
}
6、UI界面基类
/// <summary>
/// UI界面基类
/// </summary>
public class UIBase : MonoBehaviour
{//显示public virtual void Show(){gameObject.SetActive(true);}//隐藏public virtual void Hide(){gameObject.SetActive(false);}//关闭界面(销毁)public virtual void Close(){UIManager.Instance.CloseUI(gameObject.name);}
}
测试调用
欢迎面板
新增WelcomeUI.cs测试面板代码,注意记得继承UIBase基类
public class WelcomeUI : UIBase {void Awake(){//绑定按钮事件transform.Find("bg/退出按钮").GetComponent<Button>().onClick.AddListener(onCloseBtn);transform.Find("bg/确定").GetComponent<Button>().onClick.AddListener(onConfirmBtn);}void onCloseBtn(){//关闭界面Close();}void onConfirmBtn(){//隐藏Hide();}
}
新增UITest ,绘制按钮显示WelcomeUI欢迎面板
public class UITest : MonoBehaviour {private void OnGUI(){// 创建一个新的 GUIStyleGUIStyle buttonStyle = new GUIStyle(GUI.skin.button);// 设置字体大小buttonStyle.fontSize = 50; // 替换为你想要的字体大小buttonStyle.alignment = TextAnchor.MiddleCenter; // 可选择设置对齐方式if (GUI.Button(new Rect(0, 0, 500, 200), "显示欢迎面板", buttonStyle)){//显示WelcomeUI面板,创建的脚本名字记得跟预制体物体名字一致UIManager.Instance.ShowUI<WelcomeUI>("WelcomeUI");}}
}
效果
优化绑定按钮事件
每次绑定按钮事件都需要写这么多代码很麻烦,我们可以继续进行封装
新增UIEventTrigger事件监听代码
/// <summary>
/// 事件监听
/// </summary>
public class UIEventTrigger : MonoBehaviour, IPointerClickHandler
{//这是一个公共的委托,它接受两个参数,一个是被点击的游戏对象,另一个是关于点击事件的数据。public Action<GameObject, PointerEventData> onClick;//用于获取或添加 UIEventTrigger 组件public static UIEventTrigger Get(GameObject obj){UIEventTrigger trigger = obj.GetComponent<UIEventTrigger>();if (trigger == null){trigger = obj.AddComponent<UIEventTrigger>();}return trigger;}public void OnPointerClick(PointerEventData eventData){//这是 IPointerClickHandler 接口的方法,当 UI 元素被点击时,它将被调用。if (onClick != null) onClick(gameObject, eventData);}
}
修改UI界面基类UIBase
//注册事件
public UIEventTrigger Register(string name)
{Transform tf = transform.Find(name);return UIEventTrigger.Get(tf.gameObject);
}
测试调用
public class WelcomeUI : UIBase {void Awake(){//绑定按钮事件// transform.Find("bg/退出按钮").GetComponent<Button>().onClick.AddListener(onCloseBtn);// transform.Find("bg/确定").GetComponent<Button>().onClick.AddListener(onConfirmBtn);Register("bg/退出按钮").onClick = onCloseBtn;Register("bg/确定").onClick = onConfirmBtn;}void onCloseBtn(GameObject obj, PointerEventData pData){//关闭界面Close();}void onConfirmBtn(GameObject obj, PointerEventData pData){//隐藏Hide();}
}
效果,和前面一样
新增提示框
UI绘制
背景图片
配置
实现
修改UIManager
/// <summary>
/// 提示界面
/// </summary>
/// <param name="msg">文本</param>
/// <param name="color">颜色</param>
/// <param name="callback">完成回调事件</param>
public void ShowTips(string msg, Color color, float showTime = 0.5f, UnityAction callback = null, E_UIPanelLayer layer = E_UIPanelLayer.ForeFront)
{UIBase ui = ShowUI<TipsUI>(layer);TextMeshProUGUI text = ui.transform.Find("bg/text").GetComponent<TextMeshProUGUI>();text.color = color;text.text = msg;
}
测试调用
UIManager.Instance.ShowTips("成功!", Color.green);
效果
使用DOTween实现动画效果
参考:【推荐100个unity插件之2】DoTween动画插件的安装和使用整合(最全)
提示框动画
实现面板先经过0.4sY轴缩放从0变为1,再暂停showTime秒后,经过0.4sY轴缩放从1变回0,动画播放完成调用callback事件
/// <summary>
/// 提示界面
/// </summary>
/// <param name="msg">文本</param>
/// <param name="color">颜色</param>
/// <param name="showTime">显示时间</param>
/// <param name="callback">完成回调事件</param>
public void ShowTips(string msg, Color color, float showTime = 0.5f, UnityAction callback = null, E_UIPanelLayer layer = E_UIPanelLayer.ForeFront)
{DOTween.CompleteAll(true);UIBase ui = ShowUI<TipsUI>("TipsUI", layer);TextMeshProUGUI text = ui.transform.Find("bg/text").GetComponent<TextMeshProUGUI>();text.color = color;text.text = msg;// DOTween动画 方法一// Sequence sequence = DOTween.Sequence();// sequence.Append(ui.transform.DOScaleY(1, 0.4f).From(0)) // 第一个动画,缩放到 1// .Append(DOVirtual.DelayedCall(showTime, () => { })) // 延迟// .Append(ui.transform.DOScaleY(0, 0.4f).From(1)) // 第二个动画,缩放到 0// .OnComplete(() =>// {// ui.gameObject.SetActive(false); // 隐藏 UI// callback?.Invoke(); // 调用回调// });// DOTween动画 方法二ui.transform.DOScaleY(1, 0.4f).From(0).OnComplete(() =>{// 延迟显示时间DOVirtual.DelayedCall(showTime, () =>{ui.transform.DOScaleY(0, 0.4f).From(1).OnComplete(() =>{ui.gameObject.SetActive(false);callback?.Invoke();});});});
}
效果
打开UI面板动画
修改UIManager里的ShowUI方法,新增DOTween代码即可
ui.transform.DOScale(Vector3.one, 0.5f).From(Vector3.zero);
效果
关闭UI面板动画
//动画
CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();
Sequence closeSequence = DOTween.Sequence();
closeSequence.Append(canvasGroup.DOFade(0, 0.8f)) // 淡出
.Join(ui.transform.DOLocalMoveX(2000f, 0.8f)) // 同时移动
.OnComplete(() =>
{ui.gameObject.SetActive(false);
});
效果
完整代码
UIEventTrigger.cs
/// <summary>
/// 事件监听
/// </summary>
public class UIEventTrigger : MonoBehaviour, IPointerClickHandler
{//这是一个公共的委托,它接受两个参数,一个是被点击的游戏对象,另一个是关于点击事件的数据。public Action<GameObject, PointerEventData> onClick;//用于获取或添加 UIEventTrigger 组件public static UIEventTrigger Get(GameObject obj){UIEventTrigger trigger = obj.GetComponent<UIEventTrigger>();if (trigger == null){trigger = obj.AddComponent<UIEventTrigger>();}return trigger;}public void OnPointerClick(PointerEventData eventData){//这是 IPointerClickHandler 接口的方法,当 UI 元素被点击时,它将被调用。if (onClick != null) onClick(gameObject, eventData);}
}
UIBase.cs
/// <summary>
/// UI界面基类
/// </summary>
public class UIBase : MonoBehaviour
{//显示public virtual void Show(){transform.localPosition = Vector3.zero;gameObject.SetActive(true);}//隐藏public virtual void Hide(){UIManager.Instance.HideUI(gameObject.name);}//关闭界面(销毁)public virtual void Close(){UIManager.Instance.CloseUI(gameObject.name);}//注册事件public UIEventTrigger Register(string name){Transform tf = transform.Find(name);return UIEventTrigger.Get(tf.gameObject);}
}
UIManager.cs
/// <summary>
/// UI管理器
/// </summary>
public class UIManager : SingletonMono<UIManager>
{// 用于记录每个层的父物体private Dictionary<E_UIPanelLayer, Transform> layerParents;//存储加载过的界面的集合private List<UIBase> uiList = new List<UIBase>();#region 初始化void Awake(){//创建画布CreateCanvas();//创建UI面板的父物体层CreateLayer();//创建EventSystemCreateEventSystem();}/// <summary>/// 创建画布/// </summary>void CreateCanvas(){//改LayergameObject.layer = LayerMask.NameToLayer("UI");//添加并设置Canvas组件Canvas canvas = gameObject.AddComponent<Canvas>();canvas.renderMode = RenderMode.ScreenSpaceOverlay;canvas.sortingOrder = 30000;//添加并设置CanvasScaler组件CanvasScaler canvasScaler = gameObject.AddComponent<CanvasScaler>();canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;canvasScaler.referenceResolution = new Vector2(Screen.width, Screen.height);//横版屏幕设置为1 竖版屏幕设置为0canvasScaler.matchWidthOrHeight = Screen.width > Screen.height ? 1 : 0;//添加Graphic Raycaster组件gameObject.AddComponent<GraphicRaycaster>();}/// <summary>/// 创建UI面板的父物体层/// </summary>void CreateLayer(){//Rearmost层的父物体Transform rearmost = new GameObject(E_UIPanelLayer.Rearmost.ToString()).transform;rearmost.SetParent(transform, false);//Rear层的父物体Transform rear = new GameObject(E_UIPanelLayer.Rear.ToString()).transform;rear.SetParent(transform, false);//Middle层的父物体Transform middle = new GameObject(E_UIPanelLayer.Middle.ToString()).transform;middle.SetParent(transform, false);//Fornt层的父物体Transform front = new GameObject(E_UIPanelLayer.Front.ToString()).transform;front.SetParent(transform, false);//Frontmost层的父物体Transform foreFront = new GameObject(E_UIPanelLayer.ForeFront.ToString()).transform;foreFront.SetParent(transform, false);//记录每个层的父物体layerParents = new Dictionary<E_UIPanelLayer, Transform>{{ E_UIPanelLayer.Rearmost, rearmost },{ E_UIPanelLayer.Rear, rear },{ E_UIPanelLayer.Middle, middle },{ E_UIPanelLayer.Front, front },{ E_UIPanelLayer.ForeFront, foreFront }};}/// <summary>/// 创建EventSystem/// </summary>void CreateEventSystem(){//如果场景中已经有一个EventSystem了,则直接返回。if (FindObjectOfType<EventSystem>()) return;GameObject eventSystem = new GameObject("EventSystem");DontDestroyOnLoad(eventSystem);//切换场景不销毁eventSystem.AddComponent<EventSystem>();eventSystem.AddComponent<StandaloneInputModule>();}#endregion/// <summary>/// 显示面板/// </summary>/// <typeparam name="T">UI面板脚本,记得UI面板预制体名要和脚本名一样</typeparam>/// <param name="layer">父级层</param>/// <param name="doTween">是否使用doTween动画</param>/// <returns>UIBase</returns>public UIBase ShowUI<T>(E_UIPanelLayer layer = E_UIPanelLayer.Middle, bool doTween = true) where T : UIBase{DOTween.CompleteAll(true);string uiName = typeof(T).Name;//获取名称UIBase ui = Find(uiName);if (ui == null){//记录该面板要放进哪个层中来显示Transform parent = layerParents[layer];//集合中没有 需要从Resources/UI文件夹加载GameObject obj = Instantiate(Resources.Load("UI/" + uiName), parent) as GameObject;//改名字,默认实例化会加上(clone),所以得重命名obj.name = uiName;//添加需要的脚本ui = obj.AddComponent<T>();//添加CanvasGroup组件,用于后面渐变使用obj.AddComponent<CanvasGroup>();//添加到集合进行存储uiList.Add(ui);}else{//显示ui.Show();}//透明度设置为1CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();canvasGroup.alpha = 1f;//动画if (doTween) ui.transform.DOScale(Vector3.one, 0.5f).From(Vector3.zero);return ui;}/// <summary>/// 隐藏面板/// </summary>/// <param name="uiName">面板名</param>public void HideUI(string uiName, bool doTween = true){DOTween.CompleteAll(true);UIBase ui = Find(uiName);if (ui == null) return;if (doTween){//动画CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();Sequence closeSequence = DOTween.Sequence();closeSequence.Append(canvasGroup.DOFade(0, 0.8f)) // 淡出.Join(ui.transform.DOLocalMoveX(2000f, 0.8f)) // 同时移动.OnComplete(() =>{ui.gameObject.SetActive(false);});}else{ui.gameObject.SetActive(false);}}/// <summary>/// 关闭某个界面/// </summary>/// <param name="uiName">面板名</param>public void CloseUI(string uiName, bool doTween = true){DOTween.CompleteAll(true);UIBase ui = Find(uiName);if (ui == null) return;if (doTween){//动画CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();Sequence closeSequence = DOTween.Sequence();closeSequence.Append(canvasGroup.DOFade(0, 0.8f)) // 淡出.Join(ui.transform.DOLocalMoveX(2000f, 0.8f)) // 同时移动.OnComplete(() =>{uiList.Remove(ui);Destroy(ui.gameObject);});}else{uiList.Remove(ui);Destroy(ui.gameObject);}}//关闭所有界面public void CloseAllUI(){for (int i = uiList.Count - 1; i >= 0; i--){Destroy(uiList[i].gameObject);}uiList.Clear();//清空合集}/// <summary>/// 从集合中找到名字对应的界面脚本/// </summary>/// <param name="uiName">面板名</param>/// <returns>UIBase</returns>public UIBase Find(string uiName){for (int i = 0; i < uiList.Count; i++){if (uiList[i].name == uiName) return uiList[i];}return null;}/// <summary>/// 提示界面/// </summary>/// <param name="msg">文本</param>/// <param name="color">颜色</param>/// <param name="showTime">显示时间</param>/// <param name="callback">完成回调事件</param>public void ShowTips(string msg, Color color, float showTime = 0.5f, UnityAction callback = null, E_UIPanelLayer layer = E_UIPanelLayer.ForeFront){DOTween.CompleteAll(true);UIBase ui = ShowUI<TipsUI>(layer, false);TextMeshProUGUI text = ui.transform.Find("bg/text").GetComponent<TextMeshProUGUI>();text.color = color;text.text = msg;//动画Sequence sequence = DOTween.Sequence();sequence.Append(ui.transform.DOScaleY(1, 0.4f).From(0)) // 第一个动画,缩放到 1.Append(DOVirtual.DelayedCall(showTime, () => { })) // 延迟.Append(ui.transform.DOScaleY(0, 0.4f).From(1)) // 第二个动画,缩放到 0.OnComplete(() =>{ui.gameObject.SetActive(false); // 隐藏 UIcallback?.Invoke(); // 调用回调});}
}
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~