この時点までに基本的なところまで Prefabs の概念について理解できているはずです。これらはゲームを通じて再利用できる、事前に定義済みの GameObject および Components です。もしプレハブが何か分からない場合は、プレハブ でより基本的な説明があるため参照してください。
プレハブは複雑なゲームオブジェクトを実行時にインスタンス化したい場合に便利です。プレハブのインスタンス化に関する代替手段は 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
の行です。ここまで悪くはないですが、各々のレンガはテクスチャがありません。行いたいすべての追加のアクションは一行づつ必要となります、例えばテクスチャ変更、摩擦、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 を 1回調整するだけです。すべてのインスタンスで Material を別のものに変更したい場合はマテリアルをプレハブの上に1回ドラッグ&ドロップするだけです。摩擦を変更したい場合はプレハブのコライダーで Physic Material を変更するだけです。すべてのボックスにパーティクルシステムを追加したい場合はプレハブに子オブジェクトを1回追加するのみです。
プレハブをこのシナリオで使用する方法は次のとおりです:
ロケットのゲームオブジェクトを完全にコードから手動でコンポーネントを追加しプロパティーをセットして作成することもできますが、プレハブをインスタンス化したほうがはるかに簡単です。ロケットのインスタンス化を、ロケットのプレハブの複雑さに関わらず、一行のコードで行うことができます。プレハブをインスタンス化した後、さらにインスタンス化されたオブジェクトのプロパティーを変更することができます。(例えば、ロケットの 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();
}
}
完全にリギングされた敵キャラクターがいて、死亡したとします。その場合、単にキャラクター上で死亡したアニメーションを再生して、敵ロジックを常時ハンドリングしていたすべてのスクリプトを無効化します。恐らく、いくつかのスクリプトを取り除く必要があるでしょうし、死亡した敵に対して誰も攻撃しない事を確認するためのカスタムロジックの追加や、その他のクリーンアップのタスクが必要になるでしょう。
もっと良い方法は、敵キャラクターを直ちに削除して、インスタンス化された破壊物で置き換えることです。これにより柔軟性が高まります。死亡したキャラクターに対して異なるマテリアルを適用したり、まったく異なるスクリプトをアタッチしたり、いくつもの部分に破壊されたオブジェクトを含むプレハブを生成してバラバラになった敵を再現したり、または単純にキャラクターのあるバージョンを持ったプレハブをインスタンス化することもできます。
いずれのオプションであっても、1回 Instantiate() をコールするだけであり、正しいプレハブに結び付けて、それで完了です。
重要な部分は Instantiate() する破壊物は元とは全く異なるオブジェクトで作成できることです。例えば、飛行機があったとして、2つのバージョンをモデリングします。ひとつは飛行機が、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);
}
}
}