Unity: Cinemachine を使って、敵をロックオンする機能を付ける

Unity入門 - Project Bonfire

FreeLook カメラだとスクリプトでカメラの位置を調整しても、右スティックでカメラを移動できてしまいます。そこで、もう1つ Virtual Camera を追加してロックオン機能を実現しました。

カメラ側の実装

ロックオンしていない場合は、これまでどおりのFreeLook カメラで表示します。ロックオンのボタンが押されたら、Virtual カメラのPriority を変更することでカメラを切り替えて、キャラの背面からの固定表示にします。ロックオン中は、FreeLookカメラの位置をメインカメラに同期させておきます。こうすることで、ロックオン解除時に、背面のVirtualカメラからFreeLook カメラに切り替わった際に表示が大きく動かないようになります。

敵キャラにロックオンの場所を設定

まず、敵キャラをロックオンした時に注視する場所を作成します。paladin のプレハブを開いて、空の Game Object を作成し、LockOnTarget という名称にします。

LockOnTarget の Y 座標を1.6くらいに設定します。

このくらいの位置になります。

Virtual Camera の追加

続いて、Virtual カメラを追加します。

メニューバーの Cinemachine から Create Virtual Camera を選択して、Virtual カメラを作成します。

CM vcam1 が作成されます。

Inspector で以下のように設定していきます。

  • Priority を 10未満の数値にします。FreeLook カメラが 10になっているので、通常状態ではFreeLookカメラより優先度を下げておきます。
  • Follow に knight の LookTarget を設定します。
  • Look At に paladin の LockOnTarget を設定します。
  • Body, Binding Mode はそれぞれ、「Transposer」「Lock To Target With World Up 」に設定します。
  • Follow Offset でカメラの位置を設定します。今回は高さを1m、距離は背面6m の位置にしました。
  • Damping を下の画像のように設定しました。カメラの追従速度です。あまり短い時間だと動きがカクつくのでちょうど良いところを探ってください。
  • Aim は Hard Look At にします

FreeLook カメラと同様に Cinemachine Collider を追加します。

Ignore Tag と Minimum Distance From Target を設定しました。Smooth Time と Damping などはお好みで設定してください。

カメラの切り替え速度の設定

このままでは、FreeLook カメラと Virtual カメラの切り替えが遅いので、Main Camera についている Cienmachine Brain の以下の値を設定します。

FreeLook カメラの設定

カメラの位置をスクリプトから指定するために Inherit Position が必要というのをどこかで見たと思うのですが、設定しなくても動きました。

設定しなくても動くようですが、備忘録なので一応残しておきます。

スクリプトの入れ替え

PlayerStatus.cs, PlayerAnimation.cs, PlayerCtrl.cs, PlayerMove.cs を差し替えます。

ファイルは記事末尾に添付してあります。

HUD のサイズ調整

PlayerCtrl.cs のデバッグ表示で、ロックオン中かどうかのを1行増やしたので、右上の表示領域のサイズを調整します。

以下のように合計5行表示できるようにしておきます。

ロックオン位置の設定

ロックオンのする敵の LockOnTarget を PlayerMove.cs に設定します。

本来は、これはロックオンボタンが押されたときに画面内を探索し、見つけた敵をスクリプト中で設定します。今回は手抜きをして、事前にロックするターゲットを設定しておきます。

ここまで設定したらロックオンできるようになっていると思います。

ただし、実際に動かしてみるとキャラクターが思い通りに動かないはずです。後ほど、アニメーションを設定することでキャラクターを動けるようにします。

スクリプトの説明

PlayerStatus.cs

ロックオン状態を保持するために isLockOn を追加しました。

        // 状態
        public bool isGrounded = false;
        public bool isAnimationEnd = true;
        public bool isLockOn = false;

        public bool isAttackR1 = false;
        public bool isAttackR1_2 = false;
        public bool isRolling = false;

PlayerAnimation.cs

status.isLockOn を アニメーターのパラメータ IsLockOnに反映する文を追加しました。

        void Update()
        {
            animator.SetBool("IsGrounded", status.isGrounded);
            animator.SetBool("IsLockOn", status.isLockOn);
            animator.SetBool("IsAttackR1", status.isAttackR1);
            animator.SetBool("IsAttackR1_2", status.isAttackR1_2);
            animator.SetBool("IsRolling", status.isRolling);
            animator.SetBool("IsJumping", status.isJumping);
            animator.SetBool("EquipWeapon", status.EquipWepon);
            animator.SetBool("UnequipWeapon", status.UnequipWeapon);
        }

PlayerCtrl.cs

ボタン操作の定義の部分に、以下のような R3 ボタンの定義を加えています。ボタンを押すたびに、ロックオン状態のオンオフを切り替え、カメラ切り替えの関数を呼び出します。

            if (Input.GetButtonDown("Fire_R3"))
            {
                status.isLockOn = !status.isLockOn;
                playerMove.ChangeLockOnCamera();
            }

それと、HUD の右上のエリアで ロックオン状態かどうかを確認できるようにしました。

            if (debugText != null)
            {
                debugText.text =
                   string.Format("state: {0}\nnexState: {1}\nisStateEnd: {2}\nisGrounded: {3}\nisLockOn: {4}",
                                state,
                                nextState,
                                isStateEnd,
                                status.isGrounded,
                                status.isLockOn
                               );
            }

PlayerMove.cs

ロックオンしているターゲットを保持する lockOnTarget、それと Cinemachine のカメラを保持する2つの変数 freeLookCamera、virtualCamera を追加しました。

    public class PlayerMove : MonoBehaviour
    {
        [SerializeField] private Text debugText = null;
        [SerializeField] private Transform lookTarget = null;
        [SerializeField] Transform lockOnTarget = null;
<中略>
        Camera mainCamera = null;
        Animator animator = null;
        CharacterController characterController = null;
        PlayerCtrl playerCtrl = null;
        PlayerStatus status = null;
        CinemachineFreeLook freeLookCamera = null;
        CinemachineVirtualCamera virtualCamera = null;

Start 時に カメラをキャッシュします。CinemachineVirtualCamera は複数あるので、名前を確認してから変数に設定しています。

        void Start()
        {
<中略>
            freeLookCamera = FindObjectOfType<CinemachineFreeLook>();
            foreach (var vcam in FindObjectsOfType<CinemachineVirtualCamera>())
            {
                if (vcam.name == "CM vcam1")
                {
                    virtualCamera = vcam;
                }
            }
        }

Update() 内です。

ロックオン状態では、FreeLook カメラの位置を メインカメラの位置と同期させておきます。(これは無くても正しく動くかも)

ロックオン状態でターゲットがnull でない場合、キャラクターの向きを強制的に敵の方に向けます。正しい書き方ではないような気がしますが、Quaternion の x と z を0にしています。

ローリングの動きが気に入らない場合、if (playerCtrl.canRotate) を外すことも検討してみてください。これを外すと敵を回るようにローリングします。敵に近づきすぎると敵の後ろに回り込むようになってしまいます。

また、ロックオン状態なのにターゲットが null になっている場合、ロックオン状態を解除します。(このケースはあり得ないはずですが念のため書いておきました。)

            if (status.isLockOn)
            {
                freeLookCamera.transform.position = mainCamera.transform.position;

                if (lockOnTarget != null)
                {
                    if (playerCtrl.canRotate)
                    {
                        Quaternion characterTargetRotation = Quaternion.LookRotation(lockOnTarget.position - transform.position);
                        // 強引だがこれで Y 軸の回転だけにする
                        characterTargetRotation.x = 0;
                        characterTargetRotation.z = 0;
                        transform.rotation = Quaternion.RotateTowards(transform.rotation, characterTargetRotation, rotationSpeed * Time.deltaTime);
                    }
                }
                else
                {
                    status.isLockOn = false;
                }
            }
            else
            {

同じくUpdate () 内。

LockOn 状態でない場合はキャラクターの向き前方に進んでいたものを、ロックオンしている場合はスティックの入力方向に進むようにします。(若干コードが間違っているような気がする…。)

少々コードが汚くなりますが、Animator のパラメータの設定をここでやっています。

            if (status.isLockOn)
            {
                Vector3 stickDirection = playerCtrl.GetPlayerDirection();
                float h = stickDirection.x;
                float v = stickDirection.z;
                velocity = transform.right * h + transform.forward * v;
                animator.SetFloat("MoveRight", h * moveSpeed);
                animator.SetFloat("MoveForward", v * moveSpeed);
                velocity = velocity * moveSpeed;
            }
            else
            {
                velocity = transform.forward.normalized * moveSpeed;
            }

カメラの切り替えを行うメソッドです。ロックオン時に Virtual カメラ のプライオリティを上げることで、カメラを FreeLook カメラから Virtual カメラに切り替えます。

ロックオン中の FreeLook カメラは BindingMode を LockToTarget にすることでプレイヤーを注視させておきます。

ロックオン解除の場合、Virtual カメラ のプライオリティを下げると同時に BindingMode を戻します。

        public void ChangeLockOnCamera()
        {
            if (status.isLockOn)
            {
                freeLookCamera.m_BindingMode = CinemachineTransposer.BindingMode.LockToTarget;
                virtualCamera.Priority = 12;
            }
            else
            {
                freeLookCamera.m_BindingMode = CinemachineTransposer.BindingMode.SimpleFollowWithWorldUp;
                virtualCamera.Priority = 8;
            }
        }

アニメーションの追加

設定する項目が非常に多いです。頑張って入力します。

パラメータの追加

ロックオンのアニメーションのために次の3つのパラメータを追加します。

  • IsLockOn : Bool 型
  • MoveForward: Float型
  • MoveRight: Float 型

サブステートマシンの追加

ロックオン状態を1つ画面に書き足す複雑になりすぎるので、サブステートマシンを作りました。

右クリックで Create Sub State Machine を実行し、LockOn という名称にしました。

Locomotion ブレンドツリー

Lock On サブステートマシンに入った状態で、新しいブレンドツリーを作成します。名称を LockOn Locomotion にします。

Blend Type を 2D Freeform Directional にします。2D Simple Directional はそれぞれの方向に1つずつ点を置くのに対して、2D Freeform Directional は同じ方向に複数の点があっても良いとのことです。今回のように前方に歩くの先に前方に走るがあるような場合に使えそうです。実際には、前方以外の走るアニメーションがなかったので、前方以外は早さを変えてあるだけです。

面倒ですが、以下の設定を行います。

画面中央は、このようになっています。

ローリングのブレンドツリー

LockOn ステートマシン内に、もう1つブレンドツリーを作成します。名称は LockOn Rolling としておきます。

BlendType は 2D Simple Directional にしました。こちらも以下のパラメータを全て入力します。

状態遷移

LockOn Locomotion から LockOn Rolling への状態遷移を作ります。Has Exit Time のチェックを外し、タイミングを下の図のように調整し、Condition を設定します。

戻りは Has Exit Time でもどります。

こんどは、LockOn Locomotion から BaseLayer の Locomotion Blend Tree への状態遷移を作成します。

LockOn Locomotion を右クリックして Make Transition を選択し、[Up] Base Layer に状態遷移の線を引くと以下の図のよう選択肢が出るので、Locomotion Blend Tree を選択します。

状態遷移の設定は以下の画像の通りです。Has Exit Time なし。タイミングは画像のとおり。Conditions は IsLockOn を false にします。

Locomotion Blend Tree から LockOn Locomotion への状態遷移も作成します。

状態遷移の条件は、以下の画像の通りです。

全部の状態を作成

ちょっと面倒ですが、Base Layer にあった Rolling 以外のすべての状態と状態遷移を、Lock On サブステートマシンに作成します。該当するのは、Equip-Weapon, UnEquip-Weapon, 2Hand-Sword-Attack1, 2Hand-Sword-Attack4 です。設定は全て Base Layer に設定したものと同じなので、説明は省略します。

Animation Override Controller への追加

Animation Override Controller に新規で追加したアニメーションの分の unarmed 版を登録します。Roll と Strafe の部分になります。

ローリングアニメーションの加工

今回追加したローリングのアニメーション全てに対して、Roll-Forward に行った加工と同じ加工を行います。

  • Start と End の調整
    • 2Hand-Sword-Roll はEnd を 17に設定
    • Unarmed-Roll は End を 18に設定
  • Events に イベントを追加し、CallAnimationEnd を呼ぶようにする
    • 位置は、0:83付近
  • 忘れずに Apply を押す。

動作確認

ここまで設定ができれば、完成しているはずです。

ファイル

おわりに

Cinemachine のカメラではロックオン機能は作れないかと思ったのですが、妥協できる範囲のものは作れたと思います。

時間があるときに、ロックする敵を探す部分も記事にしておきたいと思います。