この時点までに プレハブ の概念について基本的なレベルで理解できているはずです。プレハブはゲームを通じて再利用できる、事前定義済みの ゲームオブジェクト と コンポーネント の集合です。もしプレハブが何か分からない場合は、プレハブ のより基本的な説明を参照してください。
プレハブは複雑なゲームオブジェクトをランタイムにインスタンス化したい場合に便利です。プレハブのインスタンス化の代替手段は 0 からコードを使用してゲームオブジェクトを作成することです。プレハブのインスタンス化はこの代替手段に比べて、以下を含む多くの長所があります。
プレハブの効果を説明するために、便利に使える基本的な場面を考えてみます。
このサンプルでは、コードからオブジェクトを作成する場合と比較して、プレハブ使用の利点を示しています。
最初に、コードからレンガの壁を作成します:
// JavaScript
function Start () {
for (var y = 0; y < 5; y++) {
for (var x = 0; x < 5; x++) {
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.AddComponent.<Rigidbody>();
cube.transform.position = Vector3 (x, y, 0);
}
}
}
// C#
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);
}
}
}
}
もしコードを実行すると、再生モードのときにレンガの壁全体が見られます。個別のレンガの機能に関連する箇所が 2 行あります。 CreatePrimitive
の行と AddComponent
の行です。ここまではそう悪くはないですが、個々のレンガにはテクスチャがありません。ブロックに行いたいすべての追加のアクションは 1 行づつ必要となります。例えば、テクスチャ、摩擦、Rigidbody の mass (重量) の変更には追加の行が必要です。
もしプレハブを作成して、設定をあらかじめ行っておくと、1 行のコードを使えば各レンガの作成と設定ができます。これにより、もし変更を行いたい場合に大量のコードの保守と変更が不要となります。プレハブがあれば、変更を行って実行するのみです。コードの変更は必要ありません。
もし各レンガにプレハブを使用している場合、次のコードで壁が作成できます。
// JavaScript
//Instantiate accepts any component type, because it instantiates the GameObject attached to the component. Therefore, Transform is accepted here.
var brick : Transform;
function Start () {
for (var y = 0; y < 5; y++) {
for (var x = 0; x < 5; x++) {
Instantiate(brick, Vector3 (x, y, 0), Quaternion.identity);
}
}
}
// C#
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 を含む必要があるなどと、どこにも書いていません。このすべてはプレハブに定義されていてエディターで速やかに作成することができます。
さて、プレハブを作成してみましょう。エディターで以下のように行います。
Brick プレハブを作成したので、これをスクリプトの brick 変数にアタッチする必要があります。スクリプトを含む空のゲームオブジェクトを選択すると、インスペクター上で Brick 変数が表示されます。
次に Project ビューの Brick プレハブを Inspector 上の brick 変数にドラッグアンドドロップします。再生ボタンをクリックすると、プレハブを使って作成した壁が表示されます。
このワークフローは Unity で繰り返し使用できるものです。はじめは、なぜこの方法がそこまでよいのか分からないかもしれません。スクリプトから作成してもたった 2 行長いだけですから。
しかし、今はプレハブを使用しているので、プレハブの調整を数秒でできます。これらすべてのインスタンスの mass を変更したい場合はプレハブの Rigidbody を 1 回調整するだけです。すべてのインスタンスで Material を別のものに変更したい場合は使用するマテリアルをプレハブの上に 1 回ドラッグするだけです。摩擦を変更したい場合は、プレハブの Collider の Physic Material を変更するだけです。すべてのボックスに Particle System を追加したい場合は、プレハブに子を 1 回追加するだけです。
プレハブをこのシナリオで使用する方法は次のとおりです。
ロケットのゲームオブジェクトを完全にコードから作成する (手動でコンポーネントを追加しプロパティーを設定する) こともできますが、プレハブをインスタンス化したほうがはるかに簡単です。ロケットのプレハブの複雑さに関わらず、1 行のコードでロケットのインスタンス化を行うことができます。プレハブをインスタンス化した後、さらに、インスタンス化したオブジェクトのプロパティーを変更することができます (例えば、ロケットの Rigidbody の速度を設定するなど)。
あつかうのが容易である事とは別にして、プレハブは後から変更することができます。そのためロケットの作成時に、急いでトレイルパーティクルを加える必要はありません。後から追加すれば良いのです。トレイルを子ゲームオブジェクトとしてプレハブに追加すると、すべてのインスタンス化されたロケットに、トレイルのパーティクルが加えられます。そして最後に、インスペクター上で速やかにロケットプレハブのプロパティーを微調整できるため、ゲームの調整が遥かに容易になります。
次のスクリプトでロケットを Instantiate() 関数を使用して発射する方法を示します。
// JavaScript
// ロケットをリジッドボディにする必要があります。
// この状態で、リジッドボディなしではプレハブを割り当てることができません。
var rocket : Rigidbody;
var speed = 10.0;
function FireRocket () {
var rocketClone : Rigidbody = Instantiate(rocket, transform.position, transform.rotation);
rocketClone.velocity = transform.forward * speed;
// 複製のコンポーネント/スクリプトにアクセスすることもできます。
rocketClone.GetComponent.<MyRocketScript>().DoSomething();
}
// ctrl かマウスを押し続けると、 fire メソッドが呼び出されます。
function Update () {
if (Input.GetButtonDown("Fire1")) {
FireRocket();
}
}
// C#
// ロケットをリジッドボディにする必要があります。
// この状態で、リジッドボディなしではプレハブを割り当てることができません。
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 かマウスを押し続けると、 fire メソッドが呼び出されます。
void Update () {
if (Input.GetButtonDown("Fire1")) {
FireRocket();
}
}
完全にリギングされた敵キャラクターが死亡したとします。その場合、単にキャラクター上で死亡するアニメーションを再生して、通常、敵ロジックを処理していたすべてのスクリプトを無効にすることもできます。恐らく、スクリプトの一部を取り除き、死亡した敵に対して誰もこれ以上攻撃しないカスタム製のロジックを加え、他のクリーンアップタスクを処理する必要があります。
もっと良い方法は、無傷のキャラクターを直ちに削除して、インスタンス化された破壊されたプレハブで置き換えることです。これにより様々なことが簡単にできます。死亡したキャラクターに対して異なるマテリアルを適用したり、まったく異なるスクリプトをアタッチしたり、多数に分解されたオブジェクトを含むプレハブを生成して、バラバラになった敵を再現したり、または単純に変形したキャラクターを含むプレハブをインスタンス化することもできます。
いずれの方法であっても、1 回 Instantiate() を呼び出すだけでよく、正しいプレハブに結び付けて、それで完了です。
重要なことは、Instantiate() する破壊された物を元とは全く異なるオブジェクトで作成できることです。例えば、飛行機があったとすると、2 つのバージョンを作ります。ひとつは、Mesh Renderer および飛行機の物理挙動を実行するスクリプトを持つ飛行機です。モデルを 1 つのオブジェクトだけにすることで、ゲームはより高速に実行されます。なぜなら、モデルをより少ない三角形で作成でき、より少ないオブジェクトで構成されるため、小さいパーツを多く使用するよりも速くレンダリングができるからです。さらに飛行機が飛び回っている間はわざわざいくつかの部分に分ける必要性がありません。
破壊された飛行機のプレハブを作成するときに典型的なステップは以下のとおりです。
次の例は、これらのステップがどのようにコード内でモデル化されているかをお見せします。
// JavaScript
var wreck : GameObject;
// 例として、自動的に3秒後にゲームオブジェクトを破棄します
function Start () {
yield WaitForSeconds(3);
KillSelf();
}
// ctrl かマウスを押すと、fire メソッドを呼び出します
function KillSelf () {
//破棄したゲームオブジェクトを同じ位置にインスタンス化します
var wreckClone = Instantiate(wreck, transform.position, transform.rotation);
// オブジェクトから破棄したものへ、いくつかの変数を引き継ぐ
// 必要がある場合もあります。
wreckClone.GetComponent.<MyScript>().someVariable = GetComponent.<MyScript>().someVariable;
// 破棄します
Destroy(gameObject);
// C#
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);
}
}
例えばたくさんのオブジェクトをグリッド状または円状に配置したいとします。既存の方法では、次のいずれかの方法をとります。
このため、代わりにプレハブで Instantiate() を使用してください。なぜゲームオブジェクトがこのシナリオで役に立つのかは理解できてるとおもいます。このシナリオのため必要なコードは次のとおりです。
// JavaScript
// プレハブを円状にインスタンス化
var prefab : GameObject;
var numberOfObjects = 20;
var radius = 5;
function Start () {
for (var i = 0; i < numberOfObjects; i++) {
var angle = i * Mathf.PI * 2 / numberOfObjects;
var pos = Vector3 (Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius;
Instantiate(prefab, pos, Quaternion.identity);
}
}
// C#
// プレハブを円状にインスタンス化
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);
}
}
// JavaScript
// プレハブをグリッド状にインスタンス化
var prefab : GameObject;
var gridX = 5;
var gridY = 5;
var spacing = 2.0;
function Start () {
for (var y = 0; y < gridY; y++) {
for (var x=0;x<gridX;x++) {
var pos = Vector3 (x, 0, y) * spacing;
Instantiate(prefab, pos, Quaternion.identity);
}
}
}
// C#
// プレハブをグリッド状にインスタンス化
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);
}
}
}