【Cocos Creator】アタッチするだけ!CooldownOverlay (再使用表示)の実装方法【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】CooldownOverlay の実装:アタッチするだけでクールダウン時間を半透明の扇形で可視化する汎用スクリプト

スキルボタンやアイテムの再使用待機時間(クールダウン)を、アイコンの上に「ぐるっと回る半透明の扇形」で表現したい場面は多いです。この CooldownOverlay コンポーネントは、任意のノードにアタッチするだけで、そのノードの上にクールダウン用の半透明扇形オーバーレイを描画し、時間経過に応じて自動的にアニメーションします。

親ノード側に特別な GameManager や他スクリプトは一切不要で、すべてインスペクタから設定・制御できるように設計します。


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

機能要件の整理

  • アタッチしたノード(通常はボタンやアイコン)の上に、半透明の扇形(円形ゲージ)を描画する。
  • クールダウン開始~終了までの時間を秒数で指定できる。
  • クールダウン中は、扇形が「残り時間」に応じて徐々に縮んでいく(例:1.0 → 0.0)。
  • クールダウン完了時には扇形を非表示にする。
  • エディタ上でテストしやすいように「自動でクールダウンを開始するオプション」や「ボタンで開始テスト」ができる。
  • 外部スクリプトには一切依存せず、この1ファイルだけで完結する。

描画アプローチ

Cocos Creator 3.8 では、UI系のノードに対してカスタム描画を行う場合、Graphics コンポーネントを利用するのが簡便です。本記事では:

  • アタッチしたノード自身Graphics を追加し、その中に扇形を描画します。
  • 描画範囲は「円の中心から外周までを三角形の集まりで塗る」形で、角度を少しずつ変えながら描画することで扇形を実現します。
  • クールダウンの「進行度(0〜1)」に応じて、描画する角度を変化させます。
    • 進行度 1.0 → 360度(全円)
    • 進行度 0.5 → 180度
    • 進行度 0.0 → 0度(非表示)

描画に必要な Graphics コンポーネントは、自動で追加を試み、見つからない場合はエラーを出す防御的実装にします。

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

コンポーネント CooldownOverlay が持つプロパティと役割は以下の通りです。

  • cooldownDuration (number)
    • クールダウンの総時間(秒)。
    • 例:3.0 にすると、3秒かけて扇形が消えていきます。
    • 0 以下の値の場合は無視し、クールダウンは開始されません。
  • autoStartOnLoad (boolean)
    • true の場合、onLoad 時に自動でクールダウンを 1 回開始します。
    • ゲーム内で「シーン開始と同時にクールダウンが走っている」状態をテストしたいときに便利です。
  • startFullCover (boolean)
    • true:クールダウン開始時にアイコンを完全に覆う(360度)状態からスタートし、時間とともに減少。
    • false:クールダウン開始時は 0 から増加する形(一般的な「チャージ」表現)。
  • reverseDirection (boolean)
    • false:標準の回転方向(時計回り)でゲージが変化。
    • true:反時計回りでゲージが変化。
  • startAngleDeg (number)
    • 扇形の開始角度(度数法)。0 度は +X 方向(右)を基準とした角度。
    • 例:90 にすると、上方向からゲージが回り始める形になります。
  • radius (number)
    • 扇形の半径(ローカル座標系の単位)。
    • 通常はボタンやアイコンの半径に合わせて調整します。
    • 0 または負の値の場合は、自動で UITransform から推定(最小の半辺)します。
  • overlayColor (Color)
    • 扇形の色(RGBA)。
    • アルファ値を 100〜200 程度にすると、下のアイコンが透けて見える半透明オーバーレイになります。
  • segments (number)
    • 扇形を構成する分割数。
    • 値が大きいほど滑らかな円になりますが、描画コストが増えます。
    • 推奨:30〜80 程度。
  • visibleWhenReady (boolean)
    • false:クールダウンが 0 のときは完全に非表示(描画しない)。
    • true:クールダウンが 0 のときも、progress = 0 の状態で描画を残す(一般的には false を推奨)。
  • debugStartOnKeySpace (boolean)
    • エディタ / デバッグ時に、Space キーを押すとクールダウンを開始できるオプション。
    • 実機ビルドでは不要なら false にしておけば無視されます。

また、スクリプト内部では現在のクールダウン進行度を示す _progress(0〜1)を管理し、update で時間を進めながら描画を更新します。


TypeScriptコードの実装

以下が、Cocos Creator 3.8.7 用の完全な CooldownOverlay.ts 実装です。


import { _decorator, Component, Node, Graphics, Color, UITransform, input, Input, EventKeyboard, KeyCode, math } from 'cc';
const { ccclass, property } = _decorator;

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

    @property({
        tooltip: 'クールダウンの総時間(秒)。0 以下の場合はクールダウンは開始されません。',
    })
    public cooldownDuration: number = 3.0;

    @property({
        tooltip: 'true の場合、onLoad 時に自動で 1 回クールダウンを開始します。',
    })
    public autoStartOnLoad: boolean = false;

    @property({
        tooltip: 'true: 360度から 0 へ減少する「残り時間」表現 / false: 0 から 360度へ増加する「チャージ」表現',
    })
    public startFullCover: boolean = true;

    @property({
        tooltip: 'true の場合、扇形の回転方向を反転(反時計回り)します。',
    })
    public reverseDirection: boolean = false;

    @property({
        tooltip: '扇形の開始角度(度数)。0 度は +X 方向(右)を基準とした角度です。',
    })
    public startAngleDeg: number = 90;

    @property({
        tooltip: '扇形の半径。0 以下の場合は UITransform のサイズから自動推定します。',
    })
    public radius: number = 0;

    @property({
        tooltip: '扇形の色(RGBA)。アルファ値を下げると半透明になります。',
    })
    public overlayColor: Color = new Color(0, 0, 0, 160);

    @property({
        tooltip: '扇形を構成する分割数。値が大きいほど滑らかになりますが、描画コストが増えます。',
        min: 3, max: 180,
    })
    public segments: number = 60;

    @property({
        tooltip: 'クールダウンが完了したときも、扇形を描画したままにするかどうか(通常は false 推奨)。',
    })
    public visibleWhenReady: boolean = false;

    @property({
        tooltip: 'デバッグ用。true のとき、Space キーを押すとクールダウンを開始します。',
    })
    public debugStartOnKeySpace: boolean = false;

    // 内部状態
    private _graphics: Graphics | null = null;
    private _cooldownRemaining: number = 0;
    private _isCoolingDown: boolean = false;
    private _progress: number = 0; // 0〜1

    onLoad() {
        // Graphics コンポーネントの取得 or 追加
        this._graphics = this.getComponent(Graphics);
        if (!this._graphics) {
            this._graphics = this.addComponent(Graphics);
        }

        if (!this._graphics) {
            console.error('[CooldownOverlay] Graphics コンポーネントを取得・追加できませんでした。ノードを確認してください。', this.node);
            return;
        }

        // UITransform がない場合は警告(自動半径推定に使う)
        const uiTrans = this.getComponent(UITransform);
        if (!uiTrans) {
            console.warn('[CooldownOverlay] UITransform が見つかりません。radius を 0 以下にすると自動推定ができません。', this.node);
        }

        // 初期色設定
        this._graphics.fillColor = this.overlayColor;

        // 自動開始オプション
        if (this.autoStartOnLoad) {
            this.startCooldown();
        }

        // デバッグ用キーボードイベント設定
        if (this.debugStartOnKeySpace) {
            input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        }
    }

    onDestroy() {
        if (this.debugStartOnKeySpace) {
            input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        }
    }

    private _onKeyDown(event: EventKeyboard) {
        if (!this.debugStartOnKeySpace) {
            return;
        }
        if (event.keyCode === KeyCode.SPACE) {
            this.startCooldown();
        }
    }

    /**
     * クールダウンを開始します。
     * cooldownDuration が 0 以下の場合は何もしません。
     */
    public startCooldown(): void {
        if (this.cooldownDuration <= 0) {
            console.warn('[CooldownOverlay] cooldownDuration が 0 以下のため、クールダウンを開始できません。', this.cooldownDuration);
            return;
        }

        this._cooldownRemaining = this.cooldownDuration;
        this._isCoolingDown = true;

        // startFullCover に応じて初期 progress を設定
        this._progress = this.startFullCover ? 1.0 : 0.0;

        this._updateOverlay();
    }

    /**
     * クールダウンを強制終了し、オーバーレイを非表示にします。
     */
    public stopCooldown(): void {
        this._cooldownRemaining = 0;
        this._isCoolingDown = false;
        this._progress = 0;
        this._updateOverlay();
    }

    /**
     * 現在のクールダウン進行度を取得します(0〜1)。
     * 0: 完了 / 1: 開始直後(startFullCover = true の場合)。
     */
    public getProgress(): number {
        return this._progress;
    }

    update(dt: number) {
        if (!this._graphics) {
            return;
        }

        if (this._isCoolingDown) {
            this._cooldownRemaining -= dt;
            if (this._cooldownRemaining <= 0) {
                this._cooldownRemaining = 0;
                this._isCoolingDown = false;
            }

            // 残り時間から progress を算出
            const t = math.clamp01(this._cooldownRemaining / Math.max(this.cooldownDuration, 0.0001));
            // startFullCover の場合:1 → 0 へ減少 / false の場合:0 → 1 へ増加
            this._progress = this.startFullCover ? t : 1.0 - t;

            this._updateOverlay();
        } else {
            // 非クールダウン時の表示制御
            if (!this.visibleWhenReady && this._progress !== 0) {
                this._progress = 0;
                this._updateOverlay();
            }
        }
    }

    /**
     * 現在の progress に基づき扇形を描画します。
     */
    private _updateOverlay(): void {
        if (!this._graphics) {
            return;
        }

        const g = this._graphics;
        g.clear();

        // progress が 0 かつ非表示設定なら描画しない
        if (this._progress <= 0) {
            if (!this.visibleWhenReady) {
                return;
            }
        }

        // 実際に描画する角度(度数)
        const fullAngle = 360.0;
        let sweepAngle = fullAngle * math.clamp01(this._progress);

        if (sweepAngle <= 0) {
            if (!this.visibleWhenReady) {
                return;
            }
        }

        // 方向反転オプション
        if (this.reverseDirection) {
            sweepAngle = -sweepAngle;
        }

        // 半径の決定
        let r = this.radius;
        if (r <= 0) {
            const uiTrans = this.getComponent(UITransform);
            if (uiTrans) {
                r = Math.min(uiTrans.width, uiTrans.height) * 0.5;
            } else {
                // UITransform がない場合は適当なデフォルト
                r = 50;
            }
        }

        const centerX = 0;
        const centerY = 0;

        // 分割数
        let segs = Math.floor(this.segments);
        if (segs < 3) segs = 3;

        // 実際に使用するステップ数:角度に応じて調整
        const absSweep = Math.abs(sweepAngle);
        const step = absSweep / segs;

        g.fillColor = this.overlayColor;
        g.moveTo(centerX, centerY);

        // 開始角度
        let angle = this.startAngleDeg;
        const sign = sweepAngle >= 0 ? 1 : -1;

        // 扇形をポリゴンとして描画
        for (let i = 0; i <= segs; i++) {
            const rad = math.toRadian(angle);
            const x = centerX + Math.cos(rad) * r;
            const y = centerY + Math.sin(rad) * r;
            g.lineTo(x, y);
            angle += step * sign;
        }

        g.close();
        g.fill();
    }
}

コードの要点解説

  • onLoad
    • Graphics コンポーネントを取得し、なければ自動追加。
    • UITransform がない場合は警告ログを出し、半径自動推定ができないことを明示。
    • autoStartOnLoadtrue なら startCooldown() を呼んで自動開始。
    • debugStartOnKeySpacetrue なら Space キーの入力ハンドラを登録。
  • startCooldown()
    • cooldownDuration が 0 以下なら警告を出して何もしない。
    • 残り時間と内部フラグを初期化し、startFullCover に応じて _progress を 1.0 または 0.0 に設定。
    • _updateOverlay() を呼び、即座に描画を反映。
  • update(dt)
    • クールダウン中であれば残り時間を減らし、0 以下になったら完了扱い。
    • 残り時間から _progress を算出(0〜1)、startFullCover に応じて増減方向を切り替え。
    • 非クールダウン時は visibleWhenReadyfalse の場合に限り、_progress を 0 にリセットして非表示。
  • _updateOverlay()
    • Graphicsclear() してから再描画。
    • _progress が 0 かつ visibleWhenReady = false の場合は描画をスキップ。
    • radius が 0 以下なら、UITransform の幅・高さから自動で半径を推定。
    • 指定された分割数 segments に基づいて、中心から外周を結ぶ三角形の集まりとして扇形を描画。
    • startAngleDegreverseDirection に応じて開始角度と回転方向を制御。

使用手順と動作確認

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

  1. エディタ左下の Assets パネルで、任意のフォルダ(例:assets/scripts/ui)を右クリックします。
  2. Create → TypeScript を選択し、ファイル名を CooldownOverlay.ts とします。
  3. 作成された CooldownOverlay.ts をダブルクリックし、エディタ(VSCode など)で開きます。
  4. 中身をすべて削除し、本記事の TypeScriptコードの実装 セクションにあるコードをそのまま貼り付けて保存します。

2. テスト用ノード(ボタン/アイコン)の作成

  1. Hierarchy パネルで右クリックし、Create → UI → Sprite を選択して、テスト用のアイコンノードを作成します。
    • 名前は分かりやすく SkillButton などに変更しておくと良いです。
  2. 選択したノードの Inspector で、Sprite に任意の画像(スキルアイコンなど)を設定します。
  3. 必要に応じて、UITransformWidth / Height を調整し、円形アイコンに近いサイズにしておきます(例:100 × 100)。

3. CooldownOverlay コンポーネントのアタッチ

  1. 先ほどの SkillButton ノードを選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。
  2. Custom Component(または Custom)の中から CooldownOverlay を選択します。
  3. これで SkillButton ノードに CooldownOverlay コンポーネントが追加されます。

この時点で、スクリプトは自動的に Graphics コンポーネントをノードに追加します。
もし何らかの理由で追加に失敗すると、コンソールにエラーログが表示されます。

4. インスペクタでプロパティを設定

SkillButton ノードの CooldownOverlay セクションで、以下のように設定してみてください:

  • Cooldown Duration: 3
    • 3 秒のクールダウンゲージになります。
  • Auto Start On Load: ON
    • シーン再生と同時にクールダウンが自動開始されます。
  • Start Full Cover: ON
    • アイコンを完全に覆う状態から始まり、時間とともに扇形が縮んでいきます。
  • Reverse Direction: OFF
    • 時計回りにゲージが減っていきます。
  • Start Angle Deg: 90
    • 上方向(12時の位置)からゲージが回り始めます。
  • Radius: 0
    • 0 にすると、自動的に UITransform のサイズから半径を推定します。
  • Overlay Color: Black (0,0,0,160) など
    • アルファ値を 160 前後にすると、アイコンが透けて見える半透明になります。
  • Segments: 60
    • 十分滑らかな円に見える値です。
  • Visible When Ready: OFF
    • クールダウン完了後はオーバーレイを完全に非表示にします。
  • Debug Start On Key Space: ON(任意)
    • エディタ再生中に Space キーで何度でもクールダウンをテストできます。

5. シーンを再生して動作確認

  1. エディタ上部の Play ボタン(▶)を押してシーンを再生します。
  2. Auto Start On Load を ON にしている場合:
    • 再生開始直後に、SkillButton の上に黒い半透明の円が表示されます。
    • 3 秒かけて円がだんだん小さくなり、最後は完全に消えます。
  3. Debug Start On Key Space を ON にしている場合:
    • 再生中に Space キーを押すと、毎回クールダウンがリスタートします。
    • 何度でもクールダウンアニメーションを確認できます。

6. ゲームロジックからの制御(任意)

本記事の前提として「外部スクリプトへの依存は持たない」設計としていますが、外部から呼ぶ場合も簡単です。例えば、ボタンに別スクリプトを付けて、クリック時にクールダウンを開始したい場合:


// 例: 同じノードにアタッチされた別スクリプトから呼び出す場合
import { _decorator, Component } from 'cc';
import { CooldownOverlay } from './CooldownOverlay';
const { ccclass } = _decorator;

@ccclass('SkillButtonController')
export class SkillButtonController extends Component {
    onClickSkill() {
        const cd = this.getComponent(CooldownOverlay);
        if (cd) {
            cd.startCooldown();
        }
    }
}

このように、CooldownOverlay 自体は外部に依存せず、必要なら他スクリプトから public メソッドを呼ぶだけで連携可能です。


まとめ

本記事では、Cocos Creator 3.8.7 と TypeScript を用いて、

  • 任意のノードにアタッチするだけで動作する、
  • アイコン上に半透明の扇形クールダウンゲージを描画する、
  • 外部スクリプトに一切依存しない、

汎用コンポーネント CooldownOverlay を実装しました。

ポイントは:

  • Graphics コンポーネントを利用したカスタム描画で、汎用的な扇形ゲージを実現したこと。
  • すべての挙動を @property 経由で調整可能にし、ゲームごとのカスタマイズを容易にしたこと。
  • 自動コンポーネント追加やログ出力による、防御的な実装を行ったこと。

このコンポーネントをプロジェクトに 1 つ追加しておけば、

  • スキルボタンのクールタイム表示
  • アイテムの再使用待機ゲージ
  • チャージ攻撃の溜め時間可視化

など、多くの 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をコピーしました!