UnityでUIを作っていると、つい「全部ひとつのスクリプトのUpdateに書いてしまう」こと、ありますよね。
スキルのクールダウン管理、入力処理、UI更新、アニメーション制御…全部を1クラスに詰め込むと、だんだん手が付けられないGodクラスになってしまいます。

特にクールダウン表示は、

  • スキルの残り時間を計算するロジック
  • UI上にどう見せるか(テキスト / アイコン / 扇形など)の表現

がごちゃ混ぜになりがちです。

この記事では「見た目のクールダウン表現」だけを担当する小さなコンポーネントとして、
親のTextureRectの上に半透明の扇形を描画し、クールダウン時間を可視化する「CooldownOverlay」コンポーネントを作っていきます。

【Unity】スキルの再使用待ちを一目で可視化!「CooldownOverlay」コンポーネント

ここでは Unity UI(UGUI)上で、アイコンの上に「扇形の黒いオーバーレイ」がぐるっと閉じていく、よくあるクールダウン表示を作ります。
ロジックはできるだけ分離し、「CooldownOverlay」はあくまで

  • 0〜1のクールダウン進行度(normalized)を受け取る
  • それに応じて扇形の描画を更新する

だけに責務を絞ります。

前提:描画方法について

Unityの標準UIだけで「扇形の塗りつぶし」をやる一番シンプルな方法は、

  • Image コンポーネント + Image.Type = Filled
  • Fill Method = Radial360(放射状)

を使うことです。
この記事でもこの仕組みを使い、コード側で fillAmount を操作してクールダウンの見た目を制御します。


ソースコード:CooldownOverlay フルコード


using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 親のTextureRect(=アイコンなどのImage)の上に、
/// 半透明の扇形オーバーレイを描画してクールダウンを可視化するコンポーネント。
/// 
/// ・0〜1の正規化クールダウン値を渡すだけでOK
/// ・Image(Type=Filled, FillMethod=Radial360) を自動で要求
/// ・見た目の色や方向はインスペクターから調整可能
/// </summary>
[RequireComponent(typeof(Image))]
public class CooldownOverlay : MonoBehaviour
{
    /// <summary>
    /// このオーバーレイが覆う対象となるアイコンなどのImage。
    /// 設定しなくても動作するが、サイズを合わせたい場合に指定しておくと便利。
    /// </summary>
    [SerializeField]
    private Image targetIconImage;

    /// <summary>
    /// クールダウン中に表示するオーバーレイの色。
    /// だいたい半透明の黒〜グレーあたりがおすすめ。
    /// </summary>
    [SerializeField]
    private Color overlayColor = new Color(0f, 0f, 0f, 0.6f);

    /// <summary>
    /// クールダウンが完了したときにオーバーレイを非表示にするかどうか。
    /// </summary>
    [SerializeField]
    private bool hideWhenReady = true;

    /// <summary>
    /// 扇形の開始角度(0〜360)。Imageの仕様に準拠。
    /// 0 は右方向、90 は上方向、180 は左方向、270 は下方向。
    /// </summary>
    [SerializeField, Range(0f, 360f)]
    private float fillOriginAngle = 90f;

    /// <summary>
    /// 時計回りに埋まっていくかどうか。
    /// true: 時計回り、false: 反時計回り。
    /// </summary>
    [SerializeField]
    private bool clockwise = false;

    /// <summary>
    /// 現在のクールダウン進行度(0〜1)。
    /// 0 = クールダウン完了、1 = クールダウン開始直後。
    /// </summary>
    [SerializeField, Range(0f, 1f)]
    private float normalizedCooldown = 0f;

    // 内部で使用するImage参照
    private Image overlayImage;

    // 実際にImageに設定するfillAmount(0〜1)
    // normalizedCooldownと連動させる。
    private float currentFillAmount = 0f;

    private void Awake()
    {
        // 同じGameObjectについているImageを取得
        overlayImage = GetComponent<Image>();

        // Filledモード+放射状に強制しておくことで、
        // セットし忘れによる表示崩れを防ぐ
        overlayImage.type = Image.Type.Filled;
        overlayImage.fillMethod = Image.FillMethod.Radial360;

        // fillOriginAngle を Image.fillOrigin に変換
        overlayImage.fillOrigin = ConvertAngleToFillOrigin(fillOriginAngle);

        // 時計回り/反時計回り
        overlayImage.fillClockwise = clockwise;

        // 色を初期設定
        overlayImage.color = overlayColor;

        // targetIconImage が指定されている場合、サイズを合わせておく
        if (targetIconImage != null)
        {
            RectTransform targetRect = targetIconImage.rectTransform;
            RectTransform myRect = overlayImage.rectTransform;

            myRect.anchorMin = targetRect.anchorMin;
            myRect.anchorMax = targetRect.anchorMax;
            myRect.pivot = targetRect.pivot;
            myRect.position = targetRect.position;
            myRect.sizeDelta = targetRect.sizeDelta;
        }

        // 初期状態を反映
        ApplyCooldownToImage();
    }

    /// <summary>
    /// エディタ上で値を変更したときにも見た目を更新する。
    /// </summary>
    private void OnValidate()
    {
        // プレイ中以外でもImageを取れるようにTryGetComponent
        if (overlayImage == null)
        {
            TryGetComponent(out overlayImage);
        }

        if (overlayImage != null)
        {
            overlayImage.type = Image.Type.Filled;
            overlayImage.fillMethod = Image.FillMethod.Radial360;
            overlayImage.fillOrigin = ConvertAngleToFillOrigin(fillOriginAngle);
            overlayImage.fillClockwise = clockwise;
            overlayImage.color = overlayColor;
        }

        ApplyCooldownToImage();
    }

    /// <summary>
    /// 0〜1のクールダウン進行度を設定するメインAPI。
    /// 
    /// normalized = 1f : クールダウン開始直後(最大まで覆う)
    /// normalized = 0f : クールダウン完了(覆いゼロ)
    /// </summary>
    /// <param name="normalized">0〜1の値</param>
    public void SetCooldownNormalized(float normalized)
    {
        // 範囲外をクランプ
        normalizedCooldown = Mathf.Clamp01(normalized);
        ApplyCooldownToImage();
    }

    /// <summary>
    /// 秒数ベースでクールダウンを設定する補助API。
    /// 例えば「クールダウン10秒中、残り3秒」といった形で呼び出せる。
    /// </summary>
    /// <param name="remainingSeconds">残り時間(秒)</param>
    /// <param name="totalSeconds">合計クールダウン時間(秒)</param>
    public void SetCooldownByTime(float remainingSeconds, float totalSeconds)
    {
        if (totalSeconds <= 0f)
        {
            SetCooldownNormalized(0f);
            return;
        }

        float normalized = Mathf.Clamp01(remainingSeconds / totalSeconds);
        SetCooldownNormalized(normalized);
    }

    /// <summary>
    /// 内部状態をImageに反映する。
    /// </summary>
    private void ApplyCooldownToImage()
    {
        if (overlayImage == null)
        {
            return;
        }

        // クールダウン値をそのままfillAmountに使う
        currentFillAmount = normalizedCooldown;
        overlayImage.fillAmount = currentFillAmount;

        // クールダウン完了時に非表示にするオプション
        if (hideWhenReady)
        {
            // ほぼゼロになったら非表示
            overlayImage.enabled = currentFillAmount > 0.001f;
        }
        else
        {
            overlayImage.enabled = true;
        }
    }

    /// <summary>
    /// 任意の角度(0〜360)をImage.fillOrigin用の値に変換する補助関数。
    /// Radial360のfillOriginは0〜3の4方向しか指定できないため、
    /// 一番近い方向に丸めて設定します。
    /// 
    /// 0   〜  45度, 315〜360度 : 右 (0)
    /// 45  〜 135度              : 上 (1)
    /// 135 〜 225度              : 左 (2)
    /// 225〜 315度              : 下 (3)
    /// </summary>
    private int ConvertAngleToFillOrigin(float angle)
    {
        angle = Mathf.Repeat(angle, 360f);

        if (angle >= 45f && angle < 135f)
        {
            return (int)Image.Origin360.Top;
        }
        if (angle >= 135f && angle < 225f)
        {
            return (int)Image.Origin360.Left;
        }
        if (angle >= 225f && angle < 315f)
        {
            return (int)Image.Origin360.Bottom;
        }

        // それ以外は右
        return (int)Image.Origin360.Right;
    }

    /// <summary>
    /// デバッグ用:インスペクターから右クリックメニューで
    /// 「クールダウンをテスト再生」できるようにする。
    /// 実際のゲームでは別コンポーネントからSetCooldownByTimeを呼び出してください。
    /// </summary>
    [ContextMenu("Test Cooldown 3s")]
    private void TestCooldown()
    {
        // 3秒かけてクールダウンを0にしていく簡易テスト
        StopAllCoroutines();
        StartCoroutine(TestCooldownRoutine(3f));
    }

    private System.Collections.IEnumerator TestCooldownRoutine(float duration)
    {
        float time = duration;
        while (time > 0f)
        {
            time -= Time.unscaledDeltaTime;
            SetCooldownByTime(time, duration);
            yield return null;
        }

        SetCooldownNormalized(0f);
    }
}

使い方の手順

ここでは「プレイヤーのスキルボタンのクールダウン表示」を例に、TextureRect(=アイコンImage)の上に扇形を重ねる手順を説明します。

① UIを用意する(プレイヤースキルアイコン)

  1. Canvas の下に Image を1つ作成し、スキルアイコンのテクスチャを設定します。
    例:GameObject > UI > Image で作成し、名前を SkillIcon にする。
  2. SkillIcon の RectTransform を、好きな位置・サイズに調整します。

② オーバーレイ用のImageを重ねる

  1. SkillIcon を右クリックして UI > Image を追加し、子オブジェクトとして作成します。
    名前を CooldownOverlayImage などにしておきましょう。
  2. CooldownOverlayImageSource Image は、アイコンと同じでも真っ白なスプライトでもOKです。
    ただし、四角のスプライト の方がきれいな扇形になります。
  3. RectTransform は Anchor = Stretch (上下左右0) にすると、親アイコンと完全に重なります。

③ CooldownOverlay コンポーネントをアタッチする

  1. CooldownOverlayImage に、先ほどの CooldownOverlay スクリプトをアタッチします。
  2. インスペクターで以下を設定します:
    • Target Icon ImageSkillIconImage をドラッグ&ドロップ
    • Overlay Color:半透明の黒(例:RGBA = 0,0,0,0.6)
    • Hide When Ready:ON(クールダウン完了時に非表示にしたい場合)
    • Fill Origin Angle:90(上から時計回りに閉じていく、など好みで)
    • Clockwise:チェックONで時計回り、OFFで反時計回り
  3. エディタ上で Normalized Cooldown を 0〜1 の間で動かすと、扇形の見た目が変化するはずです。

④ スキルのロジックから呼び出す

最後に、プレイヤーのスキルロジック側から SetCooldownByTime などを呼び出します。
ここでは簡易的な例として、「スペースキーでスキル発動 → 5秒クールダウン → 扇形で表示」というスクリプトを示します。


using UnityEngine;

/// <summary>
/// 非常にシンプルなスキルクールダウン制御の例。
/// 入力処理 + クールダウン計算だけを担当し、
/// 見た目は CooldownOverlay に任せる構成。
/// </summary>
public class SimpleSkillCooldownController : MonoBehaviour
{
    [SerializeField]
    private CooldownOverlay cooldownOverlay;

    [SerializeField]
    private float cooldownSeconds = 5f;

    private float remainingCooldown = 0f;

    private void Update()
    {
        // クールダウン時間を減らす
        if (remainingCooldown > 0f)
        {
            remainingCooldown -= Time.deltaTime;
            if (remainingCooldown < 0f)
            {
                remainingCooldown = 0f;
            }

            // 見た目の更新は CooldownOverlay に任せる
            if (cooldownOverlay != null)
            {
                cooldownOverlay.SetCooldownByTime(remainingCooldown, cooldownSeconds);
            }
        }

        // スペースキーでスキル発動
        if (Input.GetKeyDown(KeyCode.Space))
        {
            TryUseSkill();
        }
    }

    private void TryUseSkill()
    {
        // まだクールダウン中なら発動しない
        if (remainingCooldown > 0f)
        {
            Debug.Log("スキルはまだクールダウン中です");
            return;
        }

        // ここで実際のスキル処理を行う
        Debug.Log("スキル発動!");

        // クールダウン開始
        remainingCooldown = cooldownSeconds;

        if (cooldownOverlay != null)
        {
            cooldownOverlay.SetCooldownByTime(remainingCooldown, cooldownSeconds);
        }
    }
}

このように、スキルのロジック見た目のクールダウン表示 を別コンポーネントに分けることで、責務がはっきり分離できますね。


メリットと応用

メリット① プレハブ化しやすく、レベルデザインが楽になる

  • SkillIcon + CooldownOverlayImage + CooldownOverlay をひとまとめにしてプレハブ化すれば、
    どのシーンでも同じ見た目のクールダウンUIを再利用できます。
  • クールダウン時間が違うスキルでも、ロジック側で SetCooldownByTime を呼ぶだけでOK。
  • レベルデザイナーやUI担当は「アイコン画像を差し替える」「色を変える」だけで、同じ挙動を使い回せます。

メリット② Godクラス化を防ぎ、テストしやすい

  • クールダウンの計算は SimpleSkillCooldownController のような別コンポーネントに分離。
  • CooldownOverlay は「0〜1の値 → 扇形の表示」にだけ集中しているので、バグの原因を追いやすいです。
  • インスペクターの Normalized Cooldown をいじるだけで見た目を確認できるため、デザイナーが単体テストしやすいのもポイントです。

応用例

  • 敵のスキル予備動作表示:敵の頭上にアイコン+CooldownOverlayを置き、次の大技までの時間を可視化。
  • 動く床の再出現タイマー:落ちた床の上に扇形を表示し、「あと何秒で戻るか」をプレイヤーに知らせる。
  • アイテムの再取得タイマー:フィールド上の回復アイテムにクールダウンUIを重ねて、再出現までの時間を表示。

改造案:色を時間によって変化させる

例えば、クールダウンが終わりに近づくほど色を明るくしたい場合は、
CooldownOverlay に次のようなメソッドを追加し、ApplyCooldownToImage() の中から呼び出しても良いですね。


/// <summary>
/// クールダウン進行度に応じて色を変化させる例。
/// 0〜1の値をもとに、暗い色 → 明るい色に補間する。
/// </summary>
private void UpdateColorByCooldown()
{
    if (overlayImage == null)
    {
        return;
    }

    // クールダウン開始直後(1)は暗く、完了(0)に近づくほど明るくする
    Color dark = new Color(0f, 0f, 0f, overlayColor.a);
    Color bright = new Color(1f, 1f, 1f, overlayColor.a * 0.2f);

    float t = 1f - normalizedCooldown; // 0→1に反転
    overlayImage.color = Color.Lerp(dark, bright, t);
}

このように、小さな責務のコンポーネントとして作っておくと、
「色変化」「アニメーション」「テキスト併用」などの拡張も、別メソッドや別コンポーネントとして安全に積み増ししやすくなります。
ぜひ自分のプロジェクトに合わせて、少しずつカスタマイズしてみてください。