見出し画像

Unityで壁をよじ登る処理を実装

こんにちは。えあると申します。

現在友人2人と共にUnityを使ってゲーム制作を行っている中で、企画者の方から
「壁のよじ登りとかってできる?」
と言われ、意気揚々と「できるんじゃね」とか答えたはいいものの、思いの外苦戦してしまいました。
結果いい感じに実装できましたので、備忘録も兼ねてnoteに書き残しておこうと思います。


その前に…

今回、実装する上で一番やりたかったのが
「よじ登り専用のコライダーを使わない」
というものです。

これはステージを作る人が自分とは限らないため、なるだけ楽にするためにはどうしてもこれを実装したかったわけです。
(たくさんのバグを生み出しそうではありますが)


仕組み

よじ登りを実装するために、今回はRayを使って実装することにしました。
Rayは見えない光線のようなもので、光線のあたったオブジェクトの情報を取得する事ができるものです。

Rayのイメージ画像

仕組みとしては、同じ方向で高さが違うRayを2つ利用します。
1つ目は目の前に壁があるかを判定するために使います。
このRayが壁にあたっている時に目の前に壁があるとして、よじ登りができるようになります。

2つ目は、目の前にある壁がよじ登ることができる高さかどうかを判定するのに使います。
これによってよじ登る事ができる高さに制限を設けています。

つまり、1つ目のRayが壁にあたっていて、2つ目のRayがあたっていない時、目の前に壁があって、よじ登る事ができる壁であると判定します。

よじ登る事ができる壁がある状態で、ジャンプ入力をすることでよじ登る処理に移行するようにしました。


実装

壁の判定

まずは先程説明をしたRayを使い壁を判定します。

    //  壁判定に使用する変数
    Ray wallCheckRay = new Ray(transform.position + Vector3.up * wallCheckOffset, transform.forward);
    Ray upperCheckRay = new Ray(transform.position + Vector3.up * upperWallCheckOffset, transform.forward);

    //  壁判定を格納
    bool isForwardWall = Physics.Raycast(wallCheckRay, wallCheckDistance);
    bool isUpperWall = Physics.Raycast(upperCheckRay, wallCheckDistance);

これによって目の前に壁があるか、その壁が登ることができるかを見ることができます。

ジャンプ入力を受け付ける

条件を満たした状態でジャンプを入力することで崖捕まりに移行します。

    //  正面に壁があり、高い位置に壁がなく、接地している時にジャンプすると崖掴まりに移行
    if(isForwardWall && !isUpperWall &&
        inputJump && isGrounded)
    {
        isGrab = true;
    }

崖を掴んで止まる

先程の画像の1番のRayが壁に当たらなくなったところで、崖の端に到達したとして掴まるようにしました。
掴んでからすぐ登るのではなく、掴んだ状態で一旦止まります。
その後移動キーが入力されることでよじ登るようにしました。

    //  崖に捕まっているときは、重力を0にする
    if (isGrab && !isForwardWall)
    {
        acceclertion.y = 0.0f;

        //  前入力されたらよじ登る
        if (inputVec != Vector3.zero)
        {
            //  開始位置を保持
            climbOldPos = transform.position;
            //  終了位置を算出
            climbPos = transform.position + transform.forward * 4 + Vector3.up * 5.5f;
            //  掴みを解除
            isGrab = false;
            //  よじ登りを実行
            isClimb = true;
        }
    }

移動キーが入力されたタイミングで、現在の位置を保持、よじ登りの終了座標の計算を実行し、よじ登りの状態へと移行します。

よじ登って終了

よじ登りの状態になったら、アニメーションの進行度を取得して先程算出した終了座標へと徐々に移動します。

    if (isClimb)
    {
        //  よじ登りモーションの進行度を取得
        float f = anim.GetFloat("ClimbProgress");

        //  左右は後半にかけて早く移動する
        float x = Mathf.Lerp(climbOldPos.x, climbPos.x, Ease(f));
        float z = Mathf.Lerp(climbOldPos.z, climbPos.z, Ease(f));
        //  上下は等速直線で移動
        float y = Mathf.Lerp(climbOldPos.y, climbPos.y, f);

        //  座標を更新
        transform.position = new Vector3(x, y, z);
        acceclertion.y = 0.0f;
        //  進行度が8割を超えたらよじ登りの終了
        if (f >= 0.8f)
            isClimb = false;
    }

    //  イージング関数
    float Ease(float x)
    {
        return x * x * x;
    }

アニメーションの進行度には、インポートしたクリップのアニメーションカーブを使用しました。
単純に直線のカーブを用意して、それをAnimatorのパラメータを経由して取得しています。

徐々に終了座標へと移動する際には、直線的に移動すると崖の端にめり込んでしまいます。

直線的に移動するとめり込んでしまう。

そこで
「Y座標は直線的に、XZ座標は後半にかけて早く」
といった移動をさせることでめり込んでしまう量を最小限に抑えることができました。

Y座標は直線的に、XZ座標は後半にかけて早く移動させる

また
「進行度が8割を超えたらよじ登りの終了」
の処理はAnimatorのアニメーション遷移の使用上100%までの取得が上手くいかなかったため、このような処理をするようにしました。

完成

以上でよじ登りの実装ができました。


まとめ

自分で作るゲームで始めてよじ登りを実装することになりました。
実際に作ってみて、思ったより「それっぽく」なって良かったです。

ただ調子に乗って何でも「できる」とか言ってはいけないのだと学びました。

ちなみに、Twitterでもお話していますが今回のプレイヤーモデル、アニメーションは私の自作のものになります。結構上手くできたほうです。


いいなと思ったら応援しよう!