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ツインスティックシューターのプレイヤーが、マウス方向に銃口を向ける
手順①:スクリプトを用意する
MouseRotator.csという名前で、上記コードを丸ごとコピペして保存します。- Unityエディタに戻ると自動的にコンパイルされ、コンポーネントとして使えるようになります。
手順②:コンポーネントをアタッチする
例A:3Dプレイヤーの上半身をマウス方向に向ける
- プレイヤーのルートオブジェクト(例:
Player)の子として、上半身用のオブジェクト(例:Body)があるとします。 Bodyオブジェクトを選択し、「Add Component」からMouseRotatorを追加します。- インスペクターで次のように設定します:
- 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
- Target Transform: 空欄なら自動で
これで、プレイヤーの上半身がマウスカーソルの位置(地面上の交点)を追いかけて回転するようになります。
移動処理は別コンポーネント(例: PlayerMover)に分けておくと、責務がはっきりしてメンテしやすいですね。
例B:3Dタレット(砲台)がマウス位置を狙う
- タレットのベース(回転しない土台)と、回転する砲身オブジェクト(例:
TurretHead)を用意します。 TurretHeadにMouseRotatorを追加します。- 設定は例Aとほぼ同じですが、次の点に注意します:
- タレットが「地面上」を狙うなら、Use Raycast = ON にして、Raycast Layer Mask に地面レイヤーを指定。
- 砲身を水平回転だけにしたい場合は Constrain To Y Only = ON。
- 高低差も狙いたいなら Constrain To Y Only = OFF にして、3D的な回転を許可します。
このタレットに「発射処理だけを行うコンポーネント(例: TurretShooter)」を別途付けておけば、
「向きを制御するコンポーネント」と「弾を撃つコンポーネント」がきれいに分離され、後からどちらか片方だけ差し替えるのも簡単です。
例C:2Dツインスティックシューターで、プレイヤーをマウス方向に向ける
- 2Dのプレイヤーオブジェクト(例:
Player2D)にSpriteRendererやRigidbody2Dが付いているとします。 Player2DにMouseRotatorを追加します。- 設定を次のようにします:
- 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
- World Up: 2Dの場合も
メインカメラは 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責務 を意識しておくと、
「マウス追従回転」コンポーネントをベースに、ゲームのルールに合わせた細かな調整・拡張をしやすくなります。
ぜひプロジェクト内で標準装備の「マウス回転コンポーネント」として使い回してみてください。
