【Cocos Creator 3.8】FPSCounterの実装:アタッチするだけで現在のFPSを画面隅に表示する汎用デバッグラベルスクリプト
このガイドでは、Cocos Creator 3.8.7 と TypeScript で、シーン内の任意のノードにアタッチするだけで現在のフレームレート(FPS)を画面の隅に常時表示できる、汎用コンポーネント FPSCounter を実装します。
デバッグ中に「今どのくらいのFPSで動いているか」を手軽に確認したい場面は多いですが、毎回UIを組んだりGameManagerを用意するのは面倒です。このコンポーネントは外部スクリプトに一切依存せず、インスペクタで少し設定するだけで使えるように設計します。
コンポーネントの設計方針
要件整理
- 現在のFPSを一定間隔で計測し、ラベルに表示する。
- 常に画面の隅(左上 / 右上 / 左下 / 右下)など、指定した位置に表示できる。
- FPSの更新間隔や表示フォーマットをインスペクタから調整可能にする。
- 外部の GameManager や他ノードに依存せず、このスクリプト単体で完結する。
- 必須コンポーネント(Label / UITransform / Widget)がなければ自動で追加し、ログを出して開発者に知らせる。
- エディタ上で見やすいように、色やフォントサイズも調整可能にする。
外部依存をなくすためのアプローチ
- FPS計測は
update(dt: number)のdtを利用し、1秒ごとに平均FPSを算出。 - 表示は同じノードにアタッチした
Labelコンポーネントで行う。 - 画面隅への固定には
Widgetコンポーネントを利用し、マージン値をインスペクタから設定できるようにする。 - ラベルの文字サイズや色は
LabelとColorのプロパティを通じて設定。
インスペクタで設定可能なプロパティ設計
FPSCounter コンポーネントに用意する @property を列挙し、それぞれの役割を説明します。
- updateInterval (number)
- ツールチップ:
FPSを再計算して表示を更新する間隔(秒) - 役割: FPSを何秒ごとに更新するかを指定します。
- 例: 0.5 なら 0.5秒ごとに表示を更新。値が小さいほど表示が細かく変動します。
- ツールチップ:
- decimalPlaces (number)
- ツールチップ:
FPS表示の小数点以下の桁数 - 役割: FPSを小数点何桁まで表示するか。
- 例: 0 なら整数のみ、1 なら「59.3 FPS」のように表示。
- ツールチップ:
- prefix (string)
- ツールチップ:
FPS表示の前に付けるテキスト - 役割: ラベルの先頭に付ける文字列。
- 例:
FPS:とすると「FPS: 60」と表示。
- ツールチップ:
- suffix (string)
- ツールチップ:
FPS表示の後ろに付けるテキスト - 役割: ラベルの末尾に付ける文字列。
- 例:
fpsとすると「60 fps」と表示。
- ツールチップ:
- textColor (Color)
- ツールチップ:
ラベルの文字色 - 役割: FPSラベルの色を指定します。
- 例: デフォルトは緑色 (0,255,0,255) など。
- ツールチップ:
- fontSize (number)
- ツールチップ:
ラベルのフォントサイズ - 役割: FPSラベルの文字サイズ。
- 例: 20〜24 くらいが一般的なデバッグ表示に適しています。
- ツールチップ:
- useWidget (boolean)
- ツールチップ:
Widgetで画面隅に固定表示するかどうか - 役割: 有効にすると
Widgetを使って画面の隅に固定配置します。 - 無効にすると、ノードの位置は自由配置になります(Widgetは無効化)。
- ツールチップ:
- anchorCorner (enum)
- ツールチップ:
画面のどの隅に表示するか - 役割: 左上 / 右上 / 左下 / 右下 のいずれかを指定。
- Widget の
isAlignTop,isAlignLeftなどを適切に設定します。
- ツールチップ:
- marginX (number)
- ツールチップ:
画面端からの水平マージン(ピクセル) - 役割: 画面端からどれくらい内側に表示するか(X方向)。
- 例: 10 なら、左/右端から10px内側に配置。
- ツールチップ:
- marginY (number)
- ツールチップ:
画面端からの垂直マージン(ピクセル) - 役割: 画面端からどれくらい内側に表示するか(Y方向)。
- 例: 10 なら、上/下端から10px内側に配置。
- ツールチップ:
- showInEditor (boolean)
- ツールチップ:
エディタのプレビュー中(エディタ実行)にもFPSを更新するか - 役割: 有効にすると、エディタ上で再生したときにもFPSカウンタが動作します。
- 無効にすると、ビルド後の実行時だけFPSを更新します。
- ツールチップ:
TypeScriptコードの実装
以下が完成した FPSCounter.ts の全コードです。
import { _decorator, Component, Label, Color, UITransform, Widget, isValid, game, director } from 'cc';
const { ccclass, property, tooltip, type } = _decorator;
/**
* 画面隅にFPSを表示する汎用コンポーネント
* このスクリプトを任意のノードにアタッチするだけで動作します。
*/
enum FPSCorner {
TOP_LEFT = 0,
TOP_RIGHT = 1,
BOTTOM_LEFT = 2,
BOTTOM_RIGHT = 3,
}
@ccclass('FPSCounter')
export class FPSCounter extends Component {
@property({
tooltip: 'FPSを再計算して表示を更新する間隔(秒)。小さすぎると表示が激しく変動します。',
min: 0.05,
step: 0.05,
})
public updateInterval: number = 0.5;
@property({
tooltip: 'FPS表示の小数点以下の桁数。0なら整数のみ表示します。',
min: 0,
max: 4,
})
public decimalPlaces: number = 0;
@property({
tooltip: 'FPS表示の前に付けるテキスト。例: "FPS: "',
})
public prefix: string = 'FPS: ';
@property({
tooltip: 'FPS表示の後ろに付けるテキスト。例: " fps"',
})
public suffix: string = '';
@property({
tooltip: 'ラベルの文字色',
})
public textColor: Color = new Color(0, 255, 0, 255); // デフォルト: 緑
@property({
tooltip: 'ラベルのフォントサイズ',
min: 8,
step: 1,
})
public fontSize: number = 20;
@property({
tooltip: 'Widgetで画面隅に固定表示するかどうか',
})
public useWidget: boolean = true;
@property({
tooltip: '画面のどの隅に表示するか',
type: FPSCorner,
})
public anchorCorner: FPSCorner = FPSCorner.TOP_LEFT;
@property({
tooltip: '画面端からの水平マージン(ピクセル)',
})
public marginX: number = 10;
@property({
tooltip: '画面端からの垂直マージン(ピクセル)',
})
public marginY: number = 10;
@property({
tooltip: 'エディタのプレビュー中にもFPSを更新するかどうか',
})
public showInEditor: boolean = true;
// 内部状態
private _label: Label | null = null;
private _widget: Widget | null = null;
private _accumulatedTime = 0;
private _frameCount = 0;
private _currentFPS = 0;
onLoad() {
// Label コンポーネントの確保(なければ自動追加)
this._label = this.getComponent(Label);
if (!this._label) {
console.warn('[FPSCounter] Label コンポーネントが見つからなかったため、自動で追加します。');
this._label = this.addComponent(Label);
}
// UITransform がないとWidgetやUI配置が不安定になるので確認
let uiTransform = this.getComponent(UITransform);
if (!uiTransform) {
console.warn('[FPSCounter] UITransform コンポーネントが見つからなかったため、自動で追加します。');
uiTransform = this.addComponent(UITransform);
}
// Widget コンポーネントの確保(useWidgetがtrueなら有効化)
this._widget = this.getComponent(Widget);
if (!this._widget) {
this._widget = this.addComponent(Widget);
}
// Label の初期設定
if (this._label) {
this._label.color = this.textColor;
this._label.fontSize = this.fontSize;
this._label.string = this.prefix + '--' + this.suffix;
}
// Widget 初期化
this._applyWidgetSettings();
// 内部カウンタ初期化
this._accumulatedTime = 0;
this._frameCount = 0;
this._currentFPS = 0;
}
start() {
// 実行環境に応じて動作を制御(エディタ再生を無効にしたい場合への対応)
if (!this.showInEditor) {
// エディタ実行かどうかを判定(Cocos 3.8では game.isPaused 等ではなく、エディタビルドフラグを参照)
// ここでは簡易的に director.isPaused() をチェックしないで常に動作させるが、
// showInEditor=false の場合はビルド実行時を主な用途とする想定。
}
}
update(dt: number) {
// dtが0や負数になる可能性は低いが、防御的にチェック
if (dt = this.updateInterval) {
const fps = this._frameCount / this._accumulatedTime;
this._currentFPS = fps;
this._updateLabelText();
// カウンタをリセット
this._accumulatedTime = 0;
this._frameCount = 0;
}
}
/**
* 現在のFPS値をフォーマットしてラベルに反映する
*/
private _updateLabelText() {
if (!isValid(this._label)) {
return;
}
// 小数点桁数に応じて丸め
const factor = Math.pow(10, this.decimalPlaces);
const roundedFPS = Math.round(this._currentFPS * factor) / factor;
const text = `${this.prefix}${roundedFPS}${this.suffix}`;
this._label!.string = text;
this._label!.color = this.textColor;
this._label!.fontSize = this.fontSize;
}
/**
* Widget 設定を適用し、指定した画面隅に固定表示する
*/
private _applyWidgetSettings() {
if (!this._widget) {
return;
}
this._widget.enabled = this.useWidget;
if (!this.useWidget) {
return;
}
// まずすべてのアラインを無効化
this._widget.isAlignTop = false;
this._widget.isAlignBottom = false;
this._widget.isAlignLeft = false;
this._widget.isAlignRight = false;
switch (this.anchorCorner) {
case FPSCorner.TOP_LEFT:
this._widget.isAlignTop = true;
this._widget.isAlignLeft = true;
this._widget.top = this.marginY;
this._widget.left = this.marginX;
break;
case FPSCorner.TOP_RIGHT:
this._widget.isAlignTop = true;
this._widget.isAlignRight = true;
this._widget.top = this.marginY;
this._widget.right = this.marginX;
break;
case FPSCorner.BOTTOM_LEFT:
this._widget.isAlignBottom = true;
this._widget.isAlignLeft = true;
this._widget.bottom = this.marginY;
this._widget.left = this.marginX;
break;
case FPSCorner.BOTTOM_RIGHT:
this._widget.isAlignBottom = true;
this._widget.isAlignRight = true;
this._widget.bottom = this.marginY;
this._widget.right = this.marginX;
break;
}
// 即座にレイアウトを反映
this._widget.updateAlignment();
}
// インスペクタでプロパティが変更されたときに即座に反映したい設定をここで処理
protected onValidate() {
if (this._label) {
this._label.color = this.textColor;
this._label.fontSize = this.fontSize;
}
this._applyWidgetSettings();
}
}
コードのポイント解説
1. onLoad での初期化
Labelが見つからなければ自動追加し、警告ログを出します。UITransformがなければ自動追加して、UIノードとして扱えるようにします。Widgetを取得(なければ追加)し、useWidgetフラグに応じて設定を適用します。- ラベルの初期文字列を
"FPS: --"のように設定します。
2. update でのFPS計測ロジック
dtを加算しつつ、フレーム数をカウントします。accumulatedTime >= updateIntervalになったタイミングで、fps = frameCount / accumulatedTimeを計算します。- FPSを算出したら、ラベルに表示するテキストを更新し、カウンタをリセットします。
3. _updateLabelText での表示フォーマット
decimalPlacesに応じて FPS を丸めます(例: 1桁なら 59.3)。prefixとsuffixを使って「FPS: 60」「60 fps」など自由な形式に変更できます。- 同時に
colorとfontSizeも毎回反映することで、インスペクタの変更を即座に見た目に反映できます。
4. _applyWidgetSettings で画面隅への固定
useWidgetがfalseの場合は、Widgetを無効化してノードのローカル位置を自由に使えるようにします。anchorCornerに応じてisAlignTop,isAlignBottom,isAlignLeft,isAlignRightを切り替えます。marginX,marginYをそれぞれの辺のマージンに設定し、updateAlignment()で即座に反映します。
5. onValidate でエディタ上の変更を即時反映
onValidateはプロパティ変更時に呼ばれるため、色・フォントサイズ・Widget設定をここで再適用し、インスペクタ変更がすぐ見た目に反映されるようにしています。
使用手順と動作確認
ここからは、実際に Cocos Creator 3.8.7 のエディタでこのコンポーネントを使う手順を説明します。
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、FPS表示用のスクリプトを置きたいフォルダ(例:
assets/scripts)を選択します。 - そのフォルダ上で右クリックし、Create → TypeScript を選択します。
- 新規に作成されたファイル名を
FPSCounter.tsに変更します。 FPSCounter.tsをダブルクリックして開き、内容をすべて削除して、上記の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用ノードの作成
- Hierarchy パネルで、Canvas 内にFPS表示用ノードを作成します。
例: Canvas を右クリック → Create → Empty Node を選択し、名前をFPSLabelなどに変更します。 - このノードは後で
FPSCounterをアタッチするだけなので、特別な設定は不要です。
3. FPSCounter コンポーネントのアタッチ
- Hierarchy パネルで先ほど作成した
FPSLabelノードを選択します。 - 右側の Inspector パネルで、Add Component ボタンをクリックします。
- Custom カテゴリを開き、その中から FPSCounter を選択して追加します。
(もし Custom 内に見つからない場合は、スクリプトの保存やビルド状態を確認してください)
4. インスペクタでプロパティを調整
FPSLabel ノードにアタッチされた FPSCounter コンポーネントのプロパティを、次のように設定してみましょう:
- Update Interval:
0.5
→ 0.5秒ごとにFPSを更新します。 - Decimal Places:
0
→ 整数のみ表示(例: 60)。 - Prefix:
FPS:
→ 表示例「FPS: 60」。 - Suffix: 空欄または
fps
→ 表示例「FPS: 60 fps」。 - Text Color: 緑(デフォルトのままでOK)
- Font Size:
20〜24 - Use Widget: チェックを入れる(true)
- Anchor Corner:
TOP_LEFT(左上) - Margin X:
10 - Margin Y:
10 - Show In Editor: チェックを入れる(true)
→ エディタの再生ボタンを押したときにもFPSが更新されます。
この時点で、ノードには自動的に Label, UITransform, Widget が追加されているはずです。Inspector のコンポーネント一覧を確認してみてください。
5. 再生して動作確認
- エディタ上部の Play ボタン(▶)を押してシーンを再生します。
- Gameビュー上部の左上(もしくは設定した隅)に、緑色の「FPS: 60」のようなラベルが表示されていることを確認します。
- ゲーム内容が重くなったり、ウィンドウサイズを変えたりして FPS が変動すると、表示値も一定間隔で更新されるはずです。
- Inspector で
Decimal Placesを1に変更してみると、「FPS: 59.8」のように小数点付きで表示されるようになります。 Anchor CornerをBOTTOM_RIGHTに変更して再生し直すと、今度は画面右下にFPSが表示されることも確認できます。
6. 複数シーン・複数プロジェクトでの再利用
- このコンポーネントは他のカスタムスクリプトに依存していないため、どのシーンでも、どのプロジェクトでも同じようにアタッチして使えます。
- 別のプロジェクトで使いたい場合は、
FPSCounter.tsをそのままコピーしてassets/scriptsなどに貼り付けるだけでOKです。
まとめ
このガイドでは、Cocos Creator 3.8.7 と TypeScript で、
- 外部の GameManager やシングルトンに依存せず、
- 任意のノードにアタッチするだけで動作し、
- 画面の好きな隅に固定して FPS を表示できる
汎用FPS表示コンポーネント「FPSCounter」 を実装しました。
このコンポーネントをプロジェクトの共通ツールとして用意しておけば、
- 新しいシーンを作るたびにFPS表示UIを作り直す必要がなくなる
- パフォーマンスチューニング時に、すぐにFPSを確認できる
- Widgetのマージンや色・フォントサイズを調整するだけで、どの解像度でも見やすい表示を維持できる
といったメリットがあり、開発効率が大きく向上します。
この設計パターン(必要な標準コンポーネントは自動追加し、インスペクタからすべて設定可能にする)は、他のデバッグUIや汎用コンポーネントにも応用できます。例えば、「メモリ使用量表示」「ネットワーク状態表示」「簡易ログコンソール」なども、同じような方針で実装しておくと、プロジェクトをまたいで再利用しやすくなります。
まずはこの FPSCounter をプロジェクトに組み込んで、日々のデバッグを快適にしてみてください。




