Unityではプレハブ(prefab)という機能があります。
当ブログでもこのプレハブは幾度となく使用してきましたが、プレハブのみを扱う記事がなかったので改めて使ってみようと思います。
このプレハブとは一体なんなのでしょうか?
プレハブとは!?
英語の意味から言うと『組み立て式の家屋』という意味で世間一般で言うプレハブ小屋と同じ意味で問題なさそうです。
発音的にはプリファブのようですが・・・、わかりやすくプレハブとして進めましょう・・・(-_-)
Unityのプレハブは『組み立てられた物』という認識でいいと思います。
例えば銃から弾を何発も打つ、主人公キャラクターがある範囲に突入したら敵を新しく出現させる。
こういった場合はあらかじめシーンに弾や敵を置いておく事は出来ません(見えないようにしておいて必要に応じて表示する方法はあり)。
その為、弾を銃口の先から登場させる、敵を決められた場所に登場させる、という事をしなければいけません。
こういった場面で使うのがプレハブで、スクリプトから出現させます。
つまり『弾』や『敵』をプレハブとして作成しておき、決められた場所にスクリプトから登場させてやるわけです。
弾や敵のプレハブはひな型(タイ焼きの鉄板のようなもの)でそれを使ってインスタンス(それぞれのタイ焼き)を生産していきます。
プレハブの利点としてはあらかじめゲームオブジェクトをシーン上に配置しておかなくていいというだけでなく、
同じ物を大量にゲームに登場させたい時にもスクリプトを使ってプレハブからインスタンスを生成し使う事が出来ます。
Unity2018.3以降はプレハブの使い方が少し変わっておりますので、
も参照してください。
ゲームオブジェクトをプレハブにする
プレハブを機能と言っていますが、そんな大それた意味として考える必要はなく、一まとまりの機能を持ったゲームオブジェクトの集まりとして考えると良いと思います。
例えば人型のモデルをシーンに配置し、そのモデルにコライダ(当たり判定部分)やスクリプトを取り付け敵として主人公を追いかけるようにします。
このように一まとまりの機能を取り付けた敵キャラをプレハブとすれば、必要に応じて主人公を追いかける敵キャラをゲーム内に登場させる事が出来ます。
ではどうやってプレハブ化すればいいのでしょうか?
実はものすごく簡単に出来ます。
プレハブの作成の仕方
プレハブファイルを作成する場合はProjectタブ内で右クリック→Create→Prefabを選択します(Unity2018.3以降をお使いの方はヒエラルキーからドラッグ&ドロップを使用してください)。
↑のようにメニューが出るのでPrefabを選択します。
するとNew Prefabというファイルが作成されます。
これが空のプレハブファイルとなるので、後はプレハブにしたいゲームオブジェクトをそのファイルにドラッグ&ドロップするだけで、そのゲームオブジェクトがプレハブになります。
実はこの工程、やる必要がないんですが・・・・(^_^;)
プレハブファイルを作成しなくても、プレハブにしたいゲームオブジェクトをそのままProjectタブのエリアにドラッグ&ドロップすればプレハブが作成されます。
いちいち最初に空のプレハブファイルを作成しなくても出来ます(^_^)v
実際にゲームオブジェクトをプレハブ化する
文章を見てもわからない事もあると思うので、実際にシーンに配置したゲームオブジェクトをプレハブにしてみましょう。
ヒエラルキー上で右クリック→3D Object→Sphereを選択し名前をBallとします。
またゾンビのモデルをシーン上に配置し、CharacterControllerコンポーネントの取り付けや移動スクリプトの取り付けをします。
敵キャラクターの移動に関しては
等を参考に作成してください。
今回はプレハブの機能についてなのでBallの設置のみで敵キャラクターをプレハブ化しなくても問題ありません。
敵キャラクターもプレハブ化して使う事が出来るというのを確認する為に登場させただけです。
↑がプレハブ化するボールと敵キャラクターを配置した時のヒエラルキーです。
Main CameraやDirectional Lightは最初から設置されており、Cubeは床に使用しています。
プレハブにするBallはSphereの名前を変えただけなので↑のようなインスペクタの表示になっているはずです。
プレハブにする敵キャラクターにはCharacterControllerコンポーネントの取り付けとコライダのサイズや位置調整、カメラの方向に向かっていくスクリプトPrefabMoveスクリプトを取りつけています。
PrefabMoveスクリプトは
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using UnityEngine; using System.Collections; public class PrefabMove : MonoBehaviour { private CharacterController characterController; private Vector3 velocity; void Start () { characterController = GetComponent<CharacterController>(); velocity = Vector3.zero; } void Update () { if(characterController.isGrounded) { velocity = Vector3.zero; velocity = (Camera.main.transform.position - transform.position).normalized; } transform.LookAt(new Vector3(Camera.main.transform.position.x, transform.position.y, Camera.main.transform.position.z)); velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } } |
↑のようにしました。
正直なところ登場してからカメラ方向に移動する為だけに作ったものなので中身は気にしないでください・・・・(^_^;)
ちゃんとしたスクリプトではないので・・・・(-_-)
↑がプレハブ化するボールと敵の実際の画面です。
このBallと敵キャラのz@walkをProjectタブの領域にドラッグ&ドロップしましょう。
Ballはさきほど作成したNew Prefabファイルに、z@walkは何もない空間にドラッグ&ドロップします。
New PrefabファイルにBallをドラッグ&ドロップするとプレハブのファイル名はそのままNew Prefabとなり、z@walkを何もない領域にドラッグ&ドロップすると名前がそのままのプレハブファイル名となります。
これでゲームオブジェクトをプレハブにする事が出来ました。
ゲームオブジェクトへの変更をプレハブに反映させる
ゲームオブジェクトをプレハブ化した後に元のゲームオブジェクトに変更を加えそれをプレハブに反映させたい事があります。
Ballのサイズを変更しそれをプレハブに反映させてみます。
↑のようにBallのインスペクタでScaleをすべて0.5に変更します。
現時点では↑のようにBallのプレハブであるNew PrefabのScaleは変更されていません。
BallのインスペクタのRevertを押すと変更を加えたところがプレハブのデータに戻ります。
Applyボタンを押すとBallに変更を加えた部分がプレハブへと反映されます。
当然の事ながらプレハブに変更を加えるとプレハブから作成したゲームオブジェクトすべてに変更が反映されてしまいますので注意が必要です。
それではBallのインスペクタのApplyボタンを押して変更をプレハブに反映してみましょう。
↑のようにプレハブであるNew PrefabのScaleが0.5になったことを確認出来ます。
プレハブの解除
プレハブにしたゲームオブジェクトのインスタンスとプレハブとのリンクの解除をしたい事もあると思います。
プレハブにしたゲームオブジェクトのインスタンス(元のゲームオブジェクト)はヒエラルキー上で薄い青色で表示されます。
さらにインスペクタの表示でPrefab項目が表示されます。
プレハブにしたゲームオブジェクトのインスタンスとプレハブとの連結を解除したい時はインスタンスを選択した状態でUnityメニューのGameObject→Break Prefab Instanceを選択します。
解除するとヒエラルキー上のゲームオブジェクトの名前が通常の黒色に変わります。
ただこのままだとインスタンスのゲームオブジェクトのインスペクタを表示するとPrefab項目が残ったままです。
プレハブがもういらないのであればAssetsフォルダにあるプレハブを削除すると、インスタンスのゲームオブジェクトのインスペクタのPrefab項目も出なくなります。
次にプレハブの解除をする前にAssetsフォルダにあるプレハブを削除してしまった時は
↑のようにヒエラルキー上のインスタンスの名前が赤?茶?色で表示され、インスペクタではPrefab項目が表示されていた所にMissingと表示されます。
そんな時はインスタンスを選択した状態からUnityメニュー→GameObject→Break Prefab Instanceを選択すればこのインスタンスは通常のゲームオブジェクトに戻り、
↑のように通常のゲームオブジェクトを選択した時のインスペクタ表示になります。
ヒエラルキー上のゲームオブジェクトの表示も黒色に変化します。
プレハブを使ってみる
プレハブの作成と編集のやり方がわかったところで、実際にプレハブを使ってみましょう。
手動でプレハブからゲームオブジェクトを作成
まずは手動でプレハブからゲームオブジェクトを作成してみましょう。
まぁ・・・説明もいらないほどなんですが、プレハブファイルをヒエラルキー上にドラッグ&ドロップするだけです!
ボールのプレハブであるNew Prefabを2つヒエラルキー上にドラッグ&ドロップしてみましょう。
↑のようにプレハブからインスタンス(プレハブから作成したゲームオブジェクト)の作成が出来ました。
プレハブから作成したゲームオブジェクトは元のBallオブジェクトと同じ情報を持っているので、設置される場所はプレハブ化したBallの位置と同じ場所になります。
プレハブから作成したゲームオブジェクトに変更を加え、インスペクタでApplyボタンを押すとプレハブが変更されてしまう為、注意する必要があります。
プレハブからゲームオブジェクトを作成するメソッドを見てみる
次にスクリプトを使ってプレハブからゲームオブジェクトを作成してみましょう。
まずはプレハブからゲームオブジェクトを作成するメソッドを見てみます。
1 2 3 4 | // 引数に指定するもの Instantiate(プレハブ, 位置, 角度, 親) as GameObject; |
↑のように第1引数にプレハブのゲームオブジェクト、第2引数はゲームオブジェクトの位置、第3引数はゲームオブジェクトの角度、第4引数は親の設定です。
第2引数以降は設定しなくても大丈夫です。
実際の値を設定し使うとしたら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; public class PrefabTest : MonoBehaviour { // インスペクタでプレハブを設定 [SerializeField] private GameObject prefab; // 生成したゲームオブジェクトを入れておく変数 [SerializeField] private GameObject obj; void Start() { obj = Instantiate(prefab, transform.position, Quaternion.identity, transform) as GameObject; } } |
↑のようにインスペクタで設定したprefabをこのスクリプトが設定されているゲームオブジェクトの位置(transform.position)と、
Quaternion.identityで角度を指定しゲームオブジェクトを作成する事が出来ます。
Quaternion.identityは親要素の角度に合わせるので親があれば親の角度、なければワールド座標(X、Y、Zの角度が0)になります。
第4引数でtransformを設定し、このスクリプトが設定されているゲームオブジェクトをインスタンス化したゲームオブジェクトの親にします。
C#でスクリプトを記述する場合、Instantiateの戻り値はObject型となっている為GameObject型にキャストする必要があります。
ジェネリック版のInstantiateメソッド
Instantiateメソッドはジェネリック版もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class GenericInstantiate : MonoBehaviour { [SerializeField] private GameObject prefab; private GameObject instance; // Use this for initialization void Start () { instance = Instantiate<GameObject>(prefab, transform.position, Quaternion.identity, transform); } } |
型のキャストをしなくていいのでいいかもしれません。
プレハブからゲームオブジェクトを生成するサンプルの作成
それでは実際にプレハブからゲームオブジェクトを生成しゲームに登場させるサンプルを作成してみましょう。
ゲーム全体で使用するスクリプトを管理するManagementオブジェクトの作成
まずはゲーム全体で使用するスクリプトを設定する空のゲームオブジェクトを作成します。
スクリプトはその動作をさせるゲームオブジェクトに設定していくのが普通ですが、全体を管理するスクリプト等は空のゲームオブジェクトを作成し、
そこに取り付けておくとわかりやすいです。
ヒエラルキー上で右クリック→Create Emptyを選択し、名前をManagementとします。
プレハブからゲームオブジェクトを生成するCreateスクリプトの作成
次にManagementゲームオブジェクトにプレハブからゲームオブジェクトを生成するCreateInstanceスクリプトを作り取りつけます。
今回の例ではCreateInstanceスクリプトはゲーム全体を管理するようなスクリプトではありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | using UnityEngine; using System.Collections; public class CreateInstance : MonoBehaviour { // 敵プレハブ [SerializeField] private GameObject enemy; // ボールプレハブ [SerializeField] private GameObject ball; // 経過時間 private float nowTime; // 敵を生成する間隔 [SerializeField] private float createTime = 2f; void Start () { nowTime = 0f; } void Update () { // マウスの左クリックを押したら床にボールを表示する if(Input.GetButtonDown("Fire1")) { // メインカメラからマウスクリックした位置にレイを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; GameObject obj; // レイがFieldレイヤーを設定したゲームオブジェクトに接触したらそこにボールを表示 if(Physics.Raycast(ray, out hit, 1000f, LayerMask.GetMask("Field"))) { // ボールプレハブからクリックした位置にゲームオブジェクトを生成する obj = Instantiate(ball, hit.point + new Vector3(0, 0.25f, 0), Quaternion.identity) as GameObject; // 5秒後に削除 Destroy(obj, 5f); } } // 一定時間が経過したら敵を生成する if(nowTime >= createTime) { GameObject obj2; obj2 = Instantiate(enemy, new Vector3(Random.Range(-20, 20), 2, Random.Range(5, 10)), Quaternion.identity) as GameObject; Destroy(obj2, 50f); nowTime = 0f; } nowTime += Time.deltaTime; } } |
インスタンスでボールプレハブと敵プレハブを設定出来るようにします。
ボールプレハブはゲームに設置した床のゲームオブジェクトをマウスクリックしたときにその場所に表示し、
敵プレハブは一定時間毎に登場するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // マウスの左クリックを押したら床にボールを表示する if(Input.GetButtonDown("Fire1")) { // メインカメラからマウスクリックした位置にレイを飛ばす Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; GameObject obj; // レイがFieldレイヤーを設定したゲームオブジェクトに接触したらそこにボールを表示 if(Physics.Raycast(ray, out hit, 1000f, LayerMask.GetMask("Field"))) { // ボールプレハブからクリックした位置にゲームオブジェクトを生成する obj = Instantiate(ball, hit.point + new Vector3(0, 0.25f, 0), Quaternion.identity) as GameObject; // 5秒後に削除 Destroy(obj, 5f); } } |
Updateメソッド内でマウスの左クリックを押された時の処理をしていますが、ここでメインカメラからマウスクリックした位置にレイを飛ばしています。
レイが『Field』レイヤーが設定されたゲームオブジェクトに接触したらそこにボールプレハブからゲームオブジェクトを生成しています。
作成したボールプレハブが増え過ぎると困るのでDestroyメソッドを使用して今作成したゲームオブジェクトが5秒後に消えるようにしています。
1 2 3 4 5 6 7 8 9 10 11 | // 一定時間が経過したら敵を生成する if(nowTime >= createTime) { GameObject obj2; obj2 = Instantiate(enemy, new Vector3(Random.Range(-20, 20), 2, Random.Range(5, 10)), Quaternion.identity) as GameObject; Destroy(obj2, 50f); nowTime = 0f; } nowTime += Time.deltaTime; |
敵プレハブはインスペクタで設定した秒数を経過したらゲームオブジェクトを生成しています。
Random.Rangeメソッドの引数は
1 2 3 | Random.Range(最小値, 最大値); |
でint型の値を設定すると最小値と最大値を含まない範囲のランダムな値を得られ、float型の値を設定すると最小値と最大値を含む範囲のランダムな値が得られます。
なので、X軸とZ軸にランダムな値を与えて敵の登場位置を変更しています。
敵キャラクターもわんさか増えると邪魔なので50秒後には消すようにしてます。
プレハブからゲームオブジェクトが生成されるか確認する
これでスクリプトが完成したのでManagementゲームオブジェクトに設定しインスペクタでプレハブと秒数の設定をしてください。
これでインスペクタの設定が出来ましたのでUnityを実行して確認してみましょう。
床をクリックするとそこにボールゲームオブジェクトが登場し、5秒後に消えています。
またゾンビはインスペクタで設定した5秒間隔で登場しているのが確認出来ます。
オブジェクトの親を設定する
さきほどのスクリプトでは位置と回転を設定しプレハブからインスタンスを生成していましたが、すべてのインスタンスに親が設定されていませんでした。
インスタンスを何らかのゲームオブジェクトの子要素にしたい時もあると思います。
Instantiateメソッドの第4引数で親にしたいゲームオブジェクトのTransformを渡すことでインスタンス化する時に親の設定も出来ますが、後から親の設定をしたい時もあります。
そんな時は
1 2 3 4 | obj.transform.SetParent (transform); Destroy (obj, 5f); |
プレハブをインスタンス化した後にそのインスタンスを5秒後に削除していましたが、その前にSetParentを使って親要素にこのスクリプトのtransformを指定します。
するとCreateInstanceスクリプトが設定されているのはManagementゲームオブジェクトなので生成するインスタンスの親がManagementになります。
ボールのインスタンスの親をManagementにしてみましょう。
ボールのインスタンスを生成した後に先ほどのobj.transform.SetParent(transform)を追加してください。
↑のように床をクリックしてボールのインスタンスを生成すると親要素がManagementになっています。
ゾンビの方は親要素を指定していないので親がない状態で生成されています。
オブジェクトの名前を変更する
インスタンス化したゲームオブジェクトは
上のように『プレハブの名前+(Clone)』となります。
しかし、インスタンス化したゲームオブジェクトの名前を自分で設定したい事もあると思います。
インスタンス化したゲームオブジェクトはGameObjectのプロパティを持っていますので、nameプロパティを操作すれば独自の名前を付ける事が出来ます。
例えば10体のゲームオブジェクトを生成し、独自の名前+番号を付けたいとしたら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ChangeInstanceName : MonoBehaviour { [SerializeField] private GameObject prefab; private GameObject[] instances = new GameObject[10]; // Use this for initialization void Start() { for (int i = 0; i < 10; i++) { instances[i] = Instantiate<GameObject>(prefab, transform.position, Quaternion.identity); instances[i].name = "MyObject" + i; } } } |
上のようにプレハブから生成したインスタンスのゲームオブジェクトのnameプロパティに文字列を代入すると出来ます。
プレハブは便利
今回、あらためてプレハブを使ってみましたが、やっぱり便利ですね。
記事内でも触れていますが、弾を発射する時や同じ敵を量産したい時にプレハブが使えると非常に楽になります。
また、エフェクトを表示したい時にもInstantiateを使ってそのエフェクトを表示したい場所に表示すると言う事も出来ます。
つまりプレハブは今まで使っていたものとまったく別の機能ではなくただのゲームオブジェクトの集まりであるという事ですね。
試しにManagementのインスペクタでNew Prefabの代わりにProjectタブのAssetsフォルダにある他のゲームオブジェクトを設定して確認してみてください。
床をマウスクリックするとそのゲームオブジェクトがボールの代わりに表示されるはずです。
この記事では実用的なサンプルを使っていませんが、実用的なサンプルは他の記事にたくさんあるので参考にしてみてください。