Unityを始めたばかりの頃は、つい「とりあえず全部Updateに書いてしまう」実装になりがちですよね。
スキルのクールタイム(再使用時間)も、こんな感じで書いてしまうケースが多いです。
// こういう書き方、心当たりありませんか?
void Update()
{
// 入力チェック
if (Input.GetKeyDown(KeyCode.Space))
{
if (Time.time >= skillNextUseTime)
{
UseSkill();
skillNextUseTime = Time.time + skillCooldown;
}
}
// スキルUIの更新
if (Time.time < skillNextUseTime)
{
float remain = skillNextUseTime - Time.time;
skillIconImage.fillAmount = remain / skillCooldown;
cooldownText.text = remain.ToString("0.0");
skillIconImage.color = new Color(1, 1, 1, 0.5f);
}
else
{
skillIconImage.fillAmount = 0f;
cooldownText.text = "";
skillIconImage.color = Color.white;
}
// さらに他の処理がどんどん追加されていく……
}
入力、スキル発動ロジック、UI更新が全部1つのスクリプトに詰め込まれてしまうと、
- スキルUIだけ変えたいのに、プレイヤー制御スクリプトまで触る必要がある
- スキルの数が増えるたびに巨大なGodクラス化していく
- プレハブの再利用性が低くなる(UIだけ別シーンで使い回したいのにできない)
といった問題が出てきます。
そこで今回は、「スキルのクールタイム表示」だけに責務を絞ったコンポーネント
「SkillCooldownUI」 を作って、UI側をきれいに分離してみましょう。
スキルの再使用可能時刻を教えてあげるだけで、アイコンの暗転や残り秒数の表示を全部やってくれるコンポーネントです。
【Unity】スキルの再使用時間をスマートに可視化!「SkillCooldownUI」コンポーネント
このコンポーネントは「特定スキルのクールタイム状態」を監視し、
- アイコン画像を暗くする / 元に戻す
- 残りクールタイム秒数のテキスト表示
- Image.fillAmount を使ったクールタイムの円形ゲージ
といったUI処理をまとめて担当します。
スキルのロジック側は「次に使える時間」を渡すだけにしておくことで、SRP(単一責任の原則)な構成にできます。
SkillCooldownUI のフルコード
using UnityEngine;
using UnityEngine.UI;
using TMPro;
/// <summary>
/// 特定スキルのクールタイムを監視し、
/// アイコンの暗転・クールタイムゲージ・残り秒数テキストを更新するコンポーネント。
///
/// スキル側からは、以下の2つだけ呼べばOK:
/// - SetCooldown(durationSeconds)
/// - NotifySkillUsed()
///
/// もしくは、外部で「次に使える時刻」を管理している場合は
/// - SetNextAvailableTime(nextTime, durationSeconds)
/// を呼び出す運用も可能です。
/// </summary>
[DisallowMultipleComponent]
public class SkillCooldownUI : MonoBehaviour
{
[Header("参照 (UI)")]
[SerializeField]
private Image iconImage; // スキルアイコン画像(必須)
[SerializeField]
private Image cooldownFillImage;
// クールタイム用Image。
// type = Filled、Fill Method = Radial360 などに設定しておくと円形ゲージになる。
[SerializeField]
private TMP_Text cooldownText;
// 残りクールタイム秒数を表示するテキスト(任意)
[Header("見た目の設定")]
[SerializeField, Tooltip("クールタイム中のアイコンのアルファ値(0〜1)")]
private float cooldownIconAlpha = 0.5f;
[SerializeField, Tooltip("クールタイム表示の小数点以下桁数")]
private int decimalDigits = 1;
[SerializeField, Tooltip("クールタイムが0.1秒未満になったら表示を消すか")]
private bool hideTextWhenAlmostReady = true;
[Header("自動開始オプション")]
[SerializeField, Tooltip("開始時にクールタイムを自動で開始するか")]
private bool startWithCooldown = false;
[SerializeField, Tooltip("startWithCooldown が true のときの初期クールタイム秒数")]
private float initialCooldownSeconds = 3f;
// --- 内部状態 ---
// 次にスキルが使用可能になるゲーム内時刻(Time.time ベース)
private float nextAvailableTime = 0f;
// クールタイム全体の長さ(秒)
private float cooldownDuration = 0f;
// 外部で「nextAvailableTime」を管理しているかどうか
private bool externalTimingMode = false;
// 初期カラーを保存しておく(アルファ変更用)
private Color originalIconColor;
private void Awake()
{
// 参照がInspectorで未設定の場合、同じGameObject上から自動取得を試みる
if (iconImage == null)
{
iconImage = GetComponent<Image>();
}
if (cooldownFillImage == null)
{
// 子オブジェクトから取得してみる(任意)
cooldownFillImage = GetComponentInChildren<Image>();
}
if (iconImage != null)
{
originalIconColor = iconImage.color;
}
else
{
Debug.LogWarning(
$"[SkillCooldownUI] iconImage が設定されていません。"{name}" のアイコン表示は動作しません。",
this
);
}
// 初期状態ではクールタイム表示をオフにしておく
ResetVisual();
}
private void Start()
{
// オプション: 開始時に自動でクールタイムを開始する
if (startWithCooldown && initialCooldownSeconds > 0f)
{
SetCooldown(initialCooldownSeconds);
NotifySkillUsed();
}
}
private void Update()
{
UpdateCooldownVisual();
}
/// <summary>
/// クールタイムの長さ(秒)を設定する。
/// 通常はスキルの設定値をここに渡しておく。
/// </summary>
/// <param name="durationSeconds">クールタイム秒数</param>
public void SetCooldown(float durationSeconds)
{
cooldownDuration = Mathf.Max(0f, durationSeconds);
externalTimingMode = false;
}
/// <summary>
/// スキルが使用されたことをUIに通知する。
/// ここで nextAvailableTime を計算し、以後はUIが自動で更新される。
///
/// 事前に SetCooldown を呼んでおく想定。
/// </summary>
public void NotifySkillUsed()
{
if (cooldownDuration <= 0f)
{
// クールタイムが0の場合は特に何もしない
nextAvailableTime = Time.time;
return;
}
nextAvailableTime = Time.time + cooldownDuration;
externalTimingMode = false;
}
/// <summary>
/// 外部で「次に使える時刻」を管理している場合に使用する。
/// 例: サーバーから同期されたクールタイムなど。
/// </summary>
/// <param name="nextTime">次に使用可能になる Time.time ベースの時刻</param>
/// <param name="durationSeconds">クールタイム全体の長さ(UIの割合計算に使用)</param>
public void SetNextAvailableTime(float nextTime, float durationSeconds)
{
nextAvailableTime = nextTime;
cooldownDuration = Mathf.Max(0.01f, durationSeconds); // 0除算防止
externalTimingMode = true;
}
/// <summary>
/// 今スキルが使用可能かどうかを返すヘルパー。
/// UI側の都合でだけ使う想定だが、外部から参照してもよい。
/// </summary>
public bool IsReady()
{
return Time.time >= nextAvailableTime;
}
/// <summary>
/// 残りクールタイム秒数を返す(マイナスにはならない)。
/// </summary>
public float GetRemainingCooldown()
{
float remain = nextAvailableTime - Time.time;
return Mathf.Max(0f, remain);
}
/// <summary>
/// 毎フレーム、クールタイムの状態に応じてUIを更新する。
/// </summary>
private void UpdateCooldownVisual()
{
if (iconImage == null && cooldownFillImage == null && cooldownText == null)
{
// 何も参照がなければ何もしない
return;
}
float remain = GetRemainingCooldown();
if (remain > 0f && cooldownDuration > 0f)
{
// --- クールタイム中 ---
// アイコンを暗くする
if (iconImage != null)
{
Color c = originalIconColor;
c.a *= Mathf.Clamp01(cooldownIconAlpha);
iconImage.color = c;
}
// 円形ゲージなどの fillAmount を更新
if (cooldownFillImage != null)
{
float ratio = Mathf.Clamp01(remain / cooldownDuration);
cooldownFillImage.fillAmount = ratio;
cooldownFillImage.enabled = true;
}
// 残り秒数テキストを更新
if (cooldownText != null)
{
// 表示するかどうかの判定
if (hideTextWhenAlmostReady && remain < 0.1f)
{
cooldownText.text = string.Empty;
}
else
{
// 小数点以下桁数に応じてフォーマット
string format = "0";
if (decimalDigits > 0)
{
format += "." + new string('0', decimalDigits);
}
cooldownText.text = remain.ToString(format);
}
}
}
else
{
// --- クールタイム終了(または未設定) ---
ResetVisual();
}
}
/// <summary>
/// クールタイムがない状態の見た目に戻す。
/// </summary>
private void ResetVisual()
{
// アイコンカラーを元に戻す
if (iconImage != null)
{
iconImage.color = originalIconColor;
}
// fillAmountを0にして非表示にする
if (cooldownFillImage != null)
{
cooldownFillImage.fillAmount = 0f;
cooldownFillImage.enabled = false;
}
// テキストを消す
if (cooldownText != null)
{
cooldownText.text = string.Empty;
}
}
}
使い方の手順
ここでは「プレイヤーのスキルボタンUI」にクールタイム表示をつける例で説明します。
Unity UI (uGUI) + TextMeshPro を前提にしています。
手順①:UIプレハブの準備
- Canvas 配下に「スキルボタン」の GameObject を1つ作成します。
- 例:
SkillButton_Fireball
- 例:
- その中に以下の構造を用意します(例):
SkillButton_Fireball(親)Icon… Image(スキルのアイコン)CooldownMask… Image(type=Filled, Fill Method=Radial360, Fill Origin=Top)CooldownText… TextMeshPro – Text (UI)
CooldownMaskの Image は、黒やグレーの半透明画像を設定し、
Type = Filled にし、Fill Method = Radial360 にしておきましょう。
これで円形に縮んでいくクールタイムゲージになります。
手順②:SkillCooldownUI コンポーネントをアタッチ
SkillButton_Fireballの GameObject にSkillCooldownUIをアタッチします。- Inspector で以下を設定します。
- Icon Image …
Iconをドラッグ&ドロップ - Cooldown Fill Image …
CooldownMaskをドラッグ&ドロップ - Cooldown Text …
CooldownTextをドラッグ&ドロップ - Cooldown Icon Alpha … 0.4〜0.6 くらいが見やすいです
- Decimal Digits … 1 なら「3.2」のような表示になります
- Start With Cooldown … ゲーム開始時にCD状態にしたいならオン
- Initial Cooldown Seconds … そのときの秒数
- Icon Image …
手順③:スキルロジックから通知を送る
プレイヤーのスキル発動ロジックは、UIを直接いじらずに、
「スキルが使われた」ことだけを SkillCooldownUI に伝えるようにします。
例えば、プレイヤーのスキルスクリプトを以下のようにします。
using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// ごくシンプルなプレイヤースキル例。
/// スペースキー(または Input System の "Fire" アクション)でスキルを発動し、
/// UI には SkillCooldownUI を使ってクールタイムを表示する。
/// </summary>
public class PlayerSkillExample : MonoBehaviour
{
[Header("スキル設定")]
[SerializeField]
private float cooldownSeconds = 5f;
[SerializeField]
private SkillCooldownUI cooldownUI;
// 次に使用可能な時刻(Time.time ベース)
private float nextAvailableTime;
private void Start()
{
// UI側にクールタイム秒数を教えておく
if (cooldownUI != null)
{
cooldownUI.SetCooldown(cooldownSeconds);
}
}
private void Update()
{
// 入力チェック(ここでは簡単にスペースキーで)
if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
{
TryUseSkill();
}
}
private void TryUseSkill()
{
if (Time.time < nextAvailableTime)
{
// まだクールタイム中
return;
}
// スキル発動処理(ここではダミー)
Debug.Log("Fireball!");
// 次に使える時刻を更新
nextAvailableTime = Time.time + cooldownSeconds;
// UI に「使ったよ」と通知
if (cooldownUI != null)
{
cooldownUI.NotifySkillUsed();
}
}
}
このように、プレイヤー側は「クールタイムのロジック」だけを持ち、
UIの見た目は SkillCooldownUI に丸投げできます。
手順④:敵や別スキルにも簡単に流用
- 敵の強力な攻撃スキルのクールタイム表示
- 動く床が「何秒後に動き出すか」をアイコンで表示する
- ボス戦で「次のフェーズまでの時間」をスキル風アイコンで見せる
といった用途でも、同じ SkillCooldownUI をプレハブ化してポンポン置くだけで再利用できます。
ロジック側からは SetCooldown と NotifySkillUsed さえ呼んでおけばOKです。
メリットと応用
SkillCooldownUI を使うことで、以下のようなメリットがあります。
- プレハブ単位で完結したスキルボタンUI
- アイコン画像・クールタイムマスク・テキスト・ロジックが1つのプレハブにまとまる
- 別シーン・別プロジェクトへの持ち出しも楽になります
- プレイヤー制御クラスがスリムになる
- 「いつスキルを使えるか」だけを管理し、見た目の更新はUIコンポーネントに任せられる
- Godクラス化を防ぎやすくなります
- レベルデザインの柔軟性アップ
- スキルごとにクールタイム秒数や表示フォーマットを変えたいときも、Inspectorから個別調整できる
- テキストを非表示にして「ゲージだけのシンプルUI」にするなど、見た目だけ差し替えが簡単
さらに、サーバー同期型のゲームでは、サーバーから届いた「スキル再使用可能時刻」をそのまま UI に渡すだけ、という運用もできます。
// 例: サーバーから nextUseUnixTime と cooldownSeconds を受け取った場合
private void OnReceiveSkillCooldownFromServer(double nextUseUnixTime, float cooldownSeconds)
{
// ここでは簡単に「サーバー時間とクライアント時間が一致している」と仮定
// 現実にはオフセット補正などが必要になります。
float nextTime = (float)nextUseUnixTime; // サーバー時刻を Time.time 相当の値に変換したとする
if (cooldownUI != null)
{
cooldownUI.SetNextAvailableTime(nextTime, cooldownSeconds);
}
}
このように、「時間の管理」と「UIの表示」をきれいに分離しておくと、
ネットワーク同期や難しいロジックが絡んできたときでも、UI側はほとんど変更せずに済みます。
改造案:クールタイム完了時にエフェクトを出す
クールタイムが終わった瞬間に、ちょっとしたエフェクトやSEを鳴らしたい場合は、
SkillCooldownUI に「前フレームの状態」を覚えておき、
「クールタイム中 → 完了」に変わった瞬間を検出するメソッドを追加すると便利です。
/// <summary>
/// クールタイム完了時に一度だけ呼び出されるコールバックを設定する例。
/// </summary>
[SerializeField]
private ParticleSystem readyEffect;
private bool wasOnCooldownLastFrame = false;
private void LateUpdate()
{
bool onCooldownNow = !IsReady();
// 「クールタイム中だった」->「今は準備完了」の瞬間を検出
if (wasOnCooldownLastFrame && !onCooldownNow)
{
// エフェクトを再生
if (readyEffect != null)
{
readyEffect.Play();
}
// ここでSEを鳴らしたり、ボタンをポヨンとスケールさせたりもできる
}
wasOnCooldownLastFrame = onCooldownNow;
}
このように、小さな責務ごとにコンポーネントを分けていくと、
「クールタイムUI」「完了時の演出」「スキルロジック」がそれぞれ独立して進化させやすくなります。
ぜひ自分のプロジェクトでも、Godクラスを避けたコンポーネント指向の設計を意識してみてください。
