LoginSignup
0
0

More than 3 years have passed since last update.

(備忘録)Unity(C#)でのシングルトンパターン

Posted at

概要

  • ゲームの全体を通してパラメータなどを管理するクラスの作成
  • シングルトンパターンでの実装
  • prefabからのオブジェクトの取得も可
  • 別で基となる基底クラス(親クラス)を作成しない(ジェネリックを使わない)

*注意点
筆者はプログラミング苦手でUnityもC#も初心者ですし、備忘録なので、雰囲気で書いています。あまり信用しすぎないでください...

したいこと

したいことは、Unityでパラメータやprefabから取得したゲームオブジェクトをゲーム全体を通して管理できるクラスの作成です。
ただし色々調べたところ、ゲーム全体を通して管理するクラスのインスタンスは一つしか存在しない方が良いとのこと。
Unity 2Dアクションの作り方【ゲームマネージャーを作ろう】
UnityのMonoBehaviourクラスをシングルトン化する
インスタンスが複数存在している場合、そのうちの一つを変更しても他のインスタンスは何も変更されず、異なった値を持つパラメータが複数存在することになってしまいます。これでは、どれを参照すれば良いかわかりません。
そこで、インスタンスを1つしか存在できないようにしてやろう!ということです。

*補足
もう少し細かいことを言うと、パラメータを唯一にしたければ、インスタンス毎に割り振られる(または生成される)インスタンス変数を用いるのではなく、クラスに固有で全てのインスタンスに共通するクラス変数を用いるという方法もあります。もっと言うと、インスタンス変数を含まないクラスは静的クラスというものにすることができ、インスタンスの生成が一切できないクラスになりますので、これを利用してもいいです。これらの方法のほうがシンプルですね。
ですが、私の試した限りでは、prefabに入れてあるゲームオブジェクトをクラス変数に格納できませんでした。
なので仕方なくシングルトンパターンを利用しています。
また、通常UnityでC#スクリプトを書く場合、MonoBehaviourというクラスを継承した新しいクラスを作成するという形になるのですが、このMonoBehaviourというクラスの継承クラスは静的にできないらしいです。(
MonoBehaviourを継承しないという選択肢【Unity】)
ちなみにクラス変数にするには、変数定義の際にstaticを入れればいいです。また、静的クラスはクラス定義の際にstaticを入れればいいです。(詳しく解説はしないです。)

コードとコメント

以下がゲーム全体を通してパラメータやprefabを管理するクラスのシングルトンパターンでの実装コードです。ここではクラス名はゲームで扱う物を保管する場所という意味でGameStorageとしています。基本的には、上に挙げた記事を参考にして作成しました。
ちなみに、Unityのバージョンは2021.1.0b1です。

GameStorage
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameStorage : MonoBehaviour
{
    private GameObject Prefab_closed;
    private float parameter_closed;
    public GameObject Prefab
    {
        get {return Prefab_closed; }
    }
    public float parameter
    {
        get {return parameter_closed; }
    }

    private GameStorage() { }

    private static GameStorage Instance_closed;
    public static GameStorage Instance
    {
        get { return Instance_closed; }
    }

    private void Awake()
    {
        if (Instance_closed != null && Instance_closed != this)
        {
            Destroy(this.gameObject);
        }

        Instance_closed = this;
        DontDestroyOnLoad(this.gameObject);

        this.Prefab_closed = Resources.Load("OriginalObject") as GameObject;
        this.parameter_closed = Prefab.transform.localScale.y;
    }
}

まず、一番外枠の

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameStorage : MonoBehaviour
{

}

は、UnityでC#スクリプトを作成すると自動で書かれているやつですね。
次に

    private GameObject Prefab_closed;
    private float parameter_closed;
    public GameObject Prefab
    {
        get {return Prefab_closed; }
    }
    public float parameter
    {
        get {return parameter_closed; }
    }

の部分で、ゲーム全体を通して管理したいパラメータなどを格納するための変数を導入しています。最初の2行でprivate(クラスの内部だけで参照できる)インスタンス変数Prefab_closedとparameter_closedを導入します。Prefab_closedには後でprefab化したオブジェクトOriginalObjectを取得して入れます。parameter_closedにはPrefab_closedに入れたOriginalObjectのインスタンス変数を入れます。
そして、privateな変数だけでは外部からアクセスが一切できないので、次の部分でpravateなインスタンス変数Prefabとparameterを導入して外部からアクセスできるようにします。getを使うことで、Prefabやparameterという窓口を通してPrefab_closedやparameter_closedを取得するイメージです。ここで、勝手に外部から編集されたくないので、setは使わずに、外部への読み取りのみ許可するという形になっています。
次に、

    private GameStorage() { }

ですが、これはGameStorageクラスのコンストラクタです。publicではなくprivateであることが重要で、こうすることにより、クラス外で新しくGameStorageクラスのインスタンスを生成することを禁止しています。今回コンストラクタにはこの役割しか持たせないので、中括弧の中には何も書いていません。この部分は、Javaの解説記事のようですがこちらの記事を参考にしています。
5. Singleton パターン
次に、

    private static GameStorage Instance_closed;
    public static GameStorage Instance
    {
        get { return Instance_closed; }
    }

ですが、一行目でGamaStorageクラスのインスタンスInstance_closedをprivateな変数として生成しています。先ほど、コンストラクタをprivateにすることでクラス外部からのインスタンス生成を禁止すると言いましたが、ここはGameStorageクラスの定義文の中なので、インスタンスを生成することが可能なわけですね。さて、Instance_closedは外部からアクセスできないので、インスタンス変数の場合と同様に、publicはインスタンスInstanceを生成して、このInstanceを通してのみInstance_closedにアクセスできるようにします。ここでは、getのみを用いて、setは用いず、読み取りのみ可にしています。ここでもう一つ重要なのが、staticにすること。Unityでは、staticなインスタンスにしなければ、GetComponentやGameObject.Findなどを用いてインスタンスを探してくる必要がありますが、staticなインスタンスをクラス内で生成していくことで、

GameStorage.Instance

と書くだけでどこからでもアクセスが可能になります。
最後に、

    private void Awake()
    {
        if (Instance_closed != null && Instance_closed != this)
        {
            Destroy(this.gameObject);
        }

        Instance_closed = this;
        DontDestroyOnLoad(this.gameObject);

        this.Prefab_closed = Resources.Load("OriginalObject") as GameObject;
        this.parameter_closed = Prefab.transform.localScale.y;
    }

の部分に関してですが、まず、このAwake()はUnityで使う関数で、UnityでC#スクリプトを作成すると自動で書かれているStart()やUpdate()の仲間です。Start()よりは実行されるタイミングが早いということらしいですが、詳しいことは調べていないです。
さて、最初のIf文で、Instance_closedに自分自身以外のインスタンスが格納されていないかを調べています。もし別のインスタンスが入っていたらそのインスタンスが付随するゲームオブジェクトをDestroyすることで、複数のインスタンスが存在しないようにしています。この部分は(Unityで、シングルトンパターンを正しく実装するにはどうすればよいですか?)で書かれていた例を参考にしました。
その次の部分で、privateなインスタンスInstance_closedに実際に自分自身を入れています。
その下の行でDontDestroyOnLoadを用いることで、このクラスのインスタンスをゲームオブジェクトごとシーンを跨いで存在できるようにしています。
その下の部分で、最初の方に定義したインスタンス変数Prefab_closedとparameter_closedに、それぞれプレハブ化されたオブジェクトとそのオブジェクトのy軸方向の位置情報(transform.localScale.y)を代入しています。

以下、いくつか注意点です。

  • DestroyやDontDestroyOnLoadはシーン間遷移がある場合に必要な物だそうですが、また複数シーンを作成して遷移を行うテストをしていないため、上手くいくかわかりません。今後、確かめてみて問題が起きたら記事を修正するかもしれません。
  • インスタンスやインスタンス変数への代入ですが、コンストラクタやAwake()の外で行うとエラーが出ました。

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0