いろいろなオブジェクトのクローン(複製)について

2021年3月20日

Uniyで、オブジェクトは、Instantiateメソッドで複製されます。
これを使うことで、Sceneにあらたしくゲームオブジェクトを作成できます。
一般的には、Instantiateメソッドの引数は、Prefabとしますが、今回は説明のために、すでにシーン上に存在するゲームオブジェクトを引数としますので、混乱しないようにしましょう。

目次

説明の見方

エディターのオブジェクトとイメージの関連

解説でのイメージは、インスペクターでは次のように対応づけられます。

インスペクター

Unityでは、ゲームオブジェクトを単位として、様々な振る舞いを行えるコンポーネントが複数アタッチできるようになっています。
ゲームオブジェクトの機能の追加や削除は、コンポーネントの追加と削除で行え、ビジュアルでの確認ができるようになっています。

イメージ

説明で表記されているイメージになります。
インスペクターでの表示と見比べて確認しておいてください。
Unityでは、GameObjectのインスタンスに様々なクラス(Componentクラスを継承した派生クラス)が表示されています。
内部では、List<Component>型のように各オブジェクトにコンポーネントリストを保持しているイメージになります。

ゲームオブジェクトのクローン(複製)

シンプルなサンプル

単純にクローンを作成する短いコードを考えてみましょう。
Instantiateメソッドには、引数の違う、多くの(10)オーバーロードがあります。

コード

インスペクターにアウトレット接続されたゲームオブジェクトのクローンを作成します。

// インスペクターでアウトレット接続(D&D)して、初期値を代入します。
public GameObject cube;

// cubeオブジェクトのクローンを作成します。
Instantiate(cube);

イメージ

作成したゲームオブジェクトを操作するために取得する場合

Instantiateメソッドは、全て戻り値があります。
戻り値の値を、変数に代入することで、自在にオブジェクトを扱うことができます。

コード

public GameObject cube;

// 戻り値は、クローンされたゲームオブジェクトの参照
GameObject obj = Instantiate(cube);

イメージ

作成したオブジェクトに動的にコンポーネントをアタッチする

Rigidbodyなどコンポーネントをアタッチ(取り付ける)こともできます。

コード

public GameObject cube;
GameObject obj = Instantiate(cube);

// クローンにRigidbodyコンポーネントをアタッチ(取り付ける)
obj.AddComponent<Rigidbody>();

イメージ

ゲームオブジェクト以外のオブジェクトを引数にしてゲームオブジェクトを作成する

引数はGameObject型だけではありません。コンポーネントでも構いません。
その場合、そのコンポーネントがアタッチされているゲームオブジェクトのクローンが作成されます。

コード

// インスペクターで、ヒエラルキーのゲームオブジェクトをアウトレット接続(D&D)する
public Transform cubeTran;
Instantiate(cubeTran);

イメージ

作成したゲームオブジェクトを操作するために取得する場合

引数にコンポーネントを取った場合、戻り値は引数の型と同じ型になります。

コード

public Transform cubeTran;

// インスペクターでアウトレット接続(D&D)したゲームオブジェクトにアタッチされているコンポーネントの参照が代入される
Transform tran = Instantiate(cubeTran);

イメージ

作成したオブジェクトのコンポーネントを操作

コンポーネントを取得したら、新しくクローンされたオブジェクトのコンポーネントにアクセスすることができるようになります。
次のコードは、Transform型のpositionプロパティを操作する(値の代入)ものです。

コード

public Transform cubeTran;
Transform tran = Instantiate(cubeTran);

// インスペクターでアウトレット接続されたゲームオブジェクトのTransformコンポーネントのpositionを更新します。
// つまり、移動させることになります。
tran.position = new Vector3(2, 2, 2);

イメージ

自作のコンポーネント(スクリプト)の型を引数とする

ゲームオブジェクトにアタッチされた自作のスクリプトもTransformと同様にコンポーネントなので、同じように、自作スクリプトのクラスを引数の型にすることが出来ます。

コード

// アウトレット接続(D&D)されるゲームオブジェクトには、CubeControllerコンポーネント(スクリプト)がアタッチされていること
public CubeController cubeController;
Instantiate(cubeController);

イメージ

自作のコンポーネント(スクリプト)の型を引数とし、戻り値を取得

自作のスクリプトを引数とした場合、戻り値はそのゲームオブジェクトにアタッチされたスクリプトの参照になります。

コード

public CubeController cubeController;

// CubeContorllerスクリプトを取得。
// のちに各メンバー(フィールド、プロパティ、メソッド)にアクセスします。
CubeController controller = Instantiate(cubeController);

イメージ

自作のコンポーネント(スクリプト)の型を引数とし、取得したコンポーネントを操作する

自作スクリプトの場合、操作するとはスクリプトのメンバー(フィールド、プロパティ、メソッドなど)にアクセスすることを指します。

コード

public CubeController cubeController;
CubeController controller = Instantiate(cubeController);

// インスペクターでアウトレット接続したゲームオブジェクトにアタッチされているCubeControllerスクリプトのメソッドを呼び出します
controller.Show();

イメージ

テストのためのサンプル

それでは、実際のプロジェクトで動作する環境で確認してみましょう。

スクリプト

2つのスクリプトを用意します。

CubeController.cs

Cubeにアタッチするスクリプト
1つのメソッドのみ記述しています。外部クラスからアクセスする必要があるため、アクセス修飾子は、publicとしています。

using UnityEngine;

public class CubeController : MonoBehaviour
{
    public void Show()
    {
        Debug.Log($"私は {gameObject.name}といいます");
    }
}

Spawner.cs(Rigidbodyコンポーネントを追加したパターン)

クローンを生成するオブジェクト(今回は、空のオブジェクトを作成してアタッチしています)

using UnityEngine;

public class Spawer : MonoBehaviour
{
    // CubeをD&D
    public GameObject cube;

    void Start()
    {
        GameObject obj = Instantiate(cube);

        // アタッチされているゲームオブジェクトに対する操作もできます
        obj.AddComponent<Rigidbody>();
    }
}

Spawner.cs (CubeControllerのテストを追加したパターン)

さらにテストを進めたコードになります。
今回のサンプルでは、こちらのコードを使用しています。

using UnityEngine;

public class Spawner : MonoBehaviour
{
    // CubeをD&D
    public GameObject cube;

    // CubeをD&D
    public CubeController cubeController;

    void Start()
    {
        GameObject obj = Instantiate(cube);

        // アタッチされているゲームオブジェクトに対する操作もできます
        obj.AddComponent<Rigidbody>();

        CubeController controller = Instantiate(cubeController);

        // ゲームオブジェクトの名前を変えてみる(ヒエラルキーを確認)
        controller.name = "Cube(Method Call)";

        // メソッド呼び出し
        controller.Show();
    }
}

オブジェクトの配置

サンプルのオブジェクト配置を確信してください。

Cube

クローンされるゲームオブジェクトになります。
今回のテストでは、簡単にするためにあえてPrefabからのクローンの方法を使っていません。

GameObject

クローンを作成する目的で実行されるスクリプトがアタッチされています。
特に形が要求されないため、からのゲームオブジェクトを作成しています。
なお、簡単のため、ゲームオブジェクトの名前もデフォルトのままです。

クローンの作成では一般的にはPrefabを作成し、作成したPrefabをアウトレット接続しますが、今回は評価のためにシーン上のゲームオブジェクトを直接アウトレット接続しています。

実行結果

Rigidbodyコンポーネントが追加されたCubeオブジェクト

実行すると、Cube(Clone)オブジェクトは、落下します。
画面からは、すぐ見切れてしまいますが、TransformのPositionの値の変化から落下中であることが確認できます。

メソッド呼び出しされるCubeオブジェクト

ヒエラルキーでの名前は、Cube(Method Call)となっています。
これは、コードで名前を変更しているためです。
コンソールウィンドウの表示を確認すると、このオブジェクトのShow()メソッドが呼び出されることがわかります。

Showメソッドのコード

Debug.Log($"私は {gameObject.name}といいます");

コンソールウィンドウの表示

私は Cube(Method Call)といいます

クラス図

最後のコードをクラス図にしたもの

スクリプトの型でアウトレット接続だけに絞ったもの

応用1 Cube以外のオブジェクトが必要な場合、変更箇所が少なくなるように抽象クラスを作成

Spawnerクラスを抽象クラスに依存するように変更してみましょう。
目的は、SpawnerクラスのCubeControllerへの依存性を下げることにあります。
メリットは、例えば、他のコントローラ(SphereControllerなど・・・)が必要となった場合、今のままでは、SpownerクラスはCubeContollerクラスへがっちり依存しているため、汎用的なコードとはなりません。これを抽象クラスへ依存させることに変更することによって、追加に対して融通が効くようにするのです。

クラス図

まず、クラス図をみてみましょう。

Spawnerクラスは、これまでと違い、ObjectControllerBaseクラスへ依存しているのがわかります。

コード

ObjectControllerBase.cs

抽象(abstract)クラスです。
これから他の形のオブジェクトを増やしていく場合、継承される基本クラスになります。

using UnityEngine;

public abstract class ObjectControllerBase : MonoBehaviour
{
    public abstract void Show();
}

CubeController.cs

キューブをコントロールするクラスです。
抽象クラスを継承する派生クラスです。
今回は、Cube(立方体)に特化したクラスとしていますが、将来、他の形用のクラスもよく似た形で実装したいと考えています。

using UnityEngine;

public class CubeController : ObjectControllerBase
{
    public override void Show()
    {
        Debug.Log($"私は {gameObject.name} といいます。");
    }
}

Spawner.cs

今は、キューブのみを作成する振る舞い(仕事)をしています。
将来、他の形も作成したいのですが、コードの変更は避けたいです。

using UnityEngine;

public class Spawner : MonoBehaviour
{
    // ObjectContollerBaseを継承したコンポーネントをアタッチしている
    // ゲームオブジェクトをD&D
    public ObjectControllerBase objectControllerBase;

    void Start()
    {
        var controller = Instantiate(objectControllerBase);

        // ゲームオブジェクトの名前を変えてみる(ヒエラルキーを確認)
        controller.name = $"{controller.name} Instantiate Object";

        // メソッド呼び出し
        controller.Show();
    }
}

オブジェクトの配置

Game ObjectオブジェクトにアタッチされているObjectController

実行結果

応用2 CubeをSphere(球)オブジェクトに置き換えてみる。

Spawnerクラスを抽象クラスに依存するように変更してみましたので、効果の程を確認しましょう。
Sphereオブジェクトに変更してみてます。

クラス図

まず、クラス図をみてみましょう。

Spawnerクラスは、前回と一緒でObjectControllerBaseクラスへ依存しているのがわかります。
これは、Spawnerクラスのコードに変更点がないことを表しています。
今回、メリットしていたことが実現できました。
追加されたSphereControllerは、CubeControllerと同じようにObjectControllerBaseを継承している派生クラスになっています。

コード

ObjectControllerBase.cs

抽象(abstract)クラスです。
これから他の形のオブジェクトを増やしていく場合、継承される基本クラスになります。
このコードは、前回と変化がありません。
うまくいっていますね。

using UnityEngine;

public abstract class ObjectControllerBase : MonoBehaviour
{
    public abstract void Show();
}

CubeController.cs

キューブをコントロールするクラスです。
抽象クラスを継承する派生クラスです。
今回は、Cube(立方体)に特化したクラスとしていますが、将来、他の形用のクラスもよく似た形で実装したいと考えています。

using UnityEngine;

public class CubeController : ObjectControllerBase
{
    public override void Show()
    {
        Debug.Log($"私は {gameObject.name} といいます。”);
        Debug.Log("立方体ですよ");
    }
}

SphereController.cs

スフィア(球)をコントロールするクラスです。
抽象クラスを継承する派生クラスです。
今回は、Sphere(球)に特化したクラスとしていますが、また、他の形用のクラスでも同じように作成できることがわかりました。

using UnityEngine;

public class SphereController : ObjectControllerBase
{
    public override void Show()
    {
        Debug.Log($"私は {gameObject.name} といいます。");
        Debug.Log("球ですよ");
    }
}

Spawner.cs

今は、キューブのみを作成する振る舞い(仕事)をしています。
将来、他の形も作成したいのですが、コードの変更は避けたいです。

using UnityEngine;

public class Spawner : MonoBehaviour
{
    // ObjectContollerBaseを継承したコンポーネントをアタッチしている
    // ゲームオブジェクトをD&D
    public ObjectControllerBase objectControllerBase;

    void Start()
    {
        var controller = Instantiate(objectControllerBase);

        // ゲームオブジェクトの名前を変えてみる(ヒエラルキーを確認)
        controller.name = $"{controller.name} Instantiate Object";

        // メソッド呼び出し
        controller.Show();
    }
}

オブジェクトの配置

Sphereオブジェクト

実行結果

参考

UnityEngineネームスペースでのInstantiateメソッド定義

public class Object
{
    public static T Instantiate<T> (T original) where T : Object
    {
        return UnityEngineが作成したオブジェクト;
    }
}

2021年3月20日C#,Unity

Posted by hidepon