Unityの学習を進めていくと、つい何でもかんでも Update() の中に書いてしまいがちですよね。
プレイヤーの移動、カメラの制御、UIの更新、当たり判定のチェック……すべてを1つの巨大なスクリプトに詰め込むと、次のような問題が出てきます。

  • 処理の責務がごちゃ混ぜになり、どこを直せばいいか分からない
  • 似たような処理を別オブジェクトで使い回しづらい
  • Prefab化やレベルデザイン時に、ちょっとした変更でも大改造が必要になる

そこでおすすめなのが、「動きごとに小さなコンポーネントを分ける」スタイルです。
今回紹介する 「MouseRotator」コンポーネント は、その代表的な例として、

  • 「親オブジェクトを、常にマウスカーソルの方向に向ける」

という、よくあるゲーム挙動を 1コンポーネントにきれいに閉じ込めた実装です。
2Dのツインスティックシューター、マウス照準のTPS、タレットの自動旋回など、幅広く使い回せるように作っていきましょう。

【Unity】マウスを向けるだけで狙いが決まる!「MouseRotator」コンポーネント

ここでは、Unity6(2023以降相当)を想定しつつ、カメラからのレイキャストでマウス位置をワールド座標に変換し、親オブジェクトをその方向へ回転させるコンポーネントを実装します。
2D/3Dどちらでも使えるように、回転する軸やレイヤーを調整できるようにしておきます。


フルコード:MouseRotator.cs


using UnityEngine;

/// <summary>
/// マウスカーソルの位置方向に、親オブジェクトを回転させるコンポーネント。
/// - カメラからマウス位置へレイを飛ばし、ヒット位置 or 一定距離先をターゲットとする
/// - 2D/3D両対応(回転軸や平面を指定可能)
/// - 1コンポーネントで「マウス追従回転」の責務だけを持つ
/// </summary>
[DisallowMultipleComponent]
public class MouseRotator : MonoBehaviour
{
    // --- 基本設定 ---

    [SerializeField]
    [Tooltip("回転させる対象。未指定ならこのコンポーネントが付いているオブジェクト自身を回転させます。")]
    private Transform targetTransform;

    [SerializeField]
    [Tooltip("マウス位置へレイを飛ばすカメラ。未指定の場合は Camera.main を使用します。")]
    private Camera referenceCamera;

    [SerializeField]
    [Tooltip("回転時に使用するワールド上の「上方向」。2Dなら (0, 0, 1)、3Dなら (0, 1, 0) が典型です。")]
    private Vector3 worldUp = Vector3.up;

    // --- レイキャスト関連 ---

    [SerializeField]
    [Tooltip("マウス位置方向に向ける際、レイキャストを使って地面などとの交点を狙うかどうか。")]
    private bool useRaycast = true;

    [SerializeField]
    [Tooltip("レイキャストのヒット対象レイヤー。地面や床などを指定します。")]
    private LayerMask raycastLayerMask = ~0; // デフォルトは全レイヤー

    [SerializeField]
    [Tooltip("レイキャストがヒットしなかった場合に使う、カメラ前方方向の距離。")]
    private float fallbackDistance = 10f;

    // --- 回転挙動 ---

    [SerializeField]
    [Tooltip("回転をスムーズに補間するかどうか。false の場合は即座に向きます。")]
    private bool smoothRotation = true;

    [SerializeField]
    [Tooltip("スムーズ回転時の角速度(度/秒)。")]
    private float rotationSpeed = 720f;

    [SerializeField]
    [Tooltip("Y軸だけ回転させる(水平回転のみ)など、回転軸を制限したい場合に使用します。")]
    private bool constrainToYOnly = false;

    [SerializeField]
    [Tooltip("2Dゲームで、Z軸だけ回転させたい場合に使用します。")]
    private bool constrainToZOnly = false;

    // --- デバッグ表示 ---

    [SerializeField]
    [Tooltip("Sceneビュー上でレイやターゲット方向を可視化するかどうか。")]
    private bool debugDrawGizmos = false;

    private Vector3 _lastAimPoint; // デバッグ用に最後に狙ったワールド座標を保存

    private void Reset()
    {
        // コンポーネントを追加したときの初期値設定
        targetTransform = transform;
        referenceCamera = Camera.main;
        worldUp = Vector3.up;
        useRaycast = true;
        raycastLayerMask = ~0;
        fallbackDistance = 10f;
        smoothRotation = true;
        rotationSpeed = 720f;
        constrainToYOnly = false;
        constrainToZOnly = false;
        debugDrawGizmos = true;
    }

    private void Awake()
    {
        // targetTransform が未設定なら自分自身を使用
        if (targetTransform == null)
        {
            targetTransform = transform;
        }

        // カメラが未設定なら Camera.main を自動取得
        if (referenceCamera == null)
        {
            referenceCamera = Camera.main;
        }

        // worldUp がゼロベクトルだと計算に支障が出るので保険
        if (worldUp.sqrMagnitude < 0.0001f)
        {
            worldUp = Vector3.up;
        }
    }

    private void Update()
    {
        if (referenceCamera == null)
        {
            // カメラが見つからないときは何もしない(ログだけ出す)
            Debug.LogWarning($"{nameof(MouseRotator)}: 参照カメラが設定されていません。");
            return;
        }

        // マウス位置からワールド上の狙いポイントを計算
        if (!TryGetAimPoint(out Vector3 aimPoint))
        {
            // 取得に失敗した場合は何もしない
            return;
        }

        _lastAimPoint = aimPoint;

        // 目標方向ベクトルを計算
        Vector3 targetDirection = aimPoint - targetTransform.position;

        // 距離がほぼゼロなら回転不要
        if (targetDirection.sqrMagnitude < 0.0001f)
        {
            return;
        }

        // 軸制限(Yのみ/Zのみ)を適用
        targetDirection = ApplyAxisConstraint(targetDirection);

        // 目標回転を計算
        Quaternion targetRotation = Quaternion.LookRotation(targetDirection, worldUp);

        // スムーズ回転 or 即時回転
        if (smoothRotation)
        {
            targetTransform.rotation = Quaternion.RotateTowards(
                targetTransform.rotation,
                targetRotation,
                rotationSpeed * Time.deltaTime
            );
        }
        else
        {
            targetTransform.rotation = targetRotation;
        }
    }

    /// <summary>
    /// マウスカーソルが指すワールド上の狙いポイントを取得する。
    /// レイキャストを使う場合はヒット位置、使わない場合は一定距離先の点。
    /// </summary>
    private bool TryGetAimPoint(out Vector3 aimPoint)
    {
        aimPoint = Vector3.zero;

        // マウス位置(スクリーン座標)
        Vector3 mousePos = Input.mousePosition;

        // 画面外などのケースを簡易チェック(負値など)
        if (mousePos.x < 0f || mousePos.y < 0f)
        {
            return false;
        }

        Ray ray = referenceCamera.ScreenPointToRay(mousePos);

        if (useRaycast)
        {
            // カメラからレイを飛ばし、指定レイヤーと衝突した位置を狙う
            if (Physics.Raycast(ray, out RaycastHit hitInfo, Mathf.Infinity, raycastLayerMask))
            {
                aimPoint = hitInfo.point;
                return true;
            }
        }

        // レイキャストを使わない、またはヒットしなかった場合は
        // カメラ前方 fallbackDistance の位置を基準に狙い方向を決める
        aimPoint = ray.origin + ray.direction * fallbackDistance;
        return true;
    }

    /// <summary>
    /// 指定された方向ベクトルに対し、Y軸のみ/Z軸のみなどの制限を適用する。
    /// </summary>
    private Vector3 ApplyAxisConstraint(Vector3 direction)
    {
        if (constrainToYOnly)
        {
            // 水平面に投影(Y成分を無視する)
            direction.y = 0f;
        }

        if (constrainToZOnly)
        {
            // 2D(X-Y平面)で使う想定:Z成分を無視
            direction.z = 0f;
        }

        return direction;
    }

    private void OnDrawGizmosSelected()
    {
        if (!debugDrawGizmos || targetTransform == null)
        {
            return;
        }

        // 最後に狙ったポイントへのラインを表示
        Gizmos.color = Color.cyan;
        Gizmos.DrawLine(targetTransform.position, _lastAimPoint);
        Gizmos.DrawSphere(_lastAimPoint, 0.1f);

        // 現在の forward 方向も表示
        Gizmos.color = Color.yellow;
        Gizmos.DrawLine(targetTransform.position, targetTransform.position + targetTransform.forward * 2f);
    }
}

使い方の手順

ここからは、実際のシーンにどう組み込むかを具体的に見ていきます。
例として、次の3パターンを想定します。

  • ① 3Dアクションゲームのプレイヤーが、マウス方向に身体(または上半身)を向ける
  • ② 3Dタレット(砲台)が、マウス位置の地面を狙う
  • ③ 2Dツインスティックシューターのプレイヤーが、マウス方向に銃口を向ける

手順①:スクリプトを用意する

  1. MouseRotator.cs という名前で、上記コードを丸ごとコピペして保存します。
  2. Unityエディタに戻ると自動的にコンパイルされ、コンポーネントとして使えるようになります。

手順②:コンポーネントをアタッチする

例A:3Dプレイヤーの上半身をマウス方向に向ける

  1. プレイヤーのルートオブジェクト(例: Player)の子として、上半身用のオブジェクト(例: Body)があるとします。
  2. Body オブジェクトを選択し、「Add Component」から MouseRotator を追加します。
  3. インスペクターで次のように設定します:
    • Target Transform: 空欄なら自動で Body が入ります(そのままでOK)。
    • Reference Camera: 未設定なら Camera.main が使われます。メインカメラを正しく設定しておきましょう。
    • World Up: (0, 1, 0)(デフォルト)
    • Use Raycast: ON(地面などを狙いたい場合)
    • Raycast Layer Mask: 地面のレイヤー(例: Ground)を選択
    • Fallback Distance: 10〜30程度を好みで
    • Smooth Rotation: 好みでON/OFF(ON推奨)
    • Rotation Speed: 360〜720くらいから調整
    • Constrain To Y Only: ON(水平回転だけにする)
    • Constrain To Z Only: OFF

これで、プレイヤーの上半身がマウスカーソルの位置(地面上の交点)を追いかけて回転するようになります。
移動処理は別コンポーネント(例: PlayerMover)に分けておくと、責務がはっきりしてメンテしやすいですね。


例B:3Dタレット(砲台)がマウス位置を狙う

  1. タレットのベース(回転しない土台)と、回転する砲身オブジェクト(例: TurretHead)を用意します。
  2. TurretHeadMouseRotator を追加します。
  3. 設定は例Aとほぼ同じですが、次の点に注意します:
    • タレットが「地面上」を狙うなら、Use Raycast = ON にして、Raycast Layer Mask に地面レイヤーを指定。
    • 砲身を水平回転だけにしたい場合は Constrain To Y Only = ON
    • 高低差も狙いたいなら Constrain To Y Only = OFF にして、3D的な回転を許可します。

このタレットに「発射処理だけを行うコンポーネント(例: TurretShooter)」を別途付けておけば、
「向きを制御するコンポーネント」と「弾を撃つコンポーネント」がきれいに分離され、後からどちらか片方だけ差し替えるのも簡単です。


例C:2Dツインスティックシューターで、プレイヤーをマウス方向に向ける

  1. 2Dのプレイヤーオブジェクト(例: Player2D)に SpriteRendererRigidbody2D が付いているとします。
  2. Player2DMouseRotator を追加します。
  3. 設定を次のようにします:
    • World Up: 2Dの場合も (0, 1, 0) のままでOK(カメラがZマイナス方向を向いている前提)。
    • Use Raycast: OFF(2Dでは画面上の位置だけで十分なことが多い)
    • Fallback Distance: 10程度(あまり気にしなくてOK)
    • Constrain To Z Only: ON(Z軸回転だけにする)
    • Constrain To Y Only: OFF

メインカメラは Orthographic(正射影) で、プレイヤーと同じXY平面を映すようにしておくと自然な挙動になります。
この設定なら、プレイヤーがマウスの方向にクルクル回転し、クリックした方向に弾を撃つ 2Dシューターが簡単に作れます。


メリットと応用

MouseRotator を使うことで、次のようなメリットがあります。

  • 責務が明確:このコンポーネントは「マウス方向に回転させる」ことだけを担当します。
  • Prefab化しやすい:プレイヤー、敵タレット、動くギミックなど、どのオブジェクトにも同じコンポーネントをポン付けできます。
  • レベルデザインが楽:ステージ上にタレットを複数配置するとき、向きや挙動はインスペクターのパラメータ調整だけで完結します。
  • テストがしやすい:回転挙動だけを単体で確認できるため、不具合の切り分けがしやすくなります。

また、MouseRotator は「入力 → 方向ベクトル → 回転」という流れをきれいに切り出しているので、
将来的に マウス以外の入力(ゲームパッドのスティックなど) に差し替えたいときも、似た構造で別コンポーネントを用意しやすくなります。

改造案:一定角度ごとにスナップする「グリッド回転」

例えば見下ろし型アクションで、「8方向だけに向きたい」というケースもありますよね。
その場合は、Update() の中で求めた targetRotation に、角度スナップをかける処理を追加すればOKです。
イメージとして、次のような補助関数を追加できます。


/// <summary>
/// 指定した回転を、Y軸周りに一定角度ごとにスナップさせる例。
/// 例: snapAngle = 45 なら 0, 45, 90, ... に丸める。
/// 2DでZ軸回転をスナップしたい場合は、Axis を入れ替えて応用できます。
/// </summary>
private Quaternion SnapRotationY(Quaternion original, float snapAngle)
{
    // 現在のオイラー角を取得
    Vector3 euler = original.eulerAngles;

    // Y軸の角度をスナップ
    float snappedY = Mathf.Round(euler.y / snapAngle) * snapAngle;

    euler.y = snappedY;

    // Z軸やX軸をスナップしたい場合は同様に処理
    return Quaternion.Euler(euler);
}

これを Update() 内で targetRotation を決めたあとに挟めば、
「マウス方向を大まかに見つつ、実際の向きは8方向に限定する」ような挙動が簡単に作れます。

このように、1コンポーネント1責務 を意識しておくと、
「マウス追従回転」コンポーネントをベースに、ゲームのルールに合わせた細かな調整・拡張をしやすくなります。
ぜひプロジェクト内で標準装備の「マウス回転コンポーネント」として使い回してみてください。