Unity 基础框架_unity程序基础框架-程序员宅基地

技术标签: unity  unity知识学习分享  javascript  Unity 简单框架  

1、 单例模式基类

单例模式可以方便代码在不同位置调用系统

1.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 (intance == null)
                intance = new T();
            return intance;
        }
    }
}

1.2 需要继承Mono的基类 需要在Awake 中初始化,需要手动挂载到Gameobject,跨场景会销毁

using UnityEngine;
/// <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;
    }
}

1.3 需要继承mono基类,自动创建Gameobject并且跨场景不会销毁

using UnityEngine;
/// <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和内存消耗,减少对象的重复创建与销毁

using System.Collections.Generic;
using UnityEngine;
/// <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");
            GameObject.DontDestroyOnLoad(poolObj);
        }

        //里面有抽屉
        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、事件中心模块

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

//里氏转换原则  来避免装箱拆箱
using System.Collections.Generic;
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>
/// 事件中心
/// </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 AddEventListenner<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 AddEventListenner(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

using UnityEngine.Events;
using UnityEngine;
/// <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;
    }
}
using UnityEngine.Events;
using UnityEngine;
using System.Collections;
/// <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);
    }

    public void StopCoroutine(IEnumerator enumerator)
    {
        monoController.StopCoroutine(enumerator);
    }

    public void StopCoroutine(Coroutine enumerator)
    {
        monoController.StopCoroutine(enumerator);
    }
}

5、场景切换模块

提供场景切换的公共接口

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

    public void LoadScene(int sceneBuildIndex, UnityAction action)
    {
        //场景同步加载 (肯能会出现卡顿)
        SceneManager.LoadScene(sceneBuildIndex);
        //场景加载完后 执行加载完后需要执行从方法
        action();
    }

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

    public void LoadSceneAsyn(int sceneBuildIndex, UnityAction action)
    {
        MonoManager.Instance.StartCoroutine(ReallyLoadSceneAsyn(sceneBuildIndex, 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);
            Debug.Log("async.progress:" + async.progress);
            //进来一次 就返回一次进度 (更新进度条)
            yield return async.progress;
        }
        yield return async;
        //加载完后
        action();
    }

    public IEnumerator ReallyLoadSceneAsyn(int sceneBuildIndex, UnityAction action)
    {
        AsyncOperation async = SceneManager.LoadSceneAsync(sceneBuildIndex);
        //async.progress 场景加载的进度
        while (!async.isDone)//判断是否加载完成
        {
            //事件中心 派发进度条更新事件
            EventCenter.Instance.EventTrigger("进度条", async.progress);
            Debug.Log("async.progress:" + async.progress);
            //进来一次 就返回一次进度 (更新进度条)
            yield return async.progress;
        }
        yield return async;
        //加载完后
        action();
    }
}

6、资源加载模块

提供资源加载的公共接口

using UnityEngine.Events;
using UnityEngine;
using System.Collections;
/// <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、音效管理模块

统一管理音乐音效相关

using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine;
/// <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);
        }
    }
}

8、数据管理模块

统一管理数据存储相关

using System.Reflection;
using System;
using UnityEngine;
using System.Collections;
/// <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);
        }

    }
}

9、UI模块

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

using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine;
/// <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()
    {
    }
}
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine;
/// <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;
    }
}

Unity的基础程序框架_unity除了缓存池还有什么框架-程序员宅基地

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhoutao2333/article/details/136563697

智能推荐

leetcode 172. 阶乘后的零-程序员宅基地

文章浏览阅读63次。题目给定一个整数 n,返回 n! 结果尾数中零的数量。解题思路每个0都是由2 * 5得来的,相当于要求n!分解成质因子后2 * 5的数目,由于n中2的数目肯定是要大于5的数目,所以我们只需要求出n!中5的数目。C++代码class Solution {public: int trailingZeroes(int n) { ...

Day15-【Java SE进阶】IO流(一):File、IO流概述、File文件对象的创建、字节输入输出流FileInputStream FileoutputStream、释放资源。_outputstream释放-程序员宅基地

文章浏览阅读992次,点赞27次,收藏15次。UTF-8是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节。文件字节输入流:每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1。注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码。定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。UTF-8字符集:汉字占3个字节,英文、数字占1个字节。GBK字符集:汉字占2个字节,英文、数字占1个字节。GBK规定:汉字的第一个字节的第一位必须是1。_outputstream释放

jeecgboot重新登录_jeecg 登录自动退出-程序员宅基地

文章浏览阅读1.8k次,点赞3次,收藏3次。解决jeecgboot每次登录进去都会弹出请重新登录问题,在utils文件下找到request.js文件注释这段代码即可_jeecg 登录自动退出

数据中心供配电系统负荷计算实例分析-程序员宅基地

文章浏览阅读3.4k次。我国目前普遍采用需要系数法和二项式系数法确定用电设备的负荷,其中需要系数法是国际上普遍采用的确定计算负荷的方法,最为简便;而二项式系数法在确定设备台数较少且各台设备容量差..._数据中心用电负荷统计变压器

HTML5期末大作业:网页制作代码 网站设计——人电影网站(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 dreamweaver作业静态HTML网页设计模板_网页设计成品百度网盘-程序员宅基地

文章浏览阅读7k次,点赞4次,收藏46次。HTML5期末大作业:网页制作代码 网站设计——人电影网站(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 dreamweaver作业静态HTML网页设计模板常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 明星、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 军事、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他 等网页设计题目, A+水平作业_网页设计成品百度网盘

【Jailhouse 文章】Look Mum, no VM Exits_jailhouse sr-iov-程序员宅基地

文章浏览阅读392次。jailhouse 文章翻译,Look Mum, no VM Exits!_jailhouse sr-iov

随便推点

chatgpt赋能python:Python怎么删除文件中的某一行_python 删除文件特定几行-程序员宅基地

文章浏览阅读751次。本文由chatgpt生成,文章没有在chatgpt生成的基础上进行任何的修改。以上只是chatgpt能力的冰山一角。作为通用的Aigc大模型,只是展现它原本的实力。对于颠覆工作方式的ChatGPT,应该选择拥抱而不是抗拒,未来属于“会用”AI的人。AI职场汇报智能办公文案写作效率提升教程 专注于AI+职场+办公方向。下图是课程的整体大纲下图是AI职场汇报智能办公文案写作效率提升教程中用到的ai工具。_python 删除文件特定几行

Java过滤特殊字符的正则表达式_java正则表达式过滤特殊字符-程序员宅基地

文章浏览阅读2.1k次。【代码】Java过滤特殊字符的正则表达式。_java正则表达式过滤特殊字符

CSS中设置背景的7个属性及简写background注意点_background设置背景图片-程序员宅基地

文章浏览阅读5.7k次,点赞4次,收藏17次。css中背景的设置至关重要,也是一个难点,因为属性众多,对应的属性值也比较多,这里详细的列举了背景相关的7个属性及对应的属性值,并附上演示代码,后期要用的话,可以随时查看,那我们坐稳开车了······1: background-color 设置背景颜色2:background-image来设置背景图片- 语法:background-image:url(相对路径);-可以同时为一个元素指定背景颜色和背景图片,这样背景颜色将会作为背景图片的底色,一般情况下设置背景..._background设置背景图片

Win10 安装系统跳过创建用户,直接启用 Administrator_windows10msoobe进程-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏8次。Win10 安装系统跳过创建用户,直接启用 Administrator_windows10msoobe进程

PyCharm2021安装教程-程序员宅基地

文章浏览阅读10w+次,点赞653次,收藏3k次。Windows安装pycharm教程新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入下载安装PyCharm1、进入官网PyCharm的下载地址:http://www.jetbrains.com/pycharm/downl_pycharm2021

《跨境电商——速卖通搜索排名规则解析与SEO技术》一一1.1 初识速卖通的搜索引擎...-程序员宅基地

文章浏览阅读835次。本节书摘来自异步社区出版社《跨境电商——速卖通搜索排名规则解析与SEO技术》一书中的第1章,第1.1节,作者: 冯晓宁,更多章节内容可以访问云栖社区“异步社区”公众号查看。1.1 初识速卖通的搜索引擎1.1.1 初识速卖通搜索作为速卖通卖家都应该知道,速卖通经常被视为“国际版的淘宝”。那么请想一下,普通消费者在淘宝网上购买商品的时候,他的行为应该..._跨境电商 速卖通搜索排名规则解析与seo技术 pdf

推荐文章

热门文章

相关标签