Unityを触り始めてしばらくすると、つい何でもかんでも Update() に書いてしまいがちですよね。
プレイヤーの入力処理、移動処理、カメラ制御、アイテム取得判定…すべて一つの巨大スクリプトに詰め込むと、次のような問題が出てきます。
- 少し仕様を変えたいだけなのに、巨大な
Update()から該当箇所を探すのが大変 - プレイヤーと敵、アイテムなどの処理が密結合になり、再利用がしづらい
- バグが出たときに「どの処理が悪さしているのか」が特定しにくい
そこでおすすめなのが、「機能ごとに小さなコンポーネントを分ける」設計です。
今回はその一例として、プレイヤーが近くのコインだけをグイッと吸い寄せるコンポーネント
「CoinMagnet(コイン磁石)」 を実装してみましょう。
「アイテムなら何でも吸い寄せる」汎用コンポーネントではなく、お金(コイン)専用に特化させることで、責務を小さく・明確にし、プレハブごとの役割もハッキリさせる狙いがあります。
【Unity】近くのコインだけを強力吸引!「CoinMagnet」コンポーネント
このコンポーネントは、以下のような動きをします。
- プレイヤー(またはコインを吸い寄せたいオブジェクト)にアタッチ
- 指定した半径内に存在する「コイン」だけを検索
- コインに
Rigidbodyがあれば、プレイヤー方向へ力を加えて吸い寄せる - コインのタグでフィルタリングするので、他のアイテムには影響しない
では、Unity6(C#)での実装コードを見ていきましょう。
フルコード:CoinMagnet.cs
using UnityEngine;
/// <summary>
/// プレイヤー周囲の「コイン」だけを強力に吸い寄せるコンポーネント。
/// - Coinタグの付いたオブジェクトのみを対象にする
/// - 一定半径内のコインに対して、プレイヤー方向への力を加える
/// - Rigidbody付きのコインを想定
/// </summary>
public class CoinMagnet : MonoBehaviour
{
// コインを探す半径(プレイヤー中心)
[SerializeField]
private float magnetRadius = 5f;
// コインを引き寄せる強さ(力の大きさ)
[SerializeField]
private float magnetForce = 20f;
// コインの移動速度に上限を付けたい場合(0以下なら制限なし)
[SerializeField]
private float maxCoinSpeed = 10f;
// コインを判定するためのLayerMask(0なら全レイヤー対象)
// コイン用のレイヤーを作っておくと、パフォーマンスや誤判定防止に有効
[SerializeField]
private LayerMask coinLayerMask = 0;
// コインとして扱うタグ名
// ここに設定したタグ以外は吸い寄せない
[SerializeField]
private string coinTag = "Coin";
// 物理演算の更新タイミングで処理するため、FixedUpdateを使用
private void FixedUpdate()
{
// 半径内のコライダーをすべて取得
Collider[] hitColliders;
if (coinLayerMask.value != 0)
{
// LayerMaskが設定されている場合、対象レイヤーだけを検索
hitColliders = Physics.OverlapSphere(transform.position, magnetRadius, coinLayerMask);
}
else
{
// レイヤー指定なしの場合、すべてのレイヤーから検索
hitColliders = Physics.OverlapSphere(transform.position, magnetRadius);
}
// 見つかったコライダーごとに処理
foreach (var hit in hitColliders)
{
// タグがCoinでなければスキップ
if (!hit.CompareTag(coinTag))
{
continue;
}
// Rigidbodyを取得(コイン側に付いていることを想定)
Rigidbody coinRigidbody = hit.attachedRigidbody;
if (coinRigidbody == null)
{
// Rigidbodyが無いと力を加えられないのでスキップ
continue;
}
// コインの位置から、このコンポーネントの位置(プレイヤーなど)への方向ベクトル
Vector3 direction = (transform.position - coinRigidbody.position);
// 方向ベクトルの長さ(距離)が0に近い場合は、正規化でNaNになるのを防ぐ
if (direction.sqrMagnitude < 0.0001f)
{
continue;
}
// 正規化して「向き」だけを取り出す
Vector3 normalizedDirection = direction.normalized;
// プレイヤー方向へ力を加える
// Accelerationモードにすることで、質量に関係なく同じ加速度で引き寄せられる
coinRigidbody.AddForce(normalizedDirection * magnetForce, ForceMode.Acceleration);
// 速度の上限を設定したい場合
if (maxCoinSpeed > 0f)
{
LimitCoinSpeed(coinRigidbody);
}
}
}
/// <summary>
/// コインの速度がmaxCoinSpeedを超えないように制限する。
/// </summary>
/// <param name="coinRigidbody">速度制限をかけたいRigidbody</param>
private void LimitCoinSpeed(Rigidbody coinRigidbody)
{
Vector3 velocity = coinRigidbody.velocity;
float speed = velocity.magnitude;
// すでに上限以下なら何もしない
if (speed <= maxCoinSpeed)
{
return;
}
// 速度を上限にクランプする
Vector3 clampedVelocity = velocity.normalized * maxCoinSpeed;
coinRigidbody.velocity = clampedVelocity;
}
/// <summary>
/// Sceneビュー上で磁石の有効範囲を可視化するためのGizmo描画。
/// </summary>
private void OnDrawGizmosSelected()
{
// 選択中のときだけ、半透明の球で範囲を表示
Gizmos.color = new Color(1f, 0.85f, 0f, 0.25f); // 黄色っぽい色
Gizmos.DrawSphere(transform.position, magnetRadius);
// 枠線も表示しておくと見やすい
Gizmos.color = new Color(1f, 0.7f, 0f, 0.9f);
Gizmos.DrawWireSphere(transform.position, magnetRadius);
}
}
使い方の手順
ここでは、プレイヤーが近くのコインだけを吸い寄せる例で説明します。
-
コイン用プレハブを準備する
- 3Dオブジェクト(Sphereなど)を作成し、「Coin」などの名前を付ける
Rigidbodyコンポーネントを追加する(Use Gravity は好みでオン/オフ)Collider(SphereColliderなど)を追加する- インスペクターで Tag を「Coin」 に設定する(なければ Tag を新規作成)
- 必要に応じて Layer を「Coin」用に作成 して割り当てる
- プレハブ化しておくと、シーンに量産しやすくて便利です
-
プレイヤーオブジェクトに CoinMagnet をアタッチ
- プレイヤー(またはコインを吸い寄せたい中心オブジェクト)を選択
Add ComponentからCoinMagnetを追加- インスペクター上で以下の値を調整
Magnet Radius:コインを吸い寄せたい範囲(例:5〜10)Magnet Force:吸引力の強さ(例:20〜50)Max Coin Speed:コインの最大スピード(0なら制限なし)Coin Layer Mask:コイン用レイヤーを使っているなら、そのレイヤーを指定Coin Tag:コインにつけたタグ名(デフォルト「Coin」)
-
シーンにコインをばらまく
- 作成したコインプレハブをシーン内に複数配置する
- プレイヤーから少し離れた位置にも置いて、吸い寄せられる様子を確認できるようにする
-
再生して挙動を確認する
- ゲームを再生し、プレイヤーをコインの近くへ移動させる
- Magnet Radius の範囲に入ったコインだけが、プレイヤーに向かってスーッと寄ってくるはずです
- 吸引が弱い/強すぎる場合は、Magnet Force と Max Coin Speed を調整しましょう
応用例として、以下のような使い方もできます。
- 敵が落としたコインだけを吸い寄せる
敵の足元にCoinMagnetを付けておき、倒れたときに有効化すると、
敵の周りに散らばったコインが敵の位置に集まり、まとまった塊としてプレイヤーに拾わせる…といった演出ができます。 - 動く床が通過したコインを回収する
移動する足場にCoinMagnetを付けておけば、足場の下に落ちそうなコインを自動的に吸い寄せて、画面外に消えるのを防げます。
メリットと応用
CoinMagnet をプレイヤーや特定のオブジェクトに付けておくだけで、コインの吸い寄せ機能を簡単に再利用できます。
ポイントは、あくまで「コインを引き寄せることだけ」に責務を絞っているところです。
- プレイヤーの移動ロジックとは分離されているので、別キャラやペットにも簡単に流用できる
- 「コイン専用」のため、ポーションや武器など他のアイテムロジックと混ざらない
- プレハブ側(コイン)とプレイヤー側(CoinMagnet)をそれぞれ独立してチューニングできる
- レベルデザイナーは、「このステージだけ磁力を強くしたい」といった調整をインスペクターから行える
こうした小さなコンポーネント単位で機能を分割しておくと、プレハブ管理やレベルデザインが格段に楽になります。
「このエリアはコインの吸い寄せ範囲を広くして気持ちよく回収させよう」「このボス戦はあえて範囲を狭くして難しめにしよう」といった調整を、スクリプトを書き換えずにインスペクターだけで完結できます。
改造案:プレイヤーがダメージを受けたら一時的に磁力オフにする
ゲームによっては、「ダメージを受けたら一時的にコイン磁石が無効化される」といった仕様を入れたいこともありますね。
その場合、以下のようなメソッドを CoinMagnet に追加し、ダメージ処理側から呼び出す形にすると、責務分離を保ちつつ連携できます。
/// <summary>
/// 一定時間だけコイン磁石を無効化する。
/// ダメージ時などに外部から呼び出して使う想定。
/// </summary>
/// <param name="disableDuration">無効にしておく時間(秒)</param>
public void DisableMagnetTemporarily(float disableDuration)
{
// すでにコルーチンが動いている場合は止めてから開始するとより安全
StopAllCoroutines();
StartCoroutine(DisableMagnetCoroutine(disableDuration));
}
private System.Collections.IEnumerator DisableMagnetCoroutine(float duration)
{
// コンポーネント自体を無効化
enabled = false;
yield return new WaitForSeconds(duration);
// 元に戻す
enabled = true;
}
このように、「コイン磁石」という1つのはっきりした役割に絞ったコンポーネントを作っておくと、
挙動を理解しやすく、他の仕組みと組み合わせて拡張しやすい構成になります。
巨大な Update() に詰め込むのではなく、小さなコンポーネントを積み上げていくスタイルを意識していきましょう。
