UnityちゃんがBlockを破壊して、しばらくしたら元に戻るようにする


はじめに
======

[前回](http://qiita.com/mokemokechicken/items/f6d8bf10ce16099fc7c3)に続きまして、
Blockを破壊して元に戻せるようにしてみます。

ロードランナーといえば、左右の足元のブロックをビーム？みたいなので削って、何故かしばらくすると復活するというのが最大の特徴かと思います。アニメーション的な効果はちょっとおいておいて、そうなるように作ってみます。

ビームは左右に出せるので、左＝`Z`、右＝`X`を押した時にビームが出ることにします。

今回のポイント
-------------

* 破壊できるBlockを特定する
	* Physics.Raycastを使う
	* Debug.DrawRay でRaycastを確認する
	* Layerを変更して余計なものにHitしないようにする
* Blockを非表示にして、しばらくしたら元に戻す

破壊できるBlockを特定する
=====

どのブロックが破壊できるか、を判定する方法はいくつかありそうですが、
せっかく？なのでUnityの中の当たり判定を最大限に使うことにします。

Raycastというのは、Ray(光線)をCast(投げかける)という意味合いなんだと思いますが、
一点から直線を引いてぶつかったObjectや地点を調べてくれる機能です。

大体の流れは以下のようになります。

* ビームが出せるのは、「落ちていない」かつ「`Z`か`X`を押しているとき」
* Unityちゃんの頭上から斜め下45度にRaycast
* HitしたのがBlockならば、`HitGun`メッセージをBlockに送る

以下の様なコードを、`UnityChanDemo1.cs`の`Update()`メソッドの下のほうに追加します。

```cs:hitgun
		// Gun
		int gun = Input.GetKey(KeyCode.Z) ? -1 : Input.GetKey(KeyCode.X) ? 1 : 0;
		if (gun != 0 && !isFalling) {
			RaycastHit hit;
			Vector3 fromPos = transform.position+Vector3.up;
			Vector3 direction = new Vector3(gun, -1, 0);
			float length = 1.8f;
			Debug.DrawRay(fromPos, direction.normalized * length, Color.green, 1, false);
			if (Physics.Raycast(fromPos, direction, out hit, length)) {
				if (canDestroyBlock(hit.transform)) {
					hit.transform.gameObject.SendMessage("HitGun");
				}
 			}
		}
```

同様にcanDestroyBlock()を追加します。

```cs:canDestroyBlockメソッド
	private bool canDestroyBlock(Transform block) {
		return 
			block.tag == BLOCK && 
			Mathf.Abs(block.position.x - transform.position.x) < 1.2f &&
			transform.position.y - block.position.y > 0.3;
	}
```

ちなみに [UnityChanDemo1.cs](https://github.com/mokemokechicken/UnityChanRoadRunner/blob/demo1d/Assets/Script/UnityChanDemo1.cs) の全体はこうなっています。


[Physics.Raycast](https://docs.unity3d.com/Documentation/ScriptReference/Physics.Raycast.html) を使う
------------------

	Physics.Raycast(始点、方向、Hit時にセットされるObject、判定する距離)

という感じに使います。
何かにHitしたら、戻り値がTrueになって、hitオブジェクトに値が入ります。

基本的には、このObjectのTagが`Block`ならば壊しても良いわけです。
ただ、高さの制約や、2つ向こうの側のBlockを消さないかという細かい条件もあるので、
実際に破壊できるかどうかは、`canDestoryBlock()`というメソッドで精査することにします。

壊せると判断すれば、そのObjectに足して `SendMessage` を使って `HitGun` メッセージを送ります。

[Debug.DrawRay](https://docs.unity3d.com/Documentation/ScriptReference/Debug.DrawRay.html) でRaycastを確認する
---------

作っている最中におもったように判定がされない原因を調べたり、Raycastの微調整などをするときに実際のRaycast光線が見えると便利です。DrawRayを使うとそれが簡単にできます。

	Debug.DrawRay(始点、方向、色、表示時間、depthTest [カメラから見えない場合に非表示にするか]))

というように使います。
ただ一つ注意があって、このDrawRayはGame Viewでは通常は表示されません。

Game Viewで表示するようにするには、 **`Gizmos`** というのを有効にする必要があります（これが最初わからなかった・・・）。下図の右上を押しておきましょう。

![demo1_unity_-_RoadRunner_-_Web_Player.png](https://qiita-image-store.s3.amazonaws.com/0/11124/4e89510f-9777-9275-b083-acd21f2a3e98.png "demo1_unity_-_RoadRunner_-_Web_Player.png")


Layerを変更して余計なものにHitしないようにする
-------------

今回Unityちゃんの頭上から斜め下にRaycastをしているので、
実はBlockに当たる前にUnityちゃん自身にRaycastがぶつかってしまいます。
これだと本当に見つけたいBlockがわかりません。また、「はしご」「バー」に当たってしまうのも問題です。

なので、「Unityちゃん」「はしご」「バー」の３つのLayerを `Ignore Raycast` というLayerに割り当てます。
Raycastをするときは基本このLayerについて無視するようで今回は好都合です。

![demo1_unity_-_RoadRunner_-_Web_Player_と_UnityちゃんがBlockを破壊して、しばらくしたら元に戻るようにする.png](https://qiita-image-store.s3.amazonaws.com/0/11124/709e650c-e55d-bc06-6c76-a40bc5a0c379.png "demo1_unity_-_RoadRunner_-_Web_Player_と_UnityちゃんがBlockを破壊して、しばらくしたら元に戻るようにする.png")

もし複数のRaycastを管理するときは、Raycastの5番目の引数にもLayerMaskというのがあるのでこれが使えそうです（今回は使いませんが）。

Prefabに修正を適用する
------------

ちょっと説明が遅れましたが、
PrefabからGameObjectに実体化したものの属性を変更（Layerの変更のような）した場合、Inspectorの`Apply`ボタンを押すとそれが大本のPrefabに反映され、その他全てのGameObjectに適用されます。便利ですね。

逆に`Revert`を押すと、GameObjectに修正が元のPrefabの属性で上書きされます。`Select`を押すと元となっているPrefabが表示されます。

Blockを非表示にして、しばらくしたら元に戻す
====================

全てが上手くいっていれば、Block オブジェクトに`HitGun`メッセージが送られています。
ただ、今はそれを処理するメソッドがないので、Consoleにはエラーが表示されます。

Blockにスクリプトを追加
--------

BlockControl.cs というC# スクリプトを作り、Blockの **子供のCube** に関連付けます。
**実際の当たり判定(Collider)はこのCubeが持っているので、SendMessageの対象はこのCubeになります**。

![demo1_unity_-_RoadRunner_-_Web_Player_と_UnityちゃんがBlockを破壊して、しばらくしたら元に戻るようにする.png](https://qiita-image-store.s3.amazonaws.com/0/11124/1ad480ce-44f6-9208-f6c8-79feaca6c0a6.png "demo1_unity_-_RoadRunner_-_Web_Player_と_UnityちゃんがBlockを破壊して、しばらくしたら元に戻るようにする.png")

以下のような処理を追加します。

* `HitGun`メッセージを受け取ったら、Timer をセットする。今回は180Frame（約3秒）とします。
* `Update()`では、 Timerの値を一つずつ減らし、0ではない場合は `collider`(衝突判定), `renderer`(描画)を無効にする

とりあえずこれで意図通り動きました。


```cs:BlockControl.cs
using UnityEngine;
using System.Collections;

public class BlockControl : MonoBehaviour {
	private const int DISAPPEAR_TIMER_COUNT = 60*3;
	private int disappearTimer = 0;

	// Use this for initialization
	void Start () {
	}
	
	// Update is called once per frame
	void Update () {
		if (disappearTimer > 0) {
			disappearTimer--;
		}
		renderer.enabled = collider.enabled = disappearTimer == 0;
	}

	void HitGun() {
		if (disappearTimer > 0) {
			return;
		}
		disappearTimer = DISAPPEAR_TIMER_COUNT;
	}
}
```

さいごに
====

一応上手く？やるとUnityちゃんがBlockに挟まって動けなくなりますｗ

Blockが壊れる・元に戻るというのもアニメーションさせたいですね。
アニメーションの課題は山積みです。。。

今回のものは[ここからWebPlayerで見られます](https://dl.dropboxusercontent.com/u/21300511/UnityRoadRunner/demo1d/demo1d.html)。

ソースコードは[Githubに公開しています](https://github.com/mokemokechicken/UnityChanRoadRunner)。