もし自分のプロジェクトが壊れてしまった場合は、この作成例からリスタートするとよいかもしれない。
コリジョン (collision) とは「衝突」の意で、いわゆる「当たり判定」と同義に使われることがある。 Unity では物理シミュレーターの機能としてコリジョンを検出できるようになっている。
とりあえず、弾が何かに当たったら文字列を出力してみよう。次のようなスクリプトを新規作成し、弾のプレハブに与える。
#pragma strict
function OnCollisionEnter(info : Collision) {
Debug.Log("Hit");
}
動作確認してみよう。衝突時に Hit
と出力されれば成功。
この OnCollisionEnter
は衝突が始まった(接触が始まった)瞬間に呼ばれる関数で、たいがいの衝突はこれで検出できる。
ちなみに、衝突が終わった(接触しているものが離れた)瞬間に呼ばれる関数として OnCollisionExit
というのも存在する。スクリプトを次のように変更すると、衝突が終わるたびに Exit
と出力される。
#pragma strict
function OnCollisionEnter(info : Collision) {
Debug.Log("Hit");
}
function OnCollisionExit(info : Collision) {
Debug.Log("Exit");
}
衝突した相手の情報は引数 info
に格納されている。例えば衝突相手のゲームオブジェクトは info
の中にある gameObject
プロパティから取得できる。次のようにすれば、衝突した相手の名前が出力される。
#pragma strict
function OnCollisionEnter(info : Collision) {
Debug.Log(info.gameObject.name);
}
衝突した相手が何であるかを判別するにはタグ (tag) を使うのがよい。
タグとはゲームオブジェクトに任意の文字列を与える機能で、ゲームオブジェクトの種類や役割を判別するのに使うことが多い(使い方は決められていないので、プログラマーが自由に決めて使う感じ)。
ここではひとまず “Breakable” というタグを作り、このタグを貼られたゲームオブジェクトは弾で破壊可能なものとしよう。
まずタグを作らなければいけない。メインメニューの Edit → Project Settings → Tags を選択する。すると Inspector に Tag Manager が表示される。
“Tags” の部分は最初折りたたまれているので注意。 Element0 の欄に Breakable と入力する。これで Breakable タグができた。
ゲームオブジェクトに対してタグを設定するには Inspector の上の方にある “Tag” のドロップダウンを操作する。
ここでは Barrel (red) のプレハブを破壊可能なものとした。
タグを判別する処理を弾のスクリプトに追加する。
#pragma strict
function OnCollisionEnter(info : Collision) {
if (info.gameObject.tag == "Breakable") {
Destroy(info.gameObject);
}
}
タグは GameObject クラスの tag プロパティから取得できる。これを単純に文字列で判定すればよい。ここではタグが Breakable であった場合に、衝突相手のゲームオブジェクトを Destroy で破壊している。
実際に破壊できることを確認しよう。
とりあえず弾の方で衝突の判定と破壊を行うようにしたが、本来は破壊の処理はぶつけられたゲームオブジェクトの方でするべき。そうしないと、ゲームに登場する要素が増えてくるごとに弾の処理だけがどんどん複雑化してしまう。適切に分担を分けることで、各々の処理をシンプルに保つことができる。
理想的な形は次のようになる。
このような命令の伝達を行うための仕組みとしてメッセージングが用意されている。
弾から衝突相手にメッセージを送るようにする。メッセージの名前は “ApplyDamage” としよう。
#pragma strict
function OnCollisionEnter(info : Collision) {
if (info.gameObject.tag == "Breakable") {
info.gameObject.SendMessage("ApplyDamage");
}
}
メッセージを送信するには SendMessage
関数を使う。第1引数にメッセージの名前を文字列で渡す。
メッセージを受けるためのスクリプトを追加しよう。名前は Breakable としておく。
#pragma strict
function ApplyDamage() {
Destroy(gameObject);
}
メッセージを受信するには、メッセージ名と同じ名前の関数を定義する。メッセージを受信したときにこの関数が実行される。
消滅の瞬間が味気ないのでパーティクルエフェクトで演出を与えよう。パーティクルエフェクトの基礎については解説済みなので省略。
エフェクト用に使えるテクスチャ素材を CommonAssets の中に入れてあるので、これを取り敢えず使うとよい。マテリアルを作成する際にシェーダーとして Particle/Additive や Particle/Alpha Blended を選ぶのがコツ。
爆発のようなエフェクトを作るときは “One Shot” (一斉にパーティクルを生成)と “Autodestruct” (パーティクルが出終わった後に自動消滅)をオンにすること。
パーティクルエフェクトを作り終えたら、これをプレハブにする。このプレハブをインスタンス化することによってエフェクトを発動する。
Breakable スクリプトの内容を次のようにする。
#pragma strict
var explosionPrefab : GameObject;
function ApplyDamage() {
Instantiate(explosionPrefab, transform.position, Quaternion.identity);
Destroy(gameObject);
}
現状では弾がそっと触れるだけでも相手が消えてしまう。勢いよく当たったときだけ消えるようにしてみよう。
コリジョン検出時に渡される情報の中に相対速度 (relative velocity) がある。この相対速度の大きさから衝突の勢いが分かる。弾のスクリプトの中に次のような判定式を追加すればよい。
#pragma strict
function OnCollisionEnter(info : Collision) {
if (info.gameObject.tag == "Breakable") {
if (info.relativeVelocity.magnitude > 20.0) {
info.gameObject.SendMessage("ApplyDamage");
}
}
}
ここでは決め打ちで「20.0 より大きければ」という判定を行なっているが、この 20.0 という値は変数にして持った方がいいだろう(調整しやすくするため)。
弾でゲームオブジェクトを破壊するごとに得点を加えるようにしてみよう。この「得点」のような要素は各々のゲームオブジェクトが別個に扱うことはできない。どこかに統合管理する役割のゲームオブジェクトを置かなくてはいけない。
このような統合管理役のゲームオブジェクトのことを何と呼ぶか、特に決まっているわけではないけれど、ここではゲームコントローラ (game controller) と呼ぶことにする。
空のゲームオブジェクトとして Game Controller を作成する。そしてこのゲームオブジェクトのタグを “GameController” にしておく。タグ “GameController” は Unity があらかじめ定義しているので、自分で加える必要は無い。いきなりドロップダウンから選択できる。
得点を管理するためのスクリプト Scorekeeper を作成し、これをゲームコントローラに追加する。内容は次の通り。
#pragma strict
var skin : GUISkin;
var score : int;
function AddScore(value : int) {
score += value;
}
function OnGUI() {
GUI.skin = skin;
GUI.Label(Rect(0, 0, Screen.width, 0.1 * Screen.height), "SCORE: " + score);
}
AddScore
は得点を加算するためのメッセージ。ゲームオブジェクトが破壊されたときに、ゲームコントローラに向かって AddScore
メッセージを送信すれば得点が加算されるという仕組み。
ちなみに、メッセージングにおいてはこのように引数を1つだけ与えることができる。
次に Breakable スクリプトの中身を次のようにする。
#pragma strict
var explosionPrefab : GameObject;
function ApplyDamage() {
GameObject.FindWithTag("GameController").SendMessage("AddScore", 10);
Instantiate(explosionPrefab, transform.position, Quaternion.identity);
Destroy(gameObject);
}
GameObject.FindWithTag
を使って特定のタグを持つゲームオブジェクトを検索することができる。ここではゲームコントローラを取得するのにこれを利用している。見つかったゲームオブジェクトに対して AddScore
メッセージを送信すれば得点の加算が行われる。
現状使用しているシーンのコピーとして Stage1, Stage2, Stage3 を作成し、特定の得点を達成した際に次のステージへと自動的に切り替わるようにしてみよう。
まず現状のシーンをセーブする。そして “Save Scene as” (名前を付けてシーンをセーブ)を使って別名でセーブしていく。このときの名前を Stage1, Stage2, Stage3 とする。これでとりあえずシーンをコピーすることはできた。
次に Stage1, Stage2, Stage3 の内容を少しずつ変えておこう。変えておかないと、ステージが切り替わったことがよく分からない。
ひとまず現状では手抜きとして Scorekeeper からステージの切り替えを発動する。Scorekeeper の AddScore
を次のように変更する。
var nextLevel : String;
function AddScore(value : int) {
score += value;
if (score >= 20) {
Application.LoadLevel(nextLevel);
}
}
Application.LoadLevel
は指定したシーンをロードする関数。これで得点が20以上になったタイミングで変数nextLevel
に設定されたシーンがロードされる。
Ispector 上で Scorekeeper を見ると nextLevel
が設定できるようになっているはず。各シーンにおいて、クリア後にロードするシーンの名前をここに入力していく。例えばシーン Stage1 の Scorekeeper では nextLevel を “Stage2” としておく。
とりあえず Stage3 の次は Stage1 がロードされるようにしておこう。本当は Ending シーンなどを用意して、それに繋ぐようにするとよい。