Singletonってなに?
みなさんSingleton使ってますか?
私も勉強している最中ですが、いわゆるデザインパターンと呼ばれるプログラムの設計パターンの一つです。
Singletonはアプリケーションの中で該当のクラスが1つしか存在しないことを保証するデザインパターンとなります。
なにが便利?
このSingletonパターンはUnityでも使用することができます。
クラスのインスタンスがどこからでも参照できるようになるので、多用するのもあまり良くないらしいですが、私は便利なので結構使ってしまっています。
特にGameManagerなど諸々を管理するマネージャクラスは色んなところからアクセスしたくなります。
しかし、そのたびにコンポーネントにフィールドを用意して、インスペクタから参照を設定して・・・っていうことをマネージャの参照が必要なコンポーネント全てに対して行っていくのは地味に面倒です。
そこで便利なのがSingletonです。
実装例
自分の場合はSingleton用の基底クラスを下記のように実装しています。
using UnityEngine;
/// <summary>
/// 標準的なシングルトンMonoBehaviourの基底クラス。
/// 即時初期化が必要な場合や、小規模なManagerクラスに適しています。
///
/// 注意: このクラスは、GameObjectにアタッチされている場合とそうでない場合で挙動が異なります。
/// - アタッチされている場合: Awakeメソッドはシーンのロード時に自動的に呼ばれます。
/// - アタッチされていない場合: 最初のInstanceアクセス時に新しいGameObjectが作成され、
/// その時点でAwakeメソッドが呼ばれます。
/// </summary>
/// <typeparam name="T">派生クラスの型</typeparam>
public abstract class SingletonMonoBehaviour<T> : MonoBehaviour where T : SingletonMonoBehaviour<T>
{
private static T instance;
private static bool applicationIsQuitting = false; // フラグを追加
/// <summary>
/// シングルトンインスタンスへのアクセサ。
/// インスタンスが存在しない場合は新たに作成します。
/// </summary>
public static T Instance
{
get
{
if (applicationIsQuitting)
{
return null; // アプリケーション終了中は新たにインスタンスを生成しない
}
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
GameObject obj = new GameObject(typeof(T).Name);
instance = obj.AddComponent<T>();
}
}
return instance;
}
}
/// <summary>
/// DontDestroyOnLoadを使用するかどうかを指定します。
/// trueの場合、シーン遷移時もオブジェクトが破棄されません。
/// </summary>
[SerializeField] protected bool dontDestroyOnLoad = false;
/// <summary>
/// Awakeメソッド。シングルトンの設定と初期化を行います。
/// </summary>
protected virtual void Awake()
{
if (instance == null)
{
instance = this as T;
if (dontDestroyOnLoad)
{
if (transform.parent != null)
{
Debug.LogWarning($"[Singleton] {typeof(T)} is marked as DontDestroyOnLoad, but it has a parent. Detaching from parent.");
transform.SetParent(null);
}
DontDestroyOnLoad(gameObject);
}
OnAwake();
}
else if (instance != this)
{
Debug.LogWarning($"[Singleton] Instance already exists - destroying duplicate {gameObject.name}");
Destroy(gameObject);
}
}
/// <summary>
/// 派生クラスで上書きして、カスタムの初期化ロジックを実装します。
/// </summary>
protected virtual void OnAwake() { }
/// <summary>
/// アプリケーション終了時の処理。シングルトンインスタンスをクリーンアップします。
/// </summary>
protected virtual void OnApplicationQuit()
{
applicationIsQuitting = true; // フラグを設定
if (instance == this)
{
instance = null;
}
}
}
これを利用する際にはまず、これを継承したクラスを作成します。
例えば以下のような形。SingletonMonoBehaviourを継承してInputManagerを定義しています。
実装上、ここで作成したInputManagerはGameObjectにアタッチしなくてもアクセスした時点で自動的にゲームオブジェクトが作成されます。
なので、GameObjectにアタッチするかはお好みで。私はアタッチして使っています。
using System;
using UnityEngine;
public class InputManager : SingletonMonoBehaviour<InputManager>
{
protected override void Awake()
{
base.Awake();
if (Instance == this)
{
//初期化が必要であればここで
}
}
private void Start()
{
}
private void Update()
{
}
public void SampleFunction()
{
//なにかの処理
}
}
これを参照して使用するには下記のように「クラス名.Instance」という形でアクセスすることができます。
見ての通りフィールドを用意したりGetComponetなどを使うことなく、InputManagerの機能を利用することができます。
public class TestScript : MonoBehaviour
{
// Awake is called when the script instance is being loaded.
void Awake()
{
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
InputManager.Instance.SampleFunction(); //Instanceプロパティ経由でアクセスを行う
}
}
GameObjectにアタッチしていない場合にはコードの何処かで最初にアクセスしたタイミングでスクリプトがGameObjectを作成します。
さいごに
説明は以上です。
使ってみると便利なので、自分が制御できる範囲で活用してみてください。
コメント