GP11 チーム制作 05

やることリスト

現状の作成例

作成例をダウンロードする

もし自分のプロジェクトが壊れてしまった場合は、この作成例からリスタートするとよいかもしれない。

コリジョンを検出してみよう

コリジョン (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 が表示される。

Tag Editor

“Tags” の部分は最初折りたたまれているので注意。 Element0 の欄に Breakable と入力する。これで Breakable タグができた。

タグの設定

ゲームオブジェクトに対してタグを設定するには Inspector の上の方にある “Tag” のドロップダウンを操作する。

Tag Setting

ここでは 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 シーンなどを用意して、それに繋ぐようにするとよい。