Bolt でマウスクリック入力&オブジェクトへのクリック判定
画面をマウス左ボタンクリックした位置下のオブジェクト名やワールド座標取得をBoltで行うメモです。
※シングルTAPだけならAndroidでも動作すると思います。
※以後、クリック=マウス左ボタンクリックとして書いています。
今回のサンプル
以下のような感じです。
Cubeを4つ(Cude_A~Cube_D)。その下には板を1枚(Plane)置いています。
実行して画面をクリックするとクリックした各オブジェクト名やその3D空間内での座標を取得して表示するような感じです。
※左下の赤い丸は3D空間での(0,0,0)に置いてあり 座標の基点を見た目わかりやすくする為だけのものです。
補足:上記はUnity内蔵のモデルなのでColliderが自動的に付いてます。
Assetのモデルを使う場合はColliderが付いているか確認要です。付いていない場合や、自作モデルをインポートしている場合等では手動でColliderを付ける必要があります。
ノード全景は以下のようになっています。
ゴチャっとしていますが、ブロックごとに見ていけば大したことはしていません。
各ブロックの概要は以下のようになります。
・Get Camera componentブロック
Start Eventでカメラオブジェクトを探して格納しておきます(このカメラを介して後で座標変換を行います)
この「探す」処理は比較的重い処理なのでゲームに組み込む際はUpdate Eventで毎回探さずに、初回 Start Eventで1回だけ探して取得しておく方が良いです。
・Mouse Downブロック
Update Eventでマウスの画面クリックを監視。
クリック(Tap)された画面座標を取得します。
・RayCastHitブロック
クリックされた画面座標を元に、その真下の3D空間にオブジェクトがあるかを判定します。あればそのオブジェクト名、3D空間上での座標を取得します。
・その他、Update GUI Text、Clear display等々
TextMeshProuGUIで画面表示を更新しているだけなのでここでは詳細は割愛します。他、Debug.log出力等。
サンプルのダウンロード
ちなみにこのサンプルはGit-Hubで公開しています
https://github.com/maruton/Bolt_InputRayCast
Mouse Downブロック ~ クリック画面座標の取得
クリックした位置は画面の座標で取得します。
例えばディスプレイ画面の解像度が1920x1080 ならば、クリック座標のXとYはその値の範囲で取得します。
該当するノードブロックは以下です。
ノード GetMouseButtonDown でマウス左ボタンのクリック検出。
ノード mousePosition (get) でクリック位置の画面座標を取得。
ここでは変数 ScreenPosition に値を格納しておき、後段のノードで利用します。
mousePosition (get) はVector3型で座標を受け取りますが、画面は平面(2D)なのでZ値は常に0です。
これで画面のどこがクリックされたかがわかります。
ゲームで遊ぶ機種が変わると(例えばスマホの)画面の解像度が1920x1080や1280x768のように変わる場合があります。この為、画面座標をそのまま使っての位置判定はまずい場合が多く、少し工夫が必要になります。
今回はクリックした位置のスクリーン座標からその真下にあるオブジェクト名やそのワールド座標を取得することでスクリーン座標に依存しないクリック判定をBoltのノードで組んでいます。
スクリーン座標とワールド座標
少し補足(読み飛ばして次に進んでもらっても構いません)。
画面座標はスクリーン座標(Screen Position)等と呼ばれています。画面の解像度に対応しています。
対して3D空間はワールド座標(World Position)等と呼ばれています。オブジェクト(UnityのGameObject)はこの座標系で配置されています。
スクリーン座標は平面なので2D(X,Y)ですが、これは3D空間をカメラを通して2Dの枠で覗いていることになります。
クリックした画面座標の(カメラで見ている)その先にオブジェクトがあるかどうか、あるならばそのオブジェクト名、クリックしたオブジェクト位置(Colliderの検出位置座標)のワールド座標を取得することで、画面解像度に依存しないようにクリックした当たり判定を作ることができます。
クリックしたスクリーン座標(2D)をワールド座標(3D)に変換した際の奥行の値はカメラの座標位置になるのですが、厳密にはカメラ座標からカメラのInspector内Near値だけ前方の値になるので、カメラ座標そのままではありません。
Nearクリッピング範囲(Near値空間範囲)はカメラレンダリングしないので、そういう動作になっているのだと思います。
つまりこの変換値をそのままにそこからRayを照射するとNearクリッピング範囲内は検出できないということがあるので注意です。
画面クリックした真下のオブジェクトを検出する
RayCastHitブロックです。
だいたい上図の通りです。
Mouse Downブロックでクリックされた時にのみ、このブロックが実行されます。
Mouse Downブロックで取得済の画面クリック座標(スクリーン座標)、Start Eventで探しておいたカメラコンポーネントの2つを、ノードScreenPointToRay に入力して、3D空間座標(ワールド座標)に変換しています。
変換後の3D座標から真下方向にオブジェクトがあるかを判定する為、次のノードRayCast をつないでいます。
ここで変換後の3D空間座標から真下(カメラで見ている奥行方向)にRay(と呼ばれている概念上の光線)を照射して、オブジェクトに当たったかどうかで判定します。
ノードRayCast内の maxDistance はその際の照射距離です。図では定数20を記入しているので20m範囲にあるオブジェクトに限定して当たり判定を行います。
ノードRayCast の出力(Boolean)で当たったか否かがわかります。これをノードBranch でTrueならオブジェクトに当たったと判定。
当たったオブジェクトの名前、当たった3D空間座標位置を取得して変数に格納しています。
3Dモデルのボタン的なものや、複数オブジェクトのどれに当たったか等の判定だけなら、名前の取得だけで判定できます(ノードRaycastHit Collider (get) + ノードExpose Collider)。
オブジェクトに当たった3D空間座標値も取得できます(ノードRaycastHit Point (get))。
だいたいこの辺の処理はお決まりの感じになりがちなので、Super Unitにしておくなどで整理しておくと良いかもしれません。
(というか、Super Unit化して作った方が良かったような気がいまさらながらしてきました・・・)。
タッチ系など
一応このノードでシングルタップだけならAndroidでも動作するようです(とりあえずAndroid実機でのデバッグ実行では動作していた)。
スワイプ等の実装は別途Touch系のノードがあるのでそれを使いことになりそうです。
Touch系等の実装はちょっと面倒&ほとんどお決まりな感じになりやすいと思うので、良いAssetがあればそちらを利用した方が手間が省けます。C#のAssetでもBoltから呼び出すことはできるので。
(過去に自前実装してデバッグが面倒だったので。リモートデバッグは当時的に結構重くてフィーリング的な動作はやはりAndroid実機じゃないと確認しにくかった感じ)
その他の小技?など
ちょっと長くなってしまったので、ノード全体の内で使用している小技?的なものは別途プチTipsで書こうかなと思います。
よもやま
色々忙しく1カ月位程更新していませんでしたが・・・。
まだまだ年末年始に向けて忙しい・・・
Bolt+C#での試作開発も行う予定なので、その際のメモなどを少しずつ書いていければと思います。