はかせのラボ

私の頭の中を書いていく雑記ブログです

Unity ものすごくざっくりと着地判定を作る ~楽がしたいんじゃぁ~

あいさつ

どうも、はかせです。

今回はUnityで着地判定を作ってみます。
ただ単純にやってくと
やれレイヤーがなんだ
やれ管理が面倒だの色々めんどうが発生します。
なのでここではほぼ全てをスクリプトで賄うやり方を
パワープレイしていきます。

実装選択肢

おそらく
「Unity 着地判定」
とでもググれば大体出てきますが、
Unityで着地判定を行う方法はザっとこんな感じのものがあります。
・CharacterControllerのIsGroundを使う
・下方向にRayを打つ
・着地判定用のオブジェクトを作る

一つ目はUnityが用意していくれているCharacterControllerの仕組みに乗っかるだけです。
極めて楽ですが、ガバイ部分があったりパフォーマンス的にもにょることが多いです。

二つ目は下方向にRayを打ってその衝突の有無で
着地を判定する方法です。
しっかり作れば精度はそこそこいいものになりますが、
Rayの管理だったりRayの死角をどう埋めるとか頭を悩ませる要素が多いです。

三つ目は着地判定取って欲しいところに
それ用のコライダー付きオブジェクトを置いておいて
そいつのコライダーコールバックを利用する形です。
理屈単純、レイヤー等の設定も(基本)いらず、
パフォーマンスもまぁそこまで致命的になりづらい。
よし!こいつに決めた!

それでも微妙にめんどくさいところが・・・・

逐一判定してほしいところに手動でオブジェクトを配置しなきゃいけないのが
結構微妙にめんどくさいです。
一気にガッとやれるならまだしも
細かい部分になり数ミリ単位で動かすとかなってくると
もううわああぁぁーっっっ!!!ってなりますw
※私はGUIでマウスグリグリやる操作が全般的に苦手です

あとこれに限らずですが特定のオブジェクト置くだの、
設定をこうしてくださいだのは変なバグの温床です。(個人の主観です)

めんどうを解消していく

建物の配置みたいなセンスが問われる部分が手打ちなのはまぁいいとして
着地判定とかの場所って基本決め打ちに近いものだと思うんですよ。
それを手動で置こうとするからバグの温床になったり
「めんどくさっ。やーめた!」につながるわけですよ。

つまり問題は必要なオブジェクトとかを手で置いてくという作業です。
先ほども言ったようにそういったものは多少の誤差や調整はあれど
本場所等は決め打ちです。
ならばスクリプトで自動化してしまおうじゃないか。

自動で判定用オブジェクトを置いていく

さて自動で置こうぜ計画の第一歩です。
まずはオブジェクト作りましょう。
オブジェクト無きゃ話にならん。

//着地判定用GameObjectを作る
var g = new GameObject();
//親を自分に
g.transform.parent = transform;
//原点に移動
g.transform.localPosition = Vector3.zero;

GameObjectをnewするとCreateEmptyと同じことが起きます。
transform.parentにtransform情報を入れることで
親子関係を作れます。

あとは生成時のpositionがどうなってるかなんて
わかったもんじゃないんで
親の原点に場所を移します。

これで親の(0,0,0)に空のオブジェクトが出来ました。
あとはこいつを煮たり焼いたりしていきます。
とりあえず着地判定用なのでコライダーつけましょう。

//着地判定用Colliderの設定
var gcol = g.AddComponent<SphereCollider>();
gcol.isTrigger = true;
gcol.radius = GroundColliderRadius;

コンポーネントはAddComponentで付けれます。
あくまで判定用コライダーなのでisTriggerをONにして
ぶつからないようにしています。

付けたのがSphereColliderなので半径が必要です。
GroundColliderRadiusはインスペクター上で指定したfloat値です。
大きくすればするほど判定は取りやすくなりますが、
その分精度は下がっていきます。お好みの値を探してください。
ちなみに私は0.25~0.5ぐらいがお気に入りです。

さて最後は着地判定用のスクリプトですね。

using UnityEngine;

public class GroundCheck : MonoBehaviour
{
    //外部からは読み取れるが書き込みはできない
    public bool IsGround { get; private set; }
    //無視するコライダーの配列
    private Collider[] mIgnoreColliders;

    private void Start()
    {
        //とりあえず親についているコライダーは判定しない
        mIgnoreColliders = transform.parent.GetComponents<Collider>();
    }
    //コールバックに来たコライダーが無視するコライダーか
    private bool IsIgnoreCollider(Collider col)
    {
        for(int i = 0;i < mIgnoreColliders.Length; i++)
        {
            if (mIgnoreColliders[i] == col) return true;
        }
        return false;
    }

    private void OnTriggerEnter(Collider other)
    {
        if (IsIgnoreCollider(other)) return;
        IsGround = true;
    }

    private void OnTriggerExit(Collider other)
    {
        if (IsIgnoreCollider(other)) return;
        IsGround = false;
    }
}

極めて脳筋で親についてないコライダーと
接しているときは着地、
そうでないときは空中みたいな判定をしています。
このコンポーネントをAddComponentして完了です。

あとは判定結果が欲しいコンポーネントの中で
このコンポーネントへの参照を握って
IsGroundの値を見れば着地しているかがわかるという具合です。

あとがき

今回は極めて脳筋思考のパワープレイで
着地判定を作ってみました。

まぁ実際はちょこっとコライダー置いて
プレハブ化してごにょごにょやればいーじゃんって
なったりすることも多いんですが、
チーム開発で設定とかアセットのバージョン違いだとかで
変に詰まりたくないのとマウスグリグリをしたくないので
書きました。

こんな風にスクリプト作ってそれ付ければいいからって言えると
チーム開発とかで連携が極めて楽になるので
個人的にこういったやり方は大好きです。

それでは今回はこの辺でノシ