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