tanaka's Programming Memo

プログラミングについてのメモ。

(6)ブロックを消す

ブロックを壊せるようにする

ここまでで、ボールを跳ね返すことが出来るようになった。次に、ブロックが壊れるようにしてみよう。

作業の流れ

ブロックをボールが衝突すると、【Unity】は衝突イベントを呼び出す。あるゲームオブジェクトが他の衝突物(コライダー)に衝突すると、「void OnCollisionEnter(Collision col)」という関数が呼び出されて、衝突時の詳細が引数「col」に渡される。

ボールは「プレイヤーバー」「ブロック」「枠」などにぶつかる可能性がある。ぶつかったのが「ブロック」かを判定するために【タグ(Tag)】を利用する。

  • 「Block」というタグを作成する。
  • 「Block」と「Block2」プレハブに「Block」タグ(Tag)を設定する。
  • 衝突関数(OnCollisionEnter())を作成。
  • ぶつかった相手を引数から取り出す。
  • ぶつかった相手がブロックだったら、登録を削除する。

以上でブロックを画面から消すことが出来る。それでは早速実装してみよう。

タグの設定

まずは「Block」というタグを作成する。

  • 【プロジェクト(Project)】ビューの「Prefabs」フォルダから「Block」をクリックする。
  • 「タグ(Tag)」ボタンをドラッグして開いて、一番下の【Add Tag...】を選ぶ。
  • 【Tags】の左にある三角形をクリックして開く。
  • 【Element 0】をクリックして選択して、「Block」と入力する。

以上の操作で、「ブロック」タグの作成が完了である。

ブロックプレハブに作成した「Block」タグを設定する。

  • 【プロジェクト(Project)】ビューの「Prefabs」フォルダから「Block」をクリックする。
  • 【Tag】ボタンをドラッグして、一覧から「Block」を選択する。

以上で、「Block」プレハブのTagが「Block」になった。

同様に「Block2」プレハブの【Tag】も「Block」にしよう。

ボールに衝突プログラムを追加する
  • 【プロジェクト(Project)】ビューの「Scripts」フォルダから「CBall」をダブルクリックする。
  • MonoDevelop】が開いたら、一番最後の「}」を見つけて、その1行上に以下のプログラムを追加する。
	/** 衝突コールバック*/
	void OnCollisionEnter(Collision col) {
		if (col.gameObject.CompareTag("Block")) {
			Destroy(col.gameObject);
		}
	}

このプログラムは以下のような意味である。

void OnCollisionEnter(Collision col) {
  • ボールが何かに衝突した際に【Unity】から呼ばれる関数を定義する。【OnCollisionEnter()】という名前の関数を定義することで、衝突時の処理を書くことが出来る。
  • 衝突時のデータが【Collision】型の変数「col」に渡される。
if (col.gameObject.CompareTag("Block")) {
  • 「col.gameObject」で、衝突相手のゲームオブジェクトを参照できる。
  • 「col.gameObject.CompareTag(タグ名)」で、衝突相手のタグ(Tag)がチェックしたいものかを判定できる。上のように書くことで、ぶつかった相手が「Block」のタグを持っているかを判定できる。
Destroy(col.gameObject);
  • 衝突相手のタグが「Block」だった時に処理するプログラム。
  • このようにすることで、衝突相手を削除することが出来る。

以上、出来たら実行してみよう。ボールがぶつかったブロックが消えるはずである。

そういえば「階層(Hierarchy)」ビューに「Ball」が残っているかも知れない。

ボールはプログラムで生成するので、これは不要なので、右クリックして「Delete」で消しておくこと。

全てのブロックが消えたら、ステージを初期化する

ブロックが全部消えたら、ステージを読み直すようにしよう。

今は、ボールがブロックにぶつかったらボールのスクリプト内でブロックを消すようにしている。しかし、このやり方だと点数を入れたり、壊れにくいブロックを作ったりする場合に、ブロック側の処理を、ボールのスクリプトにプログラムを書くことになる。これは「オブジェクト思考」的な設計とはいえない。ボールとの衝突はボール側で検出するが、その後のブロックに関連する処理はブロック側で処理することにしよう。

ブロック用のスクリプトの作成

ブロック用のスクリプトを作成して、消える処理をそっちに移そう。

  • 【プロジェクト(Project)】ビューの「Scripts」フォルダを右クリックして、「Create」→「C# Script」を選択する。
  • 作成したスクリプトの名前を「CBlock」に修正する。
  • 「CBlock」スクリプトを別のテキストエディタで開いて、文字エンコードを「Unicode big endian」に修正する。
  • 「CBlock」スクリプトを【MonoDevelop】で開く。
  • 一番最後の「}」を見つけて、その上の行に以下のプログラムを追加する。
	public void setDamage() {
		Destroy(gameObject);
	}
  • 上書き保存する。

作成した「CBlock」スクリプトを、「Block」と「Block2」プレハブに追加する。

  • 【Unity】に戻る。
  • 【プロジェクト(Project)】ビューの「Scripts」フォルダ内の「CBlock」をドラッグして、「Prefabs」フォルダ内の「Block」にドロップする。
  • 同様に、「Block2」プレハブにも「Block」スクリプトをドラッグ&ドロップする。

作成した「setDamage()」関数を「CBall」スクリプトから呼び出す。

  • 「CBall」スクリプトをダブルクリックして、【MonoDevelop】で開く。
  • 「Destroy(col.gameObject);」という行を探して、以下に書き換える。
			col.gameObject.SendMessage("setDamage");
  • 接触相手のゲームオブジェクトに登録されているスクリプトの「setDamage()」を呼び出すプログラムである。

以上が出来たら、実行してブロックが消えるかを確認しよう。

これでボールがブロックにぶつかった時は「CBlock」スクリプトの「setDamage()」関数が呼ばれるようになる。今後、ブロックが消える際の処理は「CBlock」スクリプト側を改造すればよい。

ブロックの数を数える

ブロックが全て壊したかを確認するには、ブロックの数をステージの開始時に数えて覚えておき、ブロックが壊れるごとにその値を減らして、0になったかを判定すればよい。

ブロックの数を覚えておくための変数を「CBlock」スクリプトに追加する。

  • 「CBlock」スクリプトを【MonoDevelop】で開く。
  • 「public class CBlock : MonoBehaviour {」という行を探し、下の行に以下のプログラムを追加する。
	static int iBlockCnt;
  • 「static」は【クラス変数】にする宣言である。
  • 【クラス変数】は、全てのインスタンス間で共用される変数である。どこかのブロックの「iBlockCnt」を1減らすと、他のブロックの「iBlockCnt」も1減らされるので、最終的にこの値が0になった時に、全てのブロックがなくなったことを表すことが出来る。

ブロックを初期化する時に、「iBlockCnt」にブロックの数が入るようにする。

  • 「void Start() {」となっている行を探して、下の行に以下のプログラムを追加する。
		iBlockCnt = GameObject.FindGameObjectsWithTag("Block").Length;
  • 「GameObject.FindGameObjectsWithTag(タグ名)」という命令で、このシーンの中にあるゲームオブジェクトから、「タグ名」という【Tag】が設定されているものを全て探し出して、配列で返してくれる。
  • 「.Length」というので、見つけ出した配列の数を返す。
  • 以上から、自分でブロックを数えなくても、面に登録されている「Block」のタグの付いたオブジェクトの数が、自動的に数えられて「iBlockCnt」変数に設定される。
ブロックの残り数をカウントダウンする

「setDamage()」関数内で、ブロックの数をカウントダウンする。

  • 「CBlock」スクリプトを【MonoDevelop】で開く。
  • 「Destory(gameObject);」という行を探し、その下に以下のプログラムを追加する。
		iBlockCnt--;
		if (iBlockCnt <= 0) {
			Application.LoadLevel("Game");
		}

以上のプログラムは以下のような意味である。

iBlockCnt--;
  • 「iBlockCnt」を1減らす。
if (iBlockCnt <= 0) {
  • 減らした「iBlockCnt」の数が、0以下になっているかを確認する。
Application.LoadLevel("Game");
  • シーンの読み出し処理。Gameシーンに切り替えて、面を初期化している。
  • 別の面を作った場合は、次の面のシーンを呼び出せば、次の面に移動できる。

以上で、クリア処理が出来上がる。頑張って全てのブロックを消してみよう。

ここまでの「CBall.cs」

using UnityEngine;
using System.Collections;

public class CBall : MonoBehaviour {
	public float INIT_DEGREE = 75f;
	public float INIT_SPEED = 250f;

	// Use this for initialization
	void Start () {
		//shotBall();
	}
	
	// Update is called once per frame
	void Update () {
	
	}
	
	/** ボールを発射する*/
	void shotBall() {
		Vector3 vel = Vector3.zero;
		vel.x = INIT_SPEED*Mathf.Cos(INIT_DEGREE*Mathf.PI/180f);
		vel.y = INIT_SPEED*Mathf.Sin(INIT_DEGREE*Mathf.PI/180f);
		rigidbody.velocity = vel;
	}
	
	/** 衝突コールバック*/
	void OnCollisionEnter(Collision col) {
		if (col.gameObject.CompareTag("Block")) {
			col.gameObject.SendMessage("setDamage");
		}
	}
}

ここまでの「CBlock.cs」

using UnityEngine;
using System.Collections;

public class CBlock : MonoBehaviour {
	static int iBlockCnt;
	
	// Use this for initialization
	void Start () {
		iBlockCnt = GameObject.FindGameObjectsWithTag("Block").Length;
	}
	
	// Update is called once per frame
	void Update () {
	
	}
	
	public void setDamage() {
		Destroy(gameObject);
		iBlockCnt--;
		if (iBlockCnt <= 0) {
			Application.LoadLevel("Game");
		}
	}
}