Unityを触り始めたころは、プレイヤーの移動もジャンプも当たり判定も、つい Update() に全部書いてしまいがちですよね。
動けば一旦満足なのですが、あとから「磁石ギミックを追加したい」「特定のオブジェクトだけ引き寄せたい」といった要望が出てくると、一枚岩の巨大スクリプトが一気に足かせになります。
そこで今回は、「磁石の振る舞い」だけに責務を絞ったコンポーネント MagnetBlock を作ってみましょう。
ON/OFF の切り替えで、周囲の「鉄製オブジェクト」を引き寄せたり、逆に反発させたりできるコンポーネントです。
【Unity】引き寄せ&反発でギミック量産!「MagnetBlock」コンポーネント
MagnetBlock は、半径・力の強さ・極性(引き寄せ / 反発)などをインスペクターから調整できる、小さな独立コンポーネントです。
「磁石ブロックをここに置くだけ」で、レベルデザインの幅が一気に広がります。
フルコード:MagnetBlock.cs
using System.Collections.Generic;
using UnityEngine;
/// <summary>磁石の極性(引き寄せ or 反発)</summary>
public enum MagnetPolarity
{
Attract, // 引き寄せ
Repel // 反発
}
/// <summary>
/// 磁石ブロック
/// ON のとき、周囲の「磁力対象レイヤー」に属する Rigidbody を引き寄せ or 反発する。
/// ・Update() ではなく FixedUpdate() で物理挙動を扱う
/// ・責務は「磁力をかけること」だけに限定
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider))]
public class MagnetBlock : MonoBehaviour
{
[Header("基本設定")]
[Tooltip("磁石が有効かどうか")]
[SerializeField] private bool _isOn = true;
[Tooltip("磁石の極性(引き寄せ or 反発)")]
[SerializeField] private MagnetPolarity _polarity = MagnetPolarity.Attract;
[Tooltip("磁力が届く最大半径")]
[SerializeField] private float _radius = 5f;
[Tooltip("力の強さ(大きいほど強く引き寄せ/反発する)")]
[SerializeField] private float _force = 20f;
[Tooltip("距離が近いほど強く、遠いほど弱くするか")]
[SerializeField] private bool _useDistanceFalloff = true;
[Tooltip("最小距離。これより近いと距離0扱いによる暴走を防ぐ")]
[SerializeField] private float _minDistance = 0.2f;
[Header("対象フィルタリング")]
[Tooltip("磁力の影響を受けるレイヤー")]
[SerializeField] private LayerMask _magneticLayerMask;
[Tooltip("このタグを持つオブジェクトのみ磁力の対象にする(空ならタグ無視)")]
[SerializeField] private string _requiredTag = "";
[Header("デバッグ表示")]
[Tooltip("シーンビューに磁力範囲を表示するか")]
[SerializeField] private bool _drawGizmos = true;
[Tooltip("磁力範囲の色(ON時)")]
[SerializeField] private Color _gizmoColorOn = new Color(0.2f, 0.8f, 1f, 0.3f);
[Tooltip("磁力範囲の色(OFF時)")]
[SerializeField] private Color _gizmoColorOff = new Color(0.5f, 0.5f, 0.5f, 0.15f);
// 物理演算で使い回すバッファ(GC削減用)
private readonly Collider[] _overlapResults = new Collider[32];
/// <summary>外部から磁石の ON/OFF を切り替えるためのプロパティ</summary>
public bool IsOn
{
get => _isOn;
set => _isOn = value;
}
/// <summary>外部から極性を変更するためのプロパティ</summary>
public MagnetPolarity Polarity
{
get => _polarity;
set => _polarity = value;
}
private void Reset()
{
// 初期設定を少しだけ親切にしておく
// 「鉄製オブジェクト」レイヤーを想定して、デフォルトでは Everything にしておく
_radius = 5f;
_force = 20f;
_magneticLayerMask = Physics.DefaultRaycastLayers;
// コライダーはトリガー推奨(プレイヤーがぶつかるブロックなら別途コライダーを追加)
var col = GetComponent<Collider>();
col.isTrigger = true;
}
private void FixedUpdate()
{
if (!_isOn) return;
ApplyMagneticForce();
}
/// <summary>
/// 周囲の Rigidbody に磁力を適用する
/// </summary>
private void ApplyMagneticForce()
{
Vector3 center = transform.position;
// OverlapSphereNonAlloc でヒットしたコライダーをバッファに詰める(GCを出さない)
int hitCount = Physics.OverlapSphereNonAlloc(
center,
_radius,
_overlapResults,
_magneticLayerMask,
QueryTriggerInteraction.Collide
);
for (int i = 0; i < hitCount; i++)
{
Collider col = _overlapResults[i];
if (col == null) continue;
// 自分自身は無視
if (col.attachedRigidbody == null) continue;
if (col.attachedRigidbody.gameObject == gameObject) continue;
// タグ指定がある場合はフィルタリング
if (!string.IsNullOrEmpty(_requiredTag) && !col.CompareTag(_requiredTag))
{
continue;
}
Rigidbody rb = col.attachedRigidbody;
// 位置ベクトルを計算
Vector3 dir = (rb.worldCenterOfMass - center);
float distance = dir.magnitude;
if (distance <= 0f)
{
// 完全に同じ位置にいる場合はランダムな方向に少しだけ押し出す
dir = Random.onUnitSphere;
distance = _minDistance;
}
// 最小距離以下はクランプ
distance = Mathf.Max(distance, _minDistance);
// 方向を正規化
dir /= distance;
// 極性に応じて方向を反転
// Attract: 磁石中心に向かうベクトル
// Repel: 磁石中心から離れるベクトル
Vector3 forceDir = (_polarity == MagnetPolarity.Attract) ? -dir : dir;
// 距離減衰を計算(0〜1)
float falloff = 1f;
if (_useDistanceFalloff)
{
// シンプルに線形減衰(近いほど 1 に近づき、遠いほど 0 に近づく)
falloff = 1f - Mathf.Clamp01(distance / _radius);
}
// 最終的な力のベクトル
Vector3 force = forceDir * (_force * falloff);
// ForceMode.Acceleration にすることで質量に依存しない動きにできる
rb.AddForce(force, ForceMode.Acceleration);
}
// 次フレームのために配列をクリア(null 代入は必須ではないが、デバッグしやすくするため)
for (int i = 0; i < hitCount; i++)
{
_overlapResults[i] = null;
}
}
/// <summary>
/// シーンビューに磁力範囲を描画
/// </summary>
private void OnDrawGizmosSelected()
{
if (!_drawGizmos) return;
Gizmos.color = _isOn ? _gizmoColorOn : _gizmoColorOff;
Gizmos.DrawSphere(transform.position, _radius);
}
// --- 以下は任意で使える簡単な公開メソッド ---
/// <summary>磁石を ON にする</summary>
public void TurnOn()
{
_isOn = true;
}
/// <summary>磁石を OFF にする</summary>
public void TurnOff()
{
_isOn = false;
}
/// <summary>磁石の ON/OFF をトグルする</summary>
public void Toggle()
{
_isOn = !_isOn;
}
}
使い方の手順
ここでは「鉄製の箱を引き寄せる磁石ブロック」として使う例で説明します。
-
磁石ブロックのプレハブを作る
- 空の GameObject を作成し、名前を
MagnetBlockにする。 - 位置・スケールを調整し、見た目用の MeshRenderer や SpriteRenderer を付ける(任意)。
BoxColliderなどの Collider を追加し、isTriggerを ON にしておくと扱いやすいです。- 上記の
MagnetBlock.csをアタッチする。 - 必要ならプレハブ化しておき、ステージにポンポン配置できるようにしておきましょう。
- 空の GameObject を作成し、名前を
-
「鉄製オブジェクト」側の設定
- 磁石の影響を受けさせたいオブジェクト(例: 鉄の箱、鉄球など)に
Rigidbodyを追加する。 - 同じく
BoxColliderやSphereColliderを付けておく。 - レイヤーを新規作成して
Metalなどにし、そのオブジェクトに設定する。 - タグも
Metalにしておくと、_requiredTagでさらにフィルタリングできます。
- 磁石の影響を受けさせたいオブジェクト(例: 鉄の箱、鉄球など)に
-
MagnetBlock のインスペクター設定
MagnetBlock を選択し、以下を調整します。Is On: 最初から磁石を有効にするか。Polarity:Attractで引き寄せ、Repelで反発。Radius: 効き目の範囲。シーンビューのギズモを見ながら調整しましょう。Force: 力の強さ。Rigidbody のDragや質量に応じて調整。Magnetic Layer Mask: 先ほど作ったMetalレイヤーだけをチェック。Required Tag:Metalと入力すれば、タグも一致するものだけが対象になります。
-
具体的な使用例
いくつかの典型的なパターンを挙げます。-
プレイヤーが近づくと ON になる磁石床
MagnetBlock の子にTriggerZone用の Collider を置き、プレイヤーが入ったらTurnOn()、出たらTurnOff()を呼ぶ簡単なスクリプトを付けると、
「近づいたときだけ鉄箱が寄ってくる床」が作れます。 -
敵を反発させる防御フィールド
MagnetBlock のPolarityをRepelにし、対象レイヤーを「Enemy」に設定すると、
「近づく敵を物理的に押し返すバリアオブジェクト」として機能します。 -
動く磁石プラットフォーム
移動する足場に MagnetBlock をアタッチしておけば、足場の移動に合わせて鉄製オブジェクトが引きずられるようなギミックも簡単に作れます。
-
プレイヤーが近づくと ON になる磁石床
メリットと応用
MagnetBlock のように「磁力」という1つの責務に絞ったコンポーネントにしておくと、以下のようなメリットがあります。
- プレハブ化しやすい:磁石ブロックの見た目や大きさを変えたバリエーションを、スクリプトは共通のまま量産できます。
- レベルデザインが楽:マップ上に MagnetBlock プレハブをポンポン置くだけで、「ここは引き寄せゾーン」「ここは反発ゾーン」といったギミックを直感的に配置できます。
- 他のコンポーネントと組み合わせやすい:ON/OFF を制御するスイッチ、タイマー、トリガーゾーンなどは別コンポーネントとして作り、MagnetBlock の
TurnOn()/TurnOff()を呼ぶだけで連携できます。 - テストしやすい:磁石の挙動だけを単体で確認・調整できるので、「プレイヤーのバグなのか、磁石のバグなのか」が切り分けやすくなります。
さらに、少しコードを足すだけで「パルス状に ON/OFF する磁石」や「時間経過で極性が反転する磁石」など、応用も簡単です。
例えば「一定間隔で極性を切り替える磁石」にしたい場合は、以下のようなメソッドを追加できます。
// MagnetBlock クラスの中に追記する改造案の一例
[Header("オプション: 自動極性反転")]
[SerializeField] private bool _autoFlipPolarity = false;
[SerializeField] private float _flipInterval = 2f;
private float _flipTimer;
private void Update()
{
// 既存の物理処理は FixedUpdate() のまま、
// 極性のタイマー制御だけを Update() で行う
if (!_autoFlipPolarity) return;
_flipTimer += Time.deltaTime;
if (_flipTimer >= _flipInterval)
{
_flipTimer = 0f;
// 極性を反転
_polarity = (_polarity == MagnetPolarity.Attract)
? MagnetPolarity.Repel
: MagnetPolarity.Attract;
}
}
このように、小さな責務のコンポーネントを積み重ねていくと、後からの拡張や改造がとても楽になります。
MagnetBlock をベースに、自分のゲームに合った磁石ギミックをどんどん増やしていきましょう。




