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秒のクールダウンを挟む想定です。

  1. ① プレイヤーに CooldownTimer をアタッチする
    • プレイヤー用の GameObject(例: Player)を選択
    • Add Component ボタンから CooldownTimer を追加
    • Cooldown Duration3 など好きな秒数に設定
    • Start Ready をオンにしておけば、ゲーム開始直後からスキル使用可能になります
  2. ② スキル発射スクリプトを用意して、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;
                }
            }
        }
    }
    
  3. ③ プレハブやシーンで具体的に設定する
    • 火の玉用のプレハブ(Fireball)を作成し、Rigidbody を付けておく
    • FireballCaster_fireballPrefab にそのプレハブをドラッグ&ドロップ
    • 銃口や手の先などから出したい場合は、空の子オブジェクトを _spawnPoint に指定
    • プレイヤーオブジェクトには CooldownTimerFireballCaster の両方がアタッチされている状態にします
  4. ④ 実行して挙動を確認する
    • ゲームを再生して、左クリックで火の玉が発射されることを確認
    • 連打すると、クールダウン中は「まだクールダウン中!」のログが出て発射されないことがわかります
    • Cooldown Duration の値を変えれば、同じコンポーネントで様々なスキルのクールダウンを簡単に調整できます

同じように、敵AIの「突進攻撃」や「範囲攻撃」、動く床の「一定時間ごとにON/OFF」などにも CooldownTimer をアタッチしておけば、すべて同じ仕組みで再使用間隔を管理できます。

メリットと応用

CooldownTimer をコンポーネントとして切り出すことで、次のようなメリットがあります。

  • スキル側のコードが「撃つかどうか」だけを考えればよくなる
    クールダウンの計算ロジックを全部 CooldownTimer に任せられるので、
    スキルのスクリプトは「入力を受け取る → 使用可能なら実行」という2ステップに集中できます。
  • プレハブごとにクールダウンを簡単に調整できる
    敵ごとに攻撃頻度を変えたいときも、インスペクターの Cooldown Duration を変えるだけ。
    「ボスだけクールダウンを短くする」「弱い敵は長くする」といった調整がプレハブ単位で完結します。
  • レベルデザインがやりやすくなる
    トラップ、ギミック、スキル、敵AIなど、
    「一定間隔で何かをする」ものは全部 CooldownTimer で統一できるので、
    レベルデザイナーが値をいじるだけでゲームテンポを簡単に変えられます。
  • テスト・デバッグがしやすい
    クールダウンの挙動を単体で確認できるので、
    「クールダウンが終わらない」「UIのゲージが動かない」といった問題も切り分けやすくなります。

さらに、NormalizedProgressRemainingTime を使えば、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、トラップなどはそれぞれ小さなコンポーネントとして組み合わせていくと、
プロジェクトが大きくなっても見通しの良いコードベースを維持しやすくなります。