キャラ操作をCharacterControllerからRigidbody+コライダに変更する

わたくしは今までキャラクターの当たり判定や移動の機能に関してCharacterControllerを使用してきました。

CharacterControllerコンポーネントを使用するとコライダ+移動機能が付いているので簡単にキャラクターの移動を実現出来たからです。
ですが、

UnityのPhysic Materialを使って衝突や跳ね返りを設定する
UnityのPhysic Materialを使って衝突や跳ね返りを設定します。衝突したらそのまま勢いを無くしたり、スーパーボールのように反発して跳ね返るというような処理を作りたい時にPhysic Materialは便利です。

や、

Unityでキャラクターが乗ったらシーソーする床を作成する
UnityでCharacterControllerで操作するキャラクターが乗ったらシーソーする床を作成します。

等の機能を作成しているうちに相手方のゲームオブジェクトの物理的な影響を受けないと実装したい機能が不十分になる事があります。

細かいアクションが売りのゲームを作るとなると物理的な影響を受けないというのはかなり不便です。

衝突の影響をそれほど考慮しなくていい時はCharacterControllerの方が便利で楽です。

作成するゲームによって変更するといいかもしれません。

ただ、今回作成するRigidbody+コライダで動かすキャラクターはRigidbodyのMovePositionを使って移動させているので、実質はCharacterControllerを使った移動と同じで、小刻みにワープをしているという感じになります。

物理的な力を加えて移動させるキャラクターの場合はAddForceやAddTorqueを使って処理をする必要があり、それについては別の記事で出来たらと思います。

スポンサーリンク

CharacterControllerを削除しRigidbodyとCapsuleColliderを取りつける

CharacterControllerでキャラクターを動かしていたものをRigidbody+CapsuleColliderに変更するのは大変なんじゃないか?

と思ったんですが、やってみたらそれほどでもありませんでしたので、紹介したいと思います。

と思っていたんですが、制御がめちゃくちゃ大変でした・・・(T_T)

移動だけならともかくジャンプ機能を搭載したら全然うまくいかなくなり四苦八苦しておりました・・・・(^_^;)

今でも何らかの不具合発生しそうで怖いですが、とりあえず動くようになったので・・・・(-.-)

キャラクターのCharacterControllerを削除し、

Add Component→Physic→RigidbodyとCapsuleColliderを取りつけます。

RigidbodyとCapsuleColliderを取り付けたキャラのインスペクタ

↑のようにキャラクターのLayerをPlayerに設定します。

RigidbodyのMassに60を入れ質量を60にします。

Use Gravityにチェックを入れキャラクターに重力を働かせます。

ConstraintsでFreeze Rotationにすべてチェックを入れます。

これは外部からの物理的な影響で回転しないようにするためです。

Freeze Positionはチェックを外し、物理的な影響でキャラクターが移動するようにします。

これは外部からの影響もありますが、キーを押して自分でキャラクターを動かす時にもここでチェックがされてると動かなくなる為チェックを外す必要があります。

実際のコライダのサイズは

実際のコライダのサイズ

↑のような感じにします。

その他必要なもの

キャラクターのAnimatorコンポーネントにはAnimatorControllerを取り付けている必要があります。

またアニメーションパラメーターにはFloat型のSpeed、Trigger型のJump、Bool型のIsGroundedを作成します。

アニメーションの状態には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がトリガーされた時でHas Exit Timeのチェックを外す。
Jump→Idleの条件はIsGroundedがtrueの時でHas Exit Timeのチェックを外す。

となります。

最小限のキャラクター操作スクリプト

次に最小限のCharacterControllerを使ったキャラクターの移動スクリプトを作成します。

キャラクターの移動とジャンプ機能だけを搭載したスクリプトです。

スクリプトの変更点を考える

CharacterControllerを使った移動で使っていたCharacterControllerのisGroundedプロパティが使えなくなるのでCapsuleColliderが地面と接地しているかを別の個所で調べるようにします。

また、移動の処理をCharacterControllerのMoveからRigidbodyのMovePositionに変更します。

MovePositionで指定する引数は移動先のVector3を指定する必要があるので、現在地+入力値で計算します。

Rigidbodyでの移動処理はUpdateメソッドではなくFixedUpdateメソッドで行う必要があるので、FixedUpdateメソッド内に記述するようにします。

Rigidbodyを使った物理的な移動等はFixedUpdateメソッドで行った方がいいようです。

スクリプトで計算していたキャラクターにかける重力はRigidbodyで働かせるようにします。

RigidbodyとCapsuleColliderを使った移動に切り替えたスクリプト

それではさきほどの変更点を考慮してスクリプトを修正します。

↑が変更を加えた結果です。

ほとんど変わっていないですね。
うそつけーーー(-_-)/~~~ピシー!ピシー!

Updateメソッドでしていた処理をFixedUpdateメソッドに移動し、RigidbodyのMovePositionを使ってキャラクターを移動させています。

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で他のコライダとの接触を検知し、さらにキャラクターの下に地面として使うコライダがあるかどうかを
レイを飛ばして確認します。

ジャンプをした時にisGroudedをfalseにし、OnCollisionEnterでPhysics.CheckSphereを使ってキャラクターの地面の位置に球の判定範囲を作成し、指定したレイヤーと接触していたらisGroundedをtrueにして着地したと判定しています。

layerMaskはインスペクタで地面と判定するレイヤーを選択することが出来るので、そこで例えばGroundレイヤーを作成し、チェックを入れます。

その他のレイヤーも地面と判定したい場合はそのレイヤーにもチェックを入れます。

OnCollisionExitはキャラクターのコライダが他のコライダとの衝突から抜け出した時に呼ばれるので、キャラクターが地面から普通に落下した時の処理を記述します。

衝突していたコライダのレイヤーと地面と判定するレイヤーマスクが同じ時は、さらに下に短いレイを飛ばし、地面に設定したレイヤーと接触していない時にisGroundedにfalseを入れます。

1 << collision.gameObject.layerは1をcollision.gameObject.layerの数値分を左にシフトします。

これでレイヤーマスクを得られます。

layerMaskはインスペクタでチェックを入れたレイヤーのマスクなので、これと同じ数値であればそのどれかのレイヤーと一緒ということになります。

インスペクタでlayerMaskを選択し、地面とするレイヤーにチェックを入れます。

今回はGroundレイヤーを作成し、地面とするゲームオブジェクトにGroundレイヤーを設定しました。

RigidCharaのlayerMaskの設定

キャラクターが動くかどうか確認する

CharacterControllerで動かしていた時と違いが出てないか確認してみましょう。

RigidbodyとCapsuleColliderでキャラクターを動かすサンプル

基本的な移動も出来、他のゲームオブジェクトにも自動的に物理的な力が加わっています。

他のゲームオブジェクトからの影響も受けるのでシーソーの床に乗ると少しずつ坂を落ちていきます。

青い坂の方にはPhysic Materialを設定し摩擦を大きくしたのでキャラクターが落ちていく事がありません。

CharacterControllerとRigidbody+CapsuleColliderのキャラクターの動きを比較する

CharacterControllerとRigidbodyで動かすキャラクターを横に並べて確認してみましょう。

左側がCharacterControllerで右側がRigidbodyで動かしているキャラクターです。

最後にどちらがどちらか確認するのでよく見てください!!(ウソです)

CharacterControllerとRigidbodyでの違いを確認するサンプル

制御が違うので多少違いが出てますね。

Rigidbody君の方は接触している床の摩擦を受けるので坂を登るスピード等は遅くなりますね。

CharacterControllerと同じように段差を越えたり、坂を登れるようにする

現時点でも小さい段差を昇れたり、坂も登れますが、CharacterControllerのように高い段差を昇れるようにしたり、登れる坂の角度に制限を持たせます。

やり方としてはキャラクターの前に坂や壁があるかどうかを調べるレイを飛ばし(足元あたりから)キャラクターとレイが衝突した壁の角度が指定した角度以下だった時は坂の角度から上向きのベクトルとキャラクター前方への速度のベクトルを足して坂を上ります。

さらに、上れる段差であれば角度を関係なく上向きの移動値とキャラクター前方の移動値を計算します。

Rigidbodyの場合は移動する速さが速ければ高い山等もそのまま登れますが、早く移動するキャラクターでも登れる坂の角度や登れる段差の設定を付け、移動に制限を付ける事にします。

これらを考慮してスクリプトを作成します。

レイが地面や壁と接触しているかどうかはPhysics.Linecastで調べています。

Physics.Linecastは始点から終点に向けてレイを飛ばしてぶつかった相手をstepHit変数に入れます。

レイの使い方は

Unityでよく使う便利な標準関数について
管理人のかめくめちゃんがUnityのゲームを作成する時によく使う関数を紹介します

を参照してください。

またキャラクターの上方向(Vector3.up)とレイが接触した方向(stepHit.normal)の角度をVector3.Angleで求めています。

Vector3.Angleで2つの方向の角度を求める事が出来ます。

この角度がインスペクタで設定したslopeLimit以下の時、または昇れる段差(stepOffset)の高さから前方にレイを飛ばし接触していなければ

上方向と押したキーの方向のvelocityを計算しています。

その部分が

ですね。

Quaternion.FromToRotation(Vector3.up, stepHit.normal)で上方向とレイの接触面の間の角度を計算し、それにtransform.forwardをかける事で坂の角度方向へのベクトルを作成しています(坂の下から坂の上への方向ベクトル)。

接触面から計算した角度方向はy座標だけを使ってx、y座標の移動はキャラクターの向いている向きで計算します。

目的の方向ベクトルを計算するまで

図にすると↑のような感じです。

角度を求めたらキャラクターの進行方向をかけることで坂の上の方向のベクトルが計算出来ます。

ここら辺はちょっと難しいですね・・・。

UnityのRaycastHit.normalとQuaternion.FromToRotationを使いこなす
UnityのQuaternion.FromToRotationで2つの方向から角度を求める事が出来ますが、その時の方向にPhysics.Raycastで得られたRaycastHit.normalの値を使う事が多々あります。このRaycastHit.normalとはなんなのか?を調べました。

ここら辺の記事を参照して頂くと衝突面の角度辺りの事に触れています。

キャラクターの移動速度を進行方向と上向きにベクトルを分解すると動きがおかしくなるので、ちょっとズルいやり方をしています。(^_^;)

上のように進行方向のベクトルと上方向のベクトルに分解した場合、90度の階段を上る場合に上向きのベクトルしか得られないので上方向は坂の角度からY軸の値だけを使って計算し、キャラクターの前方にはそのまま移動速度を足しています。

Cosで斜辺方向のベクトルの大きさから底辺方向のベクトルを求め、Sinで斜辺方向のベクトルの大きさから対辺(上向き)のベクトルを求めています。

どちらにせよ元のスクリプトの方が動きが滑らかなのでこちらは無視していいです。(^_^;)

坂を上る移動速度を単純な方法に変える

先ほどの場合坂の角度からキャラクターの進む方向を求めましたが、

上のように坂の角度方向のY軸にキャラクターの移動スピードから上方向の移動値を計算し、それにキャラクターの前方方向のベクトルも加えています。

キャラクターの移動スピードが上がれば上がるほど上方向のベクトルの長さが長くなっていきます。

単純に坂を昇らせるだけならば

上のように上向きの適当な力とキャラクターの前方に移動スピードをかけるだけでも出来ます。

これでRigidbodyを使ったキャラクターの移動とジャンプの処理、坂や段差を登る事が出来ました。

終わりに

最初に記事を公開した時よりも内容が簡潔になり、キャラクターの動きもよくなりました。(^^)/

このキャラクターで移動して他のゲームオブジェクトに当たれば、力を加える事が出来ます。

ただ、RigidbodyのMovePositionを使って移動させていて、Is Kinematicはfalseとなっている為、Rigidbodyの補間の機能が働かず、厳密にはキャラクターの移動は次の位置へワープして移動しているという感じになります。

補間の機能を使うにはAddForceやAddTorqueを使ったキャラクターの移動機能を作るのがいいかもしれません。

タイトルとURLをコピーしました