この時点までに基本的なところまで Prefabs の概念について理解できているはずです。これらはゲームを通じて再利用できる,事前に定義済みの GameObject および Components です。もしプレハブが何か分からない場合は, Prefabs でより基本的な説明があるため参照下さい。
プレハブは複雑なゲームオブジェクトを実行時にインスタンス化したい場合に便利です。プレハブのインスタンス化に関する代替手段はゼロからコードを使用してゲームオブジェクトを作成することです。プレハブのインスタンス化はこの代替手段に比べて多くの長所があります:
プレハブの威力を確認するために,便利に使える基本的な場面を考えて見ます:
このサンプルではプレハブの使用をコードからオブジェクト作成と比較して長所を確認していきます。
最初に,コードからレンガの壁を作成します:
// 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);
}
}
}
}
もしコードを実行すると, Play Mode に入ったときにレンガの壁全体が見られます。個別のレンガの機能に関連する箇所が二行あります: CreatePrimitive() 行,および AddComponent() 行です。ここまで悪くはないですが,各々のレンガはテクスチャがありません。行いたい全ての追加のアクションは一行づつ必要となります,例えばテクスチャ変更,摩擦,Rigidbody の mass (重量)は別の行です。
もしプレハブを作成して,セットアップをあらかじめ行っておくと,一行のコードを使って各レンガの作成とセットアップを行います。これにより,もし変更を行いたい場合に大量のコードの保守と変更が不要となります。プレハブがあれば,変更を行って実行するのみっです。コードの修正は必要ありません。
もし各レンガでプレハブを使用している場合,次のコードで壁が作成できます。
// JavaScript
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” プレハブをインスペクタ上の brick 変数にドラッグ&ドロップします。Play をクリックすれば,プレハブで作成された壁が表示されます。
このワークフローパターンはUnityで繰り返し何回も使用できるものです。はじめは,スクリプトから作成すると2行だけが長いだけで,何故このほうがそこまで良いのか分からないかもしれません。
しかし,今はプレハブを使用しているので,プレハブの調整を数秒で出来ます。全てのインスタンスの重さを変更したい?そういった場合はプレハブのRigidbody を一回だけ調整するだけです。全てのインスタンスで Material を別のものに変更したい?マテリアルをプレハブの上に一回だけドラッグ&ドロップするだけです。摩擦を変更したい?プレハブのコライダで Physic Material を変更するだけです。全てのボックスにパーティクルシステムを追加したい? プレハブに子オブジェクトを一回追加するのみです。
プレハブをこのシナリオで使用する方法は次のとおりです:
ロケットのゲームオブジェクトを完全にコードから,手動でコンポーネントを追加しプロパティをセットして,作成することも出来ますが,プレハブをインスタンス化したほうが遥かに簡単です。ロケットのインスタンス化を,ロケットのプレハブの複雑さに関わらず,一行のコードで行うことが出来ます。プレハブをインスタンス化した後,さらにインスタンス化されたオブジェクトのプロパティを変更することが出来ます。(例えば,ロケットの Rigidbody の velocity をセットする,等)
使用することが簡単であることとは別に,後からプレハブを変更することが出来ます。このためロケットを作成している場合,すぐにトレイル パーティクルを加える必要がありません。それは後から出来ます。トレイルを子ゲームオブジェクトとしてプレハブに加えると,全てのインスタンス化されたロケットはトレイルのパーティクルが出来ます。そして最後に,速やかにインスペクタ上でロケットプレハブのプロパティを微調整できるため,ゲームの調整が遥かに容易になります。
次のスクリプトでロケットを Instantiate() 関数を使用して発射する方法を示します。
// JavaScript
// Require the rocket to be a rigidbody.
// This way we the user can not assign a prefab without rigidbody
var rocket : Rigidbody;
var speed = 10.0;
function FireRocket () {
var rocketClone : Rigidbody = Instantiate(rocket, transform.position, transform.rotation);
rocketClone.velocity = transform.forward * speed;
// You can also acccess other components / scripts of the clone
rocketClone.GetComponent(MyRocketScript).DoSomething();
}
// Calls the fire method when holding down ctrl or mouse
function Update () {
if (Input.GetButtonDown("Fire1")) {
FireRocket();
}
}
// C#
// Require the rocket to be a rigidbody.
// This way we the user can not assign a prefab without rigidbody
public Rigidbody rocket;
public float speed = 10f;
void FireRocket () {
Rigidbody rocketClone = (Rigidbody) Instantiate(rocket, transform.position, transform.rotation);
rocketClone.velocity = transform.forward * speed;
// You can also acccess other components / scripts of the clone
rocketClone.GetComponent<MyRocketScript>().DoSomething();
}
// Calls the fire method when holding down ctrl or mouse
void Update () {
if (Input.GetButtonDown("Fire1")) {
FireRocket();
}
}
完全にリギングされた敵キャラクターがいて,死亡したとします。キャラクター上で単に死亡したアニメーションを再生して,全ての敵ロジックをハンドリングしていた全てのスクリプトを無効化します。おそらくいくつかのスクリプトを取り除くことが必要となり,追加のカスタムロジックにより誰も死亡した敵に対する攻撃を行わない,などその他クリーンアップのタスクが必要です。
遥かに良いアプローチは敵キャラクターを直ちに削除して,インスタンス化された破壊物で置き換えることです。これにより柔軟性が高まります。死亡キャラクターに対して異なるマテリアルを使用できたり,まったく異なるスクリプトをアタッチしたり,いくつもの部分に破壊されたオブジェクトを含むプレハブを生成してバラバラになった敵を再現したり,または単にキャラクターのあるバージョンを持ったプレハブをインスタンス化することも出来ます。
いずれのオプションであっても,一回 Instantiate() をコールするだけであり,正しいプレハブに結び付けて,それで完了です。
覚えるべき重要な部分は Instantiate()する破壊物は元とは全く異なるオブジェクトで作成できることです。例えば,飛行機があったとして,二つのバージョンをモデリングします。ひとつは飛行機が, Mesh Renderer および飛行機の物理挙動を実行するスクリプトです。モデルをひとつのオブジェクトにだけすることでゲームは,より少ない三角形で作成され,より少ないオブジェクトで構成されるため,より小さいパーツを多く使用するよりもレンダリングが早く,より高速に実行できます。さらに飛行機が幸せそうに飛び回っている間はわざわざいくつかの部分に分ける必要性がありません。
破壊された飛行機のプレハブを作成するときに典型的なステップは:
次の例は,これらのステップがどのようにコード内でモデル化されているかをお見せします。
// JavaScript
var wreck : GameObject;
// As an example, we turn the game object into a wreck after 3 seconds automatically
function Start () {
yield WaitForSeconds(3);
KillSelf();
}
// Calls the fire method when holding down ctrl or mouse
function KillSelf () {
// Instantiate the wreck game object at the same position we are at
var wreckClone = Instantiate(wreck, transform.position, transform.rotation);
// Sometimes we need to carry over some variables from this object
// to the wreck
wreckClone.GetComponent(MyScript).someVariable = GetComponent(MyScript).someVariable;
// Kill ourselves
Destroy(gameObject);
// C#
public GameObject wreck;
// As an example, we turn the game object into a wreck after 3 seconds automatically
IEnumerator Start() {
yield return new WaitForSeconds(3);
KillSelf();
}
// Calls the fire method when holding down ctrl or mouse
void KillSelf () {
// Instantiate the wreck game object at the same position we are at
GameObject wreckClone = (GameObject) Instantiate(wreck, transform.position, transform.rotation);
// Sometimes we need to carry over some variables from this object
// to the wreck
wreckClone.GetComponent<MyScript>().someVariable = GetComponent<MyScript>().someVariable;
// Kill ourselves
Destroy(gameObject);
}
}
例えばたくさんのオブジェクトをグリッド状または円状に配置したいとします。伝統的にはこれは次のいずれかの方法をとります:
このため,代わりにプレハブで Instantiate() を使用して下さい。なぜゲームオブジェクトがこのシナリオで役に立つのかは理解できてるとおもいます。このシナリオのため必要なコードは次の通りです:
// JavaScript
// Instantiates a prefab in a circle
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#
// Instantiates a prefab in a circle
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
// Instantiates a prefab in a grid
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#
// Instantiates a prefab in a grid
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);
}
}
}