Unityを触り始めると、つい「プレイヤーの入力処理」「カメラ制御」「UI更新」などを全部ひとつの Update() に書いてしまいがちですよね。
動き始めるまでは良いのですが、あとから「ジャイロで照準を微調整したい」「パッドでもスマホでも動くようにしたい」といった要望が出てくると、一気に地獄化します。
そこでこの記事では、「ジャイロの傾きだけ」を責務とする小さなコンポーネントとして、GyroAim(ジャイロ照準) を用意します。
照準ロジックをこのコンポーネントに切り出すことで、プレイヤー本体のスクリプトはシンプルなまま、「ジャイロ対応の有無」 をコンポーネントの付け外しで切り替えられるようにしていきましょう。
【Unity】ジャイロでヌルっと照準微調整!「GyroAim」コンポーネント
このコンポーネントは、スマホやコントローラーの傾き(ジャイロ)を読み取り、ターゲットTransformの回転に「上乗せ」する形で照準を微調整します。
・スマホなら端末そのものの傾き
・ゲームパッドなら、対応しているプラットフォームのモーションセンサー
を使う想定です。
Unity6 では新しい Input System が標準なので、今回は Unity Input System を使った実装にします。
フルコード:GyroAim.cs
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
/// <summary>
/// GyroAim
/// ジャイロ/モーションセンサーの傾きをもとに、
/// ターゲットTransformの回転に「照準微調整」を加えるコンポーネント。
/// スマホ端末や対応コントローラーで利用可能。
/// </summary>
public class GyroAim : MonoBehaviour
{
// --- 設定項目 ---
[Header("照準対象")]
[SerializeField]
private Transform target;
// 照準を回転させたい対象(例:カメラ、銃口、プレイヤーの上半身)
[Header("感度設定")]
[SerializeField, Tooltip("ジャイロの回転速度をどれだけ照準に反映するか(度/秒スケール)")]
private float sensitivity = 2.0f;
[SerializeField, Tooltip("Yaw(水平)方向の回転を有効にするか")]
private bool useYaw = true;
[SerializeField, Tooltip("Pitch(垂直)方向の回転を有効にするか")]
private bool usePitch = true;
[Header("制限角度")]
[SerializeField, Tooltip("Pitch(上下)角度の最小値(度)")]
private float minPitch = -80f;
[SerializeField, Tooltip("Pitch(上下)角度の最大値(度)")]
private float maxPitch = 80f;
[Header("スムージング")]
[SerializeField, Tooltip("補間の速さ。0で補間なし、1以上で急激、0.1前後でなめらか")]
private float rotationLerpSpeed = 15f;
[Header("入力デバイス優先度")]
[SerializeField, Tooltip("スマホのジャイロを優先的に使うか(true:スマホ優先 / false:ゲームパッド優先)")]
private bool preferMobileGyro = true;
// --- 内部状態 ---
// 現在使用しているモーションセンサー
private IGyroProvider gyroProvider;
// 回転の元となる「基準角度」
private float baseYaw;
private float basePitch;
// 現在の「微調整後」角度
private float currentYaw;
private float currentPitch;
// 初期化済みフラグ
private bool initialized = false;
#region Unity Lifecycle
private void Awake()
{
// target が未指定なら、自分自身をターゲットにする
if (target == null)
{
target = transform;
}
// 初期回転を基準として保存
Vector3 euler = target.rotation.eulerAngles;
baseYaw = euler.y;
basePitch = NormalizePitch(euler.x);
currentYaw = baseYaw;
currentPitch = basePitch;
// 利用可能なジャイロ入力を探索
gyroProvider = CreateGyroProvider();
if (gyroProvider != null)
{
gyroProvider.Enable();
initialized = true;
}
else
{
Debug.LogWarning("[GyroAim] 利用可能なジャイロ/モーションセンサーが見つかりませんでした。ジャイロ照準は無効です。");
}
}
private void OnDestroy()
{
if (gyroProvider != null)
{
gyroProvider.Disable();
}
}
private void Update()
{
if (!initialized || gyroProvider == null)
{
return;
}
// デバイスの角速度(rad/s)を取得
Vector3 angularVelocity = gyroProvider.GetAngularVelocity();
// Unityの右手座標系に合わせて、Yaw/Pitch を取り出す
// ここでは「ワールド空間での回転速度」ではなく、
// デバイスのローカル空間のうち、Yaw=Y軸、Pitch=X軸を使う前提にしています。
float deltaYaw = 0f;
float deltaPitch = 0f;
if (useYaw)
{
// Y軸回転をYawとして扱う(左右のひねり)
deltaYaw = -angularVelocity.y * Mathf.Rad2Deg * sensitivity * Time.deltaTime;
}
if (usePitch)
{
// X軸回転をPitchとして扱う(上下の傾き)
deltaPitch = angularVelocity.x * Mathf.Rad2Deg * sensitivity * Time.deltaTime;
}
// 角度を積算
currentYaw += deltaYaw;
currentPitch += deltaPitch;
// Pitch に制限をかける
currentPitch = Mathf.Clamp(currentPitch, minPitch, maxPitch);
// 目標回転
Quaternion targetRotation = Quaternion.Euler(currentPitch, currentYaw, 0f);
if (rotationLerpSpeed > 0f)
{
// スムーズに補間
target.rotation = Quaternion.Slerp(
target.rotation,
targetRotation,
rotationLerpSpeed * Time.deltaTime
);
}
else
{
// 即時反映
target.rotation = targetRotation;
}
}
#endregion
#region Public API
/// <summary>
/// 現在のジャイロ照準を「0」とみなし、そこを基準角度として再キャリブレーションします。
/// 例:プレイヤーが端末を持ち直した時などに呼ぶと良いです。
/// </summary>
public void Recenter()
{
if (target == null) return;
Vector3 euler = target.rotation.eulerAngles;
baseYaw = euler.y;
basePitch = NormalizePitch(euler.x);
currentYaw = baseYaw;
currentPitch = basePitch;
}
/// <summary>
/// ジャイロ照準を一時的に有効/無効にする。
/// </summary>
public void SetEnabled(bool enable)
{
if (enable)
{
if (!initialized)
{
gyroProvider = CreateGyroProvider();
if (gyroProvider != null)
{
gyroProvider.Enable();
initialized = true;
}
}
}
else
{
if (gyroProvider != null)
{
gyroProvider.Disable();
}
initialized = false;
}
}
#endregion
#region Gyro Provider Factory
/// <summary>
/// 利用可能なジャイロ入力ソースを探して、IGyroProvider を生成する。
/// スマホ > ゲームパッド のような優先度で選択します。
/// </summary>
private IGyroProvider CreateGyroProvider()
{
IGyroProvider mobile = new MobileGyroProvider();
IGyroProvider gamepad = new GamepadGyroProvider();
bool mobileAvailable = mobile.IsAvailable();
bool gamepadAvailable = gamepad.IsAvailable();
if (!mobileAvailable && !gamepadAvailable)
{
return null;
}
if (preferMobileGyro)
{
if (mobileAvailable) return mobile;
if (gamepadAvailable) return gamepad;
}
else
{
if (gamepadAvailable) return gamepad;
if (mobileAvailable) return mobile;
}
return null;
}
#endregion
#region Utility
/// <summary>
/// UnityのEuler Xは0~360度になるので、-180~180に正規化する。
/// Pitchのクランプに使いやすくするため。
/// </summary>
private float NormalizePitch(float rawX)
{
float x = rawX;
if (x > 180f)
{
x -= 360f;
}
return x;
}
#endregion
#region Gyro Provider Interface & Implementations
/// <summary>
/// ジャイロ/モーションセンサーの抽象インターフェース。
/// 実装を差し替えることで、スマホ・ゲームパッドなどを共通の扱いにします。
/// </summary>
private interface IGyroProvider
{
/// <summary>利用可能かどうか</summary>
bool IsAvailable();
/// <summary>有効化(必要ならセンサーの有効化など)</summary>
void Enable();
/// <summary>無効化</summary>
void Disable();
/// <summary>角速度(rad/s)を返す</summary>
Vector3 GetAngularVelocity();
}
/// <summary>
/// スマホ端末のジャイロを使う実装。
/// UnityEngine.InputSystem.Gyroscope を利用します。
/// </summary>
private class MobileGyroProvider : IGyroProvider
{
private Gyroscope gyro;
public bool IsAvailable()
{
// InputSystem の Gyroscope デバイスが存在するか確認
gyro = Gyroscope.current;
return gyro != null;
}
public void Enable()
{
if (gyro == null)
{
gyro = Gyroscope.current;
}
if (gyro != null)
{
InputSystem.EnableDevice(gyro);
}
}
public void Disable()
{
if (gyro != null)
{
InputSystem.DisableDevice(gyro);
}
}
public Vector3 GetAngularVelocity()
{
if (gyro == null)
{
gyro = Gyroscope.current;
if (gyro == null) return Vector3.zero;
}
// Gyroscope の angularVelocity は Vector3 (rad/s)
// 端末ローカル座標なので、ここではそのまま返します。
return gyro.angularVelocity.ReadValue();
}
}
/// <summary>
/// ゲームパッドのモーションセンサーを使う実装。
/// 対応しているコントローラーが接続されていれば利用可能です。
/// </summary>
private class GamepadGyroProvider : IGyroProvider
{
private Gamepad gamepad;
public bool IsAvailable()
{
// 何かしらのゲームパッドが接続されているか
gamepad = Gamepad.current;
if (gamepad == null) return false;
// Motion センサーを持っているかチェックする(プラットフォーム依存)
// ここでは簡易的に「ジャイロステートが取得できるか」で判定します。
try
{
// 低レベルイベントを読むわけではなく、
// Gamepad の state を GyroscopeState にキャストできるかを
// 確認するような形にしておく。
// 実際の対応状況はプラットフォームとデバイスに依存します。
var state = gamepad.GetState();
return state is GyroscopeState;
}
catch
{
return false;
}
}
public void Enable()
{
if (gamepad == null)
{
gamepad = Gamepad.current;
}
if (gamepad != null)
{
InputSystem.EnableDevice(gamepad);
}
}
public void Disable()
{
if (gamepad != null)
{
InputSystem.DisableDevice(gamepad);
}
}
public Vector3 GetAngularVelocity()
{
if (gamepad == null)
{
gamepad = Gamepad.current;
if (gamepad == null) return Vector3.zero;
}
// ここでは「GyroscopeState を持つゲームパッド」という前提で、
// 角速度を取得する例を示します。
// 実際には各プラットフォームの拡張Gamepad実装を参照して、
// 適切なプロパティから値を取得してください。
var state = gamepad.GetState();
if (state is GyroscopeState gyroState)
{
// GyroscopeState.angularVelocity は Vector3 (rad/s)
return gyroState.angularVelocity;
}
// モーションセンサーが使えない場合は0を返す
return Vector3.zero;
}
}
#endregion
}
※ 注意: Gamepad.GetState() や GyroscopeState 部分は、実際にはプラットフォームやデバイス固有の拡張クラスになる場合があります。
「スマホでのジャイロ照準」が主目的であれば、MobileGyroProvider だけ使う形に簡略化しても構いません。
使い方の手順
-
① 新しい C# スクリプトを作成
Unity の Project ビューで右クリック → Create → C# Script → 名前をGyroAimにして、上記コードをまるごとコピペして保存します。 -
② Input System を有効化
Unity メニューから
Edit > Project Settings... > Player > Other Settings
で Active Input Handling をInput System Package (New)もしくはBothに設定しておきます。
まだ Input System パッケージを入れていない場合は、Package Manager から「Input System」をインストールしておきましょう。 -
③ コンポーネントをアタッチ
具体例として:- FPS視点のプレイヤー:カメラオブジェクトに
GyroAimを追加し、targetにそのカメラの Transform を指定。 - TPSの肩越し照準:銃口オブジェクト(GunPivot など)に
GyroAimを付けて、キャラクターの基本エイムは右スティック、微調整だけジャイロに任せる。 - タレットや固定砲台:砲台の上部(回転部分)に
GyroAimを付けて、スマホを傾けて砲身を動かすミニゲームにする。
targetを未設定にすると、自分自身の Transform に対して回転をかけるので、基本は「そのまま」でも動きます。 - FPS視点のプレイヤー:カメラオブジェクトに
-
④ キャリブレーションとチューニング
実機で試しながら、以下を調整しましょう。- Sensitivity:感度。大きいほど小さな傾きで大きく動きます。
- Min/Max Pitch:上下の可動範囲。上下に向きすぎて酔わないように制限。
- Rotation Lerp Speed:スムーズさ。0だと生のジャイロ値、10〜20くらいでヌルっとした動き。
- Prefer Mobile Gyro:スマホ優先かゲームパッド優先か。
プレイヤーが「持ち方を変えた」「向きを変えた」タイミングで、
例えば UI ボタンやキー入力からGyroAim.Recenter()を呼び出すと、その姿勢を新しい基準としてリセットできます。
メリットと応用
GyroAim をコンポーネントとして切り出すことで:
- プレイヤー制御スクリプトが細くなる
入力の種類(スティック・マウス・ジャイロ)を全部ひとつのクラスで面倒を見る必要がなくなります。
「ジャイロの有無」は GyroAim コンポーネントを付けるかどうかで切り替えられます。 - プレハブの再利用性が高まる
例えば「ジャイロ対応のFPSカメラプレハブ」「通常のマウスエイムカメラプレハブ」を作っておいて、シーンごとに使い分ける、という構成が取りやすくなります。 - レベルデザインが楽になる
特定のステージだけ「ジャイロで狙うギミック」を入れたい時は、対象のオブジェクトに GyroAim を足すだけ。
スクリプトを書き換えず、レベルデザイナーがインスペクタ操作だけで調整できるようになります。 - モーションセンサー対応の実験がしやすい
IGyroProvider インターフェースで抽象化してあるので、あとから「Switch向けの特定コントローラー用実装」「VRコントローラー用実装」などを追加しても、GyroAim 本体はほとんど触らずに済みます。
応用として、例えば「ジャイロの影響をだんだん弱める」「ADS(構え中)だけジャイロを強くする」といった改造も簡単です。
以下は、右クリックでエイムダウンサイト(ADS)中だけジャイロ感度を上げる簡単な改造案の例です。
GyroAim に追加するメソッドとして使えます。
/// <summary>
/// ADS(構え)中だけジャイロ感度を変える例。
/// Input System のボタンや、他コンポーネントから呼び出して使う想定です。
/// </summary>
public void SetAdsState(bool isAds)
{
// 通常時とADS時の感度を決め打ち
float normalSensitivity = 2.0f;
float adsSensitivity = 4.0f;
sensitivity = isAds ? adsSensitivity : normalSensitivity;
}
このように、ジャイロ照準をひとつのコンポーネントとして独立させておくと、
・プレイヤーの入力処理
・カメラ制御
・武器の挙動
といった他の責務ときれいに分離できて、後からの改造・実験がとてもやりやすくなります。
ぜひ、自分のプロジェクト用にカスタマイズしてみてください。
