【Cocos Creator 3.8】BuffTimer(バフ時間管理)の実装:アタッチするだけで「バフ残り時間バー表示&自動解除」を実現する汎用スクリプト
このガイドでは、任意のノードにアタッチするだけで
- バフの残り時間を UI バー(プログレスバー)として表示
- 時間切れで自動的に「バフ終了」イベントを発行
- 任意のコールバック(例:ステータスを元に戻す処理)を簡単にフック
できる汎用コンポーネント BuffTimer を実装します。
このコンポーネントは、例えば「攻撃力アップ」「移動速度アップ」などの一時的な強化状態の残り時間をプレイヤーに見せたいときに便利です。UI ノードにアタッチするだけで動き、他のカスタムスクリプトへの依存は一切ありません。
コンポーネントの設計方針
機能要件の整理
- バフの総時間と残り時間を管理する。
- 残り時間を 0〜1 の割合に変換し、UI バー(ProgressBar または Sprite の fillRange) に反映する。
- バフ開始時に初期化し、時間経過に応じて自動カウントダウンする。
- 残り時間が 0 になったら
- 自動でタイマーを停止
- Inspector で設定した「バフ終了時コールバック」を呼び出す(任意)
- 必要に応じて自動でノードを非表示/破棄するオプション
- 外部依存をなくすため、他の GameManager などには一切依存しない。
- 標準コンポーネント(ProgressBar / Sprite)が無い場合は
- 警告ログを出す
- 安全に動作(バー更新をスキップ)
UI バーの対応パターン
BuffTimer は、以下の 2 パターンの UI バーに対応します。
- ProgressBar コンポーネント(推奨)
- Node に
ProgressBarをアタッチしている場合、progressプロパティを更新します。
- Node に
- Sprite の fillRange を使ったバー
- Node に
Spriteをアタッチし、Fillモードに設定している場合、fillRangeを更新します。
- Node に
どちらも自動検出します。両方ついている場合は ProgressBar を優先し、それが無ければ Sprite を使います。
Inspector で設定可能なプロパティ
BuffTimer コンポーネントに用意する @property を一覧で整理します。
- totalDuration (number)
- バフの総時間(秒)。
- 例:5.0 にすると 5 秒間のバフ。
- 0 以下の場合は自動で 0.1 秒に補正してエラーを回避。
- autoStartOnLoad (boolean)
- コンポーネントが有効化されたときに自動でバフタイマーを開始するかどうか。
- true:
onEnableでstartBuff()を自動実行。 - false:外部スクリプトから
startBuff()を呼ぶまで待機。
- startFull (boolean)
- バフ開始時にバーを満タンから減らしていくかどうか。
- true:開始時に progress=1 → 0 に向かって減少。
- false:開始時に progress=0 → 1 に向かって増加(「チャージ」風表現)。
- hideNodeWhenFinished (boolean)
- バフ終了時に、この Node を自動で非表示にするかどうか。
- true:終了時に
this.node.active = false; - false:表示はそのまま。
- destroyNodeWhenFinished (boolean)
- バフ終了時に、この Node を自動で破棄するかどうか。
- true:終了時に
this.node.destroy(); - false:破棄しない。
- 注意:破棄は非表示より強い動作なので、true の場合は非表示設定より優先されます。
- loop (boolean)
- バフ終了後、自動的に再度同じ時間で開始するかどうか。
- true:ループ。終了時に
startBuff()を自動呼び出し。 - false:一度きり。
- onBuffStartEventName (string)
- バフ開始時に、この Node から発行するカスタムイベント名。
- 空文字列の場合はイベントを発行しない。
- 例:
"BuffStarted"
- onBuffEndEventName (string)
- バフ終了時に、この Node から発行するカスタムイベント名。
- 空文字列の場合はイベントを発行しない。
- 例:
"BuffEnded"
- debugLog (boolean)
- コンソールにデバッグログを出力するかどうか。
- true:開始/終了/警告などをログ出力。
- false:ログを抑制。
これらのプロパティにより、BuffTimer は単独で完結しつつ、色々な UI やゲームロジックに柔軟に対応できます。
TypeScriptコードの実装
以下が完成した BuffTimer コンポーネントの全コードです。
import { _decorator, Component, Node, ProgressBar, Sprite, UITransform, EventTarget, log, warn } from 'cc';
const { ccclass, property } = _decorator;
/**
* BuffTimer
* 任意のノードにアタッチするだけで、
* - バフ時間をカウントダウン
* - ProgressBar または Sprite(fillRange) による残り時間バー表示
* - 終了時にイベント発行 / 自動非表示 / 自動破棄 / ループ
* を行う汎用コンポーネント。
*/
@ccclass('BuffTimer')
export class BuffTimer extends Component {
@property({
tooltip: 'バフの総時間(秒)。0 以下の場合は自動で 0.1 秒に補正されます。'
})
public totalDuration: number = 5.0;
@property({
tooltip: 'コンポーネントが有効化されたときに自動でバフタイマーを開始するかどうか。'
})
public autoStartOnLoad: boolean = true;
@property({
tooltip: 'true の場合、バーは満タン(1)から 0 へ減少します。false の場合、0 から 1 へ増加します。'
})
public startFull: boolean = true;
@property({
tooltip: 'バフ終了時に、このノードを自動で非表示にするかどうか。destroyNodeWhenFinished が true の場合は無視されます。'
})
public hideNodeWhenFinished: boolean = false;
@property({
tooltip: 'バフ終了時に、このノードを自動で破棄するかどうか。true の場合、hideNodeWhenFinished より優先されます。'
})
public destroyNodeWhenFinished: boolean = false;
@property({
tooltip: 'バフ終了時に自動的に再度同じ時間で開始するかどうか。'
})
public loop: boolean = false;
@property({
tooltip: 'バフ開始時に、このノードから発行するカスタムイベント名。空文字列の場合は発行しません。'
})
public onBuffStartEventName: string = '';
@property({
tooltip: 'バフ終了時に、このノードから発行するカスタムイベント名。空文字列の場合は発行しません。'
})
public onBuffEndEventName: string = '';
@property({
tooltip: 'true の場合、開始・終了・警告などのデバッグログをコンソールに出力します。'
})
public debugLog: boolean = false;
// 内部状態
private _elapsed: number = 0; // 経過時間(秒)
private _running: boolean = false; // タイマーが動作中かどうか
private _progressBar: ProgressBar | null = null;
private _sprite: Sprite | null = null;
private _eventTarget: EventTarget = new EventTarget();
onLoad() {
// 必要な UI コンポーネントを取得(存在しない場合は null)
this._progressBar = this.getComponent(ProgressBar);
this._sprite = this.getComponent(Sprite);
if (!this._progressBar && !this._sprite) {
warn('[BuffTimer] ProgressBar も Sprite も見つかりません。このノードに ProgressBar または Sprite( Fill モード ) を追加してください。 node=', this.node.name);
}
// Sprite がある場合、Fill モードであるかを軽くチェック(強制はしない)
if (this._sprite) {
const uiTrans = this.node.getComponent(UITransform);
if (!uiTrans) {
warn('[BuffTimer] Sprite はありますが UITransform がありません。UI ノードとして使用する場合は UITransform を追加してください。 node=', this.node.name);
}
}
// totalDuration の防御的補正
if (this.totalDuration <= 0) {
warn('[BuffTimer] totalDuration が 0 以下のため、0.1 秒に補正します。');
this.totalDuration = 0.1;
}
// 初期状態としてバーを 0 か 1 に設定
this._applyProgress(this.startFull ? 1 : 0);
}
onEnable() {
if (this.autoStartOnLoad) {
this.startBuff();
}
}
update(deltaTime: number) {
if (!this._running) {
return;
}
this._elapsed += deltaTime;
let ratio = this._elapsed / this.totalDuration;
if (ratio > 1) {
ratio = 1;
}
// startFull が true の場合は 1→0 へ、false の場合は 0→1 へ
const displayProgress = this.startFull ? (1 - ratio) : ratio;
this._applyProgress(displayProgress);
if (this._elapsed >= this.totalDuration) {
this._onBuffFinished();
}
}
/**
* バフタイマーを開始またはリスタートします。
* 外部スクリプトからも呼び出し可能です。
*/
public startBuff(): void {
this._elapsed = 0;
this._running = true;
// 開始時のバーの状態をリセット
this._applyProgress(this.startFull ? 1 : 0);
if (this.debugLog) {
log('[BuffTimer] Buff started. duration =', this.totalDuration, 'sec, node =', this.node.name);
}
// カスタムイベント発行(バフ開始)
if (this.onBuffStartEventName) {
this.node.emit(this.onBuffStartEventName, this);
this._eventTarget.emit(this.onBuffStartEventName, this);
}
}
/**
* バフタイマーを停止します(時間はリセットされません)。
*/
public stopBuff(): void {
this._running = false;
if (this.debugLog) {
log('[BuffTimer] Buff stopped. node =', this.node.name);
}
}
/**
* バフタイマーを一時停止します(stopBuff と同義ですが、意味を明確にするために用意)。
*/
public pauseBuff(): void {
this.stopBuff();
}
/**
* バフタイマーが現在動作中かどうかを返します。
*/
public isRunning(): boolean {
return this._running;
}
/**
* 残り時間(秒)を返します。
*/
public getRemainingTime(): number {
return Math.max(0, this.totalDuration - this._elapsed);
}
/**
* 残り割合(0〜1)を返します。
* startFull に関わらず、0 = 終了、1 = フルタイム残り。
*/
public getRemainingRatio(): number {
const remaining = this.getRemainingTime();
return remaining / this.totalDuration;
}
/**
* カスタムイベントをリッスンするための EventTarget を取得します。
* this.node.on(...) ではなく、BuffTimer 内部のイベントで連携したい場合に使用します。
*/
public getEventTarget(): EventTarget {
return this._eventTarget;
}
// 内部:バーの見た目を更新
private _applyProgress(value: number): void {
// 0〜1 にクランプ
const v = Math.min(1, Math.max(0, value));
if (this._progressBar) {
this._progressBar.progress = v;
} else if (this._sprite) {
// Sprite の fillRange を利用する場合。Sprite 側は Fill モードに設定しておく必要があります。
this._sprite.fillRange = v;
}
}
// 内部:バフ終了時の処理
private _onBuffFinished(): void {
this._running = false;
// 終了時は常に 0 にしておく(startFull に関わらず)
this._applyProgress(this.startFull ? 0 : 1);
if (this.debugLog) {
log('[BuffTimer] Buff finished. node =', this.node.name);
}
// カスタムイベント発行(バフ終了)
if (this.onBuffEndEventName) {
this.node.emit(this.onBuffEndEventName, this);
this._eventTarget.emit(this.onBuffEndEventName, this);
}
// 自動非表示・破棄
if (this.destroyNodeWhenFinished) {
this.node.destroy();
return; // ここで処理終了
} else if (this.hideNodeWhenFinished) {
this.node.active = false;
}
// ループ設定があれば再スタート
if (this.loop) {
this.startBuff();
}
}
}
コードのポイント解説
- onLoad()
ProgressBarまたはSpriteをgetComponentで取得。- どちらもなければ
warnで警告を出し、バー更新はスキップ(防御的)。 totalDuration <= 0の場合は 0.1 秒に補正。- 開始前のバー状態を
startFullに応じて 0 or 1 に設定。
- onEnable()
autoStartOnLoadが true のとき、自動でstartBuff()を呼びます。
- update(deltaTime)
_runningが true のときだけ経過時間を加算。_elapsed / totalDurationで進行割合を計算し、startFullに応じて 1→0 or 0→1 の値に変換。- バーの見た目更新は
_applyProgress()に集約。 - 時間が尽きたら
_onBuffFinished()で終了処理。
- startBuff()
- 経過時間をリセットし、
_running = true。 - バーの状態を初期化(満タン or 空)。
- 必要なら開始イベント(
onBuffStartEventName)を発行。
- 経過時間をリセットし、
- _onBuffFinished()
_running = falseとし、バーを終了状態に更新。- 必要なら終了イベント(
onBuffEndEventName)を発行。 destroyNodeWhenFinished/hideNodeWhenFinished/loopを順に処理。
- イベント発行
this.node.emit(eventName, this)で、同じノード上の他コンポーネントから簡単に受け取れるようにしています。- さらに
_eventTargetも用意しているので、BuffTimer に直接リスナーを登録したい場合にも対応可能です。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を BuffTimer.ts にします。
- 作成された
BuffTimer.tsをダブルクリックして開き、内容をすべて削除してから、上記のコードを丸ごと貼り付けて保存します。
2. テスト用 UI ノード(ProgressBar)の作成
まずは ProgressBar を使った例で動作確認します。
- Hierarchy パネルで右クリック → Create → UI → Canvas を選択し、Canvas を作成します(既にある場合は省略)。
- Canvas を選択し、右クリック → Create → UI → ProgressBar を選択して ProgressBar ノードを作成します。
- 作成されたノード名はデフォルトで
ProgressBarになっているはずです。
- 作成されたノード名はデフォルトで
- Hierarchy で
ProgressBarノードを選択し、Inspector を確認します。- ProgressBar コンポーネント がアタッチされていることを確認します。
3. BuffTimer コンポーネントのアタッチ
- Hierarchy で先ほどの
ProgressBarノードを選択します。 - Inspector の下部で Add Component ボタンをクリックします。
- Custom カテゴリ(または Scripts カテゴリ)から BuffTimer を選択してアタッチします。
4. Inspector でプロパティを設定
ProgressBar ノードに BuffTimer がアタッチされた状態で、Inspector の BuffTimer セクションを以下のように設定します。
- totalDuration:
5- 5 秒間のバフとして動作します。
- autoStartOnLoad:
true- シーン再生と同時にバフタイマーが自動開始されます。
- startFull:
true- バーが満タンから 0 へ向かって減少します(残り時間表示)。
- hideNodeWhenFinished:
false - destroyNodeWhenFinished:
false - loop:
false - onBuffStartEventName: 空のまま(今回は未使用)
- onBuffEndEventName: 空のまま(今回は未使用)
- debugLog:
true(ログを見たい場合)
5. ゲームを再生して確認
- 上部ツールバーの Play ボタンを押してシーンを再生します。
- Game ビュー上で、ProgressBar が5 秒かけて徐々に減少して 0 になることを確認します。
- Console パネルを開くと、
[BuffTimer] Buff started...と[BuffTimer] Buff finished...のログが表示されているはずです(debugLog = true の場合)。
6. Sprite(fillRange) を使ったバーでの確認(オプション)
次に、Sprite の fillRange を使ったバーでも動作することを確認します。
- Hierarchy で Canvas を右クリック → Create → UI → Sprite を選択します。
- 新しい Sprite ノード(例:
BuffBarSprite)を選択し、Inspector の Sprite コンポーネントを確認します。 - Sprite コンポーネントの設定を変更します:
- Type を Filled に変更。
- Fill Type を好みで Horizontal や Radial などに設定。
- Fill Start を 0、Fill Range を 1 に設定。
- 同じ Sprite ノードに BuffTimer コンポーネントを追加します(ProgressBar は不要です)。
- BuffTimer のプロパティを先ほどと同様に設定します(
totalDuration = 3など、短めでも可)。 - シーンを再生し、Sprite の Fill 表示が時間経過と共に変化することを確認します。
7. バフ終了で「ステータスを戻す」処理をフックする例
BuffTimer は他のカスタムスクリプトに依存しませんが、イベントを使って他のコンポーネントから簡単にフックできます。ここでは、同じノード上の別スクリプトから「バフ終了時に攻撃力を元に戻す」イメージの処理を行う例を示します。
例:同じノードに AttackBuffHandler.ts を追加し、以下のように書きます。
import { _decorator, Component, log } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('AttackBuffHandler')
export class AttackBuffHandler extends Component {
@property({
tooltip: '攻撃力の通常値'
})
public normalAttack: number = 10;
@property({
tooltip: 'バフ中の攻撃力'
})
public buffedAttack: number = 20;
private _currentAttack: number = 0;
start() {
this._currentAttack = this.normalAttack;
// 同じノード上の BuffTimer を取得
const buffTimer = this.getComponent('BuffTimer') as any;
if (!buffTimer) {
log('[AttackBuffHandler] BuffTimer が見つかりません。同じノードに BuffTimer を追加してください。');
return;
}
// ノードのイベントを利用して、バフ開始・終了をフック
this.node.on('BuffStarted', this.onBuffStarted, this);
this.node.on('BuffEnded', this.onBuffEnded, this);
}
onBuffStarted() {
this._currentAttack = this.buffedAttack;
log('[AttackBuffHandler] Buff started. attack =', this._currentAttack);
}
onBuffEnded() {
this._currentAttack = this.normalAttack;
log('[AttackBuffHandler] Buff ended. attack =', this._currentAttack);
}
}
このとき、BuffTimer 側では以下のように設定します。
- onBuffStartEventName:
BuffStarted - onBuffEndEventName:
BuffEnded
こうすることで、BuffTimer は依然として完全に独立したまま、他コンポーネントが自由に「バフ開始/終了」に反応できる設計になります。
まとめ
- BuffTimer は、任意のノードにアタッチするだけで
- バフ時間のカウントダウン管理
- UI バー(ProgressBar / Sprite fillRange)への残り時間表示
- バフ開始/終了イベントの発行
- 自動非表示・自動破棄・ループ再生
を行う完全独立・汎用コンポーネントです。
- Inspector から
- バフ時間(totalDuration)
- 開始タイミング(autoStartOnLoad)
- 表示方向(startFull)
- 終了時の挙動(hide / destroy / loop)
- イベント名(onBuffStartEventName / onBuffEndEventName)
を調整するだけで、様々なゲームに簡単に適用できます。
- 外部の GameManager やシングルトンに依存しないため、
- UI プレハブにそのまま組み込める
- 別プロジェクトへの持ち出しも容易
- テストシーンで単体検証しやすい
といった利点があります。
この BuffTimer をベースに、例えば「デバフタイマー」「スキルクールダウン表示」「チャージバー」などにも簡単に応用できます。プロジェクトの共通 UI 部品としてプレハブ化しておくと、ゲーム開発のスピードと保守性が大きく向上するはずです。




