????????????????單例模式(Singleton Pattern)是一種常用的創建型設計模式,其核心目標是確保一個類只有一個實例,并提供一個全局訪問點。它常用于需要控制資源訪問、共享配置或管理全局狀態的場景(如數據庫連接池、日志管理器、應用配置等)。????????
單例模式的核心思想
- ?私有構造函數:防止外部通過?
new
?創建多個實例。 - ?靜態私有實例:類內部持有唯一的實例。
- ?全局訪問方法:提供一個靜態方法(如?
getInstance()
)獲取唯一實例。
????????下面來介紹一下在C#和unity中實現的單例模式基類,你某些需要進行單例模式化的腳本,就可以繼承這個基類然后就實現了自己的單例化,那你就可以在其他地方進行使用了。
一、最基本的單例基類
代碼:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;//單例模式基類模塊//1.C#泛型的知識
//2.設計模式中 單例模式的知識
public class BaseManager <T> where T : new()
{//單例模式private static T instance;public static T GetInstance(){if (instance == null){instance = new T();}return instance;}
}
使用方法:
例如下面這個腳本,我們創建了一個NewBehaviourScript的腳本,然后直接繼承單例模式基類,如果其他地方需要調用,就直接使用就行
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class NewBehaviourScript : BaseManager<NewBehaviourScript>
{ void Start(){Debug.Log(NewBehaviourScript.GetInstance());}
}
再來一個示例:
// 子類繼承 BaseManager,并滿足 new() 約束
public class GameManager : BaseManager<GameManager>
{// 必須有一個公共無參構造函數public GameManager() {Debug.Log("GameManager Created");}public void Init(){Debug.Log("GameManager Initialized");}
}// 使用方式
void Start()
{
//可以在你項目中的任意一個地方進行使用GameManager manager = GameManager.GetInstance();manager.Init();// 問題:外部仍然可以 new GameManager(),破壞單例!GameManager another = new GameManager(); // 這是允許的 但是你自己選擇可以不實現 后面我們還有保護措施 使得外部不能實例化
}
二、繼承了Mono的單例模式基類
繼承了Mono那么我們就可以使用Unity的生命周期函數了
代碼:
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{private static T _instance;// 使用屬性替代 GetInstance(),更符合 C# 習慣public static T Instance{get{// 如果實例不存在,嘗試查找或創建if (_instance == null){_instance = FindObjectOfType<T>();// 如果場景中沒有,自動創建一個新的 GameObjectif (_instance == null){GameObject obj = new GameObject(typeof(T).Name);_instance = obj.AddComponent<T>();}}return _instance;}}protected virtual void Awake(){// 如果實例已存在且不是當前對象,銷毀自身if (_instance != null && _instance != this){Destroy(gameObject);return;}// 初始化實例_instance = this as T;// 按需設置跨場景保留DontDestroyOnLoad(gameObject); }
}
還有個簡單的版本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;//C#泛型的知識
//設計模式中 單例模式的知識//繼承了MonoBehaviour的 單例模式對象 需要我們自己保證它的唯一性
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{private static T instance;public static T GetInstance(){//繼承了MonoBehaviour的類,不能直接new//只能通過拖動到對象上 或者通過加腳本的api AddComponent//U3d內部會幫助我們直接實例化return instance;}protected virtual void Awake(){instance = this as T;}
}
請注意:繼承了這個單例模式基類的話,是不能夠自己去new的你只能拖拽到物體身上。
示例:
這樣改進是為了讓我們在沒有繼承Mono的時候,仍然能使用生命周期函數
public class AudioManager : SingletonMono<AudioManager>
{public void PlaySound(string clipName){Debug.Log("Playing: " + clipName);}
}// 使用方式
void Start()
{AudioManager.Instance.PlaySound("BackgroundMusic");
}
示例:
using UnityEngine;// 繼承 SingletonMono,并指定自身為泛型類型 T
public class SoundManager : SingletonMono<SoundManager>
{// 自定義音頻方法public void PlaySound(string clipName){Debug.Log("播放音效: " + clipName);}// 初始化音頻資源(在 Awake 中調用)protected override void Awake(){base.Awake(); // 調用基類的 Awake 方法,確保單例賦值Debug.Log("SoundManager 初始化完成");}
}
創建這樣一個空物體,掛在腳本后,其他的類里面才能使用
使用:
public class PlayerController : MonoBehaviour
{private void Start(){// 獲取 SoundManager 實例并調用方法SoundManager.Instance.PlaySound("跳躍音效");}private void Update(){// 直接通過 Instance 屬性訪問if (Input.GetKeyDown(KeyCode.Space)){SoundManager.Instance.PlaySound("射擊音效");}}
}
三、繼承了mono并且已經自己實例化的
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SingletonAutoMono<T> : MonoBehaviour where T : MonoBehaviour
{private static T instance;public static T GetInstance(){if (instance == null){GameObject obj = new GameObject();//設置對象的名字為腳本名字obj.name = typeof(T).ToString();//讓這個單例模式對象過場景不移除//因為 單例模式對象 往往是存在于整個程序生命周期中的DontDestroyOnLoad(obj);instance = obj.AddComponent<T>();}return instance;}}
使用示例:
在繼承了這個類的腳本里面直接使用內部的函數即可
public class NetworkManager : SingletonAutoMono<NetworkManager>
{public void Connect(string serverIP){Debug.Log($"連接到服務器: {serverIP}");}protected override void Awake(){base.Awake(); // 調用基類 Awake 確保單例初始化Debug.Log("網絡管理器已初始化");}
}// 使用方式
void Start()
{NetworkManager.Instance.Connect("127.0.0.1");
}
?注意事項
-
?手動掛載與自動創建的沖突:
- 如果手動在場景中掛載腳本,需確保只有一個實例。
- 優化后的代碼會優先使用手動掛載的實例。
-
?跨場景行為:
- 若需某個單例僅在特定場景存在,移除?
DontDestroyOnLoad
。
- 若需某個單例僅在特定場景存在,移除?