2020/11/09
今回はUnityの3D移動に関するお話です。
私は3Dアクション用にCharacterController
のMove()
を使った移動処理を作っていますが、斜面をまったく考慮せずに実装していました。
私がUnityで作ってきたゲームにジャンプが搭載されていないのは、こういう残念な実装をしてしまうためです。
どんな急な斜面でもジャンプボタンを押しっぱなしにするだけで登れてしまいます。
そろそろ本腰を入れて直さねば…。 pic.twitter.com/Nfi6pyifg3— あかざらし (@akaiazarashi) June 23, 2019
このツイートの「着地後もジャンプボタンを押しっぱなしにするとジャンプを繰り返す」のも問題ですが、それ以上に問題なのが「急斜面でも平然と立っていられる」ことです。
ということで、一般的なアクションゲーム同様、斜面では滑り落ち、また傾斜角度を取得してジャンプやその他の判定に使えるようにしてみました!
実装にあたっては、以前Unityの標準アセットにあった「CharacterMotor」がかなり参考になります。
標準アセットのものはJavaScriptで作られていましたが、公式フォーラムでC#版を作っている方がいたので紹介します。
https://forum.unity.com/threads/charactermotor-fpsinputcontroller-platforminputcontroller-in-c.64378/
斜面の角度取得
斜面の角度についてはぐぐるといくつか方法がヒットしますが、私自身CharacterMotor
をずっと使っていたので、同コンポーネントに倣って「ControllerColliderHit
のnormal
を使う」方法でやりました!
「倣って」というより、ほぼほぼ前述の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
)でジャンプの可否を制御したり、後述のように斜面に応じてキャラクターを滑らせたりできます。
キャラクターを滑らせる
角度に応じてCharacterController
のMove()
で動かすベクトルを変化させます。
とはいえ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); }
ポイントはgroundNormal
をmoveDirection
の各軸に反映する時の処理です。
ここでY軸のみを引くことで、傾斜に応じたY軸の落下速度を表すことができます。
ちょっと不自然ですが、これで滑り落ちる挙動はできました。
あとがき
そんなわけで、UnityでCharacterController
を使った際の斜面に関するお話でした。
アクションゲームっていざ作ろうとなるといろいろ大変だと実感させられますね。