ゲーム画面クリックと、画面上のボタンクリックをきっちり分けて処理をする方法
お疲れ様です。ユウです。今日は自分が詰まった部分について書いていきたいと思います。とはいえ、いろんなサイトからの受け売りなので備考録代わりです。
画面クリックを取得したいけど、uGUIにも反応しちゃう悔しい!
よくあるやり方でGetMouseButtonDown(0)
とかで画面がクリックされたら処理を行うっていうのをやるかと思うのですが、これは画面のどこをクリックしても反応して便利なんですが、たとえばUIのボタンなども反応してしまいます。
ちょっと分かりづらいですが、画面をクリックするとき、ボタンをクリックするときで文字が変わっています。
しかし、ボタンを押すためにクリックしたときに画面をクリックしたときの処理が実行されてしまっています。 gifでいう3回目の文字の変更部分です。つまり間に違う処理が入ってきてしまっているのでこれはよろしくありません。この例だと文字が上書きするのでダメージは少ないですが、ゲームとかを作っていると処理が全然違う内容が予期せぬタイミングで走ったりとバグの温床になりかねません!
ちなみにgifで使ってるのソースコードはこちら。ボタンを押すとOnClickButton
が呼ばれています。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { // Update is called once per frame void Update() { if (Input.GetMouseButton(0)) { GameObject.Find("ThisIsText").GetComponent<Text>().text = "Screen Pushed"; } } public void OnClickButton () { GameObject.Find("ThisIsText").GetComponent<Text>().text = "Button Pushed"; } }
Rayさんで解決
いろいろやり方はあるかと思いますが、今回はRayを発射して判定していきたいと思います。クリックしたところにオブジェクトがあるかどうかを判断して画面かどうかをチェックしてみます。
まずは、画面上に配置するものでクリック判定を取りたいものにコライダーを貼ります。
そのままだと当たり判定に引っかかる可能性もなきにしもあらずということでTriggerにしておきます。
そしてさきほどのソースコードを書き換えます。Rayを発射してそこにオブジェクトがあるかの判定を取りましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { // Update is called once per frame void Update() { if (Input.GetMouseButton(0)) { // Rayを発射! Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit2D hit2d = Physics2D.Raycast((Vector2)ray.origin, (Vector2)ray.direction); // Rayで何もヒットしなかったら画面クリックと考える if (!hit2d) { GameObject.Find("ThisIsText").GetComponent<Text>().text = "Screen Pushed"; } } } public void OnClickButton () { GameObject.Find("ThisIsText").GetComponent<Text>().text = "Button Pushed"; } }
これでクリックした位置にRayを飛ばしてそこにコライダーの当たり判定があるかをチェックしてくれます。今回の場合、そこにボタンがあったら処理をしてほしくないのでクリックした位置に何もなかったら画面クリックとして考えることにしました。
hit2d
にRayを飛ばした結果が入っているのでそれをif文にかいてあげてチェックしていきます。
gifだと分かりづらいですが、ボタンを押下中でも画面をクリックした判定にはなっておらず、きちんと画面とボタンのクリック処理がわけて実現することができていますね。
このやり方を使えば、たとえば画面をクリックしたらキャラクターがジャンプするゲームでも、画面上部にUIとしてボタンを配置しておきボタンがクリックされたときはキャラクターをジャンプさせない。といった処理を行うことができます。
でもまだ詰めが甘い
さて、これにて解決かと思いましたがまだまだ荒い仕様です。このままだとコライダーの貼ってあるオブジェクトが多数登場してきたときに判定が取れなくなる危険があります。 画面タップでキャラクターがジャンプするはずなのに、ステージやコインなどをタップしたらRayが反応して処理が進まない。みたいなことですね。これはいけません。
いろいろ回避方法はあるかと思いますが、今回のやり方でUIに限る場合は、UI関連のゲームオブジェクトにUI用のタグをつけて管理する。というアプローチが良いかも知れません。
UIとして扱いたいオブジェクトにはtagとして「UIObject」を指定してあげます。タグがない場合はAdd Tagで追加してあげましょう。
ここで設定したタグがついているObjectは無視するようにソースコードを少し変更してみます。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { // Update is called once per frame void Update() { if (Input.GetMouseButton(0)) { // Rayを発射! Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit2D hit2d = Physics2D.Raycast((Vector2)ray.origin, (Vector2)ray.direction); // Objectが無い or あったとしてもUIオブジェクトではなければ画面クリックとして認識 if (!hit2d || hit2d.transform.gameObject.tag != "UIObject") { GameObject.Find("ThisIsText").GetComponent<Text>().text = "Screen Pushed"; } } } public void OnClickButton () { GameObject.Find("ThisIsText").GetComponent<Text>().text = "Button Pushed"; } }
さぁ、実行してみましょう! わかりやすいようにタグをつけていないボタンも右に追加してみました。
押す順番を間違えて分かりづらくなってますが、ちゃんと画面クリック、タグつきボタンクリック、タグなしボタンクリックで処理が分かれています!
何もない場所 or 「UIObject」タグのついていないオブジェクトをクリックすると画面クリックと認識します。
「UIObject」タグのついているオブジェクトに関しては画面クリックとは認識されていません。
ちょっとまどろっこしいやり方ではありますが、こんなやり方でも画面クリックとボタンクリックをきっちり処理をわけて実現することができます。素敵なUIを作ってゲームをより便利なものにしましょう!
参考サイト tech.pjin.jp