はじめに
・間違っている可能性は十分にありますが、その際は指摘していただけると幸いです。
・指摘の際に、00の単語を調べるなどがいいよなどと合わせてしていただけると幸せです。
対象読者
・SingleTonとMonoBeheivior組み合わせたら最強じゃね?と思ってしまった方
・SingleTonとMonoBeheiviorの組み合わせもできるんじゃない?っと思いついてしまった方
・サクッと何かを作るのにSingleTonを乱用している方。(すごく気持ちわかる)
得られる知見
・SingleTonとMonoBeheiviorを組み合わせには十分に注意をしなければいけないという事がわかる。
・MonoBehebior継承のものもエラーなくnew 出来てしまう。
ざっくり内容の要約
AddComponentでとってくる参照と
newで取得する参照が違うから
たとえよくあるnewでインスタンスを保持してそれをとってくる形式では、よくないよって話
事の経緯
ゲーム制作をしていた時にふと思ってしまいます。
「毎度更新は必要だけど、その参照をどこからでもアクセスできる便利さ。そんな力が私は欲しい」
毎度更新=MonoBehavior
どこからでもアクセスできる便利さ = Singleton
MonoBehebior Singleton にすればいいんじゃね?
「私は、とんでもない悪魔を生み出してしまったのかもしれない」
//茶番終わり
そもそもSingleTonとは?
SingleTonといえば、インスタンスさえ作ってしまえば、どのクラスからも簡易にアクセスできるため、単一が保証されているクラスに使うのが一応使う目安のものですが、その一方で、使いすぎてしまうと、あらゆるクラスからアクセスが可能になってしまう為、そのクラスが絡んだ処理を追いかける際などに困難になったりする側面を持つもの。
両者を組み合わせてしまった際の危険性
結論から言うと、初期化などのタイミングが非情に厄介なことになる。
それぞれの初期化のタイミング
MonoBehabiorの場合
・GameObjectに対して、コンポーネントとして付属されたタイミングで、初期化がかかる。
Awake、Startがこのタイミングから始まる。厳密な所は以下の内容を参考に、
イベント関数の実行順
https://docs.unity3d.com/ja/current/Manual/ExecutionOrder.html
SingleTonの場合
・クラスとして生成されているタイミングに初期化される。
Singletonの場合生成方法が色々あるがもっとも初歩的な生成方法としては
静的な関数(static)の関数から、そのクラスを生成して、その参照を取得する方法が多いです。
以下例
public class Singleton
{
private static Singleton instance_data;
//コンストラクタをプレイべーとにする。
private Singleton()
{
}
//外部から取得できるようにする まだ出来てなければ、生成
public static Singleton GetInstance()
{
if(null == instance_data)
{
instance_data = new Singleton();
}
return instance_data;
}
}
そしてそれらを組み合わせた私が思いついた、SingletonMonobehaviourが以下
public class SingletonMonoBehavior<T> : MonoBehaviour where T :MonoBehaviour
{
protected static T instance;
protected SingletonMonoBehavior()
{
}
//外部から取得できるようにする まだ出来てなければ、生成
public static Singleton GetInstance()
{
if(null == instance_data)
{
instance_data = new T();
}
return instance_data;
}
}
このクラスを継承する。
public class singletonmono : SingletonMonoBehavior<singletonmono>
{
public int a = 0;
void Awake()
{
}
// Use this for initialization
void Start () {
Debug.Log("start");
if (null == instance)
{
Debug.Log("create instance start");
instance = singletonmono.CreateInstance();
}
a = 2;
}
// Update is called once per frame
void Update () {
}
}
この継承したSingletonMonoBehebiorをこう呼び出す。
public class Sample_update : MonoBehaviour
{
//Addによる付与
private singletonmono add_component;
//CreteInstanceによるもの
private singletonmono createinstance;
// Use this for initialization
void Start ()
{
add_component = this.gameObject.AddComponent<singletonmono>();
createinstance = singletonmon.GetInstance();
}
}
シングルトンとは、単一性を保持するパターンの一つでは
上記の内容では、中の変数:aをどちらかで変更した場合、それぞれ別の値を持つことになる。
そうシングルトンを自称しているにも関わらずである。
本来、MonoBehebiorを継承しているクラスの場合
以下の警告文が出ます。
You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
UnityEngine.MonoBehaviour:.ctor()
日本語訳
'new'キーワードを使用してMonoBehaviourを作成しようとしています。 これは許可されていません。 MonoBehavioursは、AddComponent()を使用してのみ追加できます。 また、あなたのスクリプトはScriptableObjectから継承することも、基本クラスを継承することもできません
UnityEngine.MonoBehaviour:.ctor()
ざっくりいうとnew しちゃだめだよ
ってことなんですが、
今回のやり方で、継承したクラスを使用する場合
この警告がまず出ません。
単純に、この警告は、new生成する場合と、AddComponentでやる場合は、別のインスタンスができてしまう。
ためだと思われます。
これはシングルトンであろうと例外ではないというより、シングルトンで保持するインスタンスに対して、値を持っていないため
そのような状況が起きてしまう。っていうことです。
なので、SingletonMonoBehebiorを使用する場合は通常のSingletonとは違い以下のような工夫が必要です。
1.なければ、GameObjectを作成して、そこに対してつけて、その参照を保持する。
2・すべてのオブジェクトからコンポーネントを探して、それを参照として取得する(すでにある前提)
これらに関しては
「シングルトン」「MonoBehebior」と合わせて調べればいろんな方が記事に書いているっぽいので、そちらを参考に。