使用单例模式Singleton
这几天想把在实习里碰到的一些好的技巧写在这里,也算是对实习的一个总结。好啦,今天要讲的是在Unity里应用一种非常有名的设计模式——单例模式。开场白
单例模式的简单介绍请看前面的链接,当然网上还有很多更详细的介绍,有兴趣的童靴可以了解一下。其实设计模式对于一个程序员来说还是非常有用的,这点随着学习的深入感受越来越深。
好啦,现在说一下Unity里的单例模式。什么时候需要使用单例模式呢?正如它的名字一样,你认为一些东西在整个游戏中只有一个而你又想可以方便地随时访问它,这时你就可以考虑单例模式了。例如,你的游戏可能需要一个管理音乐播放的脚本,或者一个管理场景切换的脚本,或者一个管理玩家信息的通用脚本,又或者是管理游戏中各种常用UI的脚本。事实上,这些都是非常常用而且必要的。
实现
庆幸的是,单例模式的代码非常简单。下面是Singleton.cs的内容:
using System; using System.Collections; using System.Collections.Generic; public class Singleton : MonoBehaviour { private static GameObject m_Container = null; private static string m_Name = "Singleton"; private static Dictionary<string, object> m_SingletonMap = new Dictionary<string, object>(); private static bool m_IsDestroying = false; public static bool IsDestroying { get { return m_IsDestroying; } } public static bool IsCreatedInstance(string Name) { if(m_Container == null) { return false; } if (m_SingletonMap!=null && m_SingletonMap.ContainsKey(Name)) { return true; } return false; } public static object getInstance (string Name) { if(m_Container == null) { Debug.Log("Create Singleton."); m_Container = new GameObject (); m_Container.name = m_Name; m_Container.AddComponent (typeof(Singleton)); } if (!m_SingletonMap.ContainsKey(Name)) { if(System.Type.GetType(Name) != null) { m_SingletonMap.Add(Name, m_Container.AddComponent (System.Type.GetType(Name))); } else { Debug.LogWarning("Singleton Type ERROR! (" + Name + ")"); } } return m_SingletonMap[Name]; } public void RemoveInstance(string Name) { if (m_Container != null && m_SingletonMap.ContainsKey(Name)) { UnityEngine.Object.Destroy((UnityEngine.Object)(m_SingletonMap[Name])); m_SingletonMap.Remove(Name); Debug.LogWarning("Singleton REMOVE! (" + Name + ")"); } } void Awake () { Debug.Log("Awake Singleton."); DontDestroyOnLoad (gameObject); } void Start() { Debug.Log("Start Singleton."); } void Update() { } void OnApplicationQuit() { Debug.Log("Destroy Singleton"); if(m_Container != null) { GameObject.Destroy(m_Container); m_Container = null; m_IsDestroying = true; } } }
代码大部分都比较容易看懂,下面介绍几点注意的地方:
当我们在其他代码里需要访问某个单例时,只需调用getInstance函数即可,参数是需要访问的脚本的名字。我们来看一下这个函数。它首先判断所有单例所在的容器m_Container是否为空(实际上就是场景中是否存在一个Gameobject,上面易做图了一个Singleton脚本),如果为空,它将自动创建一个对象,然后以“Singleton”命名,再易做图Singleton脚本。m_SingletonMap是负责维护所有单例的映射。当第一次访问某个单例时,它会自动向m_Container上添加一个该单例类型的Component,并保存在单例映射中,再返回这个单例。因此,我们可以看出,单例的创建完全都是自动的,你完全不需要考虑在哪里、在什么时候易做图脚本,这是多么令人高兴得事情!
在Awake函数中,有一句代码DontDestroyOnLoad (gameObject);,这是非常重要的,这句话意味着,当我们的场景发生变化时,单例模式将不受任何影响。除此之外,我们还要注意到,这句话也必须放到Awake函数,而不能放到Start函数中,这是由两个函数的执行顺序决定的,如果反过来,便可能会造成访问单例不成功,下面的例子里会更详细的介绍;
在OnApplicationQuit函数中,我们将销毁单例模式。
最后一点很重要:一定不要在OnDestroy函数中直接访问单例模式!这样很有可能会造成单例无法销毁。这是因为,当程序退出准备销毁单例模式时,我们在其他脚本的OnDestroy函数中再次请求访问它,这样将重新构造一个新的单例而不会被销毁(因为之前已经销毁过一次了)。如果一定要访问的话,一定要先调用IsCreatedInstance,判断该单例是否存在。
例子
下面,我们通过一个小例子来演示单例模式的使用。
首先,我们需要创建如上的Singleton脚本。然后,再创建一个新的脚本SingletonSample.cs用于测试,其内容如下:
using UnityEngine; using System.Collections; public class SingletonSample : MonoBehaviour { // Use this for initialization void Start () { TestSingleton(); } // Update is called once per frame void Update () { } private void TestSingleton() { LitJsonSample litjson = Singleton.getInstance("LitJsonSample") as LitJsonSample; litjson.DisplayFamilyList(); } // void OnDestroy() { // LitJsonSample litjson = Singleton.getInstance("LitJsonSample") as LitJsonSample; // // litjson.DisplayFamilyList(); // } }
注意,为了方便,我使用了上一篇博文里使用的Litjson的代码,并做了少许修改。下面是修改后的LitJsonSample.cs:
using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; using LitJson; public class FamilyInfo { public string name; public int age; public string tellphone; public string address; } public class FamilyList { public List<FamilyInfo> family_list; } public class LitJsonSample : MonoBehaviour { public FamilyList m_FamilyList = null; // Use this for initialization void Awake () { ReloadFamilyData(); } private void ReloadFamilyData() { //AssetDatabase.ImportAsset("Localize/family.txt"); UnityEngine.TextAsset s = Resources.Load("Localize/family") as TextAsset; string tmp = s.text; m_FamilyList = JsonMapper.ToObject<FamilyList>( tmp ); if ( JsonMapper.HasInterpretError() ) { Debug.LogWarning( JsonMapper.GetInterpretError() ); } } public void DisplayFamilyList() { if (m_FamilyList == null) return; foreach (FamilyInfo info in m_FamilyList.family_list) { Debug.Log("Name:" + info.name + " Age:" + info.age + " Tel:" + info.tellphone + " Addr:" + info.address); } } // Update is called once per frame void Update () { } }
补充:软件开发 , C# ,