1. JunSuzukiJapan

    Posted

    JunSuzukiJapan
Changes in title
+UnityでRayの撃ち方を考えてみた
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,163 @@
+
+# 序
+
+某所で、Unityのスクリプトを読んだときになんか違うんじゃないかと思ったのでメモ。
+
+そのスクリプトは、タッチイベントを受け取りたい各GameObjectがそれぞれUpdate()内でRayを撃ってたわけだけど、それだと、オブジェクトが増えれば増えるほど動作が重くなりそうな気がした(気にならない程度かもしれないが、そもそも設計方針が変だと思われる)。
+
+# 誰がRayを撃つべきか?
+
+なんでもいいが、Rayを撃つのは1つのオブジェクトだけにしたい。複数のオブジェクトが自分勝手にRayを撃つのはダメすぎる。
+Main Camera、あるいは、空のオブジェクトを作ってそこで撃てばいいだろう。
+
+# 実装
+
+実装方針を考えてみる。
+1.Rayを撃つのは1つのオブジェクトだけ
+2.タッチイベントを受け取りたいオブジェクトは、特定のスクリプトをコンポーネントとして持たせれば良さそう
+
+実際のコードを考えていく。
+
+## タッチイベントの取得側
+
+まず、2のほう。
+タッチイベントなので、「タッチした」「タッチした指を離した」の2種類のイベントを受け取れればいいか…?
+となると、コードはこんな感じ?
+
+```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を受け取りタッチイベントを呼び出す側
+
+1のほう。
+こっちのクラスは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();
+ GameObject selectedGameObject = null;
+
+ if (Physics.Raycast(ray, out hit, distance)) {
+ selectedGameObject = hit.collider.gameObject;
+ }
+
+ if(selectedGameObject != null){
+ 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();
+ GameObject selectedGameObject = null;
+
+ if (Physics.Raycast(ray, out hit, distance)) {
+ selectedGameObject = hit.collider.gameObject;
+ }
+
+ if(selectedGameObject != null){
+ 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();
+ GameObject selectedGameObject = null;
+
+ if (Physics.Raycast(ray, out hit, distance)) {
+ selectedGameObject = hit.collider.gameObject;
+ }
+
+ if(selectedGameObject != null){
+ 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();
+ GameObject selectedGameObject = null;
+
+ if (Physics.Raycast(ray, out hit, distance)) {
+ selectedGameObject = hit.collider.gameObject;
+ }
+
+ if(selectedGameObject != null){
+ 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(){
+ // タップされてるかをフラグで管理したりして適当に更新する。
+ }
+}
+```
+
+>クラス名とか適当すぎる気はしてます、はい…