单例管理者基类

不继承自Mono

1.利用单例模式和泛形去实现模块管理者基类;

public class SingletonBase<T> where T:new()
{
    private static T instance;

    public static T GetInstance()
    {
        if (instance == null) instance = new T();
        return instance;
    }
}

继承自Mono

1.直接实例化通过生成游戏对象并且附加脚本;
2.Awake实例化的话要保证单例的唯一性;

public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    public static T GetInstance()
    {
        if (instance == null)
        {
            GameObject go = new GameObject
            {
                name = typeof(T).Name,
            };
            instance = go.AddComponent<T>();
            DontDestroyOnLoad(go);
        }
        return instance;
    }

    protected virtual void Awake()
    {
        if (instance == null)
        {
            instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
    }
}

事件管理者

1.观察者模式,利用了泛形和委托(事件)
2.这里的事件字典只能对付小游戏,大型游戏仍是不够

public interface IEventInfo { } 

public class EventInfo : IEventInfo
{
    public event UnityAction actions;

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

    public void EventTrigger()
    {
        actions?.Invoke();
    }
}

public class EventInfo<T> : IEventInfo
{
    public event UnityAction<T> actions;

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

    public void EventTrigger(T Info)
    {
        actions?.Invoke(Info);
    }
}

public class EventMgr : SingletonBase<EventMgr>
{
    //暂时只定义一个字典进行储存,小游戏够用了
    private Dictionary<string, IEventInfo> eventDic = new Dictionary<string, IEventInfo>(); 

    public void AddListenter(string name,UnityAction action)
    {
        if (eventDic.TryGetValue(name, out IEventInfo eventInfo)) (eventInfo as EventInfo).actions += action;
        else eventDic.Add(name,new EventInfo(action));
    }

    public void AddListenter<T>(string name, UnityAction<T> action)
    {
        if (eventDic.TryGetValue(name, out IEventInfo eventInfo)) (eventInfo as EventInfo<T>).actions += action;
        else eventDic.Add(name, new EventInfo<T>(action));
    }

    public void RemoveListenter(string name, UnityAction action)
    {
        if (eventDic.TryGetValue(name, out IEventInfo eventInfo)) (eventInfo as EventInfo).actions -= action;
    }

    public void RemoveListenter<T>(string name, UnityAction<T> action)
    {
        if (eventDic.TryGetValue(name, out IEventInfo eventInfo)) (eventInfo as EventInfo<T>).actions -= action;
    }

    public void EventTrigger(string name)
    {
        if (eventDic.TryGetValue(name, out IEventInfo eventInfo)) (eventInfo as EventInfo).EventTrigger();
    }

    public void EventTrigger<T>(string name,T info)
    {
        if (eventDic.TryGetValue(name, out IEventInfo eventInfo)) (eventInfo as EventInfo<T>).EventTrigger(info);
    }

    public void Clear()
    {
        eventDic.Clear();
    }
}

Mono根节点

1.可以创建一个全局唯一的Mono节点使用
2.实现游戏入口继承自该节点即可,但是实现Awake单例

public class MonoRoot : SingletonMono<MonoRoot>
{
    private event UnityAction updateActions;

    protected override void Awake()
    {
        base.Awake();
    }

    protected virtual void Update()
    {
        updateActions?.Invoke();
    }

    public void AddUpdateListenter(UnityAction updateAction)
    {
        updateActions += updateAction;
    }

    public void RemoveUpdateListenter(UnityAction updateAction)
    {
        updateActions -= updateAction;
    }
}

资源加载管理者

1.资源加载有同步加载和异步加载,异步加载用协程实现
2.资源加载管理者包括场景切换,以及配置文件加载

public class ResMgr : SingletonBase<ResMgr>
{

    #region 资源加载
    private Dictionary<string,Object> ObjCacheDic=new Dictionary<string, Object>();


    public T LoadObj<T>(string path , bool isIns=true , bool isCache= true ) where T : Object
    {
        T obj;
        if (ObjCacheDic.TryGetValue(path, out Object cacheObj)) obj = cacheObj as T;
        else 
        {
            obj = Resources.Load<T>(path);
            if (isCache) ObjCacheDic.Add(path, obj);
        };

        if (obj is GameObject && isIns) return GameObject.Instantiate(obj);
        else return obj;
    }

    public void LoadObjAsyn<T>(string path,UnityAction<T> callback, bool isIns = true, bool isCache = true) where T : Object
    {
        if (ObjCacheDic.TryGetValue(path, out Object obj))
        {
            if (obj is GameObject && isIns)
                callback(GameObject.Instantiate(obj) as T);
            else
                callback(obj as T);
        }
        else MonoRoot.GetInstance().StartCoroutine(ReallyLoadObjAsyn(path, callback, isIns, isCache));

    }


    public IEnumerator ReallyLoadObjAsyn<T>(string path, UnityAction<T> callback, bool isIns, bool isCache) where T : Object
    {
        ResourceRequest r = Resources.LoadAsync<T>(path);
        yield return r;

        if(isCache) ObjCacheDic.Add(path, r.asset);

        if (r.asset is GameObject && isIns)
            callback(GameObject.Instantiate(r.asset) as T);
        else
            callback(r.asset as T);
    }
    #endregion

        #region 场景切换
    public void LoadScene(string name, UnityAction fun)
    {
        //场景同步加载
        SceneManager.LoadScene(name);
        //加载完成过后 才会去执行fun
        fun();
    }

    public void LoadSceneAsyn(string name, UnityAction fun)
    {
        MonoRoot.GetInstance().StartCoroutine(ReallyLoadSceneAsyn(name, fun));
    }

    public IEnumerator ReallyLoadSceneAsyn(string name, UnityAction fun)
    {
        AsyncOperation op = SceneManager.LoadSceneAsync(name);
        while (!op.isDone)
        {
            //这里可以去做一些事情,例如更新进度条
            //EventMgr.GetInstance().EventTrigger("更新进度条", op.progress);
            yield return op.progress;
        }
        //加载完成过后 才会去执行fun
        fun();
    } 
    #endregion
}

缓存池管理者

1.用一个类类型去包装缓存队列,这个类里面应该封装了获取和设置缓存对象的方法
2.缓存池目前只支持预制物的缓存

public class PoolMgr :SingletonBase<PoolMgr> 
{
    private Dictionary<string, PoolData> poolDic=new Dictionary<string, PoolData>();

    private GameObject poolRoot;

    public void GetObj(string name, UnityAction<GameObject> callBack)
    {
        if(poolDic.TryGetValue(name, out PoolData poolData) && poolData.Count > 0)
        {
            callBack(poolData.GetObj());
        }
        else
        {
            //创建资源应使用资源路径
            ResMgr.GetInstance().LoadObjAsyn<GameObject>(name, (go) =>{
                go.name = name;
                callBack(go);
            });
        }
    }

    public void PushObj(string name,GameObject obj)
    {
        if(poolRoot=null)poolRoot=new GameObject("PoolRoot");

        if(poolDic.TryGetValue(name,out PoolData poolData)) poolData.PushObj(obj);
        else poolDic.Add(name, new PoolData(obj,poolRoot));
    }

    public void  Clear()
    {
        poolDic.Clear();
        poolRoot = null;
    }
}

public class PoolData
{
    private GameObject parentRoot;
    private Queue<GameObject> objQue;

    public PoolData(GameObject obj,GameObject poolObj)
    {
        parentRoot = new GameObject($"{obj.name}Root");
        parentRoot.transform.parent = poolObj.transform;

        objQue = new Queue<GameObject>();

    }

    public int Count
    {
        get { return objQue.Count; }
    }

    public void PushObj(GameObject obj)
    {
        //存起来
        objQue.Enqueue(obj);
        //失活 让其隐藏
        obj.SetActive(false);
        //设置父对象
        obj.transform.parent = parentRoot.transform;
    }

    public GameObject GetObj()
    {
        //取出第一个
        GameObject obj = objQue.Dequeue();
        //激活 让其显示
        obj.SetActive(true);
        //断开了父子关系
        obj.transform.parent = null;

        return obj;
    }
}

输入管理者

1.输入管理者耦合事件监听和Mono根节点

public class InputMgr : SingletonBase<InputMgr>
{
    private bool isStart = false;
    public InputMgr()
    {
        MonoMgr.GetInstance().AddUpdateListener(UpdateIpt);
    }

    public void StartOrEndCheck(bool isOpen)
    {
        isStart = isOpen;
    }

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

    private void UpdateIpt()
    {
        //没有开启输入检测 就不去检测 直接return
        if (!isStart)
            return;

        CheckKeyCode(KeyCode.W);
        CheckKeyCode(KeyCode.S);
        CheckKeyCode(KeyCode.A);
        CheckKeyCode(KeyCode.D);
    }
}

音效管理者

1.一般音效资源应该做缓存池处理

public class MusicMgr : MonoBehaviour
{
    private bool isMute = false;
    //背景音乐源
    private AudioSource bkSource;
    //背景音乐大小
    private float bkValue = 1;
    //一般音乐列表
    private List<AudioSource> sourceLst = new List<AudioSource>();
    //背景音乐大小
    private float soundValue = 1;

    //音乐源节点
    private GameObject musicRoot;

    public MusicMgr()
    {
        MonoMgr.GetInstance().AddUpdateListener(UpdateMsc);
    }

    //音乐播放完就回收
    private void UpdateMsc()
    {
        for (int i = sourceLst.Count - 1; i >= 0; --i)
        {
            if (!sourceLst[i].isPlaying)
            {
                GameObject.Destroy(sourceLst[i]);
                sourceLst.RemoveAt(i);
            }
        }
    }

    public void PlayBkMusic(string name)
    {
        if(!musicRoot) musicRoot=new GameObject("MusicRoot");
        if (bkSource) bkSource = musicRoot.AddComponent<AudioSource>();

        //异步加载背景音乐 加载完成后 播放
        ResMgr.GetInstance().LoadObjAsyn<AudioClip>("Music/BK/" + name, (clip) =>
        {
            bkSource.clip = clip;
            bkSource.loop = true;
            bkSource.volume = bkValue;
            bkSource.Play();
        });
    }

    public void PauseBKMusic()
    {
        bkSource?.Pause();
    }

    public void StopBKMusic()
    {
        bkSource?.Stop();
    }

    public void ChangeBKValue(float v)
    {
        bkValue = v;
        if (bkSource == null) bkSource.volume = bkValue;
    }

    public void PlaySound(string name, bool isLoop=false, GameObject parent=null, UnityAction<AudioSource> callBack = null)
    {
        if (!musicRoot) musicRoot = new GameObject("MusicRoot");

        //当音效资源异步加载结束后 再添加一个音效
        ResMgr.GetInstance().LoadObjAsyn<AudioClip>("Music/Sound/" + name, (clip) =>
        {
            AudioSource source;
            if (parent) source=parent.AddComponent<AudioSource>();
            else source = musicRoot.AddComponent<AudioSource>();
            source.clip = clip;
            source.loop = isLoop;
            source.volume = soundValue;
            source.Play();
            sourceLst.Add(source);
            if (callBack != null)
                callBack(source);
        });
    }

    public void ChangeSoundValue(float value)
    {
        soundValue = value;
        for (int i = 0; i < sourceLst.Count; ++i)
            sourceLst[i].volume = value;
    }

    public void StopSound(AudioSource source)
    {
        if (sourceLst.Contains(source))
        {
            sourceLst.Remove(source);
            source.Stop();
            GameObject.Destroy(source);
        }
    }
}

UI管理者和UI面板基类

面板基类

public class BasePanel : MonoBehaviour
{
    //通过里式转换原则 来存储所有的控件
    private Dictionary<string, List<UIBehaviour>> controlDic = new Dictionary<string, List<UIBehaviour>>();

    protected virtual void Awake()
    {
        FindChildrenControl<Button>();
        FindChildrenControl<Image>();
        FindChildrenControl<Text>();
        FindChildrenControl<Toggle>();
        FindChildrenControl<Slider>();
        FindChildrenControl<ScrollRect>();
        FindChildrenControl<InputField>();
    }

    public virtual void ShowMe()
    {

    }

    public virtual void HideMe()
    {

    }

    protected virtual void OnClick(string btnName)
    {

    }

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

    }

    protected T GetControl<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);
                });
            }
            //如果是单选框或者多选框
            else if (controls[i] is Toggle)
            {
                (controls[i] as Toggle).onValueChanged.AddListener((value) =>
                {
                    OnValueChanged(objName, value);
                });
            }
        }
    }
}

UI管理者

public class UIMgr : SingletonBase<UIMgr>
{
    public Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>();

    private Transform bot;
    private Transform mid;
    private Transform top;
    private Transform system;

    //记录我们UI的Canvas父对象 方便以后外部可能会使用它
    public RectTransform canvas;

    public UIMgr()
    {
        //创建Canvas 让其过场景的时候 不被移除
        GameObject obj = ResMgr.GetInstance().LoadObj<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.GetInstance().LoadObj<GameObject>("UI/EventSystem");
        GameObject.DontDestroyOnLoad(obj);
    }

    /// <summary>
    /// 通过层级枚举 得到对应层级的父对象
    /// </summary>
    /// <param name="layer"></param>
    /// <returns></returns>
    public Transform GetLayerFather(EUIayer layer)
    {
        switch (layer)
        {
            case EUIayer.Bot:
                return this.bot;
            case EUIayer.Mid:
                return this.mid;
            case EUIayer.Top:
                return this.top;
            case EUIayer.System:
                return this.system;
        }
        return null;
    }

    /// <summary>
    /// 显示面板
    /// </summary>
    /// <typeparam name="T">面板脚本类型</typeparam>
    /// <param name="panelName">面板名</param>
    /// <param name="layer">显示在哪一层</param>
    /// <param name="callBack">当面板预设体创建成功后 你想做的事</param>
    public void ShowPanel<T>(string panelName, EUIayer layer = EUIayer.Mid, UnityAction<T> callBack = null) where T : BasePanel
    {
        if (panelDic.ContainsKey(panelName))
        {
            panelDic[panelName].ShowMe();
            // 处理面板创建完成后的逻辑
            if (callBack != null)
                callBack(panelDic[panelName] as T);
            //避免面板重复加载 如果存在该面板 即直接显示 调用回调函数后  直接return 不再处理后面的异步加载逻辑
            return;
        }

        ResMgr.GetInstance().LoadObjAsyn<GameObject>("UI/" + panelName, (obj) =>
        {
            //把他作为 Canvas的子对象
            //并且 要设置它的相对位置
            //找到父对象 你到底显示在哪一层
            Transform father = bot;
            switch (layer)
            {
                case EUIayer.Mid:
                    father = mid;
                    break;
                case EUIayer.Top:
                    father = top;
                    break;
                case EUIayer.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;
            (obj.transform as RectTransform).offsetMin = Vector2.zero;

            //得到预设体身上的面板脚本
            T panel = obj.GetComponent<T>();
            // 处理面板创建完成后的逻辑
            if (callBack != null)
                callBack(panel);

            panel.ShowMe();

            //把面板存起来
            panelDic.Add(panelName, panel);
        });
    }

    /// <summary>
    /// 隐藏面板
    /// </summary>
    /// <param name="panelName"></param>
    public void HidePanel(string panelName)
    {
        if (panelDic.ContainsKey(panelName))
        {
            panelDic[panelName].HideMe();
            GameObject.Destroy(panelDic[panelName].gameObject);
            panelDic.Remove(panelName);
        }
    }

    /// <summary>
    /// 得到某一个已经显示的面板 方便外部使用
    /// </summary>
    public T GetPanel<T>(string name) where T : BasePanel
    {
        if (panelDic.ContainsKey(name))
            return panelDic[name] as T;
        return null;
    }

    /// <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);
    }
}

/// <summary>
/// UI层级
/// </summary>
public enum EUIayer
{
    Bot,
    Mid,
    Top,
    System,
}