Unity的基础程序框架

基础程序框架


前言

完成所有项目都有的公共模块。使用这些框架可以做一些小项目或者毕业设计。

一、为什么要做这些

1、公共模块可以简单的理解为整个程序框架,提升开发效率
2、这些模块在游戏中各处都会用到
3、往往这些模块在各个小项目中是通用的,完成一次后可以通用。

二、包含内容

1.单例模式基类

单例模式可以减少重复代码的书写
普通单例基类

/// <summary>
/// 1、C# 泛型知识点
/// 2、设计模式中 单例模式
/// </summary>
/// <typeparam name="T"></typeparam>
public class SingleBase<T> where T : new()
{
    private static T intance;

    public static T Instance
    {
        get
        {
            if (Instance == null)
                intance = new T();
            return Instance;
        }
    }
}

继承Mono的基类 需要在Awake 中初始化

/// <summary>
/// 继承MonoBehaviour的 单例模式 对象 自己去保证他的唯一性
/// </summary>
public class SingleMonoBase<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    //继承来Mono的脚本 不能直接new
    //只能通过拖动到对象身上 或者 通过 加载脚本的api AddComponent 去加脚本 unity 内部帮我们去实例化它
    public static T Instance => instance;

    protected virtual void Awake()
    {
        instance = this as T;
    }
}

继承mono基类的 升级

/// <summary>
///继承这种自动创建的 单例模式基类 不需要我们手动去拖 api去加
///想用时 直接Instance 就行
/// </summary>
/// <typeparam name="T"></typeparam>
public class SingleAutoMonoBase<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                GameObject obj = new GameObject("");
                //设置对象的名字为脚本名
                obj.name = typeof(T).ToString();
                //让单例模式对象 过场景 不移除
                //因为 单例模式对象 往往 是存在整个程序声明周期中的
                GameObject.DontDestroyOnLoad(obj);
                instance = obj.AddComponent<T>();
            }
            return instance;
        }
    }
}

2.缓存池模块

资源循环利用 节约性能 减少cpu和内存消耗

/// <summary>
///池子中的一列数据 (抽屉数据
/// </summary>
public class PoolData
{
    /// <summary>
    /// 抽屉中 对象挂载的父节点 (抽屉名)
    /// </summary>
    public GameObject fatherObj;

    /// <summary>
    /// 对象的容器 (抽屉的容器)
    /// </summary>
    public List<GameObject> poolList;

    /// <summary>
    ///
    /// </summary>
    /// <param name="obj">需要放入抽屉的物品</param>
    /// <param name="poolobj">衣柜</param>
    public PoolData(GameObject obj, GameObject poolobj)
    {
        //给抽屉创建一个父对象 并且把它作为poolobj(衣柜的子物体)
        fatherObj = new GameObject(obj.name);
        fatherObj.transform.parent = poolobj.transform;
        poolList = new List<GameObject>();
        PushObj(obj);
    }

    /// <summary>
    /// 往抽屉里面放东西
    /// </summary>
    /// <param name="obj"></param>
    public void PushObj(GameObject obj)
    {
        //存起来
        poolList.Add(obj);
        //设置父对象
        obj.transform.parent = fatherObj.transform;
        //失活
        obj.gameObject.SetActive(false);
    }

    /// <summary>
    /// 抽屉里面拿物品
    /// </summary>
    /// <returns></returns>
    public GameObject GetObj()
    {
        GameObject obj;
        obj = poolList[0];//拿抽屉里面的第一个物品
        poolList.RemoveAt(0);//然后从抽屉中移除第一个物品
        obj.transform.parent = null;
        obj.gameObject.SetActive(true);
        return obj;
    }
}

/// <summary>
/// 缓存池管理器(比喻成一个衣柜)
/// </summary>
//缓存池 管理器 管理所有的对象
//由于管理器都是 唯一的 所以需要写成单例模式
public class PoolManager : SingleBase<PoolManager>
{
    /// <summary>
    /// key : 缓存池中 物品的名称(衣柜中的抽屉的名称)
    /// value:缓存池中的物品 (抽屉)
    /// </summary>
    public Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();

    /// <summary>
    /// 缓存池中所有对象的父节点
    /// </summary>
    private GameObject poolObj;

    /// <summary>
    /// 从对象池中取出物品(从衣柜中拿东西 参数就是物品的名字)
    /// </summary>
    /// <param name="name">根据这个名字取出对应的物品</param>
    /// <returns></returns>
    public GameObject GetObj(string name)
    {
        GameObject obj = null;
        //有抽屉 并且抽屉里面有东西
        if (poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0)
        {
            obj = poolDic[name].GetObj();
        }
        else
        {
            //直接实例化一个物体
            obj = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>(name));
            obj.name = name;
        }
        return obj;
    }

    /// <summary>
    /// 暂时不用的东西放回对象池(不用的东西 放进衣柜里面)
    /// </summary>
    /// <param name="name">抽屉的名称(放到哪一个抽屉)</param>
    /// <param name="obj">具体的物品是什么</param>
    public void PushObj(string name, GameObject obj)
    {
        if (poolObj == null)
            poolObj = new GameObject("Pool");

        //里面有抽屉
        if (poolDic.ContainsKey(name))
        {
            //得到该抽屉 然后把物品放进去
            poolDic[name].PushObj(obj);
        }
        else
        //里面没有该抽屉
        {
            PoolData list = new PoolData(obj, poolObj);
            //赋值: 若不存在该Key, 添加新元素, 若已存在key, 重新赋值
            poolDic[name] = list;
        }
    }

    /// <summary>
    /// 清空缓存池 主要用于 场景切换
    /// </summary>
    public void Clear()
    {
        poolDic.Clear();
        poolObj = null;
    }
}

3.事件中心模块

减低程序耦合性 减少代码复杂度

//里氏转换原则  来避免装箱拆箱
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>
/// 事件中心
/// </summary>
public class EventCenter : SingleBase<EventCenter>
{
    /// <summary>
    /// key:事件的名字
    /// vslue: 对应的是监听该事件的委托方法(父类装子类)
    /// </summary>
    private Dictionary<string, IEventInfo> eventDic = new Dictionary<string, IEventInfo>();

    /// <summary>
    /// 监听事件(带泛型参数)
    /// </summary>
    /// <param name="eventName">事件的名字</param>
    /// <param name="action">用来处理该事件的方法</param>
    public void EventListenner<T>(string eventName, UnityAction<T> action)
    {
        //有没有对应的事件监听
        //有
        if (eventDic.ContainsKey(eventName))
        {
            //委托 一对多
            (eventDic[eventName] as EventInfo<T>).actions += action;
        }
        else//没有
        {
            eventDic[eventName] = new EventInfo<T>(action);
        }
    }

    /// <summary>
    /// 监听事件不带参数
    /// </summary>
    /// <param name="eventName"></param>
    /// <param name="action"></param>
    public void EventListenner(string eventName, UnityAction action)
    {
        //有没有对应的事件监听
        //有
        if (eventDic.ContainsKey(eventName))
        {
            //委托 一对多
            (eventDic[eventName] as EventInfo).actions += action;
        }
        else//没有
        {
            eventDic[eventName] = new EventInfo(action);
        }
    }

    /// <summary>
    /// 事件触发(带泛型参数)
    /// </summary>
    /// <param name="eventName">那个名字的事件触发了</param>
    public void EventTrigger<T>(string eventName, T info)
    {
        if (eventDic.ContainsKey(eventName))
        {
            // eventDic[eventName]?.Invoke(info);
            if ((eventDic[eventName] as EventInfo<T>).actions != null)
                (eventDic[eventName] as EventInfo<T>).actions(info);//执行委托函数
        }
    }

    /// <summary>
    /// 事件触发(不带泛型参数)
    /// </summary>
    /// <param name="eventName"></param>
    public void EventTrigger(string eventName)
    {
        if (eventDic.ContainsKey(eventName))
        {
            // eventDic[eventName]?.Invoke(info);
            if ((eventDic[eventName] as EventInfo).actions != null)
                (eventDic[eventName] as EventInfo).actions();//执行委托函数
        }
    }

    /// <summary>
    /// 移除对应事件(事件有加就有减 不然会出问题)
    /// </summary>
    /// <param name="eventName">事件的名字</param>
    /// <param name="action">对应之间添加的委托函数</param>
    public void RemoveEvent<T>(string eventName, UnityAction<T> action)
    {
        if (eventDic.ContainsKey(eventName))
        {
            (eventDic[eventName] as EventInfo<T>).actions -= action;
        }
    }

    /// <summary>
    /// 不带参数的
    /// </summary>
    /// <param name="eventName"></param>
    /// <param name="action"></param>
    public void RemoveEvent(string eventName, UnityAction action)
    {
        if (eventDic.ContainsKey(eventName))
        {
            (eventDic[eventName] as EventInfo).actions -= action;
        }
    }
}

4.公共Mono模块

让没有继承Mono的类可以开启协程,可以Update更新,统一管理Update

/// <summary>
/// Mono管理器
/// </summary>
public class MonoController : MonoBehaviour
{
    public event UnityAction updataEvent;

    private void Start()
    {
        DontDestroyOnLoad(this.gameObject);
    }

    private void Update()
    {
        if (updataEvent != null)
            updataEvent();
    }

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

    /// <summary>
    /// 移除帧更新事件
    /// </summary>
    /// <param name="fun"></param>
    public void RemoveUpdateListener(UnityAction fun)
    {
        updataEvent -= fun;
    }
}
/// <summary>
/// 可以提供给外部添加帧更新的方法
/// </summary>
public class MonoManager : SingleBase<MonoManager>
{
    private MonoController monoController;

    public MonoManager()
    {
        GameObject obj = new GameObject("monoController");
        monoController = obj.AddComponent<MonoController>();
    }

    public void AddUpdateListener(UnityAction fun)
    {
        monoController.AddUpdateListener(fun);
    }

    public void RemoveUpdateListener(UnityAction fun)
    {
        monoController.RemoveUpdateListener(fun);
    }

    /// <summary>
    /// 开启携程方法
    /// </summary>
    /// <param name="enumerator"></param>
    /// <returns></returns>
    public Coroutine StartCoroutine(IEnumerator enumerator)
    {
        return monoController.StartCoroutine(enumerator); ;
    }
}

5.场景切换模块、

提供场景切换的公共接口

/// <summary>
/// 场景切换
/// </summary>
public class SceneMgr : SingleBase<SceneMgr>
{
    /// <summary>
    /// 同步切换场景
    /// </summary>
    /// <param name="name"></param>
    public void LoadScene(string name, UnityAction action)
    {
        //场景同步加载 (肯能会出现卡顿)
        SceneManager.LoadScene(name);
        //场景加载完后 执行加载完后需要执行从方法
        action();
    }

    /// <summary>
    /// 提供给外部的异步加载方法
    /// </summary>
    /// <param name="name"></param>
    /// <param name="action"></param>
    public void LoadSceneAsyn(string name, UnityAction action)
    {
        //开启协程
        MonoManager.Instance.StartCoroutine(ReallyLoadSceneAsyn(name, action));
    }

    /// <summary>
    /// 协程异步加载场景
    /// </summary>
    /// <param name="name"></param>
    /// <param name="action"></param>
    /// <returns></returns>
    public IEnumerator ReallyLoadSceneAsyn(string name, UnityAction action)
    {
        AsyncOperation async = SceneManager.LoadSceneAsync(name);
        //async.progress 场景加载的进度
        while (!async.isDone)//判断是否加载完成
        {
            //事件中心 派发进度条更新事件
            EventCenter.Instance.EventTrigger("进度条", async.progress);
            //进来一次 就返回一次进度 (更新进度条)
            yield return async.progress;
        }
        yield return async;
        //加载完后
        action();
    }
}

6.资源加载模块

提供资源加载的公共接口

/// <summary>
/// 资源管理器
/// </summary>
public class ResManager : SingleBase<ResManager>
{
    /// <summary>
    /// 同步加载资源
    /// </summary>
    /// <param name="name"></param>
    public T Load<T>(string name) where T : Object
    {
        T res = Resources.Load<T>(name);
        //如果资源是GameObjct 实例化后在返回出去
        if (res is GameObject)
            return GameObject.Instantiate(res);
        else
            return res;
    }

    //异步加载
    public void LoadResAsync<T>(string name, UnityAction<T> callback) where T : Object
    {
        //开启异步加载协程
        MonoManager.Instance.StartCoroutine(ReallyLoadAsyn(name, callback));
    }

    /// <summary>
    /// 协调程序函数 开启资源异步加载
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name">资源名字</param>
    /// <param name="callback">资源加载完后回调函数 </param>
    /// <returns></returns>
    private IEnumerator ReallyLoadAsyn<T>(string name, UnityAction<T> callback) where T : Object
    {
        ResourceRequest request = Resources.LoadAsync<T>(name);
        yield return request;
        if (request.asset is GameObject)
        {
            callback(GameObject.Instantiate(request.asset) as T);
        }
        else
        {
            callback(request.asset as T);
        }
    }
}

7.输入控制模块

PC端键盘输入管理Input输入相关逻辑

/// <summary>
/// 输入管理类
/// </summary>
public class InputManager : SingleBase<InputManager>
{
    private bool isStart = false;

    /// <summary>
    /// 可以自动修改 输入键
    /// </summary>
    private KeyCode setCode = KeyCode.A;

    /// <summary>
    /// 构造函数中 添加input 监听
    /// </summary>
    public InputManager()
    {
        MonoManager.Instance.AddUpdateListener(MyUpdata);
    }

    private void MyUpdata()
    {
        //没有开启输入检测 就不去检测
        if (!isStart)
            return;
        CheackKey(KeyCode.W);
        CheackKey(setCode);
        CheackKey(KeyCode.S);
        CheackKey(KeyCode.D);
    }

    /// <summary>
    /// 用来检测某键按下抬起
    /// </summary>
    /// <param name="code"></param>
    private void CheackKey(KeyCode code)
    {
        if (Input.GetKeyDown(code))
            //事件中心 分发按下事件 在需要的地方去监听该事件即可
            EventCenter.Instance.EventTrigger<KeyCode>("按下", code);
        if (Input.GetKeyUp(code))
            EventCenter.Instance.EventTrigger<KeyCode>("抬起", code);
    }

    /// <summary>
    /// 是否开启或关闭输入检测
    /// </summary>
    public void StarOrStopCheack(bool isOpen)
    {
        isOpen = !isOpen;
    }

事件监听

  public void Start()
    {
        StarOrStopCheack(true);
        //监听某键按下
        EventCenter.Instance.EventListenner<KeyCode>("按下", CheackInputDown);
        EventCenter.Instance.EventListenner<KeyCode>("抬起", CheackInputUp);
    }

    private void CheackInputUp(KeyCode key)
    {
        switch (key)
        {
            case KeyCode.W:
                Debug.Log("前进");
                break;

            case KeyCode.S:
                Debug.Log("后退");
                break;

            case KeyCode.A:
                Debug.Log("向左");
                break;

            case KeyCode.D:
                Debug.Log("向右");
                break;
        }
    }

    private void CheackInputDown(KeyCode key)
    {
        switch (key)
        {
            case KeyCode.W:
                Debug.Log("前进");
                break;

            case KeyCode.S:
                Debug.Log("后退");
                break;

            case KeyCode.A:
                Debug.Log("向左");
                break;

            case KeyCode.D:
                Debug.Log("向右");
                break;
        }
    }

8.音效管理模块

统一管理音乐音效相关

/// <summary>
/// 音效管理器
/// </summary>
public class MusicManager : SingleBase<MusicManager>
{
    private AudioSource bkMusic = null;

    /// <summary>
    /// 背景音乐大小
    /// </summary>
    private float bkvalue = 1;

    /// <summary>
    /// 音效大小
    /// </summary>
    private float soundvalue = 1;

    private GameObject soundObj = null;
    private List<AudioSource> audiosList = new List<AudioSource>();

    public MusicManager()
    {
        MonoManager.Instance.AddUpdateListener(Updata);
    }

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

    /// <summary>
    /// 播放背景音效
    /// </summary>
    /// <param name="name"></param>
    public void PlayBackMusic(string name)
    {
        if (bkMusic == null)
        {
            GameObject obj = new GameObject("bkMusic");
            bkMusic = obj.AddComponent<AudioSource>();
        }
        //异步加载背景音乐 加载完成后 播放
        ResManager.Instance.LoadResAsync<AudioClip>("Music/BK/" + name, (clip) =>
        {
            bkMusic.clip = clip;
            bkMusic.loop = true;
            bkMusic.volume = bkvalue;
            bkMusic.Play();
        });
    }

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

    /// <summary>
    /// 改变背景音效大小
    /// </summary>
    /// <param name="value"></param>
    public void SetSoundVaule(float value)
    {
        bkvalue = value;
        if (bkMusic == null)
            return;
        bkMusic.volume = bkvalue;
    }

    /// <summary>
    /// 停止背景音乐
    /// </summary>
    public void StopBackMusic()
    {
        if (bkMusic == null)
            return;
        bkMusic.Stop();
    }

    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="name"></param>
    public void PlaySound(string name, bool isloop, UnityAction<AudioSource> callback = null)
    {
        if (soundObj == null)
        {
            soundObj = new GameObject();
            soundObj.name = "Sound";
        }
        //音效资源异步加载结束后 再添加一个音效
        ResManager.Instance.LoadResAsync<AudioClip>("Sound/" + name, (clip) =>
        {
            AudioSource audio = soundObj.AddComponent<AudioSource>();

            audio.clip = clip;
            audio.volume = soundvalue;
            audio.loop = isloop;
            audio.Play();
            audiosList.Add(audio);
            //如果外面想要使用这个音效 就传入一个回调函数来接它
            if (callback != null)
                callback(audio);
        });
    }

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

    /// <summary>
    /// </summary>
    /// 停止音效
    public void StopSoun(AudioSource source)
    {
        if (audiosList.Contains(source))
        {
            audiosList.Remove(source);
            source.Stop();
            GameObject.Destroy(source);
        }
    }
}

9.UI模块

统一管理UI面板的显示相关(UGUI)
UI基类面板

/// <summary>
///UI面板基类
/// </summary>
public class UIPanelBase : MonoBehaviour
{
    //找到自身的所有的子控件
    private Dictionary<string, List<UIBehaviour>> controllerDic = new Dictionary<string, List<UIBehaviour>>();

    private void Awake()
    {
        FindControlInChildren<Button>();
        FindControlInChildren<Text>();
        FindControlInChildren<Toggle>();
        FindControlInChildren<ScrollRect>();
        FindControlInChildren<Slider>();
    }

    public T GetControl<T>(string controllname) where T : UIBehaviour
    {
        if (controllerDic.ContainsKey(controllname))
        {
            for (int i = 0; i < controllerDic.Count; i++)
            {
                if (controllerDic[controllname][i] is T)
                    return controllerDic[controllname][i] as T;
            }
        }

        return null;
    }

    /// <summary>
    /// 找到子对象的对应空间
    /// </summary>
    /// <typeparam name="T"></typeparam>
    private void FindControlInChildren<T>() where T : UIBehaviour
    {
        T[] contoller = this.GetComponentsInChildren<T>();
        for (int i = 0; i < contoller.Length; i++)
        {
            if (controllerDic.ContainsKey(contoller[i].gameObject.name))
                controllerDic[contoller[i].gameObject.name].Add(contoller[i]);
            else
                controllerDic.Add(contoller[i].gameObject.name, new List<UIBehaviour>() { contoller[i] });
        }
    }

    /// <summary>
    /// 显示自己
    /// </summary>
    public virtual void ShowMe()
    {
    }

    /// <summary>
    /// 隐藏自己
    /// </summary>
    public virtual void HideMe()
    {
    }
}

UI管理面板

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

/// <summary>
/// UI管理器
/// </summary>
public class UiManager : SingleBase<UiManager>
{
    public Dictionary<string, UIPanelBase> panelDic = new Dictionary<string, UIPanelBase>();

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

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

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

    /// <summary>
    /// 通过层级枚举 得到对应层级的父对象
    /// </summary>
    /// <param name="layer"></param>
    /// <returns></returns>
    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="panelName">面板名</param>
    /// <param name="layer">显示在哪一层</param>
    /// <param name="callBack">当面板预设体创建成功后 你想做的事</param>
    public void ShowPanel<T>(string panelName, E_UI_Layer layer = E_UI_Layer.Mid, UnityAction<T> callBack = null) where T : UIPanelBase
    {
        if (panelDic.ContainsKey(panelName))
        {
            panelDic[panelName].ShowMe();
            // 处理面板创建完成后的逻辑
            if (callBack != null)
                callBack(panelDic[panelName] as T);
            //避免面板重复加载 如果存在该面板 即直接显示 调用回调函数后  直接return 不再处理后面的异步加载逻辑
            return;
        }

        ResManager.Instance.LoadResAsync<GameObject>("UI/" + panelName, (obj) =>
        {
            //把他作为 Canvas的子对象
            //并且 要设置它的相对位置
            //找到父对象 你到底显示在哪一层
            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;
            (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 : UIPanelBase
    {
        if (panelDic.ContainsKey(name))
            return panelDic[name] as T;
        return null;
    }
}

10.数据管理模块

统一管理数据存储相关

/// <summary>
/// PlayerPrefs数据管理类 统一管理数据的存储和读取
/// </summary>
public class PlayerPrefsDataMgr
{
    private static PlayerPrefsDataMgr instance = new PlayerPrefsDataMgr();

    public static PlayerPrefsDataMgr Instance
    {
        get
        {
            return instance;
        }
    }

    private PlayerPrefsDataMgr()
    {

    }

    /// <summary>
    /// 存储数据
    /// </summary>
    /// <param name="data">数据对象</param>
    /// <param name="keyName">数据对象的唯一key 自己控制</param>
    public void SaveData( object data, string keyName )
    {
        //就是要通过 Type 得到传入数据对象的所有的 字段
        //然后结合 PlayerPrefs来进行存储

        #region 第一步 获取传入数据对象的所有字段
        Type dataType = data.GetType();
        //得到所有的字段
        FieldInfo[] infos = dataType.GetFields();
        #endregion

        #region 第二步 自己定义一个key的规则 进行数据存储
        //我们存储都是通过PlayerPrefs来进行存储的
        //保证key的唯一性 我们就需要自己定一个key的规则

        //我们自己定一个规则
        // keyName_数据类型_字段类型_字段名
        #endregion

        #region 第三步 遍历这些字段 进行数据存储
        string saveKeyName = "";
        FieldInfo info;
        for (int i = 0; i < infos.Length; i++)
        {
            //对每一个字段 进行数据存储
            //得到具体的字段信息
            info = infos[i];
            //通过FieldInfo可以直接获取到 字段的类型 和字段的名字
            //字段的类型 info.FieldType.Name
            //字段的名字 info.Name;

            //要根据我们定的key的拼接规则 来进行key的生成
            //Player1_PlayerInfo_Int32_age
            saveKeyName = keyName + "_" + dataType.Name + 
                "_" + info.FieldType.Name + "_" + info.Name;

            //现在得到了Key 按照我们的规则
            //接下来就要来通过PlayerPrefs来进行存储
            //如何获取值
            //info.GetValue(data)
            //封装了一个方法 专门来存储值 
            SaveValue(info.GetValue(data), saveKeyName);
        }

        PlayerPrefs.Save();
        #endregion
    }

    private void SaveValue(object value, string keyName)                                                           
    {
        //直接通过PlayerPrefs来进行存储了
        //就是根据数据类型的不同 来决定使用哪一个API来进行存储
        //PlayerPrefs只支持3种类型存储 
        //判断 数据类型 是什么类型 然后调用具体的方法来存储
        Type fieldType = value.GetType();

        //类型判断
        //是不是int
        if( fieldType == typeof(int) )
        {
            //为int数据加密
            int rValue = (int)value;
            rValue += 10;
            PlayerPrefs.SetInt(keyName, rValue);
        }
        else if (fieldType == typeof(float))
        {
            PlayerPrefs.SetFloat(keyName, (float)value);
        }
        else if (fieldType == typeof(string))
        {
            PlayerPrefs.SetString(keyName, value.ToString());
        }
        else if (fieldType == typeof(bool))
        {
            //自己顶一个存储bool的规则
            PlayerPrefs.SetInt(keyName, (bool)value ? 1 : 0);
        }
        //如何判断 泛型类的类型呢
        //通过反射 判断 父子关系
        //这相当于是判断 字段是不是IList的子类
        else if( typeof(IList).IsAssignableFrom(fieldType) )
        {
            //父类装子类
            IList list = value as IList;
            //先存储 数量 
            PlayerPrefs.SetInt(keyName, list.Count);
            int index = 0;
            foreach (object obj in list)
            {
                //存储具体的值
                SaveValue(obj, keyName + index);
                ++index;
            }
        }
        //判断是不是Dictionary类型 通过Dictionary的父类来判断
        else if( typeof(IDictionary).IsAssignableFrom(fieldType) )
        {
            //父类装自来
            IDictionary dic = value as IDictionary;
            //先存字典长度
            PlayerPrefs.SetInt(keyName, dic.Count);
            //遍历存储Dic里面的具体值
            //用于区分 表示的 区分 key
            int index = 0;
            foreach (object key in dic.Keys)
            {
                SaveValue(key, keyName + "_key_" + index);
                SaveValue(dic[key], keyName + "_value_" + index);
                ++index;
            }
        }
        //基础数据类型都不是 那么可能就是自定义类型
        else
        {
            SaveData(value, keyName);
        }
    }

    /// <summary>
    /// 读取数据
    /// </summary>
    /// <param name="type">想要读取数据的 数据类型Type</param>
    /// <param name="keyName">数据对象的唯一key 自己控制</param>
    /// <returns></returns>
    public object LoadData( Type type, string keyName )
    {
        //不用object对象传入 而使用 Type传入
        //主要目的是节约一行代码(在外部)
        //假设现在你要 读取一个Player类型的数据 如果是object 你就必须在外部new一个对象传入
        //现在有Type的 你只用传入 一个Type typeof(Player) 然后我在内部动态创建一个对象给你返回出来
        //达到了 让你在外部 少写一行代码的作用

        //根据你传入的类型 和 keyName
        //依据你存储数据时  key的拼接规则 来进行数据的获取赋值 返回出去

        //根据传入的Type 创建一个对象 用于存储数据
        object data = Activator.CreateInstance(type);
        //要往这个new出来的对象中存储数据 填充数据
        //得到所有字段
        FieldInfo[] infos = type.GetFields();
        //用于拼接key的字符串
        string loadKeyName = "";
        //用于存储 单个字段信息的 对象
        FieldInfo info;
        for (int i = 0; i < infos.Length; i++)
        {
            info = infos[i];
            //key的拼接规则 一定是和存储时一模一样 这样才能找到对应数据
            loadKeyName = keyName + "_" + type.Name +
                "_" + info.FieldType.Name + "_" + info.Name;

            //有key 就可以结合 PlayerPrefs来读取数据
            //填充数据到data中 
            info.SetValue(data, LoadValue(info.FieldType, loadKeyName));
        }
        return data;
    }

    /// <summary>
    /// 得到单个数据的方法
    /// </summary>
    /// <param name="fieldType">字段类型 用于判断 用哪个api来读取</param>
    /// <param name="keyName">用于获取具体数据</param>
    /// <returns></returns>
    private object LoadValue(Type fieldType, string keyName)
    {
        //根据 字段类型 来判断 用哪个API来读取
        if( fieldType == typeof(int) )
        {
            //解密 减10
            return PlayerPrefs.GetInt(keyName, 0) - 10;
        }
        else if (fieldType == typeof(float))
        {
            return PlayerPrefs.GetFloat(keyName, 0);
        }
        else if (fieldType == typeof(string))
        {
            return PlayerPrefs.GetString(keyName, "");
        }
        else if (fieldType == typeof(bool))
        {
            //根据自定义存储bool的规则 来进行值的获取
            return PlayerPrefs.GetInt(keyName, 0) == 1 ? true : false;
        }
        else if( typeof(IList).IsAssignableFrom(fieldType) )
        {
            //得到长度
            int count = PlayerPrefs.GetInt(keyName, 0);
            //实例化一个List对象 来进行赋值
            //用了反射中双A中 Activator进行快速实例化List对象
            IList list = Activator.CreateInstance(fieldType) as IList;
            for (int i = 0; i < count; i++)
            {
                //目的是要得到 List中泛型的类型 
                list.Add(LoadValue(fieldType.GetGenericArguments()[0], keyName + i));
            }
            return list;
        }
        else if( typeof(IDictionary).IsAssignableFrom(fieldType) )
        {
            //得到字典的长度
            int count = PlayerPrefs.GetInt(keyName, 0);
            //实例化一个字典对象 用父类装子类
            IDictionary dic = Activator.CreateInstance(fieldType) as IDictionary;
            Type[] kvType = fieldType.GetGenericArguments();
            for (int i = 0; i < count; i++)
            {
                dic.Add(LoadValue(kvType[0], keyName + "_key_" + i),
                         LoadValue(kvType[1], keyName + "_value_" + i));
            }
            return dic;
        }
        else
        {
            return LoadData(fieldType, keyName);
        }

    }
}

原文地址:https://blog.csdn.net/qq_41860752/article/details/115795540

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍了Unity游戏开发中外观模式是什么意思,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章主要介绍Unity中地面检测方案的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.普通射线在角色坐标(一般是脚底)...
这篇文章主要介绍了Unity游戏开发中如何消除不想要的黄色警告,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带...
这篇文章主要介绍了Unity中有多少种渲染队列,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解
这篇文章主要介绍Unity中如何实现Texture,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!了解Texture2D 如上图,Texture2D是一张
小编给大家分享一下Unity中DOTS要实现的特点有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让...
这篇文章给大家分享的是有关unity中如何实现UGUI遮罩流光特效的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。下面是核心shader:Sh...
这篇文章主要为大家展示了“Unity中如何实现3D坐标转换UGUI坐标”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下...
这篇文章主要介绍了Unity游戏开发中设计模式的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章主要介绍了Unity中如何实现仿真丝袜渲染,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了...
这篇文章给大家分享的是有关Unity插件OVRLipSync有什么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。项目需要接入对话口型动...
这篇文章主要介绍了Unity性能优化之DrawCall的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章给大家分享的是有关Unity给力插件之Final IK怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。这插件有什么用:一般游...
这篇文章给大家分享的是有关Unity中如何内嵌网页插件UniWebView的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、常见Unity中内...
小编给大家分享一下Unity如何做流体物理的几个轮子,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让...
小编给大家分享一下Unity中Lod和Occlusion Culling的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收...
这篇文章将为大家详细讲解有关Unity中LineRenderer与TrailRenderer有什么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获...
这篇文章主要介绍了Unity中coroutine问题的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起...
这篇文章将为大家详细讲解有关unity中spine怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。骨骼动画首先我们来看到...
这篇文章主要为大家展示了“Unity Shader后处理中如何实现简单均值模糊”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学...