はじめに
UnityのCharacterControllerは便利ですが、
リアルな挙動ができるなどRigidbodyを使う方が自由度が高く
利点がある場合もあります。
そこでRigidbodyを使いCharacterControllerの機能をいくつか実装することで
両方の利点を生かす方法を考えます
方針
- プレイヤーの入力で水平移動・ジャンプ・y軸回転ができるようにする
- 以下機能を実装することで坂を登れる(落ちない)ようにする
ソースコード
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// プレイヤーの入力 /// </summary> public class PlayerInputs : MonoBehaviour { public float HorizontalAxis = 0; public float VerticalAxis = 0; public float RotateAxis = 0; public bool JumpButton = false; private void Update() { JumpButton = Input.GetButton("Jump"); HorizontalAxis = Input.GetAxis("Horizontal"); VerticalAxis = Input.GetAxis("Vertical"); RotateAxis = GetRotateAxis(); } private float GetRotateAxis() { float ret = 0; if (Input.GetKey(KeyCode.E)) ret = 1; else if (Input.GetKey(KeyCode.Q)) ret = -1; return ret; } }
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// キャラクターに重力を与える /// </summary> public class GravityCharacterController : MonoBehaviour { public float Gravity = -15; // 重力 public bool IsEnabledGravity; // 重力有効かどうか private Rigidbody CharacterRigidbody; private void Start() { CharacterRigidbody = GetComponent<Rigidbody>(); } void FixedUpdate() { if (IsEnabledGravity) { CharacterRigidbody.AddForce(0, Gravity, 0, ForceMode.Acceleration); } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// プレイヤーの入力をもとにキャラクターを動かす /// </summary> public class PlayerCharacterController : MonoBehaviour { public float JumpForce; // ジャンプ力 public float MoveSpeed; // 水平移動の目標速度 public float RotateSpeed; // 回転速度 public float Kp; // P項係数 public Vector3 Speed; PlayerInputs PlayerInputs; GroundChecker GroundChecker; Rigidbody CharacterRigidbody; Vector3 JumpForceVec = Vector3.zero; Vector3 MoveDirectionVec = Vector3.zero; Vector3 JumpDirectionVec = Vector3.zero; Vector3 MoveSpeedErr = Vector3.zero; private void Start() { PlayerInputs = GetComponent<PlayerInputs>(); CharacterRigidbody = GetComponent<Rigidbody>(); GroundChecker = GetComponent<GroundChecker>(); } void FixedUpdate() { Move(); Jump(); } void Jump() { if (PlayerInputs.JumpButton && GroundChecker.IsGrounded) { JumpDirectionVec.x = MoveDirectionVec.x; JumpDirectionVec.z = MoveDirectionVec.z; JumpDirectionVec.y = 1.0f - (Mathf.Abs(JumpDirectionVec.x) + Mathf.Abs(JumpDirectionVec.z)); JumpForceVec = JumpDirectionVec * JumpForce; CharacterRigidbody.AddForce(JumpForceVec, ForceMode.Impulse); } } void Move() { MoveDirectionVec = transform.forward * PlayerInputs.VerticalAxis + transform.right * PlayerInputs.HorizontalAxis; Vector3 tgtMoveSpeed = MoveDirectionVec * MoveSpeed; MoveSpeedErr = tgtMoveSpeed - CharacterRigidbody.velocity; MoveSpeedErr.y = 0; Vector3 force = MoveSpeedErr * Kp; CharacterRigidbody.AddForce(force, ForceMode.Force); float rotateSpeed = RotateSpeed * PlayerInputs.RotateAxis; CharacterRigidbody.transform.Rotate(0, rotateSpeed * Time.fixedDeltaTime, 0); Speed = CharacterRigidbody.velocity; } }
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// キャラクターの当たり判定を行う /// </summary> public class CharacterCollider : MonoBehaviour { SlopeChecker SlopeChecker; GroundChecker GroundChecker; private void Start() { SlopeChecker = GetComponent<SlopeChecker>(); GroundChecker = GetComponent<GroundChecker>(); } private void OnCollisionStay(Collision collision) { Vector3 collidedNormal = collision.contacts[0].normal; Vector3 v = Vector3.zero; v.x = collidedNormal.x; v.z = collidedNormal.z; v = v.normalized; float cosTheta = Vector3.Dot(collidedNormal, v); SlopeChecker.CosTheta = cosTheta; GroundChecker.CosTheta = cosTheta; SlopeChecker.IsCollided = true; GroundChecker.IsCollided = true; } }
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 坂のチェックを行う /// </summary> public class SlopeChecker : MonoBehaviour { public float MinSlopeCos = 0.1f; // 坂と判定する最小のcos public float MaxSlopeCos = 0.9f; // 坂と判定する最大のcos public float GravityGain = 1e-3f; // 反転重力に掛ける係数 public bool IsCollided; // 接触しているかどうか public float CosTheta; // 接触面の角度cos Rigidbody CharacterRigidbody; GravityCharacterController GravityCharacterController; Vector3 Force; private void Start() { CharacterRigidbody = GetComponent<Rigidbody>(); GravityCharacterController = GetComponent<GravityCharacterController>(); } void FixedUpdate() { if (!IsCollided) { GravityCharacterController.IsEnabledGravity = true; return; } IsCollided = false; float absCosTheta = Mathf.Abs(CosTheta); if ((absCosTheta >= MinSlopeCos) && (absCosTheta <= MaxSlopeCos)) { Force.y = -GravityCharacterController.Gravity * GravityGain; CharacterRigidbody.AddForce(Force, ForceMode.Acceleration); GravityCharacterController.IsEnabledGravity = false; } else { GravityCharacterController.IsEnabledGravity = true; } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 地面のチェックを行う /// </summary> public class GroundChecker : MonoBehaviour { public float MaxGroundCos = 0.5f; // 地面と判定する最大のcos public bool IsCollided; // 接触しているかどうか public float CosTheta; // 接触面の角度cos public bool IsGrounded; // 地面に接触しているかどうか void FixedUpdate() { if (!IsCollided) { IsGrounded = false; return; } IsCollided = false; float absCosTheta = Mathf.Abs(CosTheta); if (absCosTheta <= MaxGroundCos) IsGrounded = true; else IsGrounded = false; } }
Unityプロジェクトは以下にあります
https://github.com/hide00310/Unity_RigidbodyCharacterController
バージョン
Unity : 2019.4.31f1