CodingReptile

ゆるゆると技術に関するメモを貯めていく

【Unity】SampleAsset の ThirdPersonCharacter のスクリプトを読む(キャラ移動の準備編)

前回 は変数の宣言部分を読んだので、続いて ThirdPersonCharacter.cs の中身をメソッド毎に読んでいきます。

void Start()

初期化がされています。全ての変数はあらかじめ宣言されており特別なことは特にしていないです。

唯一気になったのは 41 行目。

m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;

OR 演算子を使う場合、「左側を評価して false なら右側を評価する」
という使い方がされることがありますが、この場合は素直に bit 演算です。

論理 OR 演算子の結果が m_Rigidbody.constraints にセットされているようです。
試しに出力してみると以下の結果になりました。

Debug.Log(RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionY) => 6
Debug.Log(RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ) => 10
Debug.Log(RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionZ) => 12

先頭 1 bit は全てなし(default)で、2 ~ 4bit 目が Position X, Y, Z のフラグになっています。
Rotation はもっと後ろの bit で管理されているようですね。
このようなフラグ管理は他の unity object でも同じなのではないでしょうか。

public void Move(Vector3 move, bool crouch, bool jump)

入力値を元にキャラクターを動かすメソッド。
移動方向、しゃがみ状態、ジャンプ状態を引数として受け取っています。

ThirdPersonCharacter ではキー入力した際に、入力した方向が(キャラクタのローカル座標系ではなく)カメラから見てどちらか、という基準でキャラクタを動かします。また、すぐに入力方向に移動を開始するのではなく、入力方向に対してキャラクタが正面を向くまでキャラクタを回転させます。つまり、グローバル座標における方向ベクトルがキャラクタのローカル座標における正面方向と一致させる必要があります。

53 行目の InverseTransformDirection はそのためのベクトルの変換をしています。inverse って逆数かなにかを示しているのかと勘ぐってしまいますが、ローカル座標からグローバル座標への変換の逆、という意味ですね。

続く 55 行目では、ProjectOnPlane を使用して、変換した move ベクトルを地面に投影しています。m_GroundNormal は CheckGroundStatus の中で更新されており、地面に対する法線ベクトルが代入されています。

平面を移動する場合と坂を移動する場合で同じ速度になるようにしているということですね。

56 行目の Mathf.Atan2 では何度方向転換する必要があるかを算出しています。move.z が現在の正面方向になるので、入力した方向との角度の差分は tan(move.x / move.z) で出すことができます。角度はラジアンで m_TurnAmount に格納されます。後は ApplyExtraTurnRotation() でキャラクタを回転させます。

void CheckGroundStatus()

地面に接しているか否かを判定して、それぞれの場合のパラメータを設定しています。
Physics.Raycast の第一引数は ray を投射する位置となり、現在位置から上方向に 0.1f 分のオフセットを含めて設定しています。(キャラクタのオブジェクトは足の裏が基準位置となっているため)
第二引数は ray の投射方向なので重力方向です。
第四引数は ray の投射距離の制限です。

(最初、すぐには 0.1f の意味を解釈できなかったので、この 0.1f は m_CheckGroundOffset みたいな変数で設定してあったら良かったのになぁと思いました。)

if 文の中では、接地フラグ・地面との法線ベクトル・ルートモーションの有効/無効化 をしています。

void ApplyExtraTurnRotation()

キャラクタを(ユーザが入力した方向に向くように)回転させます。
秒間何度回転させるかは Mathf.lerp で算出します。default の設定だと m_ForwardAmount(0 ~ 1)の値に応じて 180 ~ 360 度の間で決定されます。Time.deltaTime は前フレームからの経過時間を返すので、unity 上での処理に時間がかかった場合でも、実時間ベースでは正しい移動量となるように処理がされます。

void HandleGroundedMovement(crouch, jump)

ジャンプ中、かつ、しゃがんでいないときの状態を処理します。
m_Animator.GetCurrentAnimatorStateInfo(0) の引数はレイヤーインデックスとなっています。Animator ウィンドウで確認すると、今回は BaseLayer しかないため、0 しかありません。
if 文に入る状態を満たしていたら、m_Rigidbody.velocity に m_JumpPower を加味したベクトルをセットします。

void HandleAirborneMovement()

空中での動きを制御します。
162 行目では三項演算子を使って m_GroundCheckDistance の値をセットしています。CheckGroundStatus() では足裏から上方向に 0.1f のオフセットした上で下方向に 0.1f の ray を投射していました。落下中は m_GroundCheckDistance が 0.01f になるので ray が地面に届かなくなります。
0.1f に戻す判定は CheckGroundStatus() ではなく、このメソッド内での処理で行うということのようです。(直感的には CheckGroundStatus() でやりそうですが)




というわけで、今回はキャラの移動に必要な判定や値のセット部分を読みました。 疲れたのでアニメーションの適用部分としゃがみモーションに関する部分は次回に。。

次回読むところ

  • void UpdateAnimator(Vector3 move)
  • public void OnAnimatorMove()
  • void PreventStandingInLowHeadroom()
  • void ScaleCapsuleForCrouching(bool crouch)