UnityでUIを作っていると、つい「全部ひとつのスクリプトのUpdateに書いてしまう」こと、ありますよね。
スキルのクールダウン管理、入力処理、UI更新、アニメーション制御…全部を1クラスに詰め込むと、だんだん手が付けられないGodクラスになってしまいます。
特にクールダウン表示は、
- スキルの残り時間を計算するロジック
- UI上にどう見せるか(テキスト / アイコン / 扇形など)の表現
がごちゃ混ぜになりがちです。
この記事では「見た目のクールダウン表現」だけを担当する小さなコンポーネントとして、
親のTextureRectの上に半透明の扇形を描画し、クールダウン時間を可視化する「CooldownOverlay」コンポーネントを作っていきます。
【Unity】スキルの再使用待ちを一目で可視化!「CooldownOverlay」コンポーネント
ここでは Unity UI(UGUI)上で、アイコンの上に「扇形の黒いオーバーレイ」がぐるっと閉じていく、よくあるクールダウン表示を作ります。
ロジックはできるだけ分離し、「CooldownOverlay」はあくまで
- 0〜1のクールダウン進行度(normalized)を受け取る
- それに応じて扇形の描画を更新する
だけに責務を絞ります。
前提:描画方法について
Unityの標準UIだけで「扇形の塗りつぶし」をやる一番シンプルな方法は、
Imageコンポーネント +Image.Type = FilledFill 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を用意する(プレイヤースキルアイコン)
- Canvas の下に
Imageを1つ作成し、スキルアイコンのテクスチャを設定します。
例:GameObject > UI > Imageで作成し、名前をSkillIconにする。 SkillIconの RectTransform を、好きな位置・サイズに調整します。
② オーバーレイ用のImageを重ねる
SkillIconを右クリックしてUI > Imageを追加し、子オブジェクトとして作成します。
名前をCooldownOverlayImageなどにしておきましょう。CooldownOverlayImageのSource Imageは、アイコンと同じでも真っ白なスプライトでもOKです。
ただし、四角のスプライト の方がきれいな扇形になります。- RectTransform は
Anchor = Stretch (上下左右0)にすると、親アイコンと完全に重なります。
③ CooldownOverlay コンポーネントをアタッチする
CooldownOverlayImageに、先ほどのCooldownOverlayスクリプトをアタッチします。- インスペクターで以下を設定します:
- Target Icon Image:
SkillIconのImageをドラッグ&ドロップ - Overlay Color:半透明の黒(例:RGBA = 0,0,0,0.6)
- Hide When Ready:ON(クールダウン完了時に非表示にしたい場合)
- Fill Origin Angle:90(上から時計回りに閉じていく、など好みで)
- Clockwise:チェックONで時計回り、OFFで反時計回り
- Target Icon Image:
- エディタ上で
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);
}
このように、小さな責務のコンポーネントとして作っておくと、
「色変化」「アニメーション」「テキスト併用」などの拡張も、別メソッドや別コンポーネントとして安全に積み増ししやすくなります。
ぜひ自分のプロジェクトに合わせて、少しずつカスタマイズしてみてください。
