わたくしは今までキャラクターの当たり判定や移動の機能に関してCharacterControllerを使用してきました。
CharacterControllerコンポーネントを使用するとコライダ+移動機能が付いているので簡単にキャラクターの移動を実現出来たからです。
ですが、
や、
等の機能を作成しているうちに相手方のゲームオブジェクトの物理的な影響を受けないと実装したい機能が不十分になる事があります。
細かいアクションが売りのゲームを作るとなると物理的な影響を受けないというのはかなり不便です。
衝突の影響をそれほど考慮しなくていい時はCharacterControllerの方が便利で楽です。
作成するゲームによって変更するといいかもしれません。
物理的な力を加えて移動させるキャラクターは以下を参照してください。
CharacterControllerを削除しRigidbodyとCapsuleColliderを取りつける
CharacterControllerでキャラクターを動かしていたものをRigidbody+CapsuleColliderに変更するのは大変なんじゃないか?
と思ったんですが、やってみたらそれほどでもありませんでしたので、紹介したいと思います。
と思っていたんですが、制御がめちゃくちゃ大変でした・・・(T_T)
移動だけならともかくジャンプ機能を搭載したら全然うまくいかなくなり四苦八苦しておりました・・・・(^_^;)
今でも何らかの不具合発生しそうで怖いですが、とりあえず動くようになったので・・・・(-.-)
キャラクターのCharacterControllerを削除し、
Add Component→Physic→RigidbodyとCapsuleColliderを取りつけます。
↑のようにキャラクターのLayerをPlayerに設定します。
RigidbodyのMassに60を入れ質量を60にします。
Use Gravityにチェックを入れキャラクターに重力を働かせます。
ConstraintsでFreeze Rotationにすべてチェックを入れます。
これは外部からの物理的な影響で回転しないようにするためです。
Freeze Positionはチェックを外し、物理的な影響でキャラクターが移動するようにします。
これは外部からの影響もありますが、キーを押して自分でキャラクターを動かす時にもここでチェックがされてると動かなくなる為チェックを外す必要があります。
実際のコライダのサイズは
↑のような感じにします。
その他必要なもの
キャラクターのAnimatorコンポーネントにはAnimatorControllerを取り付けている必要があります。
またアニメーションパラメーターにはFloat型のSpeed、Bool型のJumpを作成します。
アニメーションの状態にはIdle、Walk、Jumpの3つの状態を作成します。
Idle→Walk、Walk→Idle、Idle→Jump、Walk→Jump、Jump→Idleの遷移を作成します。
Idle→Walkの条件はSpeedが0.1より上でHas Exit Timeのチェックを外す。
Walk→Idleの条件はSpeedが0.1より下でHas Exit Timeのチェックを外す。
Idle→JumpとWalk→Jumpの条件はJumpがtrueの時でHas Exit Timeのチェックを外す。
Jump→Idleの条件はJumpがfalseの時でHas Exit Timeのチェックを外す。
Idle→Walk、Walk→Idle、Jump→Idleの遷移のインスペクタのSettingsのInterruption SourceをNext Stateにします。
となります。
最小限のキャラクター操作スクリプト
次に最小限のCharacterControllerを使ったキャラクターの移動スクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | using UnityEngine; using System.Collections; public class RigidChara : MonoBehaviour { private Animator animator; private CharacterController cCon; private Vector3 velocity; [SerializeField] private float jumpPower = 5f; // 入力値 private Vector3 input; // 歩く速さ [SerializeField] private float walkSpeed = 4f; void Start () { animator = GetComponent<Animator>(); cCon = GetComponent<CharacterController>(); } void Update () { // キャラクターコライダが接地、またはレイが地面に到達している場合 if(cCon.isGrounded) { velocity = Vector3.zero; // 着地していたらアニメーションパラメータと2段階ジャンプフラグをfalse animator.SetBool("Jump", false); input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); // 方向キーが多少押されている if(input.magnitude > 0f) { animator.SetFloat("Speed", input.magnitude); transform.LookAt(transform.position + input); velocity += input.normalized * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat("Speed", 0f); } // ジャンプ if(Input.GetButtonDown("Jump")) { animator.SetBool("Jump", true); velocity.y += jumpPower; } } velocity.y += Physics.gravity.y * Time.deltaTime; cCon.Move(velocity * Time.deltaTime); } } |
キャラクターの移動とジャンプ機能だけを搭載したスクリプトです。
スクリプトの変更点を考える
CharacterControllerを使った移動で使っていたCharacterControllerのisGroundedプロパティが使えなくなるのでCapsuleColliderが地面と接地しているかを別の個所で調べるようにします。
また、移動の処理をCharacterControllerのMoveからRigidbodyのMovePositionに変更します。
MovePositionで指定する引数は移動先のVector3を指定する必要があるので、現在地+入力値で計算します。
Rigidbodyでの移動処理はUpdateメソッドではなくFixedUpdateメソッドで行う必要があるので、FixedUpdateメソッド内に記述するようにします。
これはUpdateメソッドは毎フレーム呼ばれるのに対して、FixedUpdateメソッドは固定フレームレート(デフォルトでは0.02秒に1回呼ばれる)なので移動やジャンプボタンが押されたとしても判定がされないことがある為です。
移動値の計算はUpdateメソッドで行い、Rigidbodyを使った物理的な移動等はFixedUpdateメソッドで行います。
スクリプトで計算していたキャラクターにかける重力はRigidbodyで働かせるようにします。
RigidbodyとCapsuleColliderを使った移動に切り替えたスクリプト
それではさきほどの変更点を考慮してスクリプトを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | using UnityEngine; using System.Collections; public class RigidChara : MonoBehaviour { private Animator animator; [SerializeField] private Vector3 velocity; [SerializeField] private float jumpPower = 5f; // 入力値 private Vector3 input; // 歩く速さ [SerializeField] private float walkSpeed = 4f; // rigidbody private Rigidbody rigid; // 地面に接地しているかどうか [SerializeField] private bool isGrounded; // 前方の壁に衝突しているかどうか [SerializeField] private bool isCollision; // ジャンプ中かどうか [SerializeField] private bool isJump; // ジャンプ後の着地判定までの遅延時間 [SerializeField] private float delayTimeToLanding = 0.5f; // ジャンプ後の時間 [SerializeField] private float jumpTime; // 接地確認のコライダの位置のオフセット [SerializeField] private Vector3 groundPositionOffset = new Vector3(0f, 0.02f, 0f); // 接地確認の球のコライダの半径 [SerializeField] private float groundColliderRadius = 0.29f; // 衝突確認のコライダの位置のオフセット [SerializeField] private Vector3 collisionPositionOffset = new Vector3(0f, 0.5f, 0.1f); // 衝突確認の球のコライダの半径 [SerializeField] private float collisionColliderRadius = 0.3f; void Start() { animator = GetComponent<Animator>(); rigid = GetComponent<Rigidbody>(); } void Update() { CheckGround(); // キャラクターが接地している場合 if (isGrounded) { // 接地したので移動速度を0にする velocity = Vector3.zero; input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0f) { animator.SetFloat("Speed", input.magnitude); transform.LookAt(rigid.position + input); velocity = rigid.transform.forward * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat("Speed", 0f); } // ジャンプ if (Input.GetButtonDown("Jump")) { isGrounded = false; isJump = true; jumpTime = 0f; velocity.y = jumpPower; animator.SetBool("Jump", true); // 2ax = v²-v₀²より //velocity.y = Mathf.Sqrt(-2 * Physics.gravity.y * jumpPower); } } // 接触していたら移動方向の値は0にする if (!isGrounded && isCollision) { velocity = new Vector3(0f, velocity.y, 0f); } // ジャンプ時間の計算 if (isJump && jumpTime < delayTimeToLanding) { jumpTime += Time.deltaTime; } } void FixedUpdate() { // キャラクターを移動させる処理 rigid.MovePosition(rigid.position + velocity * Time.fixedDeltaTime); } private void OnCollisionEnter(Collision collision) { // 指定したコライダと接触、かつ接触確認コライダと接触していたら衝突状態にする if (Physics.CheckSphere(rigid.position + transform.up * collisionPositionOffset.y + transform.forward * collisionPositionOffset.z, collisionColliderRadius, ~LayerMask.GetMask("Player")) ) { isCollision = true; } } private void OnCollisionExit(Collision collision) { // 指定したコライダと離れたら衝突していない状態にする isCollision = false; } // 地面のチェック private void CheckGround() { // 地面に接地しているか確認 if (Physics.CheckSphere(rigid.position + groundPositionOffset, groundColliderRadius, ~LayerMask.GetMask("Player"))) { // ジャンプ中 if (isJump) { if (jumpTime >= delayTimeToLanding) { isGrounded = true; isJump = false; } else { isGrounded = false; } } else { isGrounded = true; } } else { isGrounded = false; } animator.SetBool("Jump", !isGrounded); } private void OnDrawGizmos() { // 接地確認のギズモ Gizmos.DrawWireSphere(transform.position + groundPositionOffset, groundColliderRadius); Gizmos.color = Color.blue; // 衝突確認のギズモ Gizmos.DrawWireSphere(transform.position + transform.up * collisionPositionOffset.y + transform.forward * collisionPositionOffset.z, collisionColliderRadius); } } |
↑が変更を加えた結果です。
ほとんど変わっていないですね。
うそつけーーー(-_-)/~~~ピシー!ピシー!
rigidにはRigidbodyコンポーネントを取得して入れます。
isGroundedは接地しているかどうか
isCollisionはキャラクターの前方に出現させたコライダが他のコライダと衝突したかどうか
isJumpはジャンプしているかどうか
delayTimeToLandingはジャンプ後の着地判定までの遅延時間
jumpTimeはジャンプ後の時間です。
groundPositionOffsetは接地確認の球を作る位置のオフセット値です。
groundColliderRadiusは接地確認の球の半径です。
collisionPositionOffsetは衝突確認の球を作る位置のオフセット値です。
collisionColliderRadiusは衝突確認の球の半径です。
Updateメソッドの最初でCheckGroundメソッドを呼んで接地しているかどうかを確認しています。
CheckGroundメソッドは後で作ります。
ジャンプボタンを押した後にisGroundedをfalse、isJumpをtrue、jumpTimeを0に初期化し、velocityのyにjumpPowerを入れています。
接地せずに前方のコライダが衝突していた場合のジャンプで計算した速度以外は0にします。
ジャンプ中でジャンプ時間が指定した時間よりも小さい時はジャンプ時間を足しています。
Rigidbodyに力を加える場合はUpdateよりもFixedUpdateメソッドで行うようにします。
またTime.deltaTimeをTime.fixedDeltaTimeに変更します。
FixedUpdateメソッド内でTime.deltaTimeを使うとTime.fixedUpdateと同じ効果になるとどこかで見たような気もしますが、変更しておきます。
ジャンプ時は速度velocityのY軸にjumpPowerをそのまま足していますが、jumpPowerとキャラクターのジャンプ時の最高到達点を合わせる場合は速度と距離の方程式から
$$v = v_0 + at$$
の式からtについて解いて下の式に代入します。
$$x = v_0t + \frac{1}{2}at^2$$
すると以下の式が得られます。
$$2ax = v^2-{v_0}^2$$
の式を使ってジャンプの到達距離を計算するという方法もあります。
aは加速度、xはジャンプの最高到達点までの距離、vはY軸の速度、v₀はY軸の初速度となります。
最高到達点での速度vは0なので、
$$2ax = 0^2 – {v_0}^²$$
となり
$$v_0 = \sqrt{-2ax}$$
と-2axの平方根を求めると初速度を求められます。
aは重力加速度を入れxはここではjumpPower(最高到達点がjumpPower)を入れます。
こちらの場合はジャンプの距離がjumpPowerになり(到達点を決める)、元のやつはジャンプの初速度を設定(上向きに飛ぶ速度を設定)しているので同じ動作にはなりません。
こちらを使う場合はjumpPowerではなくjumpDistanceという名称の方がいいかもしれません。
コライダが地面と接触しているかどうかはOnCollisionEnterでキャラクターの前方に作った球と他のコライダとの接触を検知し、何らかのコライダとぶつかっていればisCollisionをtrueにします。
これは前方に何らかのコライダがある場合は移動値を0にする為に使います。
OnCollisionExitは他のコライダとの接触がなくなったら呼ばれるのでisCollisionをfalseにします。
CheckGroundメソッドでは最初に球の範囲を作成し、それがPlayerレイヤー以外のレイヤーと接触するかどうかを判定しています。
球を作る位置はrigid.position(キャラクターの現在位置)+groundPositionOffset(オフセット値)で、球の半径はgroundColliderRadiusです。
Playerレイヤー以外の他のコライダと接触していたらジャンプ中でジャンプ時間が指定した時間以上であればisGroundedをtrueにして接地にし、isJumpをfalseにしてジャンプが終了したとします。
ジャンプ中でもジャンプ時間が指定した時間より小さい場合はジャンプ後のまだ間もない時間なので接地していないとします。
そもそもジャンプ中でなければ接地とします。
球が他のコライダと接触しない場合は接地していないとします。
最後にアニメーションパラメーターのJumpに!isGroundedと接地状態の反転したbool値を渡します。
これは接地していればジャンプしていない、接地していなければジャンプ中にアニメーション状態をする為です。
キャラクターが動くかどうか確認する
CharacterControllerで動かしていた時と違いが出てないか確認してみましょう。
基本的な移動も出来、他のゲームオブジェクトにも自動的に物理的な力が加わっています。
他のゲームオブジェクトからの影響も受けるのでシーソーの床に乗ると少しずつ坂を落ちていきます。
青い坂の方にはPhysic Materialを設定し摩擦を大きくしたのでキャラクターが落ちていく事がありません。
CharacterControllerとRigidbody+CapsuleColliderのキャラクターの動きを比較する
CharacterControllerとRigidbodyで動かすキャラクターを横に並べて確認してみましょう。
左側がCharacterControllerで右側がRigidbodyで動かしているキャラクターです。
最後にどちらがどちらか確認するのでよく見てください!!(ウソです)
制御が違うので多少違いが出てますね。
Rigidbody君の方は接触している床の摩擦を受けるので坂を登るスピード等は遅くなりますね。
CharacterControllerと同じように段差を越えたり、坂を登れるようにする
現時点でも小さい段差を昇れたり、坂も登れますが、CharacterControllerのように高い段差を昇れるようにしたり、登れる坂の角度に制限を持たせます。
やり方としてはキャラクターの前に坂や壁があるかどうかを調べるレイを飛ばし(足元あたりから)キャラクターとレイが衝突した壁の角度が指定した角度以下だった時は坂の角度から上向きのベクトルとキャラクター前方への速度のベクトルを足して坂を上ります。
さらに、上れる段差であれば角度を関係なく上向きの移動値とキャラクター前方の移動値を計算します。
Rigidbodyの場合は移動する速さが速ければ高い山等もそのまま登れますが、早く移動するキャラクターでも登れる坂の角度や登れる段差の設定を付け、移動に制限を付ける事にします。
これらを考慮してスクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | using UnityEngine; using System.Collections; public class RigidChara : MonoBehaviour { private Animator animator; private Vector3 velocity; [SerializeField] private float jumpPower = 5f; // 入力値 private Vector3 input; // 歩く速さ [SerializeField] private float walkSpeed = 4f; // rigidbody private Rigidbody rigid; // 地面に接地しているかどうか [SerializeField] private bool isGrounded; // 前方の壁に衝突しているかどうか [SerializeField] private bool isCollision; // 接地確認のコライダの位置のオフセット [SerializeField] private Vector3 groundPositionOffset = new Vector3(0f, 0.02f, 0f); // 接地確認の球のコライダの半径 [SerializeField] private float groundColliderRadius = 0.29f; // 衝突確認のコライダの位置のオフセット [SerializeField] private Vector3 collisionPositionOffset = new Vector3(0f, 0.5f, 0.1f); // 衝突確認の球のコライダの半径 [SerializeField] private float collisionColliderRadius = 0.3f; // ジャンプ中かどうか [SerializeField] private bool isJump; // ジャンプ後の着地判定までの遅延時間 [SerializeField] private float delayTimeToLanding = 0.5f; // ジャンプ後の時間 [SerializeField] private float jumpTime; // 前方に段差があるか調べるレイを飛ばすオフセット位置 [SerializeField] private Vector3 stepRayOffset = new Vector3(0f, 0.05f, 0f); // レイを飛ばす距離 [SerializeField] private float stepDistance = 0.5f; // 昇れる段差 [SerializeField] private float stepOffset = 0.3f; // 昇れる角度 [SerializeField] private float slopeLimit = 65f; // 昇れる段差の位置から飛ばすレイの距離 [SerializeField] private float slopeDistance = 0.6f; void Start() { animator = GetComponent<Animator>(); rigid = GetComponent<Rigidbody>(); } void Update() { CheckGround(); // キャラクターが接地している場合 if (isGrounded) { // 接地したので移動速度を0にする velocity = Vector3.zero; input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0f) { animator.SetFloat("Speed", input.magnitude); transform.LookAt(rigid.position + input); var stepRayPosition = rigid.position + stepRayOffset; // ステップ用のレイが地面に接触しているかどうか if (Physics.Linecast(stepRayPosition, stepRayPosition + rigid.transform.forward * stepDistance, out var stepHit)) { // 進行方向の地面の角度が指定以下、または昇れる段差より下だった場合の移動処理 if (Vector3.Angle(rigid.transform.up, stepHit.normal) <= slopeLimit || (Vector3.Angle(rigid.transform.up, stepHit.normal) > slopeLimit && !Physics.Linecast(rigid.position + new Vector3(0f, stepOffset, 0f), rigid.position + new Vector3(0f, stepOffset, 0f) + rigid.transform.forward * slopeDistance)) ) { velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * rigid.transform.forward * walkSpeed).y, 0f) + rigid.transform.forward * walkSpeed; } else { // 指定した条件に当てはまらない場合は速度を0にする velocity = Vector3.zero; } Debug.Log(Vector3.Angle(Vector3.up, stepHit.normal)); // 前方の壁に接触していなければ } else { velocity = transform.forward * walkSpeed; } // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat("Speed", 0f); } // ジャンプ if (Input.GetButtonDown("Jump")) { // ジャンプしたら接地していない状態にする isGrounded = false; isJump = true; jumpTime = 0f; velocity.y = jumpPower; // 2ax = v²-v₀²より //velocity.y = Mathf.Sqrt(-2 * Physics.gravity.y * jumpPower); animator.SetBool("Jump", true); } } // 接触していたら移動方向の値は0にする if (!isGrounded && isCollision) { velocity = new Vector3(0f, velocity.y, 0f); } // ジャンプ時間の計算 if (isJump && jumpTime < delayTimeToLanding) { jumpTime += Time.deltaTime; } } void FixedUpdate() { // キャラクターを移動させる処理 rigid.MovePosition(rigid.position + velocity * Time.fixedDeltaTime); } private void OnCollisionEnter(Collision collision) { // 指定したコライダと接触、かつ接触確認コライダと接触していたら衝突状態にする if (Physics.CheckSphere(rigid.position + transform.up * collisionPositionOffset.y + transform.forward * collisionPositionOffset.z, collisionColliderRadius, ~LayerMask.GetMask("Player")) ) { isCollision = true; } } private void OnCollisionExit(Collision collision) { // 指定したコライダと離れたら衝突していない状態にする isCollision = false; } // 地面のチェック private void CheckGround() { // 地面に接地しているか確認 if (Physics.CheckSphere(rigid.position + groundPositionOffset, groundColliderRadius, ~LayerMask.GetMask("Player"))) { // ジャンプ中 if (isJump) { if (jumpTime >= delayTimeToLanding) { isGrounded = true; isJump = false; } else { isGrounded = false; } } else { isGrounded = true; } } else { isGrounded = false; } animator.SetBool("Jump", !isGrounded); } private void OnDrawGizmos() { // 接地確認のギズモ Gizmos.DrawWireSphere(transform.position + groundPositionOffset, groundColliderRadius); Gizmos.color = Color.blue; // 衝突確認のギズモ Gizmos.DrawWireSphere(transform.position + transform.up * collisionPositionOffset.y + transform.forward * collisionPositionOffset.z, collisionColliderRadius); var stepRayPosition = transform.position + stepRayOffset; Gizmos.color = Color.red; Gizmos.DrawLine(stepRayPosition, stepRayPosition + transform.forward * stepDistance); Gizmos.color = Color.green; Gizmos.DrawLine(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance); } } |
レイが地面や壁と接触しているかどうかはPhysics.Linecastで調べています。
Physics.Linecastは始点から終点に向けてレイを飛ばしてぶつかった相手をstepHit変数に入れます。
レイの使い方は
を参照してください。
またキャラクターの上方向(Vector3.up)とレイが接触した方向(stepHit.normal)の角度をVector3.Angleで求めています。
Vector3.Angleで2つの方向の角度を求める事が出来ます。
この角度がインスペクタで設定したslopeLimit以下の時、または昇れる段差(stepOffset)の高さから前方にレイを飛ばし接触していなければ
上方向と押したキーの方向のvelocityを計算しています。
その部分が
1 2 3 | velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * rigid.transform.forward * walkSpeed).y, 0f) + rigid.transform.forward * walkSpeed; |
ですね。
Quaternion.FromToRotation(Vector3.up, stepHit.normal)で上方向とレイの接触面の間の角度を計算し、それにtransform.forwardをかける事で坂の角度方向へのベクトルを作成しています(坂の下から坂の上への方向ベクトル)。
接触面から計算した角度方向はy座標だけを使ってx、y座標の移動はキャラクターの向いている向きで計算します。
図にすると↑のような感じです。
角度を求めたらキャラクターの進行方向をかけることで坂の上の方向のベクトルが計算出来ます。
ここら辺はちょっと難しいですね・・・。
ここら辺の記事を参照して頂くと衝突面の角度辺りの事に触れています。
キャラクターの移動速度を進行方向と上向きにベクトルを分解すると動きがおかしくなるので、ちょっとズルいやり方をしています。(^_^;)
1 2 3 4 | var speed = (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * rigid.transform.forward * walkSpeed).magnitude; velocity = speed * Mathf.Cos(angle * Mathf.Deg2Rad) * rigid.transform.forward + speed * Mathf.Sin(angle * Mathf.Deg2Rad) * rigid.transform.up; |
上のように進行方向のベクトルと上方向のベクトルに分解した場合、90度の階段を上る場合に上向きのベクトルしか得られないので上方向は坂の角度からY軸の値だけを使って計算し、キャラクターの前方にはそのまま移動速度を足しています。
Cosで斜辺方向のベクトルの大きさから底辺方向のベクトルを求め、Sinで斜辺方向のベクトルの大きさから対辺(上向き)のベクトルを求めています。
どちらにせよ元のスクリプトの方が動きが滑らかなのでこちらは無視していいです。(^_^;)
坂を上る移動速度を単純な方法に変える
先ほどの場合坂の角度からキャラクターの進む方向を求めましたが、
1 2 3 | velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * transform.forward * walkSpeed).y, 0f) + transform.forward * walkSpeed; |
上のように坂の角度方向のY軸にキャラクターの移動スピードから上方向の移動値を計算し、それにキャラクターの前方方向のベクトルも加えています。
キャラクターの移動スピードが上がれば上がるほど上方向のベクトルの長さが長くなっていきます。
単純に坂を昇らせるだけならば
1 2 3 | velocity = Vector3.up * 2f + transform.forward * walkSpeed; |
上のように上向きの適当な力とキャラクターの前方に移動スピードをかけるだけでも出来ます。
これでRigidbodyを使ったキャラクターの移動とジャンプの処理、坂や段差を登る事が出来ました。
終わりに
最初に記事を公開した時よりも内容が簡潔になり、キャラクターの動きもよくなりました。(^^)/
このキャラクターで移動して他のゲームオブジェクトに当たれば、力を加える事が出来ます。
ただ、RigidbodyのMovePositionを使って移動させていて、Is Kinematicはfalseとなっている為、Rigidbodyの補間の機能が働かず、厳密にはキャラクターの移動は次の位置へワープして移動しているという感じになります。
補間の機能を使うにはAddForceやAddTorqueを使ったキャラクターの移動機能を作るのがいいかもしれません。