Unityで画面をタップした時のRayの撃ち方を考えてみた

# 序

某所で、Unityのスクリプトを読んだときになんか違うんじゃないかと思ったのでメモ。

そのスクリプトは、タッチイベントを受け取りたい各GameObjectがそれぞれUpdate()内でRayを撃ってたわけだけど、それだと、オブジェクトが増えれば増えるほど動作が重くなりそうな気がした（気にならない程度かもしれないが、そもそも設計方針が変だと思われる）。

# 誰がRayを撃つべきか？

なんでもいいが、Rayを撃つのは１つのオブジェクトだけにしたい。複数のオブジェクトが自分勝手にRayを撃つのはダメすぎる。
Main Camera、あるいは、空のオブジェクトを作ってそこで撃てばいいだろう。

# 実装

実装方針を考えてみる。
１．Rayを撃つのは１つのオブジェクトだけ
２．タッチイベントを受け取りたいオブジェクトは、特定のスクリプトをコンポーネントとして持たせれば良さそう

実際のコードを考えていく。

## タッチイベントの取得側

まず、２のほう。
タッチイベントなので、「タッチした」「タッチした指を離した」の２種類のイベントを受け取れればいいか…？
となると、コードはこんな感じ？

```csharp:TapBehaviour.cs
using UnityEngine;
using System.Collections;

public abstract class TapBehaviour : MonoBehaviour {
	// タッチしたときに呼ばれる。
	public virtual void TapDown(ref RaycastHit hit){}
	// タッチを離したときに呼ばれる。
	public virtual void TapUp(ref RaycastHit hit){}
}
```

上記のクラスを継承したクラスを各オブジェクトの仕様にあわせて実装すれば良い。

## Rayを受け取りタッチイベントを呼び出す側

１のほう。
こっちのクラスはMain Cameraにでもつけとくことにする（空のゲームオブジェクトでも可）。
で、タッチを感知したら、Rayを撃って、当たったオブジェクトがTapBehaviourの子クラスを保持していれば、
TapDownかTapUpメソッドを呼び出すことにする。

```csharp:TouchHandler.cs
using UnityEngine;
using System.Collections;

public class TouchHandler : MonoBehaviour {
	public float distance = 10; // Rayの届く距離

	void Update () {
		// タッチされたとき
		if(Input.GetMouseButtonDown(0)){
			// メインカメラからクリックしたポジションに向かってRayを撃つ。
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit = new RaycastHit();

			if (Physics.Raycast(ray, out hit, distance)) {
				GameObject selectedGameObject = hit.collider.gameObject;
				TapBehaviour target = selectedGameObject.GetComponent(typeof(TapBehaviour)) as TapBehaviour;
				if(target != null){
					target.TapDown(ref hit);
				}
			}

		// 指を離したとき
		}else if(Input.GetMouseButtonUp(0)){
			// メインカメラからクリックしたポジションに向かってRayを撃つ。
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit = new RaycastHit();

			if (Physics.Raycast(ray, out hit, distance)) {
				GameObject selectedGameObject = hit.collider.gameObject;
				TapBehaviour target = selectedGameObject.GetComponent(typeof(TapBehaviour)) as TapBehaviour;
				if(target != null){
					target.TapUp(ref hit);
				}
			}

		}
	}
}
```

上のコードは、Input.GetMouseButtonDown(Up)を使ったが、マルチタップに対応したい場合は、Input.touchesを使う方法もあるだろう。

```csharp:
void Update(){
	foreach (Touch touch in Input.touches) {
		if(touch.phase == TouchPhase.Began){
			// メインカメラからクリックしたポジションに向かってRayを撃つ。
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit = new RaycastHit();

			if (Physics.Raycast(ray, out hit, distance)) {
				GameObject selectedGameObject = hit.collider.gameObject;
				TapBehaviour target = selectedGameObject.GetComponent(typeof(TapBehaviour)) as TapBehaviour;
				if(target != null){
					target.TapDown(ref hit);
				}
			}

		}else if(TouchPhase.Ended){
			// メインカメラからクリックしたポジションに向かってRayを撃つ。
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit = new RaycastHit();

			if (Physics.Raycast(ray, out hit, distance)) {
				GameObject selectedGameObject = hit.collider.gameObject;
				TapBehaviour target = selectedGameObject.GetComponent(typeof(TapBehaviour)) as TapBehaviour;
				if(target != null){
					target.TapUp(ref hit);
				}
			}

		}
	}
}
```

（いちおう書いてみた。動作テストはしていない。）

## タッチイベントの受け取り方

タッチイベントを受け取りたいオブジェクトでは、次のようにTapBehaviourの子クラスを書いてコンポーネントとして持たせておく。

```csharp:KurekureTouch.cs
public class KurekureTouch : TapBehaviour {
	public override void TapDown (ref RaycastHit hit){
		// タップされたときの処理
	}
	
	public override void TapUp (ref RaycastHit hit){
		// タップを離したときの処理
	}

	void Update(){
		// タップされてるかをフラグで管理したりして適当に更新する。
	}
}
```

>クラス名とか適当すぎる気はしてます、はい…