ゴマちゃんフロンティア

アザラシが大好きなエンジニアの開発日記です

【Unity】CharacterController使用時に地形の傾斜に応じてキャラクターを滑らせる方法

time 2019/07/14

今回はUnityの3D移動に関するお話です。
私は3Dアクション用にCharacterControllerMove()を使った移動処理を作っていますが、斜面をまったく考慮せずに実装していました。

このツイートの「着地後もジャンプボタンを押しっぱなしにするとジャンプを繰り返す」のも問題ですが、それ以上に問題なのが「急斜面でも平然と立っていられる」ことです。
ということで、一般的なアクションゲーム同様、斜面では滑り落ち、また傾斜角度を取得してジャンプやその他の判定に使えるようにしてみました!

実装にあたっては、以前Unityの標準アセットにあった「CharacterMotor」がかなり参考になります。
標準アセットのものはJavaScriptで作られていましたが、公式フォーラムでC#版を作っている方がいたので紹介します。
https://forum.unity.com/threads/charactermotor-fpsinputcontroller-platforminputcontroller-in-c.64378/

斜面の角度取得

斜面の角度についてはぐぐるといくつか方法がヒットしますが、私自身CharacterMotorをずっと使っていたので、同コンポーネントに倣って「ControllerColliderHitnormalを使う」方法でやりました!
「倣って」というより、ほぼほぼ前述のCharacterMotorから持ってきた処理だったりしますが…。

[System.NonSerialized]
public Vector3 groundNormal = Vector3.zero;

private Vector3 lastGroundNormal = Vector3.zero;

[System.NonSerialized]
public Vector3 lastHitPoint = new Vector3(Mathf.Infinity, 0, 0);

protected float groundAngle = 0;

void MotorOnControllerColliderHit(ControllerColliderHit hit)
{
    if (hit.normal.y > 0 && hit.moveDirection.y < 0) {
        if ((hit.point - lastHitPoint).sqrMagnitude > 0.001f || lastGroundNormal == Vector3.zero) {
            groundNormal = hit.normal;
        } else {
            groundNormal = lastGroundNormal;
        }

        lastHitPoint = hit.point;
    }

    // 現在の接地面の角度を取得
    groundAngle = Vector3.Angle(hit.normal, Vector3.up);
}

この傾斜角度の値(groundAngle)でジャンプの可否を制御したり、後述のように斜面に応じてキャラクターを滑らせたりできます。

キャラクターを滑らせる

角度に応じてCharacterControllerMove()で動かすベクトルを変化させます。
とはいえ1度でも傾いているだけでずり落ちるのはないですよね。もうちょっと踏ん張って欲しくなります。
なので今回はCharacterController.slopeLimitを閾値とし、それ以外の傾斜であれば滑るようにしました。

void FixedUpdate()
{
    Vector3 moveDirection = Vector3.zero;

    // 中略 (ここで移動やジャンプに応じてmoveDirectionを修正)

    // 地上で斜面にいる場合はその方向へ滑らせる
    if (IsGrounded() && characterController.slopeLimit <= groundAngle) {
        float slidingSpeed = 5f;
        moveDirection.x += groundNormal.x * slidingSpeed;
        moveDirection.y -= groundNormal.y * slidingSpeed;
        moveDirection.z += groundNormal.z * slidingSpeed;
    }

    // CharacterControllerで移動
    characterController.Move(moveDirection * Time.deltaTime);
}

public bool IsGrounded()
{
    return characterController.isGrounded && (groundNormal.y > 0.01);
}

ポイントはgroundNormalmoveDirectionの各軸に反映する時の処理です。
ここでY軸のみを引くことで、傾斜に応じたY軸の落下速度を表すことができます。

ちょっと不自然ですが、これで滑り落ちる挙動はできました。

あとがき

そんなわけで、UnityでCharacterControllerを使った際の斜面に関するお話でした。
アクションゲームっていざ作ろうとなるといろいろ大変だと実感させられますね。

down

コメントする