Unityを触り始めた頃にありがちなのが、プレイヤーの移動もカメラの回転も、UIの更新も、全部ひとつの Update() に詰め込んでしまうパターンですね。最初は動くので満足してしまいますが、時間が経つと「どこを直せばいいのか分からない」「カメラだけ別の挙動にしたいのに、プレイヤーコードにべったり依存している」といった問題が出てきます。
そこで今回は「マウスの移動量からカメラを回転させる」という一点に責務を絞った MouseLook コンポーネント を作ってみましょう。プレイヤーの移動やジャンプとは切り離し、視点制御だけを担当する小さなコンポーネントにすることで、FPS/TPSどちらにも簡単に流用できるようにします。
【Unity】マウスでヌルヌル視点操作!「MouseLook」コンポーネント
フルコード(Unity6 / C#)
using UnityEngine;
using UnityEngine.InputSystem; // 新Input Systemを利用
/// <summary>
/// マウスの移動量をカメラ(または任意のTransform)の回転に変換するコンポーネント。
/// ・X軸の回転(上下)はクランプして「見上げすぎ/見下ろしすぎ」を防止
/// ・Y軸の回転(左右)はプレイヤー本体など別オブジェクトに委譲することも可能
/// ・TPS時は「プレイヤーの体」と「カメラ」で回転先を分けると管理しやすい
/// </summary>
public class MouseLook : MonoBehaviour
{
// --- 設定項目(インスペクターから調整) ---
[Header("回転対象")]
[SerializeField]
private Transform cameraPivot;
// 実際に上下回転させる対象。
// ・FPSならカメラ自身
// ・TPSならカメラ用のPivotオブジェクトなど
[SerializeField]
private Transform yawTarget;
// 左右回転させる対象。
// ・FPSならプレイヤー本体(キャラクターのRoot)
// ・TPSならキャラクターのRootやカメラ用Pivotなど
// null の場合は、このコンポーネントが付いているTransformを使用
[Header("感度設定")]
[SerializeField]
[Tooltip("マウスの左右移動に対する感度(度/ピクセル相当)")]
private float horizontalSensitivity = 2.0f;
[SerializeField]
[Tooltip("マウスの上下移動に対する感度(度/ピクセル相当)")]
private float verticalSensitivity = 2.0f;
[Header("垂直回転の制限")]
[SerializeField]
[Tooltip("見上げられる最大角度(度)。マイナス値にしないこと。")]
private float maxLookUpAngle = 80f;
[SerializeField]
[Tooltip("見下ろせる最大角度(度)。マイナス値にしないこと。")]
private float maxLookDownAngle = 80f;
[Header("挙動オプション")]
[SerializeField]
[Tooltip("ゲーム開始時にカーソルをロック&非表示にするか")]
private bool lockCursorOnStart = true;
[SerializeField]
[Tooltip("マウスのY方向を反転するか(いわゆるリバース操作)")]
private bool invertY = false;
[Header("入力アクション(新InputSystem用)")]
[SerializeField]
[Tooltip("マウスのLook入力(Vector2)を受け取る InputActionReference")]
private InputActionReference lookAction;
// --- 内部状態 ---
// 現在の上下回転角度(X軸回転)
private float currentPitch = 0f;
private void Awake()
{
// cameraPivot が未設定なら、このコンポーネントのTransformを使う
if (cameraPivot == null)
{
cameraPivot = transform;
}
// yawTarget が未設定なら、このコンポーネントのTransformを使う
if (yawTarget == null)
{
yawTarget = transform;
}
// 垂直回転の制限値が負になっていたら安全のため絶対値にしておく
maxLookUpAngle = Mathf.Abs(maxLookUpAngle);
maxLookDownAngle = Mathf.Abs(maxLookDownAngle);
// 初期ピッチを現在のTransformから取得しておく
Vector3 euler = cameraPivot.localEulerAngles;
// Unityの角度は0〜360なので、-180〜180に変換してから保持する
currentPitch = NormalizeAngle(euler.x);
}
private void OnEnable()
{
// InputAction を有効化
if (lookAction != null && lookAction.action != null)
{
lookAction.action.Enable();
}
}
private void OnDisable()
{
// InputAction を無効化
if (lookAction != null && lookAction.action != null)
{
lookAction.action.Disable();
}
}
private void Start()
{
// ゲーム開始時にカーソルをロックするオプション
if (lockCursorOnStart)
{
LockCursor(true);
}
}
private void Update()
{
// 入力が設定されていない場合は何もしない(エラー防止)
if (lookAction == null || lookAction.action == null)
{
return;
}
// 新Input Systemからマウス移動量(Vector2)を取得
Vector2 lookDelta = lookAction.action.ReadValue<Vector2>();
// Time.deltaTime を掛けることでフレームレートに依存しない挙動にする
float mouseX = lookDelta.x * horizontalSensitivity * Time.deltaTime;
float mouseY = lookDelta.y * verticalSensitivity * Time.deltaTime;
if (invertY)
{
mouseY = -mouseY;
}
// --- Yaw(左右回転) ---
// yawTarget を Y軸周りに回転させる
if (yawTarget != null)
{
yawTarget.Rotate(Vector3.up, mouseX, Space.World);
}
// --- Pitch(上下回転) ---
// currentPitch に入力を反映(上にマウスを動かしたら見上げるように符号を調整)
currentPitch -= mouseY; // 一般的なFPSはマウス上=視点上(-mouseY)
// 上下回転をクランプ
currentPitch = Mathf.Clamp(
currentPitch,
-maxLookDownAngle, // 下方向
maxLookUpAngle // 上方向
);
// cameraPivot のローカル回転を更新(Y/Zはそのまま)
if (cameraPivot != null)
{
Vector3 currentEuler = cameraPivot.localEulerAngles;
cameraPivot.localEulerAngles = new Vector3(
currentPitch,
currentEuler.y,
currentEuler.z
);
}
}
/// <summary>
/// カーソルのロック/解除を行うヘルパー関数。
/// </summary>
public void LockCursor(bool shouldLock)
{
Cursor.lockState = shouldLock ? CursorLockMode.Locked : CursorLockMode.None;
Cursor.visible = !shouldLock;
}
/// <summary>
/// Unityのオイラー角(0〜360)を -180〜180 の範囲に正規化するユーティリティ。
/// </summary>
private float NormalizeAngle(float angle)
{
// 0〜360 → -180〜180 に変換
if (angle > 180f)
{
angle -= 360f;
}
return angle;
}
}
使い方の手順
ここでは FPS視点のプレイヤー を例に、MouseLook を導入する手順を解説します。TPSでもほぼ同じ流れで使えます。
-
① シーン構造を用意する
FPSの場合の一例です。- 空のGameObject「Player」を作成(プレイヤー本体)
- 「Player」の子として「CameraPivot」を作成
- 「CameraPivot」の子として「Main Camera」を配置
TPSなら「CameraPivot」を少し背後・上方にオフセットして、キャラクターを見下ろすように配置するとよいですね。
-
② MouseLook コンポーネントを追加する
- 「Player」オブジェクトに
MouseLookコンポーネントを追加 Camera Pivotフィールドに「CameraPivot」をドラッグ&ドロップYaw Targetフィールドには「Player」(自分自身)をドラッグ&ドロップ
これで「左右回転はプレイヤー本体」「上下回転はカメラPivot」という、典型的なFPS構成になります。
- 「Player」オブジェクトに
-
③ Input System の設定を行う
新Input Systemを使う前提のコードになっているので、以下の設定をします。- メニューから
Edit > Project Settings... > Input System Packageを開き、旧Input Managerではなく新Input Systemを有効化しておく - Input Actions アセット(例:
PlayerInputActions.inputactions)を作成 - アクションマップ(例:
Player)を作成し、その中にLookという名前のValue / Vector2アクションを追加 Lookアクションの Binding にMouse > Deltaを割り当てる- 作成したアセットを保存し、インスペクターで
MouseLookのLook ActionフィールドにLookアクションをドラッグ&ドロップ
これで
lookAction.action.ReadValue<Vector2>()からマウスの移動量が取得できるようになります。 - メニューから
-
④ パラメータを調整して動作確認する
実行して、マウスを動かしてみましょう。- 視点の回転速度が速すぎる/遅すぎる場合は
Horizontal SensitivityとVertical Sensitivityを調整 - 上下の可動域を変えたい場合は
Max Look Up Angle / Max Look Down Angleを調整 - 上下操作を反転させたい場合は
Invert Yにチェックを入れる - ゲーム開始時にカーソルをロックしたくない場合は
Lock Cursor On Startのチェックを外す
TPSキャラに使う場合も、基本は同じで「キャラクターRootをYaw Target」「カメラ用PivotをCamera Pivot」に指定すればOKです。
- 視点の回転速度が速すぎる/遅すぎる場合は
メリットと応用
MouseLook を視点制御専用のコンポーネントとして切り出すことで、次のようなメリットがあります。
- プレイヤー移動ロジックと完全に分離できる
移動(WASD、ジャンプ、ダッシュ)と視点制御を別コンポーネントに分けることで、どちらか片方だけを差し替えるのが簡単になります。例えば「マウス感度をオプションから変えたい」「TPSだけ視点制限を厳しくしたい」といった要件に柔軟に対応できます。 - プレハブの再利用性が高まる
プレイヤーのプレハブに MouseLook を付けておけば、新しいシーンを作るときもそのプレハブをポンと置くだけで同じ視点挙動が手に入ります。TPS用の敵AIキャラに対しても、カメラを追従させるだけで同じ「視点回転ロジック」を流用できます。 - レベルデザイン時の調整がラク
感度や上下限角度をインスペクターから直接いじれるので、「このマップは高低差が大きいから、もう少し見上げられるようにしたい」といった調整を、スクリプトを触らずに行えます。 - VRやコントローラ対応への拡張がしやすい
「視点回転はこのコンポーネントが担当する」と責務が決まっているので、入力元をマウスからゲームパッドやVR HMDに差し替える場合も、入力部分だけを差し替える実装にしやすいです。
さらに、MouseLook にちょっとした機能を足すことで、よりリッチな挙動にすることもできます。例えば、視点のスムージング(イージング) を入れて、急にカクッと回転しないようにする改造案です。
// 例:現在の回転と目標回転を補間して、視点の動きをスムーズにする
[SerializeField]
[Tooltip("視点のスムージング係数。0で即時、1に近づくほどスムーズ(遅く)なる")]
private float rotationSmoothTime = 0.05f;
private float pitchVelocity; // SmoothDamp用の一時変数
private float yawVelocity; // SmoothDamp用の一時変数
private float targetYaw; // 入力から計算された目標Yaw
private void LateUpdate()
{
// targetYaw と currentPitch に対して SmoothDamp を適用する例
if (yawTarget != null)
{
float currentYaw = yawTarget.eulerAngles.y;
float smoothYaw = Mathf.SmoothDampAngle(
currentYaw,
targetYaw,
ref yawVelocity,
rotationSmoothTime
);
yawTarget.rotation = Quaternion.Euler(0f, smoothYaw, 0f);
}
if (cameraPivot != null)
{
float currentX = cameraPivot.localEulerAngles.x;
float smoothPitch = Mathf.SmoothDampAngle(
currentX,
currentPitch,
ref pitchVelocity,
rotationSmoothTime
);
Vector3 euler = cameraPivot.localEulerAngles;
cameraPivot.localEulerAngles = new Vector3(smoothPitch, euler.y, euler.z);
}
}
このように、「視点制御」をひとつのコンポーネントに閉じ込めておくと、あとから「スムージングを入れたい」「スナップ回転を追加したい」「右クリックで肩越し視点に切り替えたい」といった機能追加も、他のコードに影響を与えずに実装しやすくなります。小さな責務ごとにコンポーネントを分けていく習慣をつけていきましょう。
