Unity初心者がクソゲーを作る その4 「Linecastを使った当たり判定」
さぁ、作ろう。クソゲーを。
前回までのあらすじ
初心者なのにウィザードリィ風のダンジョン探索風ゲームを作ろうと作業中 移動キーで動き回れる部分が完成し、ぐっとゲームらしくなってきたけど壁をすり抜けるという破綻がまだ直っていなかった…!
当たり判定を実装するにはどうするべきか
ダンジョンを歩き回れるようになりましたが、壁をすり抜けてしまうのでだいぶ破綻しています。どうすればいいでしょうか。 ここで知りたいのは「操作するプレイヤーの前方あるいは後方に壁があるかどうか」ですね。なんとなく糸口が見えてきました!
「当たり判定」「オブジェクト」などと調べてみると色々と出てきましたが、RaycastやLinecastを使って指定した方向に当たり判定があるかを見るのが手軽そうです。
とりあえず、壁にはコライダーを設定しよう
当たり判定があるかどうかを調べるので、壁には当たり判定を作る必要があります。
ということで、前回パパッと作っていたWall(壁)なんですが、実はまだコライダー(当たり判定)をつけていませんでした。 「Convex」にチェックをするとコライダーが設定されるので、ポチッとしておきましょう。オーバーライドしてすべての壁に適用されるようにするのを忘れずに。
もし、プレイヤーにRigbodyとかをつけて物理演算をかけるゲームだったらこれだけで当たり判定は終わりなのですが今回はそうもいきません。
だからこそプレイヤーからみて移動しようとしている先に当たり判定が見つかるかどうかで、移動の可否を決定します。
色々やり方はあるかと思うのですが、今回は試しに実装したら動いたので「Physics.Linecast」を使って実装していきたいと思います。
Physics.Linecast
簡単にいうと、スタート地点とゴール地点を設定しその間でビーム的なものを出して「何かにあたったかどうか」を判断します。あたったオブジェクトを取得することができるので、「これはあたってもOK」とか「これにあたったら駄目」みたいなことを分けることもできます。
ちなみにどうやって当たった相手を判別するかというと、当たった相手のデータを「hitInfo」で取得して、その中からtagを見て判断することがほとんどです。たとえば、当たり判定のあった相手のタグが「仲間キャラクター」とかだった場合は、別にそのまま進んで重なっても大丈夫。みたいな振り分けも可能です。
つまり、プレイヤーの位置から向いている方向に5m先まで探知ビームを発射すれば良さそうです。
ということは移動処理のあたりに書き足してあげればうまく動いてくれそうです。「PlayerController」をダブルクリックしてソースを修正していきたいと思います!
PlayerController
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (Input.GetKeyDown(KeyCode.UpArrow)) { transform.Translate(0, 0, 10); } if (Input.GetKeyDown(KeyCode.DownArrow)) { transform.Translate(0, 0, -10); } if (Input.GetKeyDown(KeyCode.LeftArrow)) { transform.Rotate(0,-90,0); } if (Input.GetKeyDown(KeyCode.RightArrow)) { transform.Rotate(0, 90, 0); } } }
ここをこのように書き換えていきます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (Input.GetKeyDown(KeyCode.UpArrow)) { if (Physics.Linecast(transform.position, transform.position + transform.forward * 5.0f)) { Debug.Log("いてっ"); } else { transform.Translate(0, 0, 10); } } if (Input.GetKeyDown(KeyCode.DownArrow)) { if (Physics.Linecast(transform.position, transform.position + -transform.forward * 5.0f)) { Debug.Log("いてっ"); } else { transform.Translate(0, 0, -10); } } if (Input.GetKeyDown(KeyCode.LeftArrow)) { transform.Rotate(0,-90,0); } if (Input.GetKeyDown(KeyCode.RightArrow)) { transform.Rotate(0, 90, 0); } } }
変わったのは二箇所ですが、値が反転してるだけなので実質一箇所です。
if (Input.GetKeyDown(KeyCode.UpArrow)) { if (Physics.Linecast(transform.position, transform.position + transform.forward * 5.0f)) { Debug.Log("いてっ"); } else { transform.Translate(0, 0, 10); } }
さぁ、いよいよ出てきました。「Physics.Linecast」。これが指定位置から指定位置までの間にビーム的なものを出して当たり判定があるかどうかを判定しています。
今回の場合、スタート地点は「transform.position」です。つまりオブジェクトが今いる位置ってことですね。
そしてゴール地点は「transform.position + transform.forward * 5.0f」となっています。むむっ…なんだか計算式が出てきてしまいました。
transform.forward
当然の権利のように出てきた「transform.forward」とは一体なんなのでしょうか? 実はこれがキモとなる要素だったりします。
簡単にいうと、「オブジェクトの向いている前方」をベクトルで表してくれています。(あってるよね?)
「transform.forward * 5.0f」ということは、オブジェクトの向いている方向のベクトルに5m進めたところ。的な意味になります。
あくまで方向ベクトルなのでこのままだと向いている方角が同じだと毎回同じになってしまいます。しかし、この値に現在の自分の位置を足してあげることで「現在の位置から + 前方に向かって5m先」という指定ができるのです。
たとえばこの画像を見てください。現在地は(0,2,30)です。オブジェクトはZ軸に正を向いているので(0,0,1)向きです。 前方にある壁の位置は(0,2,35)です。
① (0,2,30) … 現在地
② (0,0,1) ... オブジェクトの前方ベクトル
③ (0,2,35) ... 壁の位置
③を求めるには、②に5をかけてやり(壁までの距離)、①と足せば良いことがわかります。
後退については前方と反対になるので、単純にマイナスをかけてやればいいだけなので簡単ですね! 今回は対象オブジェクトを特に考慮せず当たり判定があればとりあえず進めないようにしています。
ということで、これだけで当たり判定を実装することができました。難しそうでしたが、わりとすぐになんとかなりました。よかったです(実は調べながら実装→この記事を書くというやり方なので、実際には2時間ぐらいかかってます)
動きのチェック
壁に当たったときの処理が、コンソールにデバッグ文出しているだけなのでgifだと分かりづらかったですね…。でもちゃんと壁が前or後ろにあると進めないようになっているので前回から大きく進歩しました!