かみおかのチラ裏

思い付きを書き溜めておくばしょ

RayCastによる衝突判定(2D)


ご無沙汰しております。
なんと、前回更新日から136日経ってしまっていたようです。
途中、ダウンしてしまい空いてしまいましたが、開発は細々と継続中です。

今回はRayCastによる当たり判定について、備忘録として書いておきます。

いつもの注意書き
なるべく正確な記載を心がけていますが、
私の知識不足などにより誤りが含まれている可能性があります。
誤りを発見された場合は、コメント等でご指摘いただければありがたいです(ぺこり)
 

さて、現在開発中の「そらとぶメンダコ」のステージ選択画面、
Buttonを使うことでプレイするステージを選択できるようになっているのですが、
一つ問題が...。
選択可能なステージが増えると、視認性や操作性が落ちるのですよね。

今は少ないので視認性に問題はないが、10を超えると...

ということで、画面スクロールをしてひとつの画面内に表示するボタン数を減らそうと思い最初に考えたのがJoyStickによる擬似的な画面スクロールです。

が、当然ですがCanvas内に配置されたUI系のButtonは、
JoyStickによる操作の影響を受けません。
結論を書きますと、UIのスクロールはScroll Viewによって実現できそうなのですが、
Scroll Viewはクセの強い子らしく、なんとか他の方法でできないものかと思いました。

ということで、アドベンチャーゲームなどによくある
ポイント&クリックの仕組みを利用してみることにしました。

さて、ポイント&クリックはご存知の通り、
タップなりクリックしたオブジェクトを何かしらの操作できる仕組みです。
どうやっているかというと、スクリーン上でクリックした位置からRay(光線)を飛ばして
当たり判定をしているようです。
この辺りは他のやり方もあるだろうとは思いますが、
おそらくRayCastによる衝突判定が一番素直で分かりやすいのではないかと思います。

 今回やりたいことの具体的なものとしては、
ステージごとのボタンとなるオブジェクトを配置し、
オブジェクトによって生成するステージを分岐させるなので、
Rayを飛ばす→RayがHitしたオブジェクトを検出する→検出したオブジェクトによってその後の処理を分岐させる、ということになります。

ということで、今回作成したコードは以下のような感じになりました。
(雑に完成した料理を出す某キュー◯ースタイル)

   Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit2D hit = Physics2D.Raycast((Vector2)ray.origin, (Vector2)ray.direction, 20);
            if (hit.collider)
            {
                if(hit.collider.gameObject.name == "GameObject名")
                {
                    //実行したい処理
                }
            }
        }
    }

ifだらけになってあまりスマートとは言えない実装になってしまいました...。
この実装でも問題なく動きますし、
今回はステージ数がそれほど多くないため極端に負荷が上がるとは思えませんが、
できればswitchで分岐させたかったです;;そっちの方がコード的にも読みやすい。

実際の挙動はこんな感じになります。

画像が荒くて見づらい場合もありますが、
上部分のScene Viewで実際に飛ばしたRayを可視化しています。
細いですが、赤線で可視化されています。

ちなみに、3Dの場合は少々書き方が違いまして、

   Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                if(hit.collider.gameObject.name == "GameObject名")
                {
                    //実行したい処理
                }
            }
        }
    }

と、このような感じになります。
使用している物理演算が異なりますね。

今回はRayの衝突先のGameObject名を取得して処理を分岐させていますが、
これについてもあまりスマートとは言えない方法だろうと思います。
この方法だと、せいぜい数十程度ならコピペでまあどうにかなりますが、
100を超えてくるとそもそも打ちたくありませんし、何より絶対にミスが発生します。
数が多くなりそうな場合はLayerMaskをうまく使うとよさそうです。
先述の条件分岐も含め、他に良い方法をご存知の方がいらっしゃいましたら、
コメントいただけると嬉しいです。
(ま、マサカリも受け止めます...小声)

ちょっと雑な感じになってしまいましたが、余力があれば加筆修正したいと思います。
今回はこの辺りでさようなら。