UI框架

UIPanelType枚举类 存放UI名字和资源路径
EnumExtension枚举类扩展 方便读取Description属性中的值
UIManagerUI管理类 实例化UI 使用栈控制UI的显示/关闭
BaseUIUI基类 被管理的UI需要继承此类

UIPanelType

using System.ComponentModel;

namespace ProjectBase.UIFramework
{
    public enum UIType
    {
        [Description("Prefabs/UIPanel/ItemMessagePanel")]
        ItemMessage,
        [Description("Prefabs/UIPanel/KnapsackPanel")]
        Knapsack ,
        [Description("Prefabs/UIPanel/MainMenuPanel")]
        MainMenu,
        [Description("Prefabs/UIPanel/ShopPanel")]
        Shop,
        [Description("Prefabs/UIPanel/SkillPanel")]
        Skill,
        [Description("Prefabs/UIPanel/SystemPanel")]
        System ,
        [Description("Prefabs/UIPanel/TaskPanel")]
        Task,
        
    }
}

通过Description属性来存储UI的资源路径,但是读取这个属性比较麻烦所以写了个扩展方法

EnumExtension

using System;
using System.ComponentModel;
using System.Reflection;

namespace ProjectBase.UIFramework
{
    public static class EnumExtension
    {
        /// <summary>
        /// 扩展方法,获得枚举的Description
        /// </summary>
        /// <param name="value">枚举值</param>
        /// <param name="nameInstend">当枚举没有定义DescriptionAttribute,是否用枚举名代替,默认使用</param>
        /// <returns>枚举的Description</returns>
        public static string GetDescription(this Enum value, bool nameInstend = true)
        {
            Type type = value.GetType();
            string name = Enum.GetName(type, value);
            if (name==null)
            {
                return null;
            }
            FieldInfo field = type.GetField(name);
            DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attribute==null&&nameInstend)
            {
                return name;
            }
            return attribute?.Description;
        }
    }
}

UIManager

using System.Collections.Generic;
using ProjectBase.SingleBase;
using UnityEngine;

namespace ProjectBase.UIFramework
{
    public class UIManager : Single<UIManager>
    {
        private Dictionary<UIType, BaseUI> panelDict = new Dictionary<UIType, BaseUI>();
        private Stack<BaseUI> panelStack = new Stack<BaseUI>();
        private Transform canvasTransform;

        public UIManager()
        {
            canvasTransform = GameObject.Find("Canvas").transform;
        }

        /// <summary>
        /// 获得panel 如果没有就实例化一个新的
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private BaseUI GetPanel(UIType type)
        {
            if (panelDict.TryGetValue(type, out BaseUI baseUI)) return baseUI;
            GameObject obj = Object.Instantiate(Resources.Load<GameObject>(type.GetDescription()), canvasTransform, false);
            baseUI = obj.GetComponent<BaseUI>();
            panelDict.Add(type, baseUI);
            return baseUI;
        }

        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="type"></param>
        public void PushPanel(UIType type)
        {
            if (panelStack.Count>0)
            {
                panelStack.Peek().OnPause();
            }
            BaseUI baseUI = GetPanel(type);
            baseUI.OnEnter();
            panelStack.Push(baseUI);
        }
        
        /// <summary>
        /// 出栈
        /// </summary>
        public void PopPanel()
        {
            if (panelStack.Count <= 0) return;
            BaseUI baseUI = panelStack.Pop();
            baseUI.OnExit();
            if (panelStack.Count>0)
            {
                panelStack.Peek().OnResume();
            }
        }
    }
}

BaseUI

using UnityEngine;

namespace ProjectBase.UIFramework
{
    public class BaseUI : MonoBehaviour
    {
        protected CanvasGroup canvasGroup;

        /// <summary>
        /// 界面被显示出来
        /// </summary>
        public virtual void OnEnter()
        {
            if (canvasGroup == null)
            {
                canvasGroup = GetComponent<CanvasGroup>();
                if (canvasGroup == null)
                {
                    canvasGroup = gameObject.AddComponent<CanvasGroup>();
                }
            }

            canvasGroup.alpha = 1;
            canvasGroup.blocksRaycasts = true;
        }

        /// <summary>
        /// 界面暂停
        /// </summary>
        public virtual void OnPause()
        {
            canvasGroup.blocksRaycasts = false;
        }

        /// <summary>
        /// 界面继续
        /// </summary>
        public virtual void OnResume()
        {
            canvasGroup.blocksRaycasts = true;
        }

        /// <summary>
        /// 退出页面
        /// </summary>
        public virtual void OnExit()
        {
            canvasGroup.alpha = 0;
            canvasGroup.blocksRaycasts = false;
        }

    }
}

几种单例模式基类的写法

不继承Mono 线程安全 懒汉模式

namespace ProjectBase.SingleBase
{
    /// <summary>
    /// 单例模式基类 懒汉模式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Single<T> where T : new()
    {
        public static T Instance = new T();

        protected Single()
        {
        }
    }
}

不继承Mono 线程不安全 饿汉模式

namespace ProjectBase.SingleBase
{
    /// <summary>
    /// 单例模式基类 饿汉模式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Single<T> where T : new()
    {
        private static T instance;

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

                return instance;
            }
        }

        protected Single()
        {
        }
    }
}

不继承Mono 线程安全 饿汉模式

namespace ProjectBase.SingleBase
{
    /// <summary>
    /// 单例模式基类 饿汉模式 线程安全
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Single<T> where T : new()
    {
        public static T Instance => InnerInstance.instance;
        
        protected Single()
        {
        }

        private class InnerInstance //通过静态内部类实现的单例模式
        {
            /// <summary>
            /// 当一个类有静态构造函数时,它的静态成员变量不会被beforefieldinit修饰
            /// 就会确保在被引用的时候才会实例化,而不是程序启动的时候实例化
            /// </summary>
            static InnerInstance()
            {
            }

            internal static readonly T instance = new T();
        }
    }
}

继承Mono

using UnityEngine;
/// <summary>
/// 单例模式基类 继承mono
/// </summary>
/// <typeparam name="T"></typeparam>
public class SingleMono<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance != null) return instance;
            GameObject obj = new GameObject {name = typeof(T).ToString()};
            //让这个单例模式对象在切换场景时不被移除
            DontDestroyOnLoad(obj);
            instance = obj.AddComponent<T>();
            return instance;
        }
    }
}

通过继承单例基类实现的单例模式 虽然可以少写一些代码 但继承是不能私有构造方法的 所以这个单例对象是可以被外界new出来的 目前我没找到解决方法 写代码的时候注意就好了 另外 unity本身是不支持多线程的 基本不需要考虑线程安全的问题 如果是不需要继承mono的单例 第二个是比较好的选择

公共Mono模块

作用:

  1. 让没有继承Mono的模块 也可以被Unity生命周期函数调用或使用协程
  2. 可以节约部分反射造成的性能开销
using ProjectBase.SingleBase;
using UnityEngine.Events;


public class MonoManager : SingleMono<MonoManager>
{
    private event UnityAction updateEvent;
    private event UnityAction fixedUpdateEvent;
    private void Update()
    {
        updateEvent?.Invoke();
    }

    private void FixedUpdate()
    {
        fixedUpdateEvent?.Invoke();
    }
    
    public void AddUpdateListener(UnityAction fun)
    {
        updateEvent += fun;
    }

    public void AddFixedUpdateListener(UnityAction fun)
    {
        fixedUpdateEvent += fun;
    }

    public void RemoveFixedUpdateListener(UnityAction fun)
    {
        fixedUpdateEvent -= fun;
    }

    public void RemoveUpdateListener(UnityAction fun)
    {
        updateEvent -= fun;
    }
    
}

Unity对象池实现

PoolManager对象池管理类管理不同的对象池 实现对象池的增删查功能
PoolData 对象池实例每个不同种类的对象 都存放在一个单独的对象池实例中
IPool 生命周期函数接口因为对象池是通过SetActive的方式来取出或者回收物体的 这个过程中不会调用Unity的声明周期函数 所以通过一个接口 让对象池中的对象实现这个接口 实现自己的声明周期函数

PoolManager

using System.Collections.Generic;
using ProjectBase.SingleBase;
using UnityEngine;

namespace ProjectBase.Pool
{
    /// <summary>
    /// 对象池管理类
    /// </summary>
    public class PoolManager : Single<PoolManager>
    {
        private Dictionary<string, PoolData> poolDicts = new Dictionary<string, PoolData>();

        /// <summary>
        /// 创建对象池
        /// </summary>
        /// <param name="path">资源路径</param>
        /// <param name="size">对象池大小</param>
        /// <returns></returns>
        public PoolData AddPool(string path, int size = 100)
        {
            PoolData poolData = null;
            GameObject obj = Resources.Load<GameObject>(path);
            if (obj != null)
            {
                poolDicts.Add(obj.name, new PoolData(obj, size)); //对象池的用物体名来命名
                poolData = poolDicts[obj.name];
            }

            return poolData;
        }

        /// <summary>
        /// 根据name获取对象池
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public PoolData GetPool(string name) => poolDicts.ContainsKey(name) ? poolDicts[name] : null;
        
        /// <summary>
        /// 清空对象池
        /// </summary>
        public void Clear(string name = "")
        {
            if (name.Equals(""))
            {
                poolDicts.Clear();
            }
            else if (poolDicts.ContainsKey(name))
            {
                poolDicts.Remove(name);
            }
        }
    }
}

Single是一个泛形的单例模式基类,使用物体名来命名对象池 好处是非常方便 对象池中的物体只需要通过gameObject.name或者name属性就能找到自己所在的对象池

c#扩展方法

扩展方法可以用新方法扩展现有类型改变原始类型的定义。

扩展方法是静态类的静态方法,其中this修饰符应用于第一个参数。

第一个参数的类型将是扩展的类型。

public static class StringExtension {
//需要静态的class
   public static bool IsCapitalized (this string s) {
      if (string.IsNullOrEmpty(s))
         return false;
      return char.IsUpper (s[0]);
   }
}

IsCapitalized扩展方法可以被调用,就像它是一个字符串上的实例方法,如下所示:

Console.WriteLine ("abc".IsCapitalized());

一个扩展方法调用被转换回一个普通的静态方法调用:

Console.WriteLine (StringExtension.IsCapitalized ("abc"));

使用JsonUtility解析Json

Unity5.3开始,提供了一个JSON data的序列化与反序列化的类JsonUtility

现在我要解析的Json数据如下

{
  "infoList": [
    {
      "panelTypeString": "ItemMessage",
      "path": "UIPanel/ItemMessagePanel"
    },
    {
      "panelTypeString": "Knapsack",
      "path": "UIPanel/KnapsackPanel"
    },  
  ]
}

注意,不能通过JsonUtility直接解析Json数组,需要把数组存入对象中才可以正常解析。

接下来需要创建用[Serializable]标记的类,类中的属性名要与Json中对象的名字对应,如infoList就要对应类中的的infoList

由于infoList对象中是一个数组,所以使用List来存放数组中的对象

[System.Serializable]
public class UIPanelInfo
{
   public string panelTypeString;
   public string path; 
}

[System.Serializable] //序列化标签
class UIPanelTypeJson
{
   //属性名和Json对象名保持一致
   public List<UIPanelInfo> infoList;
}

JsonUtility解析出来的数据都是string类型的,如果想要获得其他类型如枚举类型的数据,则可以通过实现ISerializationCallbackReceiver接口中的OnAfterDeserialize()方法进行转换,该方法在反序列化后会被调用。

public class UIPanelInfo : ISerializationCallbackReceiver
{
   // 枚举类型 不能反序列化
   [NonSerialized] public UIPanelType panelType;
//[NonSerialized]不被序列化 标签
   //反序列化: 文本 转 对象
   //属性名要和Json的key对应
   public string panelTypeString;
   public string path;
   public void OnBeforeSerialize()
   {
      //该方法在反序列化前调用
   }
  /// <summary>
   /// 因为FromJson不能反序列化枚举类型
   /// 所以将序列化的String字符串,转换成enum枚举类型 并赋值给panelTyle
   /// </summary>
   public void OnAfterDeserialize()
   {
      //把String转成UIPanelType类型Enum
      UIPanelType type = (UIPanelType)Enum.Parse(typeof(UIPanelType), panelTypeString);
      //赋值给panelType
      panelType = type;
   }
}

最后调用JsonUtility中的FromJson方法来解析Json

延时功能的几种实现方式

1、Update函数

  float m_timer = 0;
    void Update()
    {
        m_timer += Time.time;
        if (m_timer >= 5)//延时五秒钟调用B();
        {
            ShowB();
            m_timer = 0;
        }
    }
    private void B()
    {
    }

2、Invoke函数

private void A(){
    Invoke("B",5f);
    //在A方法中延迟五秒调用B方法
}
private void B(){

}

3、Coroutine 协程

  private IEnumerator A()
    {
        yield return new WaitForSeconds(5);
        B();
    }

    private void B()
    {
    }

4、DOTween

使用Fungus插件快速实现对话系统

官方文档:http://fungusdocs.snozbot.com/

官网:http://fungusgames.com/

1.安装Fungus

进入unity资源商店下载并导入到项目中

/usr/uploads/2019/11/QQ20191121-204437@2x.png

成功导入插件后,unity的标题栏会多出来一个tools

/usr/uploads/2019/11/14C859EA-D6FF-41B3-BDE2-BF412E34DFA7.jpg

2.使用

首先在新建一个Flowchart,此时在Hiearchy面板中会多出来一个Flowchart组件

接着把Flowchart Window窗口显示出来

/usr/uploads/2019/11/916CB74E-B91B-4F99-A771-6CBEB584F7B2.jpg

这时候会出现一个类似动画状态机的东西,我们点击New Block

/usr/uploads/2019/11/0D5C279E-88FD-43CE-A1F7-B17F4C7B6037.jpg

/usr/uploads/2019/11/QQ20191121-205844@2x.png

块名(Block Name)也就是这段对话的名字

自定义颜色(Custom Tint)

块描述(Description)这段对话的备注

重点是下面的Execute On Event属性

这里控制对话的触发事件 默认的状态是Game Started也就是在游戏场景启动时触发对话 并不是游戏开始时触发

/usr/uploads/2019/11/QQ20191121-210750@2x.png

C#中 (int) 、Int.Parse() 、Convert.toInt32()区别

强制类型转换

不遵循四舍五入,只截取整数部分;

(int)5.21       //输出5

Int.Parse

只支持将string类型转成int

String st="5.21";
double n=5.21;
Int.Parse(st);      //输出5
Int.Parse(n);     //报错

Convert.ToInt32(double value)

不完全遵循四舍五入

如果 value 为两个整数中间的数字,则返回二者中的偶数

Console.WriteLine(Convert.ToInt32(4.3));  //四舍五入,输出4
Console.WriteLine(Convert.ToInt32(4.5));  //第一位小数为5时,4.5在4和5之间,输出偶数4
Console.WriteLine(Convert.ToInt32(4.53)); //四舍五入,输出5
Console.WriteLine(Convert.ToInt32(5.3));  //四舍五入,输出5
Console.WriteLine(Convert.ToInt32(5.5));  //第一位小数为5时,5.5在5和6之间,输出偶数6
Console.WriteLine(Convert.ToInt32(5.53)); //四舍五入,输出6

注意:

Convert.ToInt32()int.Parse()对于空值的处理不同

  • Convert.ToInt32(null)返回0 不抛出异常
  • int.Parse(null) 抛出异常

Unity之SerializeField

**[HideInInspector]**表示将原本显示在面板上的序列化值隐藏起来。

**[SerializeField]**表示将原本不会被序列化的私有变量和保护变量变成可以被序列化的,那么它们在下次读取的值就是你上次赋值的值。

1、如果a是公有的序列化变量。

(1)如果想要在面板中看到变量a,那么用:

 public int a;

(2)如果不想在面板中看到变量a,那么用:

[HideInInspector] public int a;

2、如果a是私有的序列化变量

(1)如果想在面板中读取并赋值,那么用:

 [SerializeField] private int a;

(2)想在面板中读取,但是不赋值,那么用:

  [HideInInspector][SerializedField] private int a;
  public int b{ get{return a;} }

然后在Editor中显示,EditorGUILayout.LabelField(“value”,game.b.ToString());