Unityを触り始めた頃は、つい何でもかんでも Update() に書いてしまいがちですよね。
マウス入力の取得、カメラ回転、UI更新、敵のスポーン制御…全部ひとつのスクリプトに詰め込むと、だんだん「どこを触れば何が変わるのか」が分からなくなります。

特に「エイム(照準)周り」の処理は、

  • マウス感度の調整
  • 敵との当たり判定
  • エイムアシストのロジック

などが混ざりやすく、巨大なGodクラスになりがちです。

そこでこの記事では、「照準が敵に近づいたときだけマウス移動を減速させる」ことにだけ責任を持つ、シンプルなコンポーネント
AimAssist(エイムアシスト) を作っていきます。

【Unity】敵に吸い付くような狙いやすさ!「AimAssist」コンポーネント

AimAssist は、

  • カメラの中心(クロスヘア)から一定距離以内に敵がいるかを判定し
  • 近ければマウス感度を自動的に下げて「狙いやすく」し
  • 離れれば元の感度に戻す

という処理だけを担当するコンポーネントです。
実際のカメラ回転やマウス入力の取得は、別のコンポーネントに任せることで、役割をきれいに分離します。


フルコード:AimAssist.cs


using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// マウスベースのカメラ回転コンポーネント。
/// AimAssist からマウス感度を調整される前提の、シンプルな例です。
/// このスクリプトと AimAssist を同じ GameObject(カメラ)に付けて使います。
/// </summary>
[RequireComponent(typeof(Camera))]
public class MouseLook : MonoBehaviour
{
    [Header("マウス感度(基準値)")]
    [SerializeField] private float baseSensitivityX = 150f;
    [SerializeField] private float baseSensitivityY = 150f;

    [Header("縦回転の制限")]
    [SerializeField] private float minVerticalAngle = -80f;
    [SerializeField] private float maxVerticalAngle = 80f;

    // AimAssist から書き換えられる「現在の感度倍率」
    // 1.0f で標準感度、0.5f で半分、など
    [HideInInspector] public float sensitivityMultiplier = 1f;

    private float _xRotation;

    private void Start()
    {
        // カーソルをロック&非表示にしてFPSっぽい操作にする
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    private void Update()
    {
        // マウスの移動量を取得
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        // AimAssist が書き換えた倍率を反映
        float sensX = baseSensitivityX * sensitivityMultiplier;
        float sensY = baseSensitivityY * sensitivityMultiplier;

        // マウス移動量に感度を掛ける
        float deltaX = mouseX * sensX * Time.deltaTime;
        float deltaY = mouseY * sensY * Time.deltaTime;

        // 縦回転(ピッチ)
        _xRotation -= deltaY;
        _xRotation = Mathf.Clamp(_xRotation, minVerticalAngle, maxVerticalAngle);

        // カメラの回転を適用
        transform.localRotation = Quaternion.Euler(_xRotation, 0f, 0f);

        // 横回転(ヨー)は親オブジェクト(プレイヤー本体)を回転させる想定
        if (transform.parent != null)
        {
            transform.parent.Rotate(Vector3.up * deltaX);
        }
        else
        {
            // 親がない場合はカメラ自身を回す
            transform.Rotate(Vector3.up * deltaX, Space.World);
        }
    }
}

/// <summary>
/// エイムアシスト用のターゲットマーカー。
/// 敵キャラなどにこのコンポーネントを付けて、AimAssist の対象にします。
/// </summary>
public class AimAssistTarget : MonoBehaviour
{
    [Header("このターゲットの重要度(大きいほどアシストされやすい)")]
    [SerializeField] private float weight = 1f;

    public float Weight => weight;
}

/// <summary>
/// カメラ中心(クロスヘア)付近に敵(AimAssistTarget)が来たとき、
/// マウス感度を自動で下げてエイムを補助するコンポーネント。
/// MouseLook と同じ GameObject にアタッチして使います。
/// </summary>
[RequireComponent(typeof(Camera))]
[RequireComponent(typeof(MouseLook))]
public class AimAssist : MonoBehaviour
{
    [Header("ターゲット検出設定")]
    [SerializeField] private float maxAssistDistance = 50f;    // どれくらい先まで敵を検出するか
    [SerializeField] private float assistRadius = 0.05f;       // 画面中心からの許容距離(0〜1、Viewport座標)

    [Header("感度制御")]
    [SerializeField] [Range(0.1f, 1f)] private float minSensitivityMultiplier = 0.3f; // 一番強くアシストされたときの感度倍率
    [SerializeField] private float sensitivityLerpSpeed = 10f; // 感度を滑らかに変化させる速度

    [Header("デバッグ表示")]
    [SerializeField] private bool drawDebugRay = true;
    [SerializeField] private Color debugRayColor = Color.cyan;

    private Camera _camera;
    private MouseLook _mouseLook;

    // 現在の目標感度倍率(実際の感度は MouseLook 側で Time.deltaTime を使って追従)
    private float _targetMultiplier = 1f;

    private void Awake()
    {
        _camera = GetComponent<Camera>();
        _mouseLook = GetComponent<MouseLook>();
    }

    private void Update()
    {
        // 画面中心から前方に向かってレイを飛ばす
        Ray ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));

        if (drawDebugRay)
        {
            Debug.DrawRay(ray.origin, ray.direction * maxAssistDistance, debugRayColor);
        }

        // 近くのターゲットを探索
        AimAssistTarget bestTarget = FindBestTarget(ray, out float normalizedScreenDistance);

        if (bestTarget != null)
        {
            // ターゲットが近いほど感度を下げる
            // normalizedScreenDistance は 0(ど真ん中)〜 assistRadius(ギリギリ範囲内)
            float t = Mathf.InverseLerp(assistRadius, 0f, normalizedScreenDistance);
            // t: 0 => 範囲端、1 => ど真ん中
            _targetMultiplier = Mathf.Lerp(1f, minSensitivityMultiplier, t);
        }
        else
        {
            // ターゲットがいないときは感度を元に戻す
            _targetMultiplier = 1f;
        }

        // MouseLook 側の感度倍率を滑らかに補間
        _mouseLook.sensitivityMultiplier = Mathf.Lerp(
            _mouseLook.sensitivityMultiplier,
            _targetMultiplier,
            Time.deltaTime * sensitivityLerpSpeed
        );
    }

    /// <summary>
    /// 画面中心レイの近くにある AimAssistTarget を探し、
    /// 一番「狙いやすそうな」ターゲットを返す。
    /// </summary>
    private AimAssistTarget FindBestTarget(Ray centerRay, out float bestScreenDistance)
    {
        bestScreenDistance = float.MaxValue;
        AimAssistTarget bestTarget = null;

        // シーン中のすべての AimAssistTarget を取得
        AimAssistTarget[] targets = FindObjectsOfType<AimAssistTarget>();

        foreach (AimAssistTarget target in targets)
        {
            Vector3 targetPos = target.transform.position;

            // ターゲットまでの方向と距離
            Vector3 toTarget = targetPos - centerRay.origin;
            float distanceAlongRay = Vector3.Dot(toTarget, centerRay.direction);

            // 後ろ側にあるターゲットや、距離が遠すぎるものは無視
            if (distanceAlongRay < 0f || distanceAlongRay > maxAssistDistance)
            {
                continue;
            }

            // レイからターゲットまでの最近接点
            Vector3 closestPoint = centerRay.origin + centerRay.direction * distanceAlongRay;

            // レイからのオフセットが大きすぎるターゲットは無視(視線から外れている)
            float worldOffset = Vector3.Distance(closestPoint, targetPos);

            // worldOffset をスクリーン上の距離に変換するため、ターゲットのスクリーン位置を取得
            Vector3 screenPoint = _camera.WorldToViewportPoint(targetPos);

            // 画面外(Z<0 や 0〜1 の範囲外)は無視
            if (screenPoint.z <= 0f ||
                screenPoint.x < 0f || screenPoint.x > 1f ||
                screenPoint.y < 0f || screenPoint.y > 1f)
            {
                continue;
            }

            // 画面中心との距離(Viewport座標なので 0〜√2 程度)
            float screenDistance = Vector2.Distance(
                new Vector2(0.5f, 0.5f),
                new Vector2(screenPoint.x, screenPoint.y)
            );

            // 許容半径より外ならアシスト対象外
            if (screenDistance > assistRadius)
            {
                continue;
            }

            // ターゲットの「重み」を考慮して優先度を決める
            // 画面中心に近いもの&Weight が大きいものを優先
            float weightedScore = screenDistance / Mathf.Max(target.Weight, 0.01f);

            if (weightedScore < bestScreenDistance)
            {
                bestScreenDistance = screenDistance;
                bestTarget = target;
            }
        }

        return bestTarget;
    }
}

使い方の手順

  1. プレイヤーとカメラの準備
    • シーン内に Player 用の空オブジェクトを作成します。
    • その子として Main Camera を配置し、プレイヤーの目線位置に合わせます。
    • Main CameraMouseLookAimAssist をアタッチします。
      RequireComponent により、足りないものは自動で追加されます)
  2. 敵キャラに AimAssistTarget を付ける
    • 敵プレハブ(例:Enemy)に AimAssistTarget コンポーネントを追加します。
    • 「重要度(Weight)」は、ヘッドショット部位なら 2.0、胴体なら 1.0 など、狙わせたい部位ほど大きくしておくと良いです。
    • 敵のコライダーやアニメーションなどは通常どおり設定して構いません。
  3. AimAssist のパラメータを調整する
    • Max Assist Distance:エイムアシストが効く最大距離。FPS なら 30〜80 くらいから調整してみましょう。
    • Assist Radius:画面中心からどれくらい離れていてもアシスト対象にするか(0〜1)。
      0.05〜0.1 くらいにすると、クロスヘア付近でのみ自然にアシストが働きます。
    • Min Sensitivity Multiplier:ど真ん中に近いときの最低感度倍率(0.3 なら 30%まで減速)。
    • Sensitivity Lerp Speed:感度が切り替わる速さ。大きいほど「カチッ」と、小さいほど「ヌルッ」と変化します。
  4. 実際にプレイして確認する
    • ゲームを再生し、敵の近くをゆっくりなぞるようにマウスを動かしてみてください。
    • クロスヘアが敵に近づいたタイミングで、マウス感度がスッと落ちて「吸い付く」ような感覚があれば成功です。
    • 動く敵や動く床の上にいる敵にも同様に効くので、TPS や FPS、タレット操作など、さまざまなケースで使えます。

メリットと応用

この AimAssist コンポーネントを導入することで、

  • カメラ回転ロジック(MouseLook)とエイムアシストロジック(AimAssist)が分離されるため、
    「マウス感度のチューニング」と「アシストの強さのチューニング」を別々に考えられます。
  • 敵側の実装もシンプルで、狙わせたいオブジェクトに AimAssistTarget を付けるだけです。
  • プレハブ化しておけば、新しい敵を追加するときもコンポーネントを付けるだけでエイムアシスト対応にできます。
  • レベルデザイン時に「ここはヘッドショットを狙わせたい」「ここは大雑把でいい」など、
    ターゲットごとに Weight を変えるだけで、難易度や気持ちよさを簡単に調整できます。

また、エイムアシストの「効き方」を変えることで、ゲームのプレイフィールを大きく変えられます。
たとえば、TPS ではかなり強めに効かせてカジュアルに、ハードコアFPSではかなり弱くして自力エイム重視に…といった調整がしやすいです。

改造案:右クリック中だけエイムアシストを有効にする

「ADS(覗き込み)中だけエイムアシストを効かせたい」というケースも多いので、
簡単な改造例として、AimAssist 内に以下のような関数を追加してみましょう。


/// <summary>
/// 右クリック(マウスボタン1)を押している間だけ
/// エイムアシストを有効にする例。
/// Update() の先頭で呼び出す想定です。
/// </summary>
private bool IsAssistEnabled()
{
    // 右クリック中のみ true
    return Input.GetMouseButton(1);
}

そして Update() の先頭で、


private void Update()
{
    if (!IsAssistEnabled())
    {
        // アシスト無効時は徐々に感度を 1.0 に戻すだけ
        _targetMultiplier = 1f;
        _mouseLook.sensitivityMultiplier = Mathf.Lerp(
            _mouseLook.sensitivityMultiplier,
            _targetMultiplier,
            Time.deltaTime * sensitivityLerpSpeed
        );
        return;
    }

    // ここから下は元の Update() の処理
    Ray ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
    ...
}

といった形で条件分岐を入れると、「覗き込み中だけ強めのアシストを効かせる」FPSらしい挙動に発展させられます。
このように、小さな責務ごとにコンポーネントを分割しておくと、後からの改造・調整がとてもやりやすくなりますね。