こんにちは!
XRクリエイターの伊東(@ALAKIWebVRAR1)です。
※TwitterでもAR/VRの事を中心に情報発信中です。
よかったらフォローお願いします!
Follow @ALAKIWebVRAR1
今回は、Unityで2Dオブジェクトをタップ判定する方法を備忘録的にまとめていきます。
まずは、この記事を読んで何ができるようになるのかをご覧ください。
この記事の目次
実装したいこと
先にゴールからお見せします。
シーンに配置したUI Textがマウスでドラッグした時に追従するような動きです!
シンプルなインタラクションですが、さまざまなテクニックが含まれています。
順を追って見ていきましょう。
シーンを見てみる
まずはシーンの構成を見てみます。
カメラと、最低限のライト、キャンバスとTextMeshProで作ったテキストUIが2つというシンプルなシーンです。
ポイントは、CanvasのRenderModeが「World Space」になっているところです。
スクリーン座標ではなく、ワールド座標系に2Dオブジェクトを配置している形になります。
その上で、画面上のタップを検知して、ワールド座標系にある2Dオブジェクトをマウスに追従させるということが今回のゴールになります。
マウスの座標を取得する
まずはマウスクリックを検知して、座標を取得するところからスタートしましょう。
適当な名前でスクリプトを用意して、テキストオブジェクトにアタッチしてください。
今回はスクリプトの名前を「DraggableUI」としました。
そして、Update関数に、マウスクリックを判定するif文と、クリックした場所を取得してコンソールに出してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DraggableUI : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { /* マウスクリックを検知 */ if (Input.GetMouseButton(0)) { /* クリック座標(スクリーン座標系) */ Debug.Log(Input.mousePosition); } } } |
マウス座標がとれました!
ただし、取得できた座標はスクリーン座標になっています。
最終的に操作するテキストオブジェクトはワールド座標になっているので、変換してあげる必要がありますね。
スクリーン座標とワールド座標の変換
クリックした場所の座標を、スクリーン座標からワールド座標に変換するために、「ScreenToWorldPoint」というカメラに備わっている関数を利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DraggableUI : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { /* マウスクリックを検知 */ if (Input.GetMouseButton(0)) { /* クリック座標(スクリーン座標系) */ Vector3 screenPos = Input.mousePosition; /* クリック座標(ワールド座標系) */ Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); } } } |
このように、カメラを基準として、スクリーン座標をワールド座標に変換することができます。
クリック座標にオブジェクトを追従させる
ワールド座標が取れれば、その座標をオブジェクトのトランスフォームに渡してあげれば、マウスに追従する処理がかけそうです。
やってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DraggableUI : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { /* マウスクリックを検知 */ if (Input.GetMouseButton(0)) { /* クリック座標(スクリーン座標系) */ Vector3 screenPos = Input.mousePosition; screenPos.z = 1.0f; /* クリック座標(ワールド座標系) */ Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); /* オブジェクトのポジションからワールド座標系までのアニメーション */ transform.position = Vector3.Lerp( transform.position, worldPos, 3.0f * Time.deltaTime); } } } |
ポイントが2つあります。
1つ目は、取得したスクリーン座標に奥行きを付与しているところです。
そのままではZの値が0になり、ワールド座標に変換した時に毎回同じVector3の値が返ってきてしまいます。
意図した通りの値を得るために、Zの値に任意の値を入れてから、ワールド座標への変換を行うようにします。
2つ目は、Lerp関数です。Lerp関数は、ある点からある点までの値の変化を補完して、徐々に変化するような表現を可能にします。
今回はLerp関数に3つの引数を渡しています。1つ目の引数が「変化前の値」、2つ目の引数が「変化後の値」、3つ目の引数が「補完値」です。
ここまでの内容で、スクリプトを実行してみましょう。
作成した「DraggableUI」というスクリプトを、シーンにあるテキストオブジェクトにアタッチしてから実行します。
だいぶゴールに近づいてきました!
が、マウスをドラッグした瞬間、2つのオブジェクトが一緒になって追従してしまっているので、修正していきます。
RaycastHit2Dを実装
オブジェクトの部分をドラッグした場合のみ、オブジェクトがマウスに追従するようにするには、マウスとオブジェクトが衝突していることを判定する必要があります。それを実現するのが「Raycast」と呼ばれるものです。クリックした座標位置から、奥側に向かって伸びる見えない光線を伸ばし、その光線にあたっているオブジェクトを取得するという方法です。
ソースコードは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DraggableUI : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { /* マウスクリックを検知 */ if (Input.GetMouseButton(0)) { // タップした場所からRayを作成 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // Raycastを作成 RaycastHit2D hit2d = Physics2D.Raycast((Vector2)ray.origin, (Vector2)ray.direction); //Rayが何かに衝突したことを検知 & 衝突した対象が自分自身かを判別 if(hit2d && hit2d.transform.gameObject == this.gameObject) { /* クリック座標(スクリーン座標系) */ Vector3 screenPos = Input.mousePosition; screenPos.z = 1.0f; /* クリック座標(ワールド座標系) */ Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); /* オブジェクトのポジションからワールド座標系までのアニメーション */ transform.position = Vector3.Lerp( transform.position, worldPos, 3.0f * Time.deltaTime); } } } } |
カメラが持つ「ScreenPointToRay」というメソッドに、クリックした場所の座標を渡し、Ray(光線)を作成します。
次に、Physics2D.RaycastというメソッドでRayが当たった物を取得するためのRaycastHit2Dオブジェクトを作成します。
RaycastHit2DはRaycastを2D要素に対して扱うためのクラスになっています。
最後に、if文に2つの条件を&で渡して、ifの中にオブジェクトの座標を書き換える処理を移しました。
条件の1つ目は、RaycastHit2Dオブジェクトが存在するかどうかをみています。
条件の2つ目は、RaycastHit2Dオブジェクト検知した衝突したGameObjectが、自身のGameObjectと一致するかどうかです。
BoxCollider2Dをアタッチする
ここまででスクリプトの記述は完了しました。
しかしこのままで実行しても動作しません。
ドラッグするオブジェクト側に、Rayがぶつかるためのコンポーネントをアタッチする必要があります。
今回は2D用のRaycastHit2Dを使用しているため、「BoxCollider2D」というコンポーネントを使用します。
アタッチできたら、「Size」の部分を実際のオブジェクトと合わせてあげる必要があります。
ここまでで全ての準備が整いました。
実行してみます!
できましたね!
最後に
今回ご紹介した基本的なRaycastの実装は、さまざまなアプリケーションで活用できるものかと思います。
弊社では、Unityをはじめさまざまな技術を活用して、これからも世の中に新しい価値をご提供し続けます。
最近では、現実のあらゆる場所からデジタルコンテンツを誰でもAR空間にドロップできる「MUGHEN」というアプリをリリースしました。ぜひインストールしていただき、現実を拡張する感覚を体験してみてください!
■MUGHEN サービス紹介サイト
https://alaki.co.jp/lp/mughen/
また、お客さまからご相談をいただき、日本各地でARを使ったワクワクするプロジェクトをリリースしてまいりました。
ARのパイオニアとして、高い技術力とビジネス提案が可能ですので、以下のサイトよりお気軽にご相談ください!
■WebAR開発 特設サイト
https://alaki.co.jp/lp/webar/
ALAKI株式会社では、「MUGHEN」をはじめとする最先端技術を使って世の中に価値を提供するため、一緒に大きな目標に挑戦する仲間を募集中です!興味のある方は、以下のリンクからご応募ください!
■ALAKI リクルートサイト
https://recruit.alaki.co.jp/