【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();
}
}
コードの要点解説
onLoadGraphicsコンポーネントを取得し、なければ自動追加。UITransformがない場合は警告ログを出し、半径自動推定ができないことを明示。autoStartOnLoadがtrueならstartCooldown()を呼んで自動開始。debugStartOnKeySpaceがtrueなら Space キーの入力ハンドラを登録。
startCooldown()cooldownDurationが 0 以下なら警告を出して何もしない。- 残り時間と内部フラグを初期化し、
startFullCoverに応じて_progressを 1.0 または 0.0 に設定。 _updateOverlay()を呼び、即座に描画を反映。
update(dt)- クールダウン中であれば残り時間を減らし、0 以下になったら完了扱い。
- 残り時間から
_progressを算出(0〜1)、startFullCoverに応じて増減方向を切り替え。 - 非クールダウン時は
visibleWhenReadyがfalseの場合に限り、_progressを 0 にリセットして非表示。
_updateOverlay()Graphicsをclear()してから再描画。_progressが 0 かつvisibleWhenReady = falseの場合は描画をスキップ。radiusが 0 以下なら、UITransformの幅・高さから自動で半径を推定。- 指定された分割数
segmentsに基づいて、中心から外周を結ぶ三角形の集まりとして扇形を描画。 startAngleDeg、reverseDirectionに応じて開始角度と回転方向を制御。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、任意のフォルダ(例:
assets/scripts/ui)を右クリックします。 - Create → TypeScript を選択し、ファイル名を
CooldownOverlay.tsとします。 - 作成された
CooldownOverlay.tsをダブルクリックし、エディタ(VSCode など)で開きます。 - 中身をすべて削除し、本記事の
TypeScriptコードの実装セクションにあるコードをそのまま貼り付けて保存します。
2. テスト用ノード(ボタン/アイコン)の作成
- Hierarchy パネルで右クリックし、Create → UI → Sprite を選択して、テスト用のアイコンノードを作成します。
- 名前は分かりやすく
SkillButtonなどに変更しておくと良いです。
- 名前は分かりやすく
- 選択したノードの Inspector で、
Spriteに任意の画像(スキルアイコンなど)を設定します。 - 必要に応じて、
UITransformのWidth/Heightを調整し、円形アイコンに近いサイズにしておきます(例:100 × 100)。
3. CooldownOverlay コンポーネントのアタッチ
- 先ほどの
SkillButtonノードを選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。 - Custom Component(または Custom)の中から CooldownOverlay を選択します。
- これで
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のサイズから半径を推定します。
- 0 にすると、自動的に
Overlay Color: Black (0,0,0,160) など- アルファ値を 160 前後にすると、アイコンが透けて見える半透明になります。
Segments: 60- 十分滑らかな円に見える値です。
Visible When Ready: OFF- クールダウン完了後はオーバーレイを完全に非表示にします。
Debug Start On Key Space: ON(任意)- エディタ再生中に Space キーで何度でもクールダウンをテストできます。
5. シーンを再生して動作確認
- エディタ上部の Play ボタン(▶)を押してシーンを再生します。
Auto Start On Loadを ON にしている場合:- 再生開始直後に、
SkillButtonの上に黒い半透明の円が表示されます。 - 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 演出を 「ノードにアタッチしてプロパティを少し調整するだけ」で実現できます。
必要に応じて、
- クールダウン完了時に色を変える
- 完了時にエフェクトを出す(別コンポーネントと連携)
- 非円形用にマスクと組み合わせる
といった拡張も容易です。まずは本記事のコードをそのまま導入し、プロジェクト内のボタンやアイコンにアタッチして、クールダウン表現を統一・簡略化してみてください。




