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

記事内に広告が含まれています。

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

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

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

や、

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

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

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

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

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

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

物理的な力を加えて移動させるキャラクターは以下を参照してください。

UnityのRigidbodyのAddForceを使ったキャラクターの移動スクリプト
UnityのRigidbodyのAddForceメソッドを使ったキャラクター移動スクリプトを作成していきます。
スポンサーリンク

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、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を使ったキャラクターの移動スクリプトを作成します。

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

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

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を使った移動に切り替えたスクリプト

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

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

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

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で動かしていた時と違いが出てないか確認してみましょう。

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をコピーしました