【Cocos Creator】アタッチするだけ!SkillCooldownUI (スキルCD表示)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【Cocos Creator 3.8】SkillCooldownUI の実装:アタッチするだけでスキルのクールタイムを暗転&数字で表示する汎用スクリプト

このガイドでは、スキルのクールタイム(再使用時間)を UI 上で簡単に表現できる SkillCooldownUI コンポーネントを実装します。
ボタンやスキルアイコンのノードにアタッチするだけで、

  • クールタイム中はアイコンを暗くする(グレイアウト・半透明)
  • 残り時間を数字で表示する(例: 3.5 → 「3.5」秒)
  • クールタイム終了で自動的に元の見た目に戻る

という UI を、他スクリプトに一切依存せずに実現します。
クールタイムの秒数はインスペクタから設定して、ゲーム開始時に自動でカウントダウンしてもよし、ボタン押下時に startCooldown() を呼んで使うこともできます。


コンポーネントの設計方針

1. 要件整理

  • 特定スキルのクールタイム(秒)を管理し、UI に反映する。
  • クールタイム中はアイコンを暗くする。
    • Sprite の色を暗くする or 全体の不透明度を下げる。
  • クールタイム中は残り時間をラベルに表示する。
    • 小数あり / なしを選べる。
    • フォーマット(例: 0.0 / 0)を選べる。
  • 外部の GameManager や SkillManager などには一切依存しない。
  • インスペクタからだけで設定可能にする。
  • 必要な標準コンポーネント(Sprite, Label 等)は、自動取得を試み、無い場合はエラーを出して開発者に気づかせる。

2. 動作イメージ

  • クールタイムを開始するトリガー
    • パターン A: ゲーム開始と同時にクールタイムを開始(自動)
    • パターン B: ボタンのクリックイベントなどから startCooldown() を呼ぶ(手動)
  • クールタイム開始時:
    • 内部タイマーを cooldownDuration 秒にセット
    • アイコンを暗くする(色変更 or 不透明度変更)
    • 残り時間ラベルを表示し、数値をセット
  • クールタイム中(update):
    • 毎フレーム deltaTime で残り時間を減らす
    • 残り時間が 0 以下になったらクールタイム終了処理
    • ラベルの表示を更新(指定フォーマットで数値を表示)
  • クールタイム終了時:
    • アイコンの色・不透明度を元に戻す
    • ラベルを非表示にする or 空文字にする(設定で選択)

3. インスペクタで設定可能なプロパティ

以下のようなプロパティを用意します。

  • cooldownDuration: number
    • クールタイムの総時間(秒)。
    • 例: 5.0 なら 5 秒間クールタイム。
  • autoStartOnLoad: boolean
    • true: ノードが有効化されたタイミング(onEnable)で自動的にクールタイム開始。
    • false: 外部から startCooldown() を呼ぶまで何もしない。
  • iconSprite: Sprite | null
    • スキルアイコンの Sprite コンポーネント。
    • 未設定の場合は、アタッチされているノード自身から getComponent(Sprite) で自動取得を試みる。
    • 見つからない場合は警告ログを出す(暗転機能が使えないことを知らせる)。
  • cooldownLabel: Label | null
    • 残りクールタイムを表示する Label コンポーネント。
    • 同ノード上に置いても、子ノードの Label をドラッグ&ドロップで指定してもよい。
    • 未設定の場合は、ラベル表示機能はスキップし、警告ログを出す。
  • useOpacityForDarken: boolean
    • true: ノード全体の不透明度(UIOpacity)で暗くする。
    • false: Sprite の色を暗くする。
  • darkenOpacity: number
    • useOpacityForDarken = true のときに使用。
    • 0〜255 の値。不透明度をどこまで下げるか。
    • 例: 255 → 100 など。
  • darkenColorMultiplier: number
    • useOpacityForDarken = false のときに使用。
    • 0〜1 の値。Sprite の色をどれくらい暗くするかの係数。
    • 例: 0.5 → 元の色の半分の明るさ。
  • showLabelWhenReady: boolean
    • true: クールタイム終了後もラベルを残す(例: 「Ready」などを表示したいとき)。
    • false: クールタイム終了時にラベルを空文字にして非表示とみなす。
  • readyText: string
    • showLabelWhenReady = true のときに使用。
    • クールタイム終了後にラベルに表示する文字列。
    • 例: "READY", "" など。
  • decimalPlaces: number
    • 残り時間表示の小数点以下桁数。
    • 0 を指定すると整数表示(3.4 → 3)。
    • 1 を指定すると 1 桁(3.45 → 3.5)。
  • minDisplayValue: number
    • 残り時間がこの値を下回ったら 0 として表示するための閾値。
    • 例: 0.05 を指定すると、0.04 秒は「0」と表示される。

TypeScriptコードの実装


import { _decorator, Component, Node, Sprite, Label, Color, UIOpacity, warn, log } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('SkillCooldownUI')
export class SkillCooldownUI extends Component {

    @property({
        tooltip: 'クールタイムの総時間(秒)。例: 5.0 なら 5秒間クールタイム。'
    })
    public cooldownDuration: number = 5.0;

    @property({
        tooltip: 'ノードが有効化されたタイミングで自動的にクールタイムを開始するかどうか。'
    })
    public autoStartOnLoad: boolean = false;

    @property({
        type: Sprite,
        tooltip: 'スキルアイコンの Sprite。未設定の場合、このノードから自動取得を試みます。'
    })
    public iconSprite: Sprite | null = null;

    @property({
        type: Label,
        tooltip: '残りクールタイムを表示する Label。未設定の場合、時間表示は行われません。'
    })
    public cooldownLabel: Label | null = null;

    @property({
        tooltip: 'true: 不透明度(UIOpacity)で暗くする / false: Spriteの色を暗くする。'
    })
    public useOpacityForDarken: boolean = true;

    @property({
        tooltip: 'useOpacityForDarken=true のときに使用。不透明度(0〜255)をどこまで下げるか。',
        min: 0,
        max: 255,
        step: 1
    })
    public darkenOpacity: number = 120;

    @property({
        tooltip: 'useOpacityForDarken=false のときに使用。Spriteの色をどれくらい暗くするか(0〜1)。',
        min: 0,
        max: 1,
        step: 0.05
    })
    public darkenColorMultiplier: number = 0.4;

    @property({
        tooltip: 'クールタイム終了後もラベルを残すかどうか。'
    })
    public showLabelWhenReady: boolean = false;

    @property({
        tooltip: 'showLabelWhenReady=true のとき、クールタイム終了後にラベルへ表示する文字列。'
    })
    public readyText: string = '';

    @property({
        tooltip: '残り時間表示の小数点以下桁数。0で整数表示。',
        min: 0,
        max: 3,
        step: 1
    })
    public decimalPlaces: number = 0;

    @property({
        tooltip: 'この値を下回ったら0として表示する閾値(秒)。',
        min: 0,
        max: 1,
        step: 0.01
    })
    public minDisplayValue: number = 0.05;

    // 内部状態
    private _isCooling: boolean = false;
    private _remainingTime: number = 0;

    // 元の見た目を保存
    private _originalColor: Color | null = null;
    private _originalOpacity: number | null = null;
    private _uiOpacity: UIOpacity | null = null;

    onLoad() {
        // Sprite 自動取得
        if (!this.iconSprite) {
            const sprite = this.getComponent(Sprite);
            if (sprite) {
                this.iconSprite = sprite;
            } else {
                warn('[SkillCooldownUI] iconSprite が設定されておらず、このノードにも Sprite がありません。暗転機能は無効になります。');
            }
        }

        // UIOpacity を取得(無ければ追加)
        this._uiOpacity = this.getComponent(UIOpacity);
        if (!this._uiOpacity) {
            // 不透明度で暗くするモードを使う場合は UIOpacity が必要
            this._uiOpacity = this.addComponent(UIOpacity);
        }

        // 元の見た目を記録
        if (this.iconSprite) {
            this._originalColor = this.iconSprite.color.clone();
        }
        if (this._uiOpacity) {
            this._originalOpacity = this._uiOpacity.opacity;
        }

        // ラベル初期化
        if (this.cooldownLabel) {
            if (this.showLabelWhenReady) {
                this.cooldownLabel.string = this.readyText;
            } else {
                this.cooldownLabel.string = '';
            }
        }
    }

    onEnable() {
        if (this.autoStartOnLoad) {
            this.startCooldown();
        }
    }

    update(deltaTime: number) {
        if (!this._isCooling) {
            return;
        }

        this._remainingTime -= deltaTime;
        if (this._remainingTime <= 0) {
            this._remainingTime = 0;
            this._isCooling = false;
            this._onCooldownFinished();
        } else {
            this._updateLabel();
        }
    }

    /**
     * 外部から呼び出してクールタイムを開始する。
     * 例: Button のクリックイベントにこのメソッドを登録して使う。
     */
    public startCooldown(): void {
        if (this.cooldownDuration <= 0) {
            warn('[SkillCooldownUI] cooldownDuration が 0 以下のため、クールタイムを開始できません。');
            return;
        }

        this._isCooling = true;
        this._remainingTime = this.cooldownDuration;

        this._applyDarken();
        this._updateLabel();

        log('[SkillCooldownUI] Cooldown started. duration = ' + this.cooldownDuration + 's');
    }

    /**
     * 現在クールタイム中かどうかを返す。
     */
    public get isCooling(): boolean {
        return this._isCooling;
    }

    /**
     * 残りクールタイム(秒)を返す。
     */
    public get remainingTime(): number {
        return this._remainingTime;
    }

    /**
     * クールタイム中の見た目(暗転)を適用。
     */
    private _applyDarken(): void {
        if (this.useOpacityForDarken) {
            if (!this._uiOpacity) {
                this._uiOpacity = this.getComponent(UIOpacity) || this.addComponent(UIOpacity);
            }
            if (this._uiOpacity) {
                if (this._originalOpacity === null) {
                    this._originalOpacity = this._uiOpacity.opacity;
                }
                this._uiOpacity.opacity = this.darkenOpacity;
            }
        } else {
            if (this.iconSprite) {
                if (!this._originalColor) {
                    this._originalColor = this.iconSprite.color.clone();
                }
                const c = this.iconSprite.color;
                const factor = this.darkenColorMultiplier;
                this.iconSprite.color = new Color(
                    c.r * factor,
                    c.g * factor,
                    c.b * factor,
                    c.a
                );
            }
        }
    }

    /**
     * クールタイム終了時に見た目を元に戻す。
     */
    private _restoreAppearance(): void {
        if (this.useOpacityForDarken) {
            if (this._uiOpacity && this._originalOpacity !== null) {
                this._uiOpacity.opacity = this._originalOpacity;
            }
        } else {
            if (this.iconSprite && this._originalColor) {
                this.iconSprite.color = this._originalColor;
            }
        }
    }

    /**
     * クールタイム終了時の処理。
     */
    private _onCooldownFinished(): void {
        this._restoreAppearance();

        if (this.cooldownLabel) {
            if (this.showLabelWhenReady) {
                this.cooldownLabel.string = this.readyText;
            } else {
                this.cooldownLabel.string = '';
            }
        }

        log('[SkillCooldownUI] Cooldown finished.');
    }

    /**
     * 残り時間ラベルを更新。
     */
    private _updateLabel(): void {
        if (!this.cooldownLabel) {
            return;
        }

        let t = this._remainingTime;
        if (t < this.minDisplayValue) {
            t = 0;
        }

        const factor = Math.pow(10, this.decimalPlaces);
        const rounded = Math.round(t * factor) / factor;

        this.cooldownLabel.string = rounded.toFixed(this.decimalPlaces);
    }
}

コードのポイント解説

  • onLoad
    • iconSprite が未設定なら this.getComponent(Sprite) で自動取得。
    • UIOpacity を取得し、無ければ addComponent(UIOpacity) で追加。
    • 元の色・不透明度を保存しておき、終了時に戻せるようにしている。
    • ラベルの初期表示を readyText or 空文字に設定。
  • onEnable
    • autoStartOnLoadtrue の場合、自動で startCooldown() を呼ぶ。
  • update
    • クールタイム中のみ動作。_remainingTime を減らし、0 以下になったら終了処理。
    • 毎フレーム _updateLabel() で残り時間をラベルに反映。
  • startCooldown()
    • 外部から自由に呼び出せる API。
    • cooldownDuration <= 0 の場合は警告を出して終了。
    • 内部状態を初期化し、暗転とラベル更新を行う。
  • _applyDarken() / _restoreAppearance()
    • 不透明度モード or 色変更モードのどちらかで見た目を変更・復元。
    • 元の値を保持しておくことで、複数回クールタイムを繰り返しても見た目が壊れないようにしている。
  • _updateLabel()
    • decimalPlaces に応じて小数点以下桁数を制御。
    • minDisplayValue 未満は 0 として表示して、0.01 などの細かすぎる値を見せないようにしている。

使用手順と動作確認

1. スクリプトファイルの作成

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. ファイル名を SkillCooldownUI.ts にします。
  3. 自動生成されたコードをすべて削除し、本記事の SkillCooldownUI のコードをそのまま貼り付けて保存します。

2. テスト用の UI ノードを作成

  1. Hierarchy パネルで右クリック → Create → Canvas を選択し、Canvas が無い場合は作成します。
  2. Canvas を選択し、右クリック → Create → UI → Sprite でスキルアイコン用の Sprite ノードを作成します。
    • 例: ノード名を SkillIcon に変更。
    • Inspector の Sprite コンポーネントで任意のスキルアイコン画像を設定します。
  3. 残り時間表示用の Label を作成します。
    • SkillIcon を右クリック → Create → UI → Label を選択。
    • 例: ノード名を CooldownLabel に変更。
    • Label の Text を一旦空文字にしておき、フォントサイズ・位置をアイコン中央などに調整します。

3. SkillCooldownUI コンポーネントをアタッチ

  1. Hierarchy で SkillIcon ノードを選択します。
  2. Inspector の下部で Add ComponentCustomSkillCooldownUI を選択します。
  3. プロパティを設定します。
    • Cooldown Duration: 例として 5 に設定(5 秒間のクールタイム)。
    • Auto Start On Load: テストしやすいように true に設定。
    • Icon Sprite:
      • 空欄のままでも、このノードの Sprite を自動取得するので OK。
      • 明示的に SkillIcon の Sprite をドラッグ&ドロップしてもよいです。
    • Cooldown Label:
      • Hierarchy から CooldownLabel ノードをドラッグ&ドロップします。
    • Use Opacity For Darken: true(不透明度で暗くする)を推奨。
    • Darken Opacity: 例として 120 に設定(元が 255 の場合、半分くらいの暗さ)。
    • Darken Color Multiplier: 今回は不透明度モードなのでとりあえずデフォルトのままで OK。
    • Show Label When Ready: テスト用に false に設定(クールタイム終了で数字を消す)。
    • Ready Text: 何も表示しないので空文字のままで OK。
    • Decimal Places: 0(整数表示)。
    • Min Display Value: 0.05(0.05 秒未満は 0 と表示)。

4. ゲームを再生して動作確認

  1. エディタ右上の Play ボタンを押して、プレビューを開始します。
  2. ゲーム開始と同時に、SkillIcon が暗くなり、CooldownLabel に「5」「4」「3」… と秒数がカウントダウン表示されることを確認します。
  3. 5 秒経過すると、アイコンが元の明るさに戻り、ラベルの数字が消えることを確認します。

5. ボタンから手動でクールタイムを開始してみる

次に、ボタンを押したときだけクールタイムが開始されるように試してみます。

  1. SkillIconAuto Start On Loadfalse に変更します。
  2. Canvas の子として UI ボタンを作成します。
    • Canvas を右クリック → Create → UI → Button
    • ノード名を CastButton などに変更し、ラベルを「CAST」などにします。
  3. Button コンポーネントの Click Events に、SkillIcon ノードを登録します。
    • Button の Inspector → Click Events の「+」ボタンを押します。
    • 新しく追加された要素の TargetSkillIcon ノードをドラッグ&ドロップ。
    • Component のドロップダウンから SkillCooldownUI を選択。
    • Handler のドロップダウンから startCooldown を選択。
  4. プレビューを再生し、CAST ボタンを押すたびにクールタイムが開始され、アイコンが暗くなりカウントダウンが表示されることを確認します。

6. 色で暗くするモードの確認(任意)

見た目の好みで、不透明度ではなく色を暗くすることもできます。

  1. SkillIconUse Opacity For Darkenfalse に変更します。
  2. Darken Color Multiplier0.40.6 くらいに設定します。
  3. プレビューを再生し、クールタイム中にアイコンの色が暗く(黒に近づく)なり、終了時に元の色に戻ることを確認します。

まとめ

この SkillCooldownUI コンポーネントは、

  • 他のゲーム管理スクリプトに一切依存せず、
  • スキルアイコンのノードにアタッチしてプロパティを設定するだけで、
  • クールタイム中の暗転と残り時間表示を自動で行う

という、汎用的な UI パーツとして機能します。

応用例としては、

  • 複数のスキルスロットにそれぞれ SkillCooldownUI をアタッチし、各スキルボタンから startCooldown() を呼ぶことで、簡易スキルバーを構築する。
  • readyText に「READY」や「OK」を設定し、クールタイム終了を視覚的に強調する。
  • decimalPlaces を 1 にして、短いクールタイムスキルの残り時間を細かく表示する。

スキルのロジックやダメージ計算などは別コンポーネントで自由に実装しつつ、SkillCooldownUI はあくまで「見た目のクールタイム表示」に専念させることで、責務が分離され、ゲーム全体の設計がシンプルになります。

プロジェクト内のあらゆるスキルボタンにこのコンポーネントを再利用することで、UI 実装の手間を大きく削減できるはずです。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!