Unityを触り始めた頃は、つい何でもかんでも Update() の中に書いてしまいがちですよね。プレイヤーの移動、カメラの追従、入力処理、UIの更新……全部ひとつのスクリプトに詰め込んでしまうと、だんだん「どこを触ればいいのか分からない巨大スクリプト」が生まれてしまいます。
特にカメラまわりは、「プレイヤー追従カメラ」と「デバッグ用の自由カメラ」がごちゃまぜになりやすいポイントです。プレイヤーにくっついたままでは、レベル全体の確認やデバッグがしづらいですよね。
そこでこの記事では、プレイヤーからカメラを切り離し、WASD+マウスでステージ全体を自由に見回せるためのコンポーネント「FreeCamera」を用意します。カメラの役割をひとつのコンポーネントに閉じ込めることで、プレイヤー制御と綺麗に分離し、シーンの確認やレベルデザインがかなり楽になります。
【Unity】ステージを自由に飛び回る!「FreeCamera」コンポーネント
FreeCamera.cs(フルコード)
using UnityEngine;
/// <summary>
/**
* WASD+マウスで自由に動かせるデバッグ用カメラコンポーネント。
* - プレイヤーからカメラを切り離して、ステージ全体を見回したいときに使う
* - シーンビューの「Fly」操作に近い感覚
*
* 主な操作:
* - マウス移動: カメラの向き(ヨー・ピッチ)を変更
* - W / S: 前後移動
* - A / D: 左右移動
* - Space / LeftControl: 上下移動
* - Shift: 移動速度アップ(ブースト)
*/
/// </summary>
[RequireComponent(typeof(Camera))]
public class FreeCamera : MonoBehaviour
{
[Header("移動設定")]
[SerializeField] private float moveSpeed = 5f; // 通常移動速度
[SerializeField] private float boostMultiplier = 3f; // Shift押下時の速度倍率
[SerializeField] private float verticalMoveSpeed = 5f; // 上下移動速度(Space / Ctrl)
[Header("マウス視点操作")]
[SerializeField] private float mouseSensitivity = 2f; // マウス感度
[SerializeField] private bool invertY = false; // Y軸反転の有無
[SerializeField] private float minPitch = -89f; // 下向き最大角度
[SerializeField] private float maxPitch = 89f; // 上向き最大角度
[Header("カーソルロック")]
[SerializeField] private bool lockCursorOnStart = true; // 開始時にカーソルロックするか
[SerializeField] private KeyCode toggleCursorKey = KeyCode.Escape; // ロック切り替えキー
// 内部状態
private float _yaw; // 水平方向の回転角(ヨー)
private float _pitch; // 垂直方向の回転角(ピッチ)
private void Start()
{
// 現在の回転からヨー・ピッチを初期化
Vector3 euler = transform.rotation.eulerAngles;
_yaw = euler.y;
_pitch = euler.x;
if (lockCursorOnStart)
{
LockCursor(true);
}
}
private void Update()
{
// カーソルロックの切り替え
if (Input.GetKeyDown(toggleCursorKey))
{
bool shouldLock = Cursor.lockState != CursorLockMode.Locked;
LockCursor(shouldLock);
}
// カーソルがロックされているときだけ視点操作&移動を受け付ける
if (Cursor.lockState == CursorLockMode.Locked)
{
HandleMouseLook();
HandleMovement();
}
}
/// <summary>
/// マウス移動による視点操作を処理する
/// </summary>
private void HandleMouseLook()
{
// マウス移動量を取得
float mouseX = Input.GetAxisRaw("Mouse X");
float mouseY = Input.GetAxisRaw("Mouse Y");
// ヨー(左右回転)はX方向の移動量で決定
_yaw += mouseX * mouseSensitivity;
// ピッチ(上下回転)はY方向の移動量で決定
float invert = invertY ? 1f : -1f;
_pitch += mouseY * mouseSensitivity * invert;
// ピッチに制限をかけて、真上真下を向きすぎないようにする
_pitch = Mathf.Clamp(_pitch, minPitch, maxPitch);
// 回転を反映
Quaternion rotation = Quaternion.Euler(_pitch, _yaw, 0f);
transform.rotation = rotation;
}
/// <summary>
/// キーボード入力による移動を処理する
/// </summary>
private void HandleMovement()
{
// WASD入力
float horizontal = Input.GetAxisRaw("Horizontal"); // A(-1) / D(+1)
float vertical = Input.GetAxisRaw("Vertical"); // S(-1) / W(+1)
// 上下移動(Space / LeftControl)
float up = 0f;
if (Input.GetKey(KeyCode.Space))
{
up += 1f;
}
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
up -= 1f;
}
// カメラのローカル軸に沿って移動方向ベクトルを作る
Vector3 moveDir =
transform.forward * vertical +
transform.right * horizontal +
Vector3.up * up;
// 斜め移動で速くなりすぎないように正規化
if (moveDir.sqrMagnitude > 1f)
{
moveDir.Normalize();
}
// Shiftでブースト
float speed = moveSpeed;
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
{
speed *= boostMultiplier;
}
// 実際の移動
transform.position += moveDir * speed * Time.deltaTime;
}
/// <summary>
/// カーソルロック状態を切り替える
/// </summary>
/// <param name="shouldLock">trueでロック、falseで解放</param>
private void LockCursor(bool shouldLock)
{
if (shouldLock)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
else
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
}
}
使い方の手順
- カメラにコンポーネントを追加する
シーン内のMain Camera(または自由カメラにしたいカメラ)を選択し、
「Add Component」からFreeCameraを追加します。
[RequireComponent(typeof(Camera))]を付けているので、カメラが必ず一緒に存在する状態になります。 - プレイヤー追従カメラと切り替える
例えば、普段はプレイヤー追従カメラを使い、デバッグ時だけFreeCameraを有効にしたい場合は、
– プレイヤー追従カメラ用オブジェクト(例:PlayerCamera)
– 自由カメラ用オブジェクト(例:FreeCamera)
の2つを用意しておき、インスペクターからGameObject.SetActiveのON/OFFで切り替えます。
たとえば、プレイヤーにくっついたカメラは通常ON、レベルチェック時はそれをOFFにしてこのFreeCameraをONにする、という運用ですね。 - パラメータを調整する
- Move Speed: 通常の移動速度。ステージのスケールに合わせて 5~20 くらいで調整
- Boost Multiplier: Shift押下時の倍率。大きなマップなら 5~10 くらいにすると快適
- Vertical Move Speed: 上下移動の速度。Move Speed と同じか、やや遅めがおすすめ
- Mouse Sensitivity: マウス感度。好みに合わせて 1.0~3.0 くらい
- Invert Y: フライトゲーム風にしたいならチェックを入れてY軸反転
- Lock Cursor On Start: 再生開始と同時にマウスカーソルをロックするかどうか
- Toggle Cursor Key: デフォルトは
Esc。一時的にカーソルを解放したいときに使います
- 実際に操作してみる(具体例)
例えば以下のようなシーンで使うと便利です。- プレイヤーの移動範囲を確認したいとき
プレイヤーとは別にFreeCameraを持つカメラを用意し、
デバッグ中にFreeCameraの GameObject を有効化してステージ全体を見回します。
崖のコライダー漏れや、敵の湧き位置、動く床の動作などを上空からチェックできます。 - 敵AIの行動範囲を俯瞰したいとき
敵のパトロールルートや索敵範囲が正しく機能しているかを、
上から自由に見下ろして確認できます。
NavMeshエージェントの経路がおかしい箇所も見つけやすくなります。 - 巨大なステージのレベルデザイン
広いマップを作っている場合、プレイヤーを走らせて確認するのはかなり大変です。
FreeCameraなら、空中から高速で移動して、
「この丘の高さは適切か」「このエリアから次のエリアへの見通しはどうか」などを素早くチェックできます。
- プレイヤーの移動範囲を確認したいとき
メリットと応用
FreeCamera を独立したコンポーネントとして用意しておくと、次のようなメリットがあります。
- プレイヤー制御とカメラ制御を完全に分離できる
プレイヤーのスクリプトに「デバッグ用カメラ機能」を足してしまうと、
本番ビルドで無効化し忘れたり、入力が競合したりとトラブルの元になります。
カメラはカメラ、プレイヤーはプレイヤーで責務を分けておくと、コードの見通しがかなり良くなります。 - プレハブ化してどのプロジェクトでも再利用できる
FreeCameraを付けたカメラを1つプレハブにしておけば、
新しいシーンや新プロジェクトにドラッグ&ドロップするだけで、すぐに自由カメラ環境が整います。
レベルデザイン用の「ツール」として持ち歩けるのが便利です。 - レベルデザインの試行錯誤が圧倒的に速くなる
「この足場、プレイヤーからちゃんと見えるかな?」
「この敵、出現位置からプレイヤーをちゃんと視認できるかな?」
といった確認を、プレイヤーを動かさずにサクサク行えます。
ステージを作る人とプレイヤー挙動を作る人が別でも、それぞれが自分の作業に集中できるのも嬉しいポイントです。
さらに、コンポーネント指向の思想に従って、必要に応じて小さく改造していくのもおすすめです。
例えば「特定のキーを押している間だけ高速移動する」のではなく、「マウスホイールで速度を可変にしたい」場合、次のような関数を追加してみるのも良いですね。
/// <summary>
/// マウスホイールで移動速度を可変にする(応用例)
/// </summary>
private void HandleSpeedByScroll()
{
// マウスホイールの入力値(上スクロールで正、下スクロールで負)
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Approximately(scroll, 0f))
{
return;
}
// スクロール量に応じて移動速度を増減
moveSpeed += scroll * 10f;
// 負の値にならないように下限を設定
moveSpeed = Mathf.Clamp(moveSpeed, 1f, 100f);
}
Update() の中から HandleSpeedByScroll() を呼び出すだけで、
「今は細かく調整したいから低速で」「マップを大きく移動したいから高速で」といった操作が簡単にできます。
このように、FreeCamera というひとつの責務を持ったコンポーネントをベースに、必要な機能を小さく積み上げていくと、プロジェクト全体の設計もぐっと綺麗になりますね。
