【Cocos Creator】アタッチするだけ!VersionChecker (バージョン確認)の実装方法【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】VersionCheckerの実装:アタッチするだけで起動時にサーバーへHTTPリクエストを送り、最新版があるか確認・通知する汎用スクリプト

このコンポーネントは、ゲーム起動時(または任意のタイミング)に指定したURLへHTTPリクエストを送り、サーバー側に置かれた「最新バージョン情報」とローカルのアプリバージョンを比較して、最新版があるかどうかを判定・通知するための汎用スクリプトです。

VersionChecker を任意のノードにアタッチし、インスペクタから URL や現在のバージョン、比較方法などを設定するだけで動作します。他の GameManager やシングルトンに依存しないため、どのプロジェクトにも簡単に組み込めます。


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

1. 機能要件の整理

  • 起動時(start())に HTTP(S) リクエストを送る。
  • 任意のタイミングで再チェックできるように、パブリックメソッド checkNow() も用意する。
  • レスポンスから「最新バージョン文字列」を取得し、インスペクタで指定した「現在のバージョン」と比較する。
  • バージョンが異なる場合(または新しい場合)、ログ・UI(任意)で通知できるようにする。
  • 外部スクリプトに依存せず、インスペクタの設定だけで完結する。
  • HTTP エラーや JSON パースエラーなどを防御的に扱い、エラー内容をログ出力する。

2. 通信仕様の前提

汎用性を高めるため、レスポンス形式を以下のようなシンプルな JSON とします:


{
  "latestVersion": "1.2.0",
  "message": "新しいバージョンがあります。",
  "forceUpdate": false
}

ただし、キー名やパスはインスペクタから設定できるようにし、異なる API 仕様にも対応できるようにします。

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

  • enabledOnStart: boolean
    • 起動時に自動でバージョンチェックを行うかどうか。
    • true の場合、start() で自動実行。
  • requestUrl: string
    • バージョン情報を取得する HTTP/HTTPS エンドポイント URL。
    • 例: https://example.com/api/version
  • httpMethod: Enum ("GET" | "POST")
    • HTTP メソッドの指定。
    • GET の場合はクエリパラメータ、POST の場合は JSON ボディを送信(必要な場合)。
  • requestBodyJson: string
    • POST 時に送信する JSON 文字列。
    • 空文字ならボディ送信なし。
  • currentVersion: string
    • クライアント側の現在バージョン。
    • 例: 1.0.0(セマンティックバージョン形式を推奨)
  • responseLatestKey: string
    • レスポンス JSON 内の「最新バージョン」が入っているキー名。
    • デフォルト: "latestVersion"
  • responseMessageKey: string
    • レスポンス JSON 内のユーザー向けメッセージが入っているキー名。
    • デフォルト: "message"
  • compareAsSemantic: boolean
    • バージョンを major.minor.patch の数値として比較するかどうか。
    • true: 数値として比較(1.10.0 > 1.2.0)。
    • false: 文字列比較(単純な < / > 比較)。
  • logDetail: boolean
    • 詳細ログをコンソールに出力するかどうか。
    • デバッグ中は true、本番では false 推奨。
  • showResultAsAlert: boolean
    • ブラウザ環境で window.alert による簡易ダイアログ表示を行うかどうか。
    • ネイティブ環境ではログ出力のみ。
  • timeoutMs: number
    • リクエストのタイムアウト時間 (ミリ秒)。
    • 0 以下ならタイムアウトなし。

UI コンポーネント(Label など)には依存せず、すべてログ出力 + 任意のアラートで完結させることで、完全な独立性を保ちます。必要であれば、後から Label 連携などを追加するのは簡単です。


TypeScriptコードの実装

以下が、Cocos Creator 3.8.7 向けの VersionChecker コンポーネントの完全実装です。


import { _decorator, Component, sys } from 'cc';
const { ccclass, property } = _decorator;

/**
 * VersionChecker
 * 起動時または任意のタイミングでサーバーに HTTP リクエストを送り、
 * 最新バージョンと現在バージョンを比較して結果を通知する汎用コンポーネント。
 *
 * このスクリプト単体で完結し、他のカスタムスクリプトには依存しません。
 */
enum HttpMethod {
    GET = 0,
    POST = 1,
}

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

    @property({
        tooltip: '起動時(start)に自動でバージョンチェックを行うかどうか。',
    })
    public enabledOnStart: boolean = true;

    @property({
        tooltip: 'バージョン情報を取得する HTTP/HTTPS エンドポイント URL。\n例: https://example.com/api/version',
    })
    public requestUrl: string = '';

    @property({
        type: HttpMethod,
        tooltip: 'HTTP メソッドの指定。\nGET: クエリパラメータなどで送信\nPOST: JSON ボディを送信(requestBodyJson が空でなければ)',
    })
    public httpMethod: HttpMethod = HttpMethod.GET;

    @property({
        tooltip: 'POST 時に送信する JSON 文字列。\n空文字の場合はボディ送信なし。\n例: {"platform":"web","channel":"release"}',
        multiline: true,
    })
    public requestBodyJson: string = '';

    @property({
        tooltip: '現在のアプリバージョンを文字列で指定します。\n例: 1.0.0',
    })
    public currentVersion: string = '1.0.0';

    @property({
        tooltip: 'レスポンス JSON 内で最新バージョンが格納されているキー名。\n例: latestVersion',
    })
    public responseLatestKey: string = 'latestVersion';

    @property({
        tooltip: 'レスポンス JSON 内でメッセージが格納されているキー名。\n例: message',
    })
    public responseMessageKey: string = 'message';

    @property({
        tooltip: 'バージョンをセマンティックバージョン(major.minor.patch)として数値比較するかどうか。\nfalse の場合は単純な文字列比較になります。',
    })
    public compareAsSemantic: boolean = true;

    @property({
        tooltip: '詳細ログをコンソールに出力するかどうか。\nデバッグ時は true、本番では false 推奨。',
    })
    public logDetail: boolean = true;

    @property({
        tooltip: 'ブラウザ環境で結果を window.alert で表示するかどうか。\nネイティブ環境ではログ出力のみになります。',
    })
    public showResultAsAlert: boolean = false;

    @property({
        tooltip: 'リクエストのタイムアウト時間(ミリ秒)。\n0 以下の場合はタイムアウトなし。',
        min: 0,
    })
    public timeoutMs: number = 5000;

    // 内部状態
    private _isChecking: boolean = false;
    private _lastResultText: string = '';

    onLoad() {
        // 特別な標準コンポーネント依存はないのでチェック不要。
        // ただし、URL 未設定などの初期状態を警告しておく。
        if (!this.requestUrl) {
            console.warn('[VersionChecker] requestUrl が設定されていません。インスペクタから URL を設定してください。');
        }
    }

    start() {
        // 起動時に自動チェック
        if (this.enabledOnStart) {
            this.checkNow();
        }
    }

    /**
     * 外部(ボタンの onClick など)から呼び出して、
     * 任意のタイミングでバージョンチェックを実行できます。
     */
    public checkNow() {
        if (this._isChecking) {
            if (this.logDetail) {
                console.warn('[VersionChecker] 既にバージョンチェック中です。二重実行はスキップします。');
            }
            return;
        }

        if (!this.requestUrl) {
            console.error('[VersionChecker] requestUrl が空です。インスペクタから有効な URL を設定してください。');
            return;
        }

        this._isChecking = true;
        if (this.logDetail) {
            console.log('[VersionChecker] バージョンチェック開始: ', this.requestUrl);
        }

        this._sendRequest()
            .then((responseText) => {
                this._handleResponse(responseText);
            })
            .catch((err) => {
                console.error('[VersionChecker] リクエストエラー: ', err);
                this._lastResultText = `バージョンチェックに失敗しました: ${err}`;
                this._notifyUser(this._lastResultText);
            })
            .finally(() => {
                this._isChecking = false;
            });
    }

    /**
     * 実際の HTTP リクエスト送信処理。
     * Cocos Creator 3.8 ではブラウザ/ネイティブともに fetch が利用可能なため、
     * fetch API を使用しています。
     */
    private _sendRequest(): Promise<string> {
        const url = this.requestUrl;
        const method = this.httpMethod === HttpMethod.POST ? 'POST' : 'GET';

        const fetchOptions: RequestInit = {
            method,
            headers: {
                'Content-Type': 'application/json',
            },
        };

        if (method === 'POST' && this.requestBodyJson.trim().length > 0) {
            fetchOptions.body = this.requestBodyJson;
        }

        let fetchPromise = fetch(url, fetchOptions).then((res) => {
            if (!res.ok) {
                throw new Error(`HTTP エラー: ${res.status} ${res.statusText}`);
            }
            return res.text();
        });

        if (this.timeoutMs > 0) {
            // タイムアウト制御
            const timeoutPromise = new Promise<never>((_, reject) => {
                const id = setTimeout(() => {
                    clearTimeout(id);
                    reject(new Error(`タイムアウト: ${this.timeoutMs}ms`));
                }, this.timeoutMs);
            });

            // 先に完了した方を採用
            fetchPromise = Promise.race([fetchPromise, timeoutPromise]) as Promise<string>;
        }

        return fetchPromise;
    }

    /**
     * レスポンス文字列を解析し、バージョン比較と通知を行う。
     */
    private _handleResponse(responseText: string) {
        if (this.logDetail) {
            console.log('[VersionChecker] レスポンス受信: ', responseText);
        }

        let json: any;
        try {
            json = JSON.parse(responseText);
        } catch (e) {
            console.error('[VersionChecker] JSON 解析に失敗しました。レスポンスが JSON 形式ではありません。', e);
            this._lastResultText = 'サーバーからのレスポンス形式が不正です。';
            this._notifyUser(this._lastResultText);
            return;
        }

        const latestKey = this.responseLatestKey || 'latestVersion';
        const messageKey = this.responseMessageKey || 'message';

        const latestVersion = json[latestKey];
        const messageFromServer = json[messageKey];

        if (typeof latestVersion !== 'string') {
            console.error(`[VersionChecker] レスポンス JSON に文字列型の "${latestKey}" が存在しません。`);
            this._lastResultText = 'サーバーから最新バージョン情報を取得できませんでした。';
            this._notifyUser(this._lastResultText);
            return;
        }

        const current = this.currentVersion;
        const latest = latestVersion;

        let comparisonResult: number; // -1: 最新より古い, 0: 同じ, 1: 最新より新しい
        if (this.compareAsSemantic) {
            comparisonResult = this._compareSemanticVersion(current, latest);
        } else {
            // 文字列比較(辞書順)
            if (current === latest) {
                comparisonResult = 0;
            } else if (current < latest) {
                comparisonResult = -1;
            } else {
                comparisonResult = 1;
            }
        }

        if (comparisonResult < 0) {
            // クライアントが古い
            this._lastResultText = `新しいバージョンが利用可能です。\n現在: ${current}\n最新: ${latest}`;
            if (messageFromServer) {
                this._lastResultText += `\nメッセージ: ${messageFromServer}`;
            }
            console.warn('[VersionChecker] アップデートが必要です: ', this._lastResultText);
            this._notifyUser(this._lastResultText);
        } else if (comparisonResult === 0) {
            this._lastResultText = `現在のバージョンは最新です。\nバージョン: ${current}`;
            console.log('[VersionChecker] 最新バージョンを使用しています。', this._lastResultText);
            this._notifyUser(this._lastResultText, /*onlyLogIfAlertDisabled*/ true);
        } else {
            // クライアントがサーバーより新しい(テスト中など)
            this._lastResultText = `クライアントのバージョンがサーバーより新しい可能性があります。\n現在: ${current}\nサーバー: ${latest}`;
            console.warn('[VersionChecker] クライアントの方が新しいバージョンです。', this._lastResultText);
            this._notifyUser(this._lastResultText, /*onlyLogIfAlertDisabled*/ true);
        }
    }

    /**
     * セマンティックバージョン (major.minor.patch) を比較する。
     * v1 < v2 の場合 -1, v1 == v2 の場合 0, v1 > v2 の場合 1 を返す。
     */
    private _compareSemanticVersion(v1: string, v2: string): number {
        const parse = (v: string): number[] => {
            return v.split('.').map((s) => {
                const n = parseInt(s, 10);
                return isNaN(n) ? 0 : n;
            });
        };

        const a = parse(v1);
        const b = parse(v2);

        const len = Math.max(a.length, b.length);
        for (let i = 0; i < len; i++) {
            const x = a[i] || 0;
            const y = b[i] || 0;
            if (x < y) return -1;
            if (x > y) return 1;
        }
        return 0;
    }

    /**
     * 結果をユーザーに通知する。
     * showResultAsAlert が true かつブラウザ環境のときは window.alert で表示。
     * それ以外はコンソールログのみ。
     *
     * @param text 表示するメッセージ
     * @param onlyLogIfAlertDisabled true の場合、showResultAsAlert が false のときのみ通知する
     */
    private _notifyUser(text: string, onlyLogIfAlertDisabled: boolean = false) {
        if (this.showResultAsAlert && sys.isBrowser && typeof window !== 'undefined' && typeof (window as any).alert === 'function') {
            if (!onlyLogIfAlertDisabled) {
                (window as any).alert(text);
            }
        } else {
            // アラート未使用の場合はログのみ
            if (this.logDetail || !onlyLogIfAlertDisabled) {
                console.log('[VersionChecker] 結果: ', text);
            }
        }
    }

    /**
     * 直近のチェック結果テキストを取得するためのヘルパー。
     * UI に表示したい場合などに利用できます(このコンポーネント単体では使用しません)。
     */
    public getLastResultText(): string {
        return this._lastResultText;
    }
}

コードの要点解説

  • onLoad()
    • 必須コンポーネント依存はないため取得処理は不要。
    • ただし requestUrl が未設定の場合に警告ログを出し、エディタでの設定漏れに気づけるようにしています。
  • start()
    • enabledOnStart が true のとき、自動で checkNow() を呼び出します。
    • ゲーム起動時に一度だけチェックしたい場合、このフラグを true のままにしておけば OK です。
  • checkNow()
    • 外部(ボタンの onClick など)からも呼び出せる公開メソッド。
    • 二重実行防止のため、_isChecking フラグで制御。
    • URL 未設定の場合はエラーログを出して即終了。
    • 内部で _sendRequest()_handleResponse() の流れを Promise チェーンで処理しています。
  • _sendRequest()
    • fetch API を用いて HTTP リクエストを送信。
    • httpMethod に応じて GET / POST を切り替え。
    • POST かつ requestBodyJson が空でない場合のみボディを送信。
    • timeoutMs > 0 の場合は Promise.race でタイムアウト制御。
  • _handleResponse()
    • レスポンス文字列を JSON.parse し、responseLatestKey / responseMessageKey で必要な情報を取り出します。
    • compareAsSemantic に応じてセマンティック比較 or 文字列比較を実行。
    • 結果に応じて _lastResultText を更新し、ログ + _notifyUser() で通知。
  • _compareSemanticVersion()
    • 1.2.3 のような形式を [1, 2, 3] に分解して数値比較。
    • 桁数が異なる場合にも対応(例: 1.2 vs 1.2.1)。
  • _notifyUser()
    • showResultAsAlert が true かつブラウザ環境のとき、window.alert で簡易ダイアログ表示。
    • ネイティブ環境やアラート無効時はコンソールログのみ。

使用手順と動作確認

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

  1. エディタ左下の Assets パネルで、任意のフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択します。
  3. ファイル名を VersionChecker.ts に変更します。
  4. 作成された VersionChecker.ts をダブルクリックして開き、内容をすべて削除して、前述の TypeScript コードをそのまま貼り付けて保存します。

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

  1. エディタ左上の Hierarchy パネルで、Canvas(なければ先に Create → UI → Canvas で作成)を選択します。
  2. Canvas を右クリックして Create → Empty Node を選択し、名前を VersionCheckerNode などに変更します。

このノードは UI などを持たない空ノードで構いません。VersionChecker は他のコンポーネントに依存していないため、空ノードにアタッチするだけで動作します。

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

  1. Hierarchy で先ほど作成した VersionCheckerNode を選択します。
  2. 右側の Inspector パネルで、Add Component ボタンをクリックします。
  3. Custom カテゴリの中から VersionChecker を選択して追加します。

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

Inspector に表示された VersionChecker の各プロパティを、次のように設定してみましょう。

  • Enabled On Start: true
    • ゲーム起動時に自動チェックしたいので ON。
  • Request Url: https://example.com/api/version
    • 実際の API エンドポイントに置き換えてください。
  • Http Method: GET
    • まずは GET でテストするのがおすすめです。
  • Request Body Json: 空のままで OK(GET のため無視されます)。
  • Current Version: 1.0.0
    • 今のアプリバージョンを入力します。
  • Response Latest Key: latestVersion
    • サーバーのレスポンスが {"latestVersion":"1.2.0"} であることを想定。
  • Response Message Key: message
    • 任意のメッセージ(「新しいバージョンがあります」など)が入るキー名。
  • Compare As Semantic: true
    • セマンティックバージョンでの比較を有効化。
  • Log Detail: true
    • 動作確認中は詳細ログを見たいので ON。
  • Show Result As Alert: true(Web ビルドで確認する場合)
    • ブラウザ上で alert ダイアログが表示されます。
  • Timeout Ms: 5000
    • 5 秒以内に応答がない場合はタイムアウト扱い。

5. サーバーレスポンスの準備例

テスト用に、以下のような JSON を返す簡易 API を用意すると分かりやすいです(ローカルサーバーでも可)。


{
  "latestVersion": "1.2.0",
  "message": "新しいバージョンが公開されています。ストアからアップデートしてください。"
}

この場合、Current Version = 1.0.0 としておけば、「新しいバージョンが利用可能です」という結果になります。

6. 実行して動作を確認する

  1. エディタ右上の Play ボタン(プレビュー)をクリックします。
  2. Game ビューが開き、シーンが再生されます。
  3. Console(右下または別ウィンドウ)を開き、ログを確認します。
  4. 設定が正しければ、数秒以内に次のようなログ(またはアラート)が表示されます。
    • アップデートが必要な場合:
      [VersionChecker] アップデートが必要です: 新しいバージョンが利用可能です。
      現在: 1.0.0
      最新: 1.2.0
      メッセージ: 新しいバージョンが公開されています。ストアからアップデートしてください。
      
    • 最新バージョンの場合:
      [VersionChecker] 最新バージョンを使用しています。 現在のバージョンは最新です。
      バージョン: 1.2.0
      
    • エラーやタイムアウトの場合:
      [VersionChecker] リクエストエラー: Error: タイムアウト: 5000ms
      

7. 任意タイミングでの再チェック(ボタン連携例)

VersionChecker は外部から checkNow() を呼び出せるようになっているため、UI ボタンから再チェックすることもできます。

  1. Hierarchy で Canvas を右クリック → Create → UI → Button を選択します。
  2. 作成された Button ノードを選択し、Inspector の Button コンポーネントの Click Events に新しい要素を追加します。
  3. Click Events の TargetVersionCheckerNode をドラッグ&ドロップします。
  4. Component ドロップダウンから VersionChecker を選択します。
  5. Handler ドロップダウンから checkNow を選択します。

これで、実行中にボタンをクリックするたびにサーバーへバージョンチェックをリクエストできるようになります。


まとめ

今回実装した VersionChecker コンポーネントは、

  • 起動時に自動でバージョンチェックを行う。
  • ボタンなどから任意タイミングで再チェックできる。
  • レスポンス形式やバージョン比較方法をインスペクタから柔軟に変更できる。
  • UI コンポーネントや他のカスタムスクリプトに一切依存しない。

という特徴を持つ、完全に独立した汎用コンポーネントです。

実プロジェクトでは、このコンポーネントの結果をもとに、

  • 「アップデートしてください」ダイアログを自作 UI で表示する。
  • 強制アップデートフラグ(forceUpdate など)をレスポンスに追加し、ゲーム進行をブロックする。
  • プラットフォーム情報(iOS/Android/Web)やチャンネル情報を requestBodyJson に含め、サーバー側でバージョンを出し分ける。

といった拡張が簡単に行えます。

まずはこの記事のコードをそのまま導入し、ログやアラートで動作を確認したうえで、自分のプロジェクトの仕様に合わせて 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をコピーしました!