Unity程序基础框架

单例模式基类(Base文件夹)

不继承Mono的单例模式基类

using System;
using System.Reflection;
using UnityEngine;
/// <summary>
/// 单例模式基类 避免代码冗余
/// </summary>
/// <typeparam name="T">继承class类 用于创建实例</typeparam>
public abstract class BaseManager<T> where T : class
{
    private static T instance;
    //用于加锁的对象
    protected static readonly object lockObj = new object();
    //属性的方式
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObj)
                {
                    if (instance == null)
                    {
                        //通过反射创建实例 
                        //方法一 通过Activator.CreateInstance直接实例化 参数true:允许反射实例化私有构造函数(推荐使用)
                        instance = (T)Activator.CreateInstance(typeof(T), true);

                        //方法二 利用反射获取私有构造函数 通过私有构造函数实例对象(不推荐使用)
                        //Type type = typeof(T);
                        //ConstructorInfo constructor = type.GetConstructor(
                        //    BindingFlags.Instance | BindingFlags.NonPublic, //表示成员私有方法
                        //    null,                                           //表示没有绑定对象
                        //    Type.EmptyTypes,                                //表示没有参数
                        //    null);                                      //表示没有参数修饰符
                        //if (constructor != null)
                        //{
                        //    instance = (T)constructor.Invoke(null);
                        //}
                        //else
                        //{
                        //    Debug.LogError("没有得到相应的私有构造函数");
                        //}
                    }
                }
            }
            return instance;
        }
    }
}

继承Mono的手动挂载式单例模式基类

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

/// <summary>
/// 挂载式 继承MonoBehaviour基类
/// </summary>
/// <typeparam name="T"></typeparam>
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;
    public static T Instance => instance;
    protected virtual void Awake()
    {
        if (instance != null)
        {
            Destroy(this.gameObject);
            return;
        }
        instance = this as T;
        DontDestroyOnLoad(this.gameObject);
    }
}

继承Mono的自动挂载式单例模式基类

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

/// <summary>
/// 自动挂载 继承MonoBehaviour基类 推荐使用 无需手动挂载脚本 无需考虑切场景带来的问题
/// </summary>
/// <typeparam name="T"></typeparam>
public class SingletonAutoMono<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                //动态创建物体
                GameObject obj = new GameObject(typeof(T).Name);
                //得到T脚本 的类名 为对象改名 这样在编辑器中可以明确的看到 该单例模式脚本对象依附的GameObject
                obj.name = typeof(T).Name;
                //为物体添加脚本
                instance = obj.AddComponent<T>();
                //过场景不移除对象
                DontDestroyOnLoad(obj);
            }
            return instance;
        }
    }

}

缓存池(对象池)模块(Pool文件夹)

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// 抽屉对象
/// </summary>
public class PoolData
{
    //抽屉根对象 用来布局
    private GameObject rootObj;
    //用来存储抽屉中对象的容器
    private Stack<GameObject> dataList = new Stack<GameObject>();
    //获取容器中对象的数量
    public int Count => dataList.Count;

    //传入name作为抽屉对象物体名 传入poolObj作为抽屉对象的父对象 
    public PoolData(string name, GameObject poolObj)
    {
        if (PoolMgr.isOpenLayout)
        {
            rootObj = new GameObject(name);
            rootObj.transform.parent = poolObj.transform;
        }
    }
    /// <summary>
    /// 取出容器中对象
    /// </summary>
    public GameObject GetObj()
    {
        //取出对象物体
        GameObject obj = dataList.Pop();
        //激活取出的对象
        obj.SetActive(true);
        if (PoolMgr.isOpenLayout)
        {
            //断开取出对象和抽屉对象的父子关系
            obj.transform.parent = null;
        }
        return obj;
    }
    /// <summary>
    /// 将对象放入抽屉中
    /// </summary>
    /// <param name="obj"></param>
    public void PushObj(GameObject obj)
    {
        //失活放入的对象
        obj.SetActive(false);
        if (PoolMgr.isOpenLayout)
        {
            //建立放入对象和抽屉对象的父子关系
            obj.transform.parent = rootObj.transform;
        }
        //将放入对象存入容器dataList中
        dataList.Push(obj);
    }
}

/// <summary>
/// 缓存池模块 管理器
/// </summary>
public class PoolMgr
{
    private static PoolMgr instance;
    public static PoolMgr Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new PoolMgr();
            }
            return instance;
        }
    }
    //柜子容器当中有抽屉的体现
    private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
    private GameObject poolObj;
    //是否开启布局功能
    public static bool isOpenLayout = false;
    private PoolMgr() { }
    /// <summary>
    /// 拿东西的方法
    /// </summary>
    /// <param name="name">抽屉容器的名字</param>
    /// <returns>从缓存池中取出的对象</returns>
    public void GetObj(string name, UnityAction<GameObject> callBack)
    {
        GameObject obj;
        //有抽屉 并且 抽屉里 有对象 才去直接拿(同步加载资源)
        if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
        {
            //弹出栈中的对象 直接返回给外部使用
            obj = poolDic[name].GetObj();
            //取出缓存池对象操作 可用于对象初始化(避免激活失活无法运行Start初始化)
            callBack?.Invoke(obj);
        }
        //否则 就应该去创造
        else
        {
            //异步加载资源
            ResourceRequest rq = Resources.LoadAsync<GameObject>(name);
            rq.completed += (a) =>
            {
                obj = GameObject.Instantiate(rq.asset as GameObject);
                obj.name = name;
                //取出缓存池对象操作 可用于对象初始化(避免激活失活无法运行Start初始化)
                callBack?.Invoke(obj);
            };
        }
    }

    /// <summary>
    /// 往缓存池中放入对象
    /// </summary>
    /// <param name="name">抽屉(对象)的名字</param>
    /// <param name="obj">希望放入的对象 </param>
    public void PushObj(GameObject obj)
    {
        //放入缓存池对象相关操作
        //callBack?.Invoke(obj);
        if (poolObj == null && isOpenLayout)
        {
            poolObj = new GameObject("Pool");
        }
        //没有抽屉 创建抽屉
        if (!poolDic.ContainsKey(obj.name))
        {
            poolDic.Add(obj.name, new PoolData(obj.name, poolObj));
        }
        //往抽屉当中放对象
        poolDic[obj.name].PushObj(obj);
    }

    /// <summary>
    /// 用于清除整个柜子当中的数据
    /// 主要是 切场景时 使用
    /// </summary>
    public void ClearPool()
    {
        poolDic.Clear();
        poolObj = null;
    }
}

事件中心模块(Event文件夹)

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

public interface IEventInfo
{

}

public class EventInfo<T> : IEventInfo
{
    public UnityAction<T> actions;
    public EventInfo(UnityAction<T> action)
    {
        actions += action;
    }

}

public class EventInfo : IEventInfo
{
    public UnityAction actions;
    public EventInfo(UnityAction action)
    {
        actions += action;
    }
}

/// <summary>
/// 事件中心 单例模式对象
/// 1.Dictionary
/// 2.委托
/// 3.观察者设计模式
/// 4.泛型
/// </summary>
public class EventCenter
{
    private static EventCenter instance;
    public static EventCenter Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new EventCenter();
            }
            return instance;
        }
    }
    // key——事件的名字(比如:怪物死亡 玩家死亡 通过 等等)
    // value——对应的是 监听这个事件 对应的委托函数们
    private Dictionary<string, IEventInfo> eventDic = new Dictionary<string, IEventInfo>();

    private EventCenter() { }


    /// <summary>
    /// 添加事件监听(需要参数的)
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">准备用来处理事件 的委托函数</param>
    public void AddEventListener<T>(string name, UnityAction<T> action)
    {
        // 有没有对应的事件监听
        // 有的情况
        if (eventDic.ContainsKey(name))
        {
            (eventDic[name] as EventInfo<T>).actions += action;
        }
        // 没有的情况
        else
        {
            eventDic.Add(name, new EventInfo<T>(action));
        }
    }

    /// <summary>
    /// 添加事件监听(不需要参数的)
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">准备用来处理事件 的委托函数</param>
    public void AddEventListener(string name, UnityAction action)
    {
        // 有没有对应的事件监听
        // 有的情况
        if (eventDic.ContainsKey(name))
        {
            (eventDic[name] as EventInfo).actions += action;
        }
        // 没有的情况
        else
        {
            eventDic.Add(name, new EventInfo(action));
        }
    }

    /// <summary>
    /// 移除对应的事件监听(需要参数的)
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">对应之前添加的委托函数</param>
    public void RemoveEventListener<T>(string name, UnityAction<T> action)
    {
        // 有没有对应的事件监听
        // 有的情况
        if (eventDic.ContainsKey(name))
        {
            (eventDic[name] as EventInfo<T>).actions -= action;
        }
    }

    /// <summary>
    /// 移除对应的事件监听(不需要参数的)
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">对应之前添加的委托函数</param>
    public void RemoveEventListener(string name, UnityAction action)
    {
        // 有没有对应的事件监听
        // 有的情况
        if (eventDic.ContainsKey(name))
        {
            (eventDic[name] as EventInfo).actions -= action;
        }
    }

    /// <summary>
    /// 事件触发(需要参数的)
    /// </summary>
    /// <param name="name">哪一个名字的事件触发了</param>
    /// <param obj="obj">传入回调的本对象</param>
    public void EventTrigger<T>(string name, T obj)
    {
        // 有没有对应的事件监听
        // 有的情况
        if (eventDic.ContainsKey(name))
        {
            if ((eventDic[name] as EventInfo<T>).actions != null)
            {
                (eventDic[name] as EventInfo<T>).actions.Invoke(obj);
            }
        }
    }

    /// <summary>
    /// 事件触发(不需要参数的)
    /// </summary>
    /// <param name="name">哪一个名字的事件触发了</param>
    /// <param obj="obj">传入回调的本对象</param>
    public void EventTrigger(string name)
    {
        // 有没有对应的事件监听
        // 有的情况
        if (eventDic.ContainsKey(name))
        {
            if ((eventDic[name] as EventInfo).actions != null)
            {
                (eventDic[name] as EventInfo).actions.Invoke();
            }
        }
    }

    /// <summary>
    /// 清空事件中心
    /// 主要用在 场景切换时
    /// </summary>
    public void Clear()
    {
        eventDic.Clear();
    }
}

公共Mono模块(Mono文件夹)

MonoMgr.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Internal;

// 为什么不将MonoMgr 和 MonoController合并
// 生命周期管理不够灵活
// 问题点
// 当你把管理器和控制器合并成一个类时,这个单例 MonoBehaviour 的生命周期就和它挂载的 GameObject 完全绑定。
// 如果场景切换时没有 DontDestroyOnLoad,所有的帧更新事件、协程都会被销毁。
// 如果场景切换时加了 DontDestroyOnLoad,但你想要某些帧更新事件只在当前场景生效,就比较麻烦,需要手动移除。
/// <summary>
/// 1. 可以提供给外部添加帧更新事件的方法
/// 2. 可以提供给外部添加 协程的方法
/// </summary>
public class MonoMgr
{
    private static MonoMgr instance;
    public static MonoMgr Instance
    {
        get
        {
            if (instance == null)
                instance = new MonoMgr();
            return instance;
        }
    }
    private MonoController controller;
    private MonoMgr()
    {
        GameObject obj = new GameObject("MonoController");
        controller = obj.AddComponent<MonoController>();
    }

    /// <summary>
    /// 提供给外部 添加帧更新事件的函数
    /// </summary>
    /// <param name="fun"></param>
    public void AddUpdateListener(UnityAction fun)
    {
        controller.AddUpdateListener(fun);
    }

    /// <summary>
    /// 提供 给外部 用于移除帧更新事件的函数
    /// </summary>
    /// <param name="fun"></param>
    public void RemoveUpdateListener(UnityAction fun)
    {
        controller.RemoveUpdateListener(fun);
    }
    /// <summary>
    /// 提供给外部 用于协程
    /// </summary>
    /// <param name="routine"></param>
    /// <returns></returns>
    public Coroutine StartCoroutine(IEnumerator routine)
    {
        return controller.StartCoroutine(routine);
    }

    public Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value)
    {
        return controller.StartCoroutine(methodName, value);
    }

    public Coroutine StartCoroutine(string methodName)
    {
        return controller.StartCoroutine(methodName);
    }
}

MonoController.cs

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

/// <summary>
/// Mono的管理者
/// </summary>
public class MonoController : MonoBehaviour
{
    private event UnityAction updateEvent;
    // Start is called before the first frame update
    void Start()
    {
        DontDestroyOnLoad(gameObject);
    }

    // Update is called once per frame
    void Update()
    {
        updateEvent?.Invoke();
    }

    /// <summary>
    /// 提供给外部 添加帧更新事件的函数
    /// </summary>
    /// <param name="fun"></param>
    public void AddUpdateListener(UnityAction fun)
    {
        updateEvent += fun;
    }

    /// <summary>
    /// 提供 给外部 用于移除帧更新事件的函数
    /// </summary>
    /// <param name="fun"></param>
    public void RemoveUpdateListener(UnityAction fun)
    {
        updateEvent -= fun;
    }
}

场景切换模块(Scenes文件夹)

using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;

public class SceneMgr
{
    private static SceneMgr instance;
    public static SceneMgr Instance
    {
        get
        {
            if(instance == null)
            {
                instance = new SceneMgr();
            }
            return instance;
        }
    }
    private SceneMgr() { }

    /// <summary>
    /// 切换场景 同步
    /// </summary>
    /// <param name="name">场景名</param>
    public void LoadScene(string name,UnityAction fun)
    {
        //场景同步加载
        SceneManager.LoadScene(name);
        //加载完成过后 才会去执行fun
        fun();
    }

    /// <summary>
    /// 提供给外部的 异步加载的接口方法
    /// </summary>
    /// <param name="name"></param>
    /// <param name="fun"></param>
    public void LoadSceneAsync(string name,UnityAction fun)
    {
        MonoMgr.Instance.StartCoroutine(ReallyLoadSceneAsync(name, fun));
    }

    /// <summary>
    /// 协程异步加载
    /// </summary>
    /// <param name="name"></param>
    /// <param name="callback"></param>
    /// <returns></returns>
    private IEnumerator ReallyLoadSceneAsync(string name, UnityAction callback)
    {
        AsyncOperation ao = SceneManager.LoadSceneAsync(name);
        //可以得到场景加载的一个进度
        while (!ao.isDone)
        {
            //事件中心 向外分发 进度情况 外面想用就用
            EventCenter.Instance.EventTrigger("进度条", ao.progress);
            //在这里面去更新进度条
            yield return ao.progress;
        }
        //加载完成过后 才会去执行fun
        callback();
    }
}

资源加载模块(Res文件夹)

using System.Collections;
using UnityEngine;
using UnityEngine.Events;

public class ResMgr
{
    private static ResMgr instance;
    public static ResMgr Instance
    {
        get
        {
            if (instance == null)
                instance = new ResMgr();
            return instance;
        }
    }
    private ResMgr()
    {

    }
    //同步加载资源
    public T Load<T>(string name) where T : Object
    {
        T res = Resources.Load<T>(name);
        //如果对象是一个GameObject类型的 我把他实例化后 再返回出去 外部 直接使用即可
        if (res is GameObject)
        {
            return GameObject.Instantiate(res);
        }
        else//AduioClip TextAsset
        {
            return res;
        }
    }

    //异步加载
    public void LoadAsync<T>(string name, UnityAction<T> callback) where T : Object
    {
        MonoMgr.Instance.StartCoroutine(ReallyLoad<T>(name, callback));
    }

    private IEnumerator ReallyLoad<T>(string name, UnityAction<T> callback) where T : Object
    {
        ResourceRequest rq = Resources.LoadAsync<T>(name);
        yield return rq;
        if (rq.asset is GameObject)
        {
            callback?.Invoke(GameObject.Instantiate(rq.asset) as T);
        }
        else
        {
            callback?.Invoke(rq.asset as T);
        }
    }
}

输入控制模块(Input文件夹)

using UnityEngine;
public class InputMgr
{
    private static InputMgr instance;
    public static InputMgr Instance
    {
        get
        {
            if (instance == null)
                instance = new InputMgr();
            return instance;
        }
    }

    //是否开启输入检测
    private bool isStart = false;

    /// <summary>
    /// 构造函数中 添加Update监听
    /// </summary>
    private InputMgr()
    {
        MonoMgr.Instance.AddUpdateListener(MyUpdate);
    }

    /// <summary>
    /// 是否开启或关闭 我的输入检测
    /// </summary>
    /// <param name="isOpen"></param>
    public void StartOrEndCheck(bool isOpen)
    {
        isStart = isOpen;
    }

    /// <summary>
    /// 用来检测按键抬起按下 分发事件的
    /// </summary>
    private void MyUpdate()
    {
        //没有开启输入检测 就不去检测 直接return
        if (!isStart)
        {
            return;
        }
        CheckKeyCode(KeyCode.W);
        CheckKeyCode(KeyCode.A);
        CheckKeyCode(KeyCode.S);
        CheckKeyCode(KeyCode.D);
    }

    private static void CheckKeyCode(KeyCode key)
    {
        //事件中心模块 分发按下抬起事件
        if (Input.GetKey(key))
        {
            EventCenter.Instance.EventTrigger<KeyCode>("某键按下", key);
        }
        //事件中心模块 分发按下抬起事件
        if (Input.GetKeyUp(key))
        {
            EventCenter.Instance.EventTrigger<KeyCode>("某键抬起", key);
        }
    }
}

音效管理模块(Music)

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

public class MusicMgr
{
    private static MusicMgr instance;
    public static MusicMgr Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new MusicMgr();
            }
            return instance;
        }
    }
    //背景音乐组件
    private AudioSource bkMusic = null;
    //背景音乐默认大小
    private float bkVolume = 1f;

    //音效物体
    private GameObject soundObj = null;
    //音效物体上挂载的音效列表
    private List<AudioSource> soundList = new List<AudioSource>();
    //音效音量
    private float soundVolume = 1f;

    private MusicMgr()
    {
        MonoMgr.Instance.AddUpdateListener(Update);
    }

    private void Update()
    {
        for (int i = soundList.Count - 1; i >= 0; i--)
        {
            if (!soundList[i].isPlaying)
            {
                GameObject.Destroy(soundList[i]);
                soundList.RemoveAt(i);
            }
        }
    }

    /// <summary>
    /// 播放背景音乐
    /// </summary>
    /// <param name="name"></param>
    public void PlayBKMusic(string name)
    {
        if (bkMusic == null)
        {
            GameObject obj = new GameObject("BKMusic");
            bkMusic = obj.AddComponent<AudioSource>();
            //背景音乐默认开启循环
            bkMusic.loop = true;
        }
        if (bkMusic.clip != null && bkMusic.clip.name == name)
        {
            if (bkMusic.isPlaying)
            {
                return;
            }
            bkMusic.Play();
            return;
        }
        //异步加载背景音乐 加载完成后 播放
        ResourceRequest rq = Resources.LoadAsync<AudioClip>($"Music/BK/{name}");
        rq.completed += (value) =>
        {
            if (rq.asset as AudioClip == null)
            {
                Debug.LogError($"找不到Music/BK{name}音频文件");
                return;
            }
            bkMusic.clip = rq.asset as AudioClip;
            bkMusic.volume = bkVolume;
            bkMusic.Play();
        };
    }
    /// <summary>
    /// 改变背景音乐 音量大小
    /// </summary>
    /// <param name="volume"></param>
    public void ChangeBKVolume(float volume)
    {
        if (bkMusic == null)
        {
            return;
        }
        bkVolume = volume;
        bkMusic.volume = bkVolume;
    }

    /// <summary>
    /// 暂停背景音乐
    /// </summary>
    public void PauseBKMusic()
    {
        if (bkMusic == null)
        {
            return;
        }
        bkMusic.Pause();
    }

    /// <summary>
    /// 停止背景音乐
    /// </summary>
    public void StopBKMusic()
    {
        if (bkMusic == null)
        {
            return;
        }
        bkMusic.Stop();
    }
    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="name"></param>
    public void PlaySound(string name, bool isloop = false, UnityAction<AudioSource> callBack = null)
    {
        if (soundObj == null)
        {
            soundObj = new GameObject("SoundMusic");
        }
        //当音效资源异步加载结束后 再添加音效
        ResourceRequest rq = Resources.LoadAsync<AudioClip>($"Music/Sound/{name}");
        rq.completed += (value) =>
        {
            AudioSource source = soundObj.AddComponent<AudioSource>();
            source.clip = rq.asset as AudioClip;
            source.loop = isloop;
            source.volume = soundVolume;
            source.Play();
            soundList.Add(source);
            if (callBack != null)
            {
                callBack(source);
            }
        };
    }
    /// <summary>
    /// 改变音效音量大小
    /// </summary>
    /// <param name="volume"></param>
    public void ChangeSoundVolume(float volume)
    {
        if (soundList.Count == 0)
        {
            return;
        }
        soundVolume = volume;
        for (int i = 0; i < soundList.Count; i++)
        {
            soundList[i].volume = soundVolume;
        }
    }

    /// <summary>
    /// 停止音效(一个物体上音效有多个 需要指定特定的音效删除)
    /// </summary>
    public void StopSound(AudioSource audioSource)
    {
        if (soundList.Contains(audioSource))
        {
            soundList.Remove(audioSource);
            audioSource.Stop();
            GameObject.Destroy(audioSource);
        }
    }
}

UI管理模块(UI文件夹)

BasePanel.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 面板基类
/// 帮助我们通过代码快速的找到所有的子控件
/// 方便我们在子类中处理逻辑
/// 节约找控件的工作量
/// </summary>
public abstract class BasePanel : MonoBehaviour
{
    //通过里氏转换原则 来存储所有控件
    private Dictionary<string, List<UIBehaviour>> controlDic = new Dictionary<string, List<UIBehaviour>>();

    private CanvasGroup cg;
    private bool isShow;
    private UnityAction hidePanel;
    private float hideSpeed = 10f;
    // Start is called before the first frame update
    protected virtual void Awake()
    {
        FindChildrenControl<Button>();
        FindChildrenControl<Image>();
        FindChildrenControl<Text>();
        FindChildrenControl<Toggle>();
        FindChildrenControl<Slider>();
        FindChildrenControl<ScrollRect>();
        FindChildrenControl<InputField>();
        cg = GetComponent<CanvasGroup>();
        if (cg == null)
        {
            cg = this.gameObject.AddComponent<CanvasGroup>();
        }
    }

    protected virtual void Start()
    {
        Init();
    }

    protected abstract void Init();

    // Update is called once per frame
    protected virtual void Update()
    {
        if (isShow && cg.alpha < 1)
        {
            cg.alpha += Time.deltaTime * hideSpeed;
            if (cg.alpha > 1)
            {
                cg.alpha = 1;
            }
        }
        if (!isShow)
        {
            cg.alpha -= Time.deltaTime * hideSpeed;
            if (cg.alpha <= 0)
            {
                cg.alpha = 0;
                //由外界 隐藏面板
                hidePanel?.Invoke();
            }
        }
    }

    public virtual void ShowMe()
    {
        cg.alpha = 0;
        isShow = true;
    }

    public virtual void HideMe(UnityAction callback)
    {
        cg.alpha = 1;
        isShow = false;
        hidePanel = callback;
    }

    public virtual T GetControler<T>(string controlName) where T : UIBehaviour
    {
        if (controlDic.ContainsKey(controlName))
        {
            for (int i = 0; i < controlDic[controlName].Count; i++)
            {
                if (controlDic[controlName][i] is T)
                {
                    return controlDic[controlName][i] as T;
                }
            }
        }
        return null;
    }

    private void FindChildrenControl<T>() where T : UIBehaviour
    {
        T[] controls = this.GetComponentsInChildren<T>();
        for (int i = 0; i < controls.Length; i++)
        {
            string objName = controls[i].gameObject.name;
            if (controlDic.ContainsKey(objName))
            {
                controlDic[objName].Add(controls[i]);
            }
            else
            {
                controlDic.Add(objName, new List<UIBehaviour>() { controls[i] });
            }
            //如果是按钮组件
            if (controls[i] is Button)
            {
                (controls[i] as Button).onClick.AddListener(() =>
                {
                    OnClick(objName);
                });
            }
            //如果是单选框或多选框
            if (controls[i] is Toggle)
            {
                (controls[i] as Toggle).onValueChanged.AddListener((value) =>
                {
                    OnValueChanged(objName, value);
                });
            }
        }
    }

    protected virtual void OnClick(string btnName) { }
    protected virtual void OnValueChanged(string toggleName, bool value) { }
}

UIManager.cs

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
/// <summary>
/// UI层级
/// </summary>
public enum E_UI_Layer
{
    Bot, Mid, Top, System,
}
public class UIManager
{
    private static UIManager instance = new UIManager();
    public static UIManager Instance => instance;

    //存储面板的容器
    public Dictionary<Type, BasePanel> panelDic = new Dictionary<Type, BasePanel>();

    private Transform bot;//背景、底图
    private Transform mid;//常规 UI
    private Transform top;//弹窗、提示
    private Transform system;//Loading / Mask / 强制遮挡
    //记录UI的Canvas父对象 方便以后外部使用
    public RectTransform canvas;

    private UIManager()
    {
        //动态创建Canvas 过场景不移除
        GameObject obj = ResMgr.Instance.Load<GameObject>("UI/Canvas");
        canvas = obj.transform as RectTransform;
        GameObject.DontDestroyOnLoad(obj);

        //找到各层
        bot = canvas.Find("Bot");//底层
        mid = canvas.Find("Mid");//中层
        top = canvas.Find("Top");//顶层
        system = canvas.Find("System");//系统层(最上方)

        //动态创建EventSystem 过场景不移除
        obj = ResMgr.Instance.Load<GameObject>("UI/EventSystem");
        GameObject.DontDestroyOnLoad(obj);

    }

    public Transform GetLayerFather(E_UI_Layer layer)
    {
        switch (layer)
        {
            case E_UI_Layer.Bot:
                return this.bot;
            case E_UI_Layer.Mid:
                return this.mid;
            case E_UI_Layer.Top:
                return this.top;
            case E_UI_Layer.System:
                return this.system;
        }
        return null;
    }


    /// <summary>
    /// 获取面板
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="panel"></param>
    public T GetPanel<T>() where T : BasePanel
    {
        if (panelDic.ContainsKey(typeof(T)))
        {
            return (T)panelDic[typeof(T)];
        }
        return null;
    }

    /// <summary>
    /// 显示面板
    /// </summary>
    /// <typeparam name="T">面板脚本类型</typeparam>
    /// <param name="layer">显示在那一层</param>
    /// <param name="callBack">当面板预设体创建成功后 想做的事</param>
    public void ShowPanel<T>(E_UI_Layer layer = E_UI_Layer.Mid, UnityAction<T> callBack = null) where T : BasePanel
    {
        T panel = GetPanel<T>();
        //是否已经显示了该面板 如果有 不用创建 直接指向回调
        if (panel != null)
        {
            callBack?.Invoke(panel);
            return;
        }
        //根据得到的 类名 就是面板名 直接创建
        ResMgr.Instance.LoadAsync<GameObject>("UI/" + typeof(T).Name, (obj) =>
        {
            Transform father = bot;
            switch (layer)
            {
                case E_UI_Layer.Mid:
                    father = mid;
                    break;
                case E_UI_Layer.Top:
                    father = top;
                    break;
                case E_UI_Layer.System:
                    father = system;
                    break;
            }
            //设置父对象 设置相对位置和大小
            obj.transform.SetParent(father);

            obj.transform.localPosition = Vector3.zero;
            obj.transform.localScale = Vector3.one;

            (obj.transform as RectTransform).offsetMax = Vector2.zero;//offsetMin表示物体左下角相对AnchorMin的偏移
            (obj.transform as RectTransform).offsetMin = Vector2.zero;//offsetMax表示物体右上角相对AnchorMax的偏移

            //得到预设体身上的面板脚本
            panel = obj.GetComponent<T>();
            //调用显示自己的逻辑
            panel.ShowMe();

            callBack?.Invoke(panel);
            //把面板存起来
            panelDic.Add(typeof(T), panel);
        });
    }

    /// <summary>
    /// 隐藏面板
    /// </summary>
    /// <typeparam name="T">要隐藏的面板类</typeparam>
    /// <param name="isFade">淡出:true,直接隐藏:false</param>
    public void HidePanel<T>(bool isFade = true) where T : BasePanel
    {
        //得到根据泛型类型 得到 面板名字
        T panel = GetPanel<T>();
        //判断字典有没有要隐藏的面板
        if (panel != null)
        {
            if (isFade)
            {
                panel.HideMe(() =>
                {
                    //删除面板后 从字典中移除
                    panelDic.Remove(typeof(T));
                    //面板淡出成功后删除面板
                    GameObject.Destroy(panel.gameObject);
                });
            }
            else
            {
                //直接删除面板
                GameObject.Destroy(panel.gameObject);
                //从字典移除
                panelDic.Remove(typeof(T));
            }
        }
    }

    /// <summary>
    /// 给控件添加自定义事件监听
    /// </summary>
    /// <param name="control">控件对象</param>
    /// <param name="type">事件类型</param>
    /// <param name="callBack">事件的响应函数</param>
    public static void AddCustomEventListener(UIBehaviour control, EventTriggerType type, UnityAction<BaseEventData> callBack)
    {
        EventTrigger trigger = control.GetComponent<EventTrigger>();
        if (trigger == null)
        {
            trigger = control.gameObject.AddComponent<EventTrigger>();
        }
        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = type;
        entry.callback.AddListener(callBack);

        trigger.triggers.Add(entry);
    }
}
posted @ 2025-12-18 16:31  高山仰止666  阅读(5)  评论(0)    收藏  举报