Unity:简易UI框架

我的心在滴血呢,本来这篇博客都快写完了的,奈何直接浏览器卡死,忘记保存了,之前写好的都没了,太心痛了

最近在抓紧时间学习一下,提升自己的能力,毕竟实力实在太弱了,基础又不好,又没有工作经验,所以开始学着写博客,记录一下自己的学习过程吧,也方便以后查看,如果不记录一下的话,学过的都会忘掉,相当于没有学,这是以前血的教训啊,希望以后翻到现在写的东西,能够有这样的反应:“哇,这写的都是什么呀?太辣眼睛了,我那时候写的代码简直无法直视呀!”,这样的话就证明我一直在进步哈哈哈哈...

这篇博客实现的是一个简易的UI框架,并且加入了事件消息机制,方便数据的传递,虽然有很多很多很多的不合理的地方,但是也记录一下吧,同时也希望有大佬能够指点一番哈

实现功能:

  1. UI面板的打开,暂停,恢复,关闭
  2. 加入了事件消息机制,可以进行各个面板之间的通信,传递数据

实现思路:

  • UI管理使用了栈结构,当UI面板打开时,先取栈顶面板暂停,在将要打开的面板打开并入栈,关闭面板的时,先将栈顶面板关闭并出栈,再重新取栈顶面板恢复(参考:Siki学院——UI框架 - 基于Unity5.3UGUI
  • 事件消息机制使用的是我在前面的博客中记录的那个简易的事件消息系统,虽然在这里使用的并不是很合理,但是先这样凑合一下吧
  • UI面板的预制体路径信息使用的Json文本记录 

 单例模板:

因为UIManager和EventCenter都是单例,所以使用了一个单例模板,毕竟将类设置为单例类还是要写那么些代码的,使用单例模板可以省去这些重复的代码,如果需要将类设置为单例,直接继承这个单例模板类就可以了

using System;
using System.Reflection;

public abstract class Singleton<T> where T : Singleton<T>
{ 
    private static T m_Instance;
    public static T Instance
    {
        get
        {
            if(m_Instance == null)
            {
                var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
                var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);
                if (ctor == null)
                    throw new Exception("Non-public ctor() not found!");
                m_Instance = ctor.Invoke(null) as T;
            }
            return m_Instance;
        }
    }

    protected Singleton() { }
}

UI管理器: 

UIManager是这个UI框架的核心类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// UI管理器
/// </summary>
public class UIManager : Singleton<UIManager>
{
    // UI根节点
    private Transform m_UIRoot;
    // 所有UIPaenl预设路径
    private Dictionary<string, string> m_PanelPathDict;
    // UIPanel缓存字典
    private Dictionary<string, BaseUIPanel> m_PanelCacheDict;
    // UI栈
    private Stack<BaseUIPanel> m_PanelStack;

    // 打开面板
    public void Open(string panelName)
    {
        if (m_PanelStack.Count > 0)
        {
            BaseUIPanel topPanel = m_PanelStack.Peek();
            topPanel.OnPause();
        }

        BaseUIPanel panel = null;
        if(!m_PanelCacheDict.TryGetValue(panelName,out panel))
        {
            panel = LoadUIPanel(panelName);
        }
        m_PanelStack.Push(panel);
        panel.OnEnter();
    }

    // 关闭面板
    public void Close()
    {
        if (m_PanelStack.Count <= 0)
            return;
        BaseUIPanel topPanel = m_PanelStack.Pop();
        topPanel.OnExit();
        if (m_PanelStack.Count > 0)
        {
            BaseUIPanel panel = m_PanelStack.Peek();
            panel.OnResume();
        }
    }

    // 关闭所有面板
    public void CloseAll()
    {
        while (m_PanelStack.Count > 0)
        {
            Close();
        }
    }

    private UIManager()
    {
        Init();
    }

    // 初始化
    private void Init()
    {
        LoadPanelPath();

        m_PanelCacheDict = new Dictionary<string, BaseUIPanel>();
        m_PanelStack = new Stack<BaseUIPanel>();

        GameObject uiRootPrefab = Resources.Load<GameObject>(PathConst.UI_ROOT_PATH);
        if (uiRootPrefab == null)
        {
            throw new System.Exception(string.Format("UIRootPrefab is null! UIRootPrefab Path:{0}",PathConst.UI_ROOT_PATH));
        }
        GameObject uiRootGo = GameObject.Instantiate(uiRootPrefab);
        uiRootGo.name = "UIRoot";
        m_UIRoot = uiRootGo.transform;
    }

    // 加载UIPanel路径
    private void LoadPanelPath()
    {
        IConfigResolver<Dictionary<string, string>> resolver = new UIPanelPathConfigResolverByJson(PathConst.UI_PANEL_JSON_CONFIG_PATH);
        m_PanelPathDict = resolver.Resolve();
    }
    
    // 加载UIPanel
    private BaseUIPanel LoadUIPanel(string panelName)
    {
        string path;
        if(m_PanelPathDict.TryGetValue(panelName,out path))
        {
            GameObject panelPrefab = Resources.Load<GameObject>(path);
            if(panelPrefab == null)
            {
                throw new System.Exception(string.Format("UIPanelPrefab is null! UIPanelPath:{0}", path));
            }
            GameObject panelGo = GameObject.Instantiate(panelPrefab, m_UIRoot);
            panelGo.name = panelName;
            panelGo.transform.localScale = Vector3.one;
            panelGo.transform.localPosition = Vector3.zero;
            BaseUIPanel uiPanel = panelGo.GetComponent<BaseUIPanel>();
            m_PanelCacheDict.Add(panelName, uiPanel);
            return uiPanel;
        }
        else
        {
            throw new System.Exception(string.Format("Not found PanelPath! PanelName:{0}", panelName));
        }
    }
}

配置文件解析器接口: 

为了方便解析配置文件,和扩展配置文件解析方式,所以定义了一个接口

/// <summary>
/// 配置文件解析器接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IConfigResolver<T>
{
    T Resolve();
}

Json配置文件抽象类:

public abstract class JsonConfigResolver<T> : IConfigResolver<T>
{
    protected string m_ConfigPath;

    public JsonConfigResolver(string configPath)
    {
        m_ConfigPath = configPath;
    }

    public abstract T Resolve();
}

UI面板路径Json配置文件解析类:继承自JsonConfigResolver ,这里解析Json使用的是LitJson,请在Plugins文件夹下加入LitJson.dll,没有这个动态链接库可以到网上下载

就是在这里,总感觉鸡肋了,气哭...

using System.Collections.Generic;
using LitJson;
using UnityEngine;

public class UIPanelPathConfigResolverByJson : JsonConfigResolver<Dictionary<string, string>>
{
    public UIPanelPathConfigResolverByJson(string configPath) : base(configPath) { }

    public override Dictionary<string, string> Resolve()
    {
        Dictionary<string, string> dataDict = new Dictionary<string, string>();
        TextAsset textAsset = Resources.Load<TextAsset>(m_ConfigPath);
        JsonData datas = JsonMapper.ToObject(textAsset.text);
        foreach (JsonData data in datas)
        {
            string panelName = data["panelName"].ToString();
            string panelPath = data["panelPath"].ToString();
            dataDict.Add(panelName, panelPath);
        }
        return dataDict;
    }
}

Json配置文件: 

我将UI面板的Prefab放在Resources/UIPanels文件夹下,Json配置文件放在Resources/Configs文件夹下

注意:配置文件中的路径一定要正确,否则会加载不出面板,并且会报错,面板名字最好和Prefab的名字保持一直,防止混淆

[
  {
    "panelName": "LoginPanel",
    "panelPath": "UIPanels/LoginPanel"
  },
  {
    "panelName": "SelectServerPanel",
    "panelPath": "UIPanels/SelectServerPanel"
  },
  {
    "panelName": "MainPanel",
    "panelPath": "UIPanels/MainPanel"
  },
  {
    "panelName": "ShopPanel",
    "panelPath": "UIPanels/ShopPanel"
  },
  {
    "panelName": "InfoPanel",
    "panelPath": "UIPanels/InfoPanel"
  }
]

UI面板基类:所有的UI面板都继承这个类

在这个类中,我将AddListener,RemoveListener,Broadcast写在了这里,方便子类中添加事件监听,移除事件监听和广播消息,并且定义了一个字典,存储该面板中所有的监听,这样可以只需要手动添加事件监听,移除事件监听放在OnDestroy中进行,在面板销毁的时候自动移除监听,如果需要在其他时候移除监听,还是可以使用RemoveListenter移除监听

注意:

  1. 如果要添加事件监听,可以在子类中重写InitListener方,在方法中使用AddListener添加事件监听,方便管理
  2. 事件类型是字符串,可以将用到的事件类型统一定义到一个类中,使用常量字符串,方便管理,避免出错
  3. 事件监听的回调方法不要使用Lambda表达式,使用Lambda表达式无法移除监听,除非能够保证监听不需要被移除
  4. 事件监听回调必须有一个object类型的参数,用来接收传递的数据,使用的时候要确定传递过来的数据是什么类型的,将参数强转为原来的类型使用,如果强制的类型不正确会出错
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// UI面板基类
/// </summary>
[RequireComponent(typeof(CanvasGroup))]
public abstract class BaseUIPanel : MonoBehaviour
{
    protected CanvasGroup m_CanvasGroup;
    // 事件列表
    protected Dictionary<string,System.Action<object>> m_EventDict;
    
    protected virtual void Awake()
    {
        m_CanvasGroup = GetComponent<CanvasGroup>();
        InitListener();
    }

    // 进入
    public virtual void OnEnter()
    {
        m_CanvasGroup.alpha = 1;
        m_CanvasGroup.blocksRaycasts= true;
    }
    // 冻结
    public virtual void OnPause()
    {
        m_CanvasGroup.blocksRaycasts= false;
    }
    // 恢复
    public virtual void OnResume()
    {
        m_CanvasGroup.blocksRaycasts= true;
    }

    // 退出
    public virtual void OnExit()
    {
        m_CanvasGroup.alpha = 0;
        m_CanvasGroup.blocksRaycasts= false;
    }

    // 初始化事件监听,如果要添加事件监听,重写此方法,在方法里添加事件监听
    protected virtual void InitListener() { }

    // 添加事件监听
    protected void AddListener(string eventType, System.Action<object> callback)
    {
        if (m_EventDict == null)
            m_EventDict = new Dictionary<string, System.Action<object>>();
        EventCenter.Instance.AddListener(eventType, callback);
        if (!m_EventDict.ContainsKey(eventType))
            m_EventDict[eventType] = null;
        m_EventDict[eventType] += callback;
    }

    // 移除事件监听
    protected void RemoveListener(string eventType, System.Action<object> callback)
    {
        EventCenter.Instance.RemoveListener(eventType, callback);
        if (m_EventDict == null)
            return;
        if (m_EventDict[eventType] == null)
            return;
        m_EventDict[eventType] -= callback;
    }

    // 广播消息
    protected void Broadcast(string eventType,object data)
    {
        EventCenter.Instance.Broadcast(eventType, data);
    }

    // 在物体销毁的时候移除事件监听
    protected virtual void OnDestroy()
    {
        if (m_EventDict != null)
        {
            foreach (var item in m_EventDict)
            {
                RemoveListener(item.Key, item.Value);
            }
        }
    }
}

事件中心:

using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// 事件中心
/// </summary>
public class EventCenter : Singleton<EventCenter>
{
    private Dictionary<string, Action<object>> m_EventDict = new Dictionary<string, Action<object>>();

    public void AddListener(string eventType, Action<object> callback)
    {
        if (!m_EventDict.ContainsKey(eventType))
            m_EventDict[eventType] = null;
        m_EventDict[eventType] += callback;
    }

    public void RemoveListener(string eventType,Action<object> callback)
    {
        if (!m_EventDict.ContainsKey(eventType))
            return;
        if (m_EventDict[eventType] == null)
            return;
        m_EventDict[eventType] -= callback;
    }

    public void RemoveAllListener()
    {
        m_EventDict.Clear();
    }

    public void Broadcast(string eventType, object data)
    {
        if (!m_EventDict.ContainsKey(eventType))
            return;

        Action<object> callback = m_EventDict[eventType];

        if (callback != null)
        {
            callback(data);
        }
    }
}

 存储事件类型的静态类:

/// <summary>
/// 事件类型
/// </summary>
public static class EventType
{
    public const string E_SHOW_INFO = "e_show_info";//事件类型定义示例
}
    
/// <summary>
/// 面板名称常量
/// </summary>
public static class PanelNameConst
{
    public const string LOGIN_PANEL= "LoginPanel";
    public const string SELECT_SERVER_PANEL= "SelectServerPanel";
    public const string MAIN_PANEL= "MainPanel";
    public const string SHOP_PANEL = "ShopPanel";
    public const string INFO_PANEL = "InfoPanel";
}

/// <summary>
/// 路径常量
/// </summary>
public static class PathConst
{
    public const string UI_ROOT_PATH = "UIRoot";
    public const string UI_PANEL_JSON_CONFIG_PATH = "Configs/UIPanelConfig";
}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值