Unityを触り始めた頃にやりがちなのが、Update() の中で「入力チェック」「移動」「アニメーション」「クールダウン管理」などを全部まとめて書いてしまうパターンですね。
動き始めるまでは早いのですが、スキルが増えるたびに if (canUseSkillA) { ... } / if (canUseSkillB) { ... } が雪だるま式に増えて、気づけば巨大なGodクラスが完成… という流れになりがちです。
この記事では、その中でも「スキルのクールダウン管理」だけをきれいに切り出した 「CooldownTimer」コンポーネント を紹介します。
「スキル使用可否」を bool で返すシンプルなコンポーネントに分離しておくことで、スキル側のコードは「撃つかどうか」だけに集中できるようになります。
【Unity】スキルの再使用待ちをスッキリ管理!「CooldownTimer」コンポーネント
フルコード:CooldownTimer.cs
using UnityEngine;
namespace Samples.Cooldown
{
/// <summary>
/// スキルなどのクールダウン(再使用待ち時間)を管理するコンポーネント。
///
/// ・「今スキルを使えるか?」を bool で返す
/// ・「スキルを使った」タイミングで StartCooldown() を呼ぶ
/// ・時間経過は Update() で自動的に進める
///
/// 単体で完結しており、どんなスキルにも再利用できます。
/// </summary>
public class CooldownTimer : MonoBehaviour
{
[Header("クールダウン設定")]
[Tooltip("スキル再使用までの時間(秒)")]
[SerializeField] private float _cooldownDuration = 3f;
[Header("開始時の状態")]
[Tooltip("ゲーム開始時にすでに使用可能な状態にするか")]
[SerializeField] private bool _startReady = true;
// 現在の残りクールダウン時間(0以下なら使用可能)
private float _remainingTime = 0f;
/// <summary>
/// 現在スキルが使用可能かどうか。
///
/// ・true なら「いつでも使ってOK」
/// ・false なら「まだクールダウン中」
///
/// 外部からは読み取り専用にしたいので public getter のみ公開しています。
/// </summary>
public bool IsReady => _remainingTime <= 0f;
/// <summary>
/// クールダウンの進捗(0~1)。
///
/// 0 : 完全にクールダウン終了(使用可能)
/// 1 : クールダウン開始直後(最大)
///
/// UIのゲージ表示などに利用できます。
/// </summary>
public float NormalizedProgress
{
get
{
if (_cooldownDuration <= 0f) return 0f;
// 残り時間 / 総時間 を 0~1 にクランプ
float t = Mathf.Clamp01(_remainingTime / _cooldownDuration);
return t;
}
}
/// <summary>
/// 残りクールダウン時間(秒)。読み取り専用。
/// </summary>
public float RemainingTime => Mathf.Max(_remainingTime, 0f);
private void Awake()
{
// 開始時に使用可能にするかどうかを設定
if (_startReady)
{
_remainingTime = 0f;
}
else
{
// 開始直後からクールダウン状態にしたい場合
_remainingTime = _cooldownDuration;
}
}
private void Update()
{
// すでに使用可能なら何もしない
if (_remainingTime <= 0f) return;
// 経過時間ぶんだけ残り時間を減らす
_remainingTime -= Time.deltaTime;
// マイナスになりすぎないように 0 で止めておく
if (_remainingTime < 0f)
{
_remainingTime = 0f;
}
}
/// <summary>
/// クールダウンを開始します。
///
/// 通常は「スキルを実行した瞬間」に呼びます。
/// すでにクールダウン中でも呼べるので、
/// ・残り時間をリセットしたい
/// ・連打でクールダウンを延長したい
/// といった挙動も実現できます。
/// </summary>
public void StartCooldown()
{
_remainingTime = _cooldownDuration;
}
/// <summary>
/// クールダウンを強制的に完了させます。
///
/// ・デバッグで即時使用可能にしたい
/// ・アイテムやスキルで「クールダウン短縮」を実現したい
/// ときなどに便利です。
/// </summary>
public void ForceReady()
{
_remainingTime = 0f;
}
/// <summary>
/// クールダウン時間を動的に変更します。
/// 例) バフ・デバフでクールダウン短縮/延長したい場合。
/// </summary>
/// <param name="newDuration">新しいクールダウン時間(秒)</param>
public void SetCooldownDuration(float newDuration)
{
_cooldownDuration = Mathf.Max(0f, newDuration);
// 残り時間が総時間を超えていたら補正しておく
_remainingTime = Mathf.Min(_remainingTime, _cooldownDuration);
}
/// <summary>
/// 現在のクールダウン時間(秒)を取得します。
/// 外部からは読み取り専用にしたいので getter のみ。
/// </summary>
public float GetCooldownDuration()
{
return _cooldownDuration;
}
}
}
使い方の手順
ここでは、プレイヤーが「Fireball(火の玉)」スキルを撃つ例で説明します。
キーボードの左クリックで発射し、3秒のクールダウンを挟む想定です。
-
① プレイヤーに CooldownTimer をアタッチする
- プレイヤー用の GameObject(例:
Player)を選択 Add ComponentボタンからCooldownTimerを追加Cooldown Durationを3など好きな秒数に設定Start Readyをオンにしておけば、ゲーム開始直後からスキル使用可能になります
- プレイヤー用の GameObject(例:
-
② スキル発射スクリプトを用意して、CooldownTimer を参照する
例として、マウス左クリックで発射する簡単なスクリプトを用意します。using UnityEngine; using UnityEngine.InputSystem; // 新Input Systemを使う場合 namespace Samples.Cooldown { /// <summary> /// シンプルなスキル発射サンプル。 /// CooldownTimer と連携して「撃てるときだけ撃つ」処理を行います。 /// </summary> [RequireComponent(typeof(CooldownTimer))] public class FireballCaster : MonoBehaviour { [Header("発射設定")] [SerializeField] private GameObject _fireballPrefab; [SerializeField] private Transform _spawnPoint; [SerializeField] private float _fireballSpeed = 10f; // 同じオブジェクト上の CooldownTimer をキャッシュ private CooldownTimer _cooldown; private void Awake() { _cooldown = GetComponent<CooldownTimer>(); } private void Update() { // ここではサンプルとしてマウス左クリックで発射 // 新Input Systemを使っていない場合は Input.GetMouseButtonDown(0) でもOK if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) { TryCast(); } } /// <summary> /// スキル発動処理。 /// CooldownTimer.IsReady を見て、撃てるときだけ処理を実行します。 /// </summary> private void TryCast() { // クールダウン中なら何もしない if (!_cooldown.IsReady) { Debug.Log("まだクールダウン中! 残り: " + _cooldown.RemainingTime.ToString("F2") + " 秒"); return; } // ここまで来たら発射可能 SpawnFireball(); // 発射したのでクールダウンを開始 _cooldown.StartCooldown(); } private void SpawnFireball() { if (_fireballPrefab == null) { Debug.LogWarning("Fireball Prefab が設定されていません。"); return; } // 発射位置が設定されていなければ、自分の位置から撃つ Transform origin = _spawnPoint != null ? _spawnPoint : transform; GameObject fireball = Instantiate( _fireballPrefab, origin.position, origin.rotation ); // もし Rigidbody がついていれば前方向に飛ばす if (fireball.TryGetComponent(out Rigidbody rb)) { rb.velocity = origin.forward * _fireballSpeed; } } } } -
③ プレハブやシーンで具体的に設定する
- 火の玉用のプレハブ(
Fireball)を作成し、Rigidbodyを付けておく FireballCasterの_fireballPrefabにそのプレハブをドラッグ&ドロップ- 銃口や手の先などから出したい場合は、空の子オブジェクトを
_spawnPointに指定 - プレイヤーオブジェクトには
CooldownTimerとFireballCasterの両方がアタッチされている状態にします
- 火の玉用のプレハブ(
-
④ 実行して挙動を確認する
- ゲームを再生して、左クリックで火の玉が発射されることを確認
- 連打すると、クールダウン中は「まだクールダウン中!」のログが出て発射されないことがわかります
Cooldown Durationの値を変えれば、同じコンポーネントで様々なスキルのクールダウンを簡単に調整できます
同じように、敵AIの「突進攻撃」や「範囲攻撃」、動く床の「一定時間ごとにON/OFF」などにも CooldownTimer をアタッチしておけば、すべて同じ仕組みで再使用間隔を管理できます。
メリットと応用
CooldownTimer をコンポーネントとして切り出すことで、次のようなメリットがあります。
- スキル側のコードが「撃つかどうか」だけを考えればよくなる
クールダウンの計算ロジックを全部CooldownTimerに任せられるので、
スキルのスクリプトは「入力を受け取る → 使用可能なら実行」という2ステップに集中できます。 - プレハブごとにクールダウンを簡単に調整できる
敵ごとに攻撃頻度を変えたいときも、インスペクターのCooldown Durationを変えるだけ。
「ボスだけクールダウンを短くする」「弱い敵は長くする」といった調整がプレハブ単位で完結します。 - レベルデザインがやりやすくなる
トラップ、ギミック、スキル、敵AIなど、
「一定間隔で何かをする」ものは全部CooldownTimerで統一できるので、
レベルデザイナーが値をいじるだけでゲームテンポを簡単に変えられます。 - テスト・デバッグがしやすい
クールダウンの挙動を単体で確認できるので、
「クールダウンが終わらない」「UIのゲージが動かない」といった問題も切り分けやすくなります。
さらに、NormalizedProgress や RemainingTime を使えば、UIとの連携も簡単です。
例えば、スキルアイコンの上にクールダウンゲージを表示したり、「あと何秒で使えるか」をテキストで出したりできます。
改造案:UIのImageフィル量と連動させる
最後に、CooldownTimer を少し応用して、
UIの Image.fillAmount と連動させる簡単な例を載せておきます。
using UnityEngine;
using UnityEngine.UI;
namespace Samples.Cooldown
{
/// <summary>
/// CooldownTimer と UI Image を連動させて、
/// スキルアイコン上にクールダウンゲージを表示するサンプル。
/// </summary>
[RequireComponent(typeof(CooldownTimer))]
public class CooldownUIBinder : MonoBehaviour
{
[SerializeField] private Image _cooldownMaskImage; // 黒いマスク用Image(Fill Method: Radial など)
private CooldownTimer _cooldown;
private void Awake()
{
_cooldown = GetComponent<CooldownTimer>();
}
private void Update()
{
if (_cooldownMaskImage == null) return;
// CooldownTimer の進捗をそのまま fillAmount に反映
// 1 = クールダウン中、0 = 使用可能
_cooldownMaskImage.fillAmount = _cooldown.NormalizedProgress;
}
}
}
このように、「時間管理」そのものは CooldownTimer に任せておき、
UI、攻撃ロジック、敵AI、トラップなどはそれぞれ小さなコンポーネントとして組み合わせていくと、
プロジェクトが大きくなっても見通しの良いコードベースを維持しやすくなります。
