【Unity】Cinemachine と Input System で作るお手軽 TPS カメラ

本記事は Advent Calendar 2023 「Unity #2」 6 日目の記事です。

【5 日】[unsafeでアンマネージドな構造体でポインタアクセスとポリモーフィズムのテスト](https://kenken5050.hatenablog.com/entry/2023/12/05/002046)（@fukaken5050 さん）
【6 日】本記事
【7 日】AI だけでシューティングゲームを作る（@k-taro56 さん）

---
お久しぶりです。Qiita。  
イキテマス・シレット。  
![ikitemasu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/07935264-31cf-d3c5-186a-c3e191f7607c.png)  

# はじめに

今回は **[Cinemachine](https://docs.unity3d.com/Manual/com.unity.cinemachine.html) × [Input System](https://docs.unity3d.com/Manual/com.unity.inputsystem.html)** というモダンな組み合わせを利用しつつ、**できるだけシンプルに TPS（三人称視点）カメラを構築する方法**の紹介です。  

完成系のイメージ  
![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/3dd77339-b315-4fd9-cb57-142bd3b15c67.gif)  


Cinemachine と Input System はそれぞれ素晴らしいライブラリですが、理解するのが難しめだと思います。
なのでそれらを合わせて使おうとなると 1 + 1 = 200 となり **10 倍のパワー**で襲ってくることになり頭を悩ませることに…。  

私自身十分理解しているとは言えず、それゆえ「細かいことはいい！とりあえずモダンにお手軽に作りたいんだよ！」という人向けの記事になります。（いいんか…？）  

# 今回使ったバージョン

- Unity: 2022.3.7f1  
- Cinemachine: 2.9.7  
- Input System: 1.6.3  

また、サンプルプロジェクトを GitHub 上にアップしていますので、必要に応じて参考にしてください。  

https://github.com/neuneu9/unity-tps-sample  


# 準備

## パッケージのインストール

Package Manager で **Cinemachine および Input System のパッケージをインストール**していることを前提とします。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/3b835c39-0371-871e-b8fc-1d98028b1234.png)  

インストール方法は他の Unity 公式パッケージと同じように Unity Registry の一覧から探して追加すれば OK です。  
詳しくは公式のドキュメントを参照してください。  

https://docs.unity3d.com/Packages/com.unity.cinemachine@2.9/manual/index.html#installing-cinemachine  

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Installation.html  

## シーンとキャラクターの用意

**適当なシーンに適当なキャラクターを配置**しておいてください。  
今回キャラ制御については本題ではないので、最悪動かない Cube とかでも大丈夫です。  
超適当なキャラクターコントローラーでよければ動作テスト用にこちらのスクリプトを使って頂いても大丈夫です。  

https://github.com/neuneu9/unity-tps-sample/blob/main/Assets/Scripts/TekitoMove.cs

# 作り方

## カメラの作成

Cinemachine でカメラを作ります。  
さあ、Cinemachine Free Look Camera を作成しま…**せん**。  
もちろんそれも構わないのですが、今回は可能な限りシンプルにいきたいのでより設定がしやすい **Cinemachine Virtural Camera** でやることを考えます。  

Hierarchy ウィンドウの + ボタンなどから `Cinemachine -> Virtual Camera` を選択します。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/b7e9ad65-bb8b-6a45-6666-133fa16539ce.png)

作成したらインスペクターから以下の通り設定していきましょう。  

### Body の設定

対象に対して一定距離を保ち続ける `Framing Transposer` にし、以下のように設定します。  

| 項目 | 設定値 | 補足 |
| --- | --- | --- |
| Tracked Object Offset | 0, 0, 0 | ここは変えないことを推奨します。Cinemachine Collider とかを合わせて使ったときに正しくぶつかってくれなくなるため。 |
| X Damping | 0 | 上下左右にカメラの遅れがあるとキャラ移動方向にずれが生じるため消します。 |
| Y Damping | 0 | 同上 |
| Z Damping | 0.2 | カメラ前後方向の遅れですが、キャラの移動速度次第でめり込んだりしてしまうため下げて遅れにくくします。好みの値をどうぞ。 |
| Camera Distance | 3 | 好みの距離をどうぞ。 |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/680c0454-fd0a-ed41-8522-31d25bb02967.png)  

### Aim の設定

入力に応じて視線をコントロールできる `POV` にし、以下のように設定します。  

| 項目 | 設定値 | 補足 |
| --- | --- | --- |
| Vertical Axis > Speed | 1 as Input Value Gain | Input System でデバイスごとの調整を行うため、ここでは基準となる値を入力します。 |
| Vertical Axis > Input Axis Value > Invert | OFF | 同上 |
| Horizontal Axis > Speed | 1 as Input Value Gain | 同上 |
| Horizontal Axis > Input Axis Value > Invert | OFF | 同上 |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/aaa781e2-b5c0-018a-db8c-089f7e9b05da.png)

## 入力の作成

Input System で入力を作ります。  
入力を高度に抽象化してくれるのが Input System のいいところなので様々なデバイスに簡単に（本当に簡単かは諸説あり）対応できます。今回はマウスを想定して作成します。  

1. Project ウィンドウで右クリックし Create -> Input Actions を選択して inputactions アセットを作成します。  
  または、既存のものがあるならそれを開きます。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/1f95d0e0-6aad-56fd-af9d-6516d1d82f6a.png)  
1. 作成した inputactions をダブルクリックして開き、Action Maps の + ボタンを押して操作用のマップを作ります。仮に Player としました。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/e19cfe3f-e29a-ce17-5f3b-1d9debd49430.png)    
1. Actions からカメラ操作アクションを追加・編集します。仮に Look としました。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/42a07a8a-b544-ee1a-ee44-ddb1ec862b13.png)
1. Action Properties の Action Type と Control Type をそれぞれ Value と Vector2 にします。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/6fe4dbda-3177-02ce-acc1-e52756cc2a72.png)  
1. Look の下に Binding を追加・編集します。今回はマウス操作想定で `Pointer -> Delta` を選択しました。    
  そうしたら Processers に Invert Vector 2 と Scale Vector 2 を追加して以下のように値を設定してください。  
    | 項目 | 設定値 | 補足 |
    | --- | --- | --- |
    | Invert Vector 2 -> Invert X | OFF | 入力を**カメラ視線**移動の上下左右と合わせたい場合。**カメラ位置**移動の上下左右と合わせる方が好みなら逆で。 |
    | Invert Vector 2 -> Invert Y | ON | 同上 |
    | Scale Vector 2 -> X | 0.3 | 好みの感度をどうぞ。 |
    | Scale Vector 2 -> Y | 0.2 | 同上 |
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/79954af8-d507-dd1f-4d1c-b5c7d4126ec9.png)

1. Save Asset ボタンを押して保存します。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/0bd2f835-3ed7-a56f-c711-85fbdfe76730.png)  

## カメラと入力の接続

Cinemachine Virtural Camera のゲームオブジェクトで Add Component ボタンを押して、`Cinemachine Input Provider` を選択します。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/80a4203f-d72c-8e6b-d20d-aa1718f75fb2.png)

`XY Axis` に前述した inputactions の Look をドラッグアンドドロップします。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/8f7ea066-8d4a-a308-249f-ee1aba36a3cf.png)  

## カメラをキャラクターにセット

1. カメラがターゲットにする Transform を用意します。  
  Hierarchy ウィンドウでプレイヤーオブジェクトを右クリックし、Create Empty を選択します。名前を仮に FocusTarget としました。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/87d21cf1-d315-64c0-a9f4-388e37a2687c.png)  
1. 好みの相対位置に調整します。今回は Position > Y を 1 にしました。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/03479ea6-7682-70f9-afce-fc1b39cfd0c3.png)  
1. Cinemachine Virtural Camera の `Follow` と `Look At` に先ほど作ったターゲットをドラッグアンドドロップします。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/b9bd9f6e-79ff-c49a-3192-159ae7db530b.png)  

# 完成！

Unity のプレイボタンを押してマウスを動かしてみましょう。  
冒頭の gif のようにキャラ回りを移動できると思います。  
ということで**ノーコードで TPS カメラ**ができてしまいましたね！🙌  

それでは、よい TPS ライフを！！  

# Z 方向は？

~~勘のいいガキは嫌いです。~~ 聡明な皆様はお気づきでしょう…上で掲載した `Cinemachine Input Provider` には `Z Axis` もあります。つまり、ドリーやズームで**キャラとの距離感を調整する方向軸**ですね。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/c05d06cc-926e-57b1-77cc-af257af166fe.png)  

これはいかにもそのまま動作してくれそうなのですが、調べたり試した感じではそのまま使えませんでした。　　
なのでここで**少しだけスクリプトを書く**ことになります。🖊️😢  

```CinemachineDollyInputProvider.cs
using Cinemachine;
using UnityEngine;

/// <summary>
/// CinemachineFramingTransposer のカメラ前後方向のコントロールを付与する
/// </summary>
public class CinemachineDollyInputProvider : MonoBehaviour
{
    [SerializeField]
    private CinemachineVirtualCamera _cinemachineVirtualCamera = null;
    
    [SerializeField]
    private CinemachineInputProvider _cinemachineInputProvider = null;
    
    [SerializeField]
    private float _sensitivity = 1f;
    
    [SerializeField]
    private float _minDistance = 1f;
    
    [SerializeField]
    private float _maxDistance = 5f;


    public float Sensitivity
    {
        get => _sensitivity;
        set => _sensitivity = value;
    }

    private void OnEnable()
    {
        _cinemachineInputProvider.ZAxis.action.Enable();
    }

    private void OnDisable()
    {
        _cinemachineInputProvider.ZAxis.action.Disable();
    }

    private void Awake()
    {
        var cinemachineComponent = _cinemachineVirtualCamera.GetCinemachineComponent(CinemachineCore.Stage.Body);
        if (cinemachineComponent is CinemachineFramingTransposer cinemachineFramingTransposer)
        {
            _cinemachineInputProvider.ZAxis.action.performed += context =>
            {
                float distance = Mathf.Clamp(cinemachineFramingTransposer.m_CameraDistance + context.ReadValue<float>() * _sensitivity, _minDistance, _maxDistance);
                cinemachineFramingTransposer.m_CameraDistance = distance;
            };
        }
        else
        {
            Debug.LogWarning($"{nameof(CinemachineFramingTransposer)} が存在しません。");
        }
    }
}
```

見ていただいている通り、このスクリプトがやっていることは単純です。  **`Cinemachine Input Provider` に割り当てられた Z 入力アクションを `Framing Transposer` の `Camera Distance` に流し込む**処理を記述しています。  

このスクリプトを Cinemachine Virtural Camera のゲームオブジェクトに追加し、`Cinemachine Virtual Camera` と `Cinemachine Input Provider` をドラッグアンドドロップで割り当てます。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/1ea44561-acb6-6588-c961-1cfafcc9b7be.png)

上で作った inputactions アセットを再度開き、  
1. Actions からカメラドリー操作アクションを追加します。仮に LookDolly としました。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/1d292ae1-9422-59b3-0794-4dc03ccf51b3.png)  
1. Action Properties の Action Type と Control Type をそれぞれ Value と Axis にします。  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/d7087f96-bca8-3632-976c-12868c1eea89.png)  
1. LookDolly の下の Binding を編集します。マウス操作想定ですので `Mouse -> Scroll/Y` を選択しました。    
  そうしたら Processers に Invert と Scale を追加して以下のように値を設定してください。  
    | 項目 | 設定値 | 補足 |
    | --- | --- | --- |
    | Scale -> Factor | 0.003 | 好みの感度をどうぞ。 |
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/2048b7b6-3825-ed5a-d26a-ad875fb9ed38.png)  

1. Save Asset ボタンを押して保存します。  

この inputactions の LookDolly を `Cinemachine Input Provider` の `Z Axis` にドラッグアンドドロップします。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/003d3dfc-b86b-7db4-556d-2d1cc9f829b2.png)  

# 本当に完成！

Unity のプレイボタンを押してマウスを動かしてみましょう。  
この gif のようにキャラ回りを移動したり近づいたり離れたりできると思います。  
![demo_dolly.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/226cf46a-4d34-89d7-bde0-6319f99d5fd0.gif)

# おわりに

思ったよりボリュームが大きくなってしまいました…。ここまで読んでいただいてありがとうございます。  
素直なフローを踏んでいると思うので、要件によっては十分ベストプラクティスの一つになりえるのではないでしょうか…！  

改めて、よい TPS ライフを！！