Version: 2018.4
言語: 日本語
プレハブインスタンスの展開
Input

ランタイムのプレハブのインスタンス化

この時点までに プレハブ の概念について基本的なレベルで理解できているはずです。プレハブはゲームを通じて再利用できる、事前定義済みの ゲームオブジェクトコンポーネント の集合です。もしプレハブが何か分からない場合は、プレハブ のより基本的な説明を参照してください。

プレハブは複雑なゲームオブジェクトをランタイムにインスタンス化したい場合に便利です。プレハブのインスタンス化の代替手段は 0 からコードを使用してゲームオブジェクトを作成することです。プレハブのインスタンス化はこの代替手段に比べて、以下を含む多くの長所があります。

  • プレハブのインスタンス化はコード一行で済み、完全な機能つきで行うことができます。もし、コードから同等のゲームオブジェクトを作成しようとすると、平均で 5 行か、実際にはそれ以上かかります。
  • シーンとインスペクター上で早く簡単にプレハブを設定、テスト、修正できます。
  • インスタンス化に使用するコードを変更することなく、インスタンス化されるプレハブを変更することができます。コードの変更の必要なしに、平凡なロケットを最速のロケットに変えることができます。

基本的なシナリオ

プレハブの効果を説明するために、便利に使える基本的な場面を考えてみます。

  1. ひとつのレンガを異なる場所に複数回作成して、壁を作成します。
  2. ユーザーが発射ボタンを押すと、ロケットランチャーはロケットのプレハブをインスタンス化します。そのプレハブは、メッシュ、RigidbodyCollider、トレイルの Particle System を含む子ゲームオブジェクトから構成されます。
  3. ロボットが細かくバラバラになります。完全で機能するロボットは破壊されて、壊れたロボットプレハブに置き換えられます。このプレハブは多数の部分に分割されたロボットから成り、すべてにそれぞれの Rigidbody や Particle System が設定されています。この方法を使うと、たった 1 行のコードでオブジェクトをプレハブに置き換えて、ロボットをバラバラに爆発させることができます。

壁の作成

このサンプルでは、コードからオブジェクトを作成する場合と比較して、プレハブ使用の利点を示しています。

最初に、コードからレンガの壁を作成します。

public class Instantiation : MonoBehaviour 
{
    void Start()
    {
        for (int y = 0; y < 5; y++) 
        {
            for (int x = 0; x < 5; x++) 
            {
                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.AddComponent<Rigidbody>();
                cube.transform.position = new Vector3(x, y, 0);
            }
        }
    }
}
  • 上のスクリプトを使用するには、単にスクリプトを保存して空のゲームオブジェクトにドラッグアンドドロップします。
  • 空のゲームオブジェクトを GameObject->Create Empty で作成します。

もしコードを実行すると、再生モードのときにレンガの壁全体が見られます。個別のレンガの機能に関連する箇所が 2 行あります。 CreatePrimitive の行と AddComponent の行です。ここまではそう悪くはないですが、個々のレンガにはテクスチャがありません。ブロックに行いたいすべての追加のアクションは 1 行づつ必要となります。例えば、テクスチャ、摩擦、Rigidbody の mass (重量) の変更には追加の行が必要です。

もしプレハブを作成して、設定をあらかじめ行っておくと、1 行のコードを使えば各レンガの作成と設定ができます。これにより、もし変更を行いたい場合に大量のコードの保守と変更が不要となります。プレハブがあれば、変更を行って実行するのみです。コードの変更は必要ありません。

もし各レンガにプレハブを使用している場合、次のコードで壁が作成できます。

//ゲームオブジェクトをインスタンス化するため、インスタンス化はすべてのコンポーネントタイプを受け付けます。

public Transform brick;

void Start() 
{
    for (int y = 0; y < 5; y++)
    {
        for (int x = 0; x < 5; x++) 
        {
            Instantiate(brick, new Vector3(x, y, 0), Quaternion.identity);
        }
    }
}

これはクリーンであるだけでなく再利用性も高いものです。キューブをインスタンス化する、とか Rigidbody を含む必要があるなどと、どこにも書いていません。このすべてはプレハブに定義されていてエディターで速やかに作成することができます。

さて、プレハブを作成してみましょう。エディターで以下のように行います。

  1. GameObject > 3D Object > Cube を選択します。
  2. Component > Physics > Rigidbody を選択します。
  3. Assets > Create > Prefab を選択します。
  4. Project ビュー でプレハブの名前を Brick に変更します。
  5. Hierarchy で作成したキューブを Project ビュー の Brick プレハブにドラッグアンドドロップします。
  6. プレハブを作成したので、Hierarchy ビューから安全にキューブを削除できます (Windows では Delete、Mac では Command-Backspace)。

Brick プレハブを作成したので、これをスクリプトの brick 変数にアタッチする必要があります。スクリプトを含む空のゲームオブジェクトを選択すると、インスペクター上で Brick 変数が表示されます。

次に Project ビューの Brick プレハブを Inspector 上の brick 変数にドラッグアンドドロップします。再生ボタンをクリックすると、プレハブを使って作成した壁が表示されます。

このワークフローは Unity で繰り返し使用できるものです。はじめは、なぜこの方法がそこまでよいのか分からないかもしれません。スクリプトから作成してもたった 2 行長いだけですから。

しかし、今はプレハブを使用しているので、プレハブの調整を数秒でできます。これらすべてのインスタンスの mass を変更したい場合はプレハブの Rigidbody を 1 回調整するだけです。すべてのインスタンスで Material を別のものに変更したい場合は使用するマテリアルをプレハブの上に 1 回ドラッグするだけです。摩擦を変更したい場合は、プレハブの Collider の Physic Material を変更するだけです。すべてのボックスに Particle System を追加したい場合は、プレハブに子を 1 回追加するだけです。

ロケットと爆発のインスタンス化

プレハブをこのシナリオで使用する方法は次のとおりです。

  1. ユーザーが発射ボタンを押すと、ロケットランチャーはロケットのプレハブをインスタンス化します。そのプレハブは、メッシュ、 Rigidbody、Collider、トレイルの Particle System を含む子ゲームオブジェクトから構成されます。
  2. ロケットが命中し、爆発のプレハブがインスタンス化されます。爆発プレハブには、Particle System、時間の経過とともにフェードアウトするライト、および周囲のゲームオブジェクトにダメージを与えるスクリプトが含まれています。

ロケットのゲームオブジェクトを完全にコードから作成する (手動でコンポーネントを追加しプロパティを設定する) こともできますが、プレハブをインスタンス化したほうがはるかに簡単です。ロケットのプレハブの複雑さに関わらず、1 行のコードでロケットのインスタンス化を行うことができます。プレハブをインスタンス化した後、さらに、インスタンス化したオブジェクトのプロパティを変更することができます (例えば、ロケットの Rigidbody の速度を設定するなど)。

あつかうのが容易である事とは別にして、プレハブは後から変更することができます。そのためロケットの作成時に、急いでトレイルパーティクルを加える必要はありません。後から追加すれば良いのです。トレイルを子ゲームオブジェクトとしてプレハブに追加すると、すべてのインスタンス化されたロケットに、トレイルのパーティクルが加えられます。そして最後に、インスペクター上で速やかにロケットプレハブのプロパティを微調整できるため、ゲームの調整が遥かに容易になります。

次のスクリプトでロケットを Instantiate() 関数を使用して発射する方法を示します。

//ロケットにリジッドボディを設定する必要があります。
//この方法では、ユーザーがリジッドボディなしでプレハブを割り当てることはできません。
public Rigidbody rocket;
public float speed = 10f;

void FireRocket () 
{
    Rigidbody rocketClone = (Rigidbody) Instantiate(rocket, transform.position, transform.rotation);
    rocketClone.velocity = transform.forward * speed;
    
    // 他のコンポーネント/スクリプトの複製にもアクセスできます
    rocketClone.GetComponent<MyRocketScript>().DoSomething();
}

// Ctrl かマウスを押し続けると、発射のメソッドを呼び出します
void Update () 
{
    if (Input.GetButtonDown("Fire1"))
    {
        FireRocket();
    }
}


キャラクターをラグドールまたは破壊物で置き換える

完全にリギングされた敵キャラクターが死亡したとします。その場合、単にキャラクター上で死亡するアニメーションを再生して、通常、敵ロジックを処理していたすべてのスクリプトを無効にすることもできます。恐らく、スクリプトの一部を取り除き、死亡した敵に対して誰もこれ以上攻撃しないカスタム製のロジックを加え、他のクリーンアップタスクを処理する必要があります。

もっと良い方法は、無傷のキャラクターを直ちに削除して、インスタンス化された破壊されたプレハブで置き換えることです。これにより様々なことが簡単にできます。死亡したキャラクターに対して異なるマテリアルを適用したり、まったく異なるスクリプトをアタッチしたり、多数に分解されたオブジェクトを含むプレハブを生成して、バラバラになった敵を再現したり、または単純に変形したキャラクターを含むプレハブをインスタンス化することもできます。

いずれの方法であっても、1 回 Instantiate() を呼び出すだけでよく、正しいプレハブに結び付けて、それで完了です。

重要なことは、Instantiate() する破壊された物を元とは全く異なるオブジェクトで作成できることです。例えば、飛行機があったとすると、2 つのバージョンを作ります。ひとつは、Mesh Renderer および飛行機の物理挙動を実行するスクリプトを持つ飛行機です。モデルを 1 つのオブジェクトだけにすることで、ゲームはより高速に実行されます。なぜなら、モデルをより少ない三角形で作成でき、より少ないオブジェクトで構成されるため、小さいパーツを多く使用するよりも速くレンダリングができるからです。さらに飛行機が飛び回っている間はわざわざいくつかの部分に分ける必要性がありません。

破壊された飛行機のプレハブを作成するときに典型的なステップは以下のとおりです。

  1. 好みのモデリングツールを使用して飛行機をたくさんのパーツから作成します。
  2. 空のシーンを作成
  3. 空のシーンにモデルをドラッグします。
  4. Component->Physics->Rigidbody を選択して、Rigidbody をすべてのパーツに加えます。
  5. Component->Physics->Box Collider を選択して、Box Colliders をすべてのパーツに加えます。
  6. より特別なエフェクトのために、煙のような Particle Systemを子ゲームオブジェクトとして各パーツに加えます。
  7. これで飛行機に爆発するパーツが複数設定され、地面には物理挙動で落ち、アタッチされたParticle Systemによるトレイルパーティクルが作成されます。再生を押して、モデルの動きをプレビューし、必要な調整を加えます。
  8. Assets->Create Prefab を選択します。
  9. すべての飛行機のパーツを含むルートのゲームオブジェクトをプレハブにドラッグアンドドロップします。

次の例は、これらのステップがどのようにコード内で形成されているかを示しています。

public GameObject wreck;
//例として、自動的に 3 秒後にゲームオブジェクトを爆破します。
IEnumerator Start() 
{
    yield return new WaitForSeconds(3);
    KillSelf();
}

//Ctrl またはマウスを押したままにするときに、fire メソッドを呼び出します。
void KillSelf () 
{
    // 同じ位置で、壊れたゲームオブジェクトをインスタンス化します
    GameObject wreckClone = (GameObject) Instantiate(wreck, transform.position, transform.rotation);
    
    // ときどき、このオブジェクトから壊れたものへ、いくつかの変数を
    // 引き継ぐ必要があります。
    wreckClone.GetComponent<MyScript>().someVariable = GetComponent<MyScript>().someVariable;
    
    //自動的に削除します 
    Destroy(gameObject);
}

たくさんのオブジェクトを特定パターンで配置する

例えばたくさんのオブジェクトをグリッド状または円状に配置したいとします。既存の方法では、次のいずれかの方法をとります。

  1. すべて、コードからオブジェクトを作成します。これは退屈な作業です。スクリプトから値を入力するのは遅く、直感的でなく、労力に見合いません。
  2. 完全にリギングされたオブジェクトを作成し、複製して、シーン上に複数回配置します。これは退屈な作業で、グリッドに正確に配置することが難しい作業です。

代わりにプレハブで Instantiate() を使用してください。このシナリオで、なぜゲームオブジェクトが役に立つのかはお判りでしょう。このシナリオのため必要なコードは次のとおりです。

//プレハブを円状にインスタンス化する

public GameObject prefab;
public int numberOfObjects = 20;
public float radius = 5f;

void Start() 
{
    for (int i = 0; i < numberOfObjects; i++)
    {
        float angle = i * Mathf.PI * 2 / numberOfObjects;
        Vector3 pos = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius;
        Instantiate(prefab, pos, Quaternion.identity);
    }
}

//プレハブをグリッド状にインスタンス化する

public GameObject prefab;
public float gridX = 5f;
public float gridY = 5f;
public float spacing = 2f;

void Start()
{
    for (int y = 0; y < gridY; y++) 
    {
        for (int x = 0; x < gridX; x++)
        {
            Vector3 pos = new Vector3(x, 0, y) * spacing;
            Instantiate(prefab, pos, Quaternion.identity);
        }
    }
}


プレハブインスタンスの展開
Input