Unityの学習を進めていくと、つい何でもかんでも Update() に書いてしまいがちですよね。プレイヤーの入力、移動、攻撃判定、HP管理、UI更新……すべてを1つの巨大なスクリプトで処理してしまうと、次のような問題が出てきます。
- どこで何をしているのか分かりにくく、バグ調査が大変
- 1つの機能だけ変更したいのに、他の処理に影響してしまう
- Prefabを再利用しづらく、プロジェクトが大きくなるほど破綻しやすい
特に「HPの自動回復」のような機能は、ついプレイヤーの大きなスクリプトに直書きしがちです。しかし、HP管理と回復ロジックは本来別々の責務ですよね。そこでこの記事では、
- HPそのものを管理する HealthManager
- 一定時間ごとにHPを回復させる RegenEffect
という2つのコンポーネントに分けて、シンプルかつ再利用しやすい「自動回復コンポーネント」を作っていきます。
【Unity】放っておくだけでHPがじわじわ回復!「RegenEffect」コンポーネント
ここでは、次のような仕様の RegenEffect を実装します。
- 一定間隔ごとに親オブジェクトの
HealthManagerを呼び出す - 1回ごとに指定量だけHPを回復する
- HPが最大値に達している場合は、無駄な処理をしない
- ゲーム開始時に自動で回復を開始するかどうかを選べる
- スクリプトから一時停止・再開ができる
また、この記事だけで完結するように、最低限のHealthManagerコンポーネントも一緒に掲載します。
HealthManager(HP管理コンポーネント)
using UnityEngine;
/// <summary>シンプルなHP管理コンポーネント</summary>
public class HealthManager : MonoBehaviour
{
[Header("HP 設定")]
[SerializeField] private int maxHealth = 100; // 最大HP
[SerializeField] private int currentHealth = 100; // 現在HP
/// <summary>現在のHP(読み取り専用)</summary>
public int CurrentHealth => currentHealth;
/// <summary>最大HP(読み取り専用)</summary>
public int MaxHealth => maxHealth;
/// <summary>現在HPが0以下かどうか</summary>
public bool IsDead => currentHealth <= 0;
private void OnValidate()
{
// インスペクター変更時に値をクランプしておく
if (maxHealth < 1) maxHealth = 1;
currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
}
/// <summary>ダメージを受けてHPを減らす</summary>
public void TakeDamage(int amount)
{
if (amount <= 0) return;
currentHealth -= amount;
currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
// ここで死亡処理などを呼び出してもOK
// if (IsDead) { ... }
}
/// <summary>HPを回復する(最大値を超えない)</summary>
public void Heal(int amount)
{
if (amount <= 0) return;
if (currentHealth >= maxHealth) return; // すでに満タンなら何もしない
currentHealth += amount;
currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
}
/// <summary>HPを全回復する</summary>
public void HealFull()
{
currentHealth = maxHealth;
}
}
RegenEffect(自動回復コンポーネント)フルコード
using System.Collections;
using UnityEngine;
/// <summary>
/// 一定時間ごとに親の HealthManager を呼び出してHPを自動回復するコンポーネント。
/// 「HPを持つコンポーネント」と「回復ロジック」を分離することで、
/// プレイヤー・敵・回復する床などに簡単に再利用できるようにしています。
/// </summary>
[DisallowMultipleComponent] // 同じオブジェクトに複数付くのを防ぐ
[RequireComponent(typeof(HealthManager))] // 同じオブジェクトに HealthManager が必須
public class RegenEffect : MonoBehaviour
{
[Header("回復設定")]
[SerializeField] private int healAmount = 1; // 1回ごとの回復量
[SerializeField] private float intervalSeconds = 1.0f; // 回復間隔(秒)
[Header("動作設定")]
[SerializeField] private bool startOnAwake = true; // Awake 時に自動で開始するか
[SerializeField] private bool stopWhenFull = true; // HP満タンなら処理をスキップするか
// 参照キャッシュ
private HealthManager _healthManager;
private Coroutine _regenRoutine;
private bool _isPaused = false;
private void Awake()
{
// RequireComponent により、必ず同じ GameObject に HealthManager が付いている前提
_healthManager = GetComponent<HealthManager>();
// 安全策として null チェックも入れておく
if (_healthManager == null)
{
Debug.LogError(
$"[RegenEffect] HealthManager が見つかりません。" +
$" GameObject: {gameObject.name}",
this
);
}
}
private void OnEnable()
{
// 有効化されたタイミングで自動開始するかどうか
if (startOnAwake)
{
StartRegen();
}
}
private void OnDisable()
{
// 無効化時は必ずコルーチンを止めておく
StopRegen();
}
/// <summary>
/// 自動回復を開始する。すでに動作中なら何もしない。
/// </summary>
public void StartRegen()
{
if (_healthManager == null) return;
if (_regenRoutine != null) return; // すでに回復中
_isPaused = false;
_regenRoutine = StartCoroutine(RegenLoop());
}
/// <summary>
/// 自動回復を停止する。再開するには StartRegen() を呼ぶ。
/// </summary>
public void StopRegen()
{
if (_regenRoutine != null)
{
StopCoroutine(_regenRoutine);
_regenRoutine = null;
}
}
/// <summary>
/// 一時停止フラグを切り替える(コルーチンは生きたまま処理だけ止める)
/// </summary>
public void SetPaused(bool paused)
{
_isPaused = paused;
}
/// <summary>
/// 一時停止状態かどうか
/// </summary>
public bool IsPaused => _isPaused;
/// <summary>
/// コルーチン本体:一定間隔でHPを回復するループ
/// </summary>
private IEnumerator RegenLoop()
{
// マイナスやゼロの設定はバグの元なので、ここで保険をかけておく
if (intervalSeconds <= 0f)
{
intervalSeconds = 0.1f;
Debug.LogWarning(
"[RegenEffect] intervalSeconds が 0 以下だったため 0.1 に補正しました。",
this
);
}
if (healAmount <= 0)
{
healAmount = 1;
Debug.LogWarning(
"[RegenEffect] healAmount が 0 以下だったため 1 に補正しました。",
this
);
}
// 無限ループで一定間隔ごとに処理
while (true)
{
// 一定時間待つ
yield return new WaitForSeconds(intervalSeconds);
// 無効状態・一時停止中・HealthManager無しならスキップ
if (!isActiveAndEnabled) continue;
if (_isPaused) continue;
if (_healthManager == null) continue;
// HP満タン時に処理をスキップするかどうか
if (stopWhenFull && _healthManager.CurrentHealth >= _healthManager.MaxHealth)
{
continue;
}
// 実際の回復処理
_healthManager.Heal(healAmount);
}
}
// --- デバッグ・調整用のインスペクタ補助(任意) ---
#if UNITY_EDITOR
private void OnValidate()
{
if (healAmount < 0) healAmount = 0;
if (intervalSeconds < 0f) intervalSeconds = 0f;
}
#endif
}
使い方の手順
ここでは、プレイヤーキャラクターに自動回復を付ける例をベースにしつつ、敵や「回復する床」への応用も含めて手順を説明します。
手順①:HealthManager をアタッチする
- シーン内のプレイヤーの GameObject を選択します(例:
Player)。 Add ComponentボタンからHealthManagerを追加します。- インスペクターで以下の値を設定します。
- Max Health: 100(お好みで調整)
- Current Health: 100(初期HP)
この時点で、プレイヤーは TakeDamage() や Heal() を通じてHPを管理できるようになっています。
手順②:RegenEffect をアタッチする
- 同じ
PlayerGameObject に、RegenEffectコンポーネントを追加します。 - インスペクターで以下の値を設定します。
- Heal Amount: 2(1回ごとの回復量。例: 2HP)
- Interval Seconds: 1.0(1秒ごとに回復)
- Start On Awake: チェックを入れる(ゲーム開始時から自動回復)
- Stop When Full: チェックを入れる(HP満タンなら回復処理をスキップ)
この状態でゲームを再生し、別の仕組み(敵の攻撃やデバッグ用スクリプトなど)で TakeDamage() を呼ぶと、一定時間ごとにじわじわHPが回復していくのが確認できます。
手順③:敵キャラクターへの応用
同じ仕組みを敵にも使いたい場合は、敵Prefabに対して同じようにコンポーネントを追加するだけです。
- 敵のPrefab(例:
EnemyGoblin)を開きます。 HealthManagerを追加し、Max Health = 50などに設定します。RegenEffectを追加し、例えば- Heal Amount: 1
- Interval Seconds: 3.0(3秒ごとに1回復)
- Start On Awake: チェックを入れる
これで、敵は時間経過で少しずつ体力を取り戻すようになります。ボスだけ回復量を増やす、特定の敵だけ自動回復をオフにする、といった調整もPrefab単位で楽にできます。
手順④:動く床(回復ゾーン)への応用
「床の上に乗っている間だけ自動回復する」ようなギミックも、責務を分けて組み合わせればシンプルに実現できます。例として、プレイヤーに RegenEffect を常に付けておき、回復ゾーンに入ったときだけ有効化する方法を紹介します。
- プレイヤーの
RegenEffectの Start On Awake のチェックを外しておきます(最初はオフ)。 - 回復ゾーン用の GameObject(例:
HealingZone)を作成し、BoxColliderを追加して Is Trigger にチェックを入れます。 - 以下のような簡単なスクリプトを
HealingZoneに追加します。
using UnityEngine;
/// <summary>トリガーに入っている間だけ RegenEffect を有効化する例</summary>
public class HealingZone : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
var regen = other.GetComponent<RegenEffect>();
if (regen != null)
{
regen.StartRegen();
}
}
private void OnTriggerExit(Collider other)
{
var regen = other.GetComponent<RegenEffect>();
if (regen != null)
{
regen.StopRegen();
}
}
}
これで、「この床の上にいる間だけ自動回復」というギミックが完成します。
プレイヤーだけでなく、敵にも同じ仕組みを使えるのがコンポーネント指向の強みですね。
メリットと応用
RegenEffect を HealthManager から分離したことで、次のようなメリットがあります。
- 責務が明確:HealthManager は「HPの増減・最大値管理」だけに集中し、RegenEffect は「時間経過による回復」だけを担当します。
- Prefabの再利用性が高い:プレイヤー、敵、回復するオブジェクトなど、HPを持つあらゆるオブジェクトに簡単に自動回復を付け外しできます。
- レベルデザインが楽:
- 「この敵だけ回復量を多く」「このステージだけ回復間隔を長く」などをインスペクターから調整できます。
- 回復ゾーンや回復アイテムなど、ギミック側から
StartRegen()/StopRegen()を呼ぶだけで挙動を制御できます。
- テストしやすい:HPの挙動に問題があるのか、回復ロジックに問題があるのかを切り分けやすくなります。
プロジェクトが大きくなってくると、「とりあえず1つのスクリプトに全部書く」やり方は必ず限界が来ます。今回のように「HPを持つ」「時間で回復する」といった機能をコンポーネントに分解しておくと、あとからの仕様変更や新ギミック追加がかなり楽になります。
改造案:ダメージを受けたら一定時間だけ回復を止める
よくあるゲーム仕様として、「ダメージを受けた直後は一定時間だけ自動回復が止まる」というものがあります。これも、RegenEffect に小さな関数を1つ追加するだけで実現できます。
public void PauseForSeconds(float duration)
{
// すでに一時停止中でも、とりあえず再度コルーチンを走らせる
StartCoroutine(PauseRoutine(duration));
}
private IEnumerator PauseRoutine(float duration)
{
SetPaused(true);
yield return new WaitForSeconds(duration);
SetPaused(false);
}
あとは、ダメージ処理を行っているスクリプト側で
var regen = GetComponent<RegenEffect>();
if (regen != null)
{
regen.PauseForSeconds(3f); // 3秒間だけ自動回復を停止
}
のように呼び出せば、「被弾後しばらくは回復しない」挙動になります。
このように、小さな責務のコンポーネント同士を組み合わせていくと、複雑なゲーム仕様も無理なく実現できますね。
