最终效果
文章目录
前言
unity在4.6版本之后,引入了自己的界面显示系统,全称unity graphic user interface,即我们所熟知的ugui。
毕竟是unity的亲儿子,这个系统一经推出,就与其灵活快速可视化,迅速抢占用户市场,逐渐成为unity ui的主流系统,但是它也并不是完美的,对于开发人员来说,使用这套系统往往需要面对如下困境,比如缺乏跨场景的u管理器,界面的上下层关系紊乱三,界面之间的通信手段贫乏等等,上述几个问题大大影响到我们的开发效率。
针对上述问题,我们可以选择制作一套UI管理器解决。
UI管理器
1、新增UI面板层枚举
csharp
/// <summary>
/// UI面板层枚举
/// </summary>
public enum E_UIPanelLayer
{
None,
Rearmost,//最后方
Rear,//后方
Middle,//中间
Front,//前方
Forefront//最前方
}
2、初始化
2.1、用代码创建画布
csharp
/// <summary>
/// 创建画布
/// </summary>
void CreateCanvas()
{
//改Layer
gameObject.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 竖版屏幕设置为0
canvasScaler.matchWidthOrHeight = Screen.width > Screen.height ? 1 : 0;
//添加Graphic Raycaster组件
gameObject.AddComponent<GraphicRaycaster>();
}
效果
2.2、用代码创建UI面板的父物体层
csharp
// 用于记录每个层的父物体
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物体
csharp
/// <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显示面板方法
csharp
//存储加载过的界面的集合
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隐藏面板的方法
csharp
/// <summary>
/// 隐藏面板
/// </summary>
/// <param name="uiName">面板名</param>
public void HideUI(string uiName)
{
UIBase ui = Find(uiName);
if (ui != null)
{
ui.Hide();
}
}
调用
5、CloseUI关闭界面的方法
csharp
/// <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界面基类
csharp
/// <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基类
csharp
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欢迎面板
csharp
public class UITest : MonoBehaviour {
private void OnGUI()
{
// 创建一个新的 GUIStyle
GUIStyle 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事件监听代码
csharp
/// <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
csharp
//注册事件
public UIEventTrigger Register(string name)
{
Transform tf = transform.Find(name);
return UIEventTrigger.Get(tf.gameObject);
}
测试调用
csharp
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
csharp
/// <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;
}
测试调用
csharp
UIManager.Instance.ShowTips("成功!", Color.green);
效果
使用DOTween实现动画效果
提示框动画
实现面板先经过0.4sY轴缩放从0变为1,再暂停showTime秒后,经过0.4sY轴缩放从1变回0,动画播放完成调用callback事件
csharp
/// <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代码即可
csharp
ui.transform.DOScale(Vector3.one, 0.5f).From(Vector3.zero);
效果
关闭UI面板动画
csharp
//动画
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
csharp
/// <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
csharp
/// <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
csharp
/// <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();
//创建EventSystem
CreateEventSystem();
}
/// <summary>
/// 创建画布
/// </summary>
void CreateCanvas()
{
//改Layer
gameObject.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 竖版屏幕设置为0
canvasScaler.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();
}
//透明度设置为1
CanvasGroup 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); // 隐藏 UI
callback?.Invoke(); // 调用回调
});
}
}
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~