VR、ゲーム制作、プログラミング。Unity とか Oculus Rift とか。

2013年1月21日月曜日

[Unity]インスタンス生成時に引数を渡したい

Unity で、インスタンス生成時にパラメータを設定したいとします。

たとえばプレハブから生成する場合、Object.Instantiate() は引数を取れないので、一般的には以下のように生成後にセットすることになります。

public class MyClass : MonoBehaviour
{
    public int param1 { get; set; }
    public int param2 { get; set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass obj = Instantiate(myClassPrefab) as MyClass;
        obj.param1 = paramA;
        obj.param2 = paramB;
    }
}

これでも大きな問題があるわけではありませんが、MyClass の param1, 2 がいつでも外部から書き換え可能になっているので、MyClass 側では初期化以降もパラメータが変わることの考慮や、MyClass を使う側では param1, 2 を後から変えていいのかどうかの判断が必要になります。

コメントで「初期化直後以外は変えるな!」と書いて済ましてしまうのもひとつの手ではありますが、もうちょっと考えてみましょう。(以下、こういう細かい話が続きますよ!)

さて、とりあえず param1, 2 を外部から直接変更できなくして、いかにも初期化時しか呼んではいけなさそうな関数 Initialize() でのみセットできるようにしてみます。

public class MyClass : MonoBehaviour
{
    public int param1 { get; private set; }
    public int param2 { get; private set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
    
    public void Initialize(int _param1, int _param2)
    {
        param1 = _param1;
        param2 = _param2;
    }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass obj = Instantiate(myClassPrefab) as MyClass;
        obj.Initialize(paramA, paramB);
    }
}

これはこれでもう大体解決という感じではありますが、Initialize() を呼ぶまでの間に不完全なインスタンスが見えたり、毎回忘れずに 2 行書かないといけないことが気になる日もあるかもしれません。(以下、気になる日の人向けです!)

というわけで引数を取れる MyClass.Instantiate() を作ってみます。

public class MyClass : MonoBehaviour
{
    public int param1 { get; private set; }
    public int param2 { get; private set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
        
    public static MyClass Instantiate(MyClass prefab, int _param1, int _param2)
    {
        MyClass obj = Instantiate(prefab) as MyClass;
        obj.param1 = _param1;
        obj.param2 = _param2;
        return obj;
    }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass obj = MyClass.Instantiate(myClassPrefab, paramA, paramB);
    }
}

やっていることは前とそう変わりませんが、生成が 1 行になって、パラメータの設定忘れもなくなりそうですね。

もう一つ、略式の Builder パターンっぽくやってみます。

public class MyClass : MonoBehaviour
{
    public int param1 { get; private set; }
    public int param2 { get; private set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
        
    public class Builder
    {
        public MyClass prefab { get; private set; }
        public int param1 { get; set; }
        public int param2 { get; set; }
        
        public Builder(MyClass _prefab)
        {
            prefab = _prefab;
        }
        
        public Builder SetParam1(int _param1)
        {
            param1 = _param1;
            return this;
        }
        
        public Builder SetParam2(int _param2)
        {
            param2 = _param2;
            return this;
        }
        
        public MyClass Build()
        {
            MyClass obj = Instantiate(prefab) as MyClass;
            obj.param1 = param1;
            obj.param2 = param2;
            return obj;
        }
    }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass.Builder builder = new MyClass.Builder(myClassPrefab);
        MyClass obj = builder.SetParam1(paramA).SetParam2(paramB).Build();
    }
}

同じ設定で量産したり、あとでまた生成するために Builder を保持しておいたりするのには便利です。

パラメータ初期化込みで GameObject.AddComponent() するために、以下のようなものも用意しておくと役立つときもあるかもしれません。

public class Builder
{
    //...
    
    public MyClass AddComponentTo(GameObject gameObject)
    {
        MyClass obj = gameObject.AddComponent<MyClass>();
        obj.param1 = param1;
        obj.param2 = param2;
        return obj;
    }
}

…とはいえ、普通に Object.Instantiate() でパラメータ未設定なインスタンスが生成できてしまうのを禁止はできないので、これも気にするならデフォルト値のまま Start() が呼ばれたらエラーにするなどの対処が必要になると思います。

コンストラクタを private にしても new しか禁止できないからなあ。(private にしても Instantiate() できるのはどうなってるんだろう)

以上、使うシーンに合わせてお好みで。

0 件のコメント:

コメントを投稿