Unity 6|図解で学ぶCinemachine 3のマウスホイールズーム
ホイールを回して対象を大きく見せる方法には、カメラの画角を狭める方法と、カメラを対象に近づける方法があります。この教材では、Cinemachine Cameraを1台使い、方式を切り替えて違いを観察します。
扱うのはPerspective(透視投影)のカメラです。Orthographicの拡大縮小は別の設定を使います。掲載コードは公式APIと照合していますが、Unity Editorでの実行検証は行っていません。
1. FOVとFollow Offsetは何が違う?
FOV(Field Of View)はカメラが写す角度です。値を小さくすると写る範囲が狭まり、同じ場所から対象を大きく写せます。Follow Offsetは追従対象からの位置のずれです。こちらを変えると視点が移動します。

| 比較点 | FOV方式 | Follow Offset方式 |
|---|---|---|
| 変更するもの | LensのField Of View | FollowのFollow Offset.z |
| 視点 | 固定 | 移動する |
| 同じ大きさの手前・奥の物体 | 両方が同じ倍率で拡大する | 近い物体ほど拡大率が大きい |
| この教材の制限 | 20〜60度 | Zが-30〜-10 |
「FOVでは遠近関係が変わらない」とは、視点・向き・物体の位置が同じなら、手前と奥の見かけの大きさの比が変わらないという意味です。画面に入る範囲は変わります。視点移動では、その比や重なり方も変わり得ます。Unityのカメラと視野の説明
2. パッケージと入力の準備
Package ManagerでCinemachineとInput Systemを導入します。使用版を確認してください。本稿はCinemachine 3.1系、Input System 1.11系の公式資料を参照しています。
Edit > Project Settings > PlayerのActive Input HandlingをInput System Package (New)またはBothにします。再起動の案内が出たら従います。Input System PackageのUpdate ModeはProcess Events In Dynamic Updateにします。本稿はUpdateで値を読む構成です。
3. カメラと対象を配置する
まずCubeを作成し、名前をTarget、Positionを(0, 0, 0)にします。次にCinemachine Cameraを作成します。作成メニューの位置は版により異なります。Main CameraにはCinemachine Brainが必要です。
Main CameraのCameraはPerspectiveにし、Physical Cameraをオフ、FOV AxisをVerticalにします。練習中は有効なCinemachine Cameraを1台とし、Timelineなどによる制御を重ねないようにします。

Cinemachine CameraのInspector
| 項目 | 設定 |
|---|---|
| Tracking Target | HierarchyのTargetをドラッグ |
| Lens / Field Of View | 40(Vertical) |
| Lens / Mode Override | None |
| Position Control | Follow |
| Rotation Control | Hard Look At |
| Follow / Binding Mode | World Space |
| Follow / Follow Offset | X=0、Y=0、Z=-20 |
| Follow / Position Damping | X=0、Y=0、Z=0 |
Position ControlでFollowを選び、Cinemachine Followが追加されたことを確認します。Tracking Targetを指定する操作と、Followを選ぶ操作は別です。Look At Targetを別指定するオプションは使わず、Targetを注視する構成にします。
World Spaceではオフセットをワールド座標で解釈します。対象が回転しても、このオフセットの方向は回転しません。Dampingは追従の遅れを生む設定です。最初は0にすると変化を観察しやすくなります。Cinemachine Camera・Follow
Input Actionsエディター
ProjectでCameraZoomControlsというInput Actionsアセットを作成して開き、次の設定を保存します。
| 場所 | 入力する値 |
|---|---|
| Action Map | Camera |
| Action | Zoom |
| Action Type | Value |
| Control Type | Vector2 |
| Binding Path | <Mouse>/scroll |
| Interactions / Processors | 追加なし |
縦スクロールはVector2のyです。<Mouse>/scroll/yにするとfloatの入力になるため、今回のVector2を読むコードとは一致しません。Mouse.scroll API
4. Input Systemは誰が管理する?
本稿では、CameraZoomControlsをズーム専用にします。Project-wide Actionsに登録せず、PlayerInputにも設定せず、以下のスクリプトだけがEnable/Disableを担当します。
| 管理方式 | 有効・無効を管理する場所 | 注意点 |
|---|---|---|
| 本稿の専用Action | MouseWheelZoom | 他の利用者とActionを共有しない |
| Project-wide Actions | プロジェクト側の入力管理 | 既定で有効になる。所有者を決めてから無効化する |
| PlayerInput | PlayerInputとゲームの入力設計 | PlayerInputが利用するActionを取得する |
InputActionReferenceはActionへの参照であり、設定するだけであらゆる構成で入力が有効になるわけではありません。またEnable/Disableは、このスクリプトだけの読み取り許可を切り替える操作ではありません。同じActionを共有していれば、Disableが別の利用箇所にも影響します。
PlayerInputを導入済みのプロジェクトへ本稿のコードをそのまま重ねないでください。PlayerInputでは、同じアセットを複数のプレイヤーで使う際にActionのコピーを管理します。元アセットへの参照と、実際にプレイヤーが使うActionを混同せず、必要ならplayerInput.actions.FindAction("Camera/Zoom", true)などでそのインスタンスから取得する構成へ変更します。有効化するAction Mapやデバイスの割り当ても、PlayerInput側の設計に合わせます。Actions・PlayerInput
5. MouseWheelZoomを作る
MouseWheelZoom.csを作成します。次のスクリプトは、専用Actionを使う構成です。
using UnityEngine;
using UnityEngine.InputSystem;
using Unity.Cinemachine;
public class MouseWheelZoom : MonoBehaviour
{
public enum ZoomMode { FieldOfView, FollowOffset }
[SerializeField] private CinemachineCamera cinemachineCamera;
[SerializeField] private InputActionReference zoomActionReference;
[SerializeField] private ZoomMode mode = ZoomMode.FieldOfView;
[SerializeField, Min(0f)] private float fovSensitivity = 0.02f;
[SerializeField, Min(0f)] private float offsetSensitivity = 0.01f;
[SerializeField] private float minFov = 20f;
[SerializeField] private float maxFov = 60f;
[SerializeField] private float minOffsetZ = -30f;
[SerializeField] private float maxOffsetZ = -10f;
private CinemachineFollow follow;
private InputAction zoomAction;
private void OnEnable()
{
if (cinemachineCamera == null || zoomActionReference == null ||
zoomActionReference.action == null)
{
Debug.LogError("CameraとZoom Actionを割り当ててください。", this);
enabled = false;
return;
}
follow = cinemachineCamera.GetComponent<CinemachineFollow>();
if (follow == null || minFov <= 0f || maxFov >= 180f ||
minFov > maxFov || minOffsetZ > maxOffsetZ || maxOffsetZ >= 0f)
{
Debug.LogError("Followと、FOV・負のZの範囲を確認してください。", this);
enabled = false;
return;
}
zoomAction = zoomActionReference.action;
zoomAction.Enable();
}
private void OnDisable()
{
if (zoomAction == null) return;
zoomAction.Disable();
zoomAction = null;
}
private void Update()
{
float scrollY = zoomAction.ReadValue<Vector2>().y;
if (Mathf.Approximately(scrollY, 0f)) return;
if (mode == ZoomMode.FieldOfView)
{
var lens = cinemachineCamera.Lens;
lens.FieldOfView = Mathf.Clamp(
lens.FieldOfView - scrollY * fovSensitivity, minFov, maxFov);
cinemachineCamera.Lens = lens;
}
else
{
Vector3 offset = follow.FollowOffset;
offset.z = Mathf.Clamp(
offset.z + scrollY * offsetSensitivity, minOffsetZ, maxOffsetZ);
follow.FollowOffset = offset;
}
}
}
このコードはFOV方式でもFollowを必須とし、両方式を同じシーンで比較します。設定や参照の変更は再生を止めてから行います。
LensSettingsは構造体なので、コードでは値を取り出して変更し、Cameraへ戻しています。ただしCinemachine 3.1のLensは公開フィールドであり、直接そのFieldOfViewへ代入する書き方も可能です。CinemachineCamera API・LensSettings API
Z座標とClampを図で確かめる

Mathf.Clamp(value, min, max)は、範囲外なら端の値を、範囲内なら入力値を返します。minは小さい数なので、負数の場合は-30、maxは-10です。引数を逆にした結果は公式仕様で未定義です。Mathf.Clamp
正のscrollYではFOVを減らし、Zを増やしています。Zが-20から-18になれば、この配置では距離が20から18へ縮まります。Yを3にすると距離は√(3²+Z²)になり、Zの絶対値だけでは距離を表せません。
スクロール値はそのフレームの移動量として扱い、ここではTime.deltaTimeを掛けません。入力値120なら、FOVの変化量は120×0.02=2.4度です。これは計算例であり、1目盛りが常に120になるという意味ではありません。デバイスや設定に合わせて感度を調整します。
6. ZoomControllerのInspectorを設定する
空のGameObjectをZoomControllerと名付け、MouseWheelZoomを追加します。Cinemachine Camera欄にはHierarchyのカメラをドラッグします。Zoom Action Reference欄ではアセット全体ではなく、CameraZoomControlsのCamera/Zoomを選びます。
ModeをField Of Viewにし、感度はFOV Sensitivity=0.02、Offset Sensitivity=0.01、範囲はFOV=20〜60、Offset Z=-30〜-10にします。再生してGameビューをクリックし、スクロールします。LensのFOVが変わり、Follow Offsetが変わらないことを確認してください。
次に再生を止め、FOV=40、Follow Offset=(0,0,-20)へ戻してからModeをFollow Offsetにして再生します。今度はZだけが変わるはずです。操作方向が想定と逆ならscrollYの符号を確認します。OSなどの設定により、手の操作と入力の正負の対応は異なる場合があります。
| 確認 | 期待する結果 |
|---|---|
| 両方式で限界まで操作 | 指定範囲の端で止まる |
| 逆方向へ操作 | 端から戻れる |
| ZoomControllerを無効化 | 入力によるズームが止まる |
| 再度有効化 | 操作を再開できる |
7. 観察して理解する実験課題
課題1:手前と奥の比を比べる
Targetを注視点として残し、同じ大きさのCubeを(-2,0,0)と(2,0,10)へ追加します。カメラのZ=-20なら、それぞれの奥行きは20と30です。両方が見えることを確認します。
FOV=60から30へ変更した画像と、FOV=60のままZ=-20から-10へ移動した画像を撮り、手前と奥のCubeの高さの比を測ってください。初期状態へ戻してから、各実験を行います。
予想:FOV変更では比は約1.5のまま、移動後は約2になります。これは投影した寸法が奥行きに反比例することからの予測です。Cubeには厚みがあるため測定値は近似になります。2つの操作で手前のCubeの大きさが厳密に一致する必要はありません。
課題2:Clampの境界を予想する
Z=-29で変化量-5、Z=-11で変化量+5を与えたとき、次のZを紙に書きます。答えはそれぞれ-30と-10です。Inspectorでも範囲の端に止まるか確認します。
課題3:高さとダンピングを変える
再生を止めてY=3へ変更します。Z=-10でも距離が10ではなく約10.44になる理由を説明してください。次にPosition DampingのZだけを大きくし、Follow Offsetの設定値と、画面上の移動の遅れを比べます。各実験後は元の設定へ戻します。
課題4:自分のホイールの値を調べる
Update内でscrollYが0でないときだけ一時的にDebug.Logで出力し、1目盛りと素早い操作を比べます。感度を半分にして、同じ入力値なら変化量も半分になるか確認します。確認後はログを外します。
8. うまく動かないとき
| 症状 | 確認する場所 |
|---|---|
| エラーが出て停止 | Camera、Zoom Action、Follow、範囲の設定 |
| スクロールが届かない | Gameビューのフォーカス、入力設定、Binding、Actionの有効状態 |
| 読み取り型が違う | Vector2とscrollの組み合わせか |
| 値だけ変わる | Brain、現在Liveのカメラ、Perspective、別の制御の有無 |
| 一瞬で端に達する | 感度、入力値、Processors |
| 意図しない方向へ動く | Binding Mode、オフセット、他のカメラ制御 |
まず固定対象で確認し、その後に移動する対象へ応用します。このサンプルには壁を避ける処理がありません。実際のステージに導入する際は、遮蔽物への対応も設計してください。










ディスカッション
コメント一覧
まだ、コメントがありません