Unityの学習を進めていくと、つい「とりあえず全部 Update に書いてしまう」コードになりがちですよね。
ゲームのロジック、入力、UI更新、サーバー通信…すべてを1つの巨大なスクリプトに押し込むと、次のような問題が出てきます。

  • どこで何をしているのか分かりづらく、バグの原因を特定しづらい
  • ちょっとした修正で別の機能が壊れやすい
  • プレハブごとに挙動を変えたいのに、1つの巨大スクリプトに依存していて柔軟性がない

特に「オンラインゲームのバージョン確認」や「メンテナンス情報の取得」などのネットワーク処理を、Updateの中でベタ書きしてしまうと、フレームごとに無駄なチェックをしてしまったり、シーンが変わるたびに同じ処理を書き直す羽目になったりします。

そこでこの記事では、「バージョン確認」という機能だけに責務を絞ったコンポーネント VersionChecker を作ってみましょう。
起動時に一度だけHTTPリクエストを送り、最新版があるかを判定し、結果をイベントで通知するコンポーネントです。

【Unity】起動時にサクッとオンライン版数チェック!「VersionChecker」コンポーネント

以下は Unity 6(C#)で動作する、完全な VersionChecker コンポーネントの実装例です。
Unity標準の UnityWebRequest を使い、JSON形式のレスポンスを想定しています。


using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

/// <summary>
/**
 * アプリ起動時にHTTPリクエストでサーバーのバージョン情報を取得し、
 * 現在のクライアントバージョンと比較するコンポーネント。
 *
 * - 責務は「バージョン確認と結果通知」に限定
 * - 実際のUI表示やシーン遷移は、他コンポーネントに任せる設計
 */
/// </summary>
public class VersionChecker : MonoBehaviour
{
    // ==============================
    // 設定項目(インスペクターから編集)
    // ==============================

    [Header("バージョン設定")]
    [Tooltip("現在のクライアントバージョン(例: 1.0.0)")]
    [SerializeField] private string currentVersion = "1.0.0";

    [Tooltip("バージョン情報を取得するAPIのURL")]
    [SerializeField] private string versionApiUrl = "https://example.com/api/version";

    [Header("起動タイミング")]
    [Tooltip("Awakeで自動チェックするかどうか")]
    [SerializeField] private bool checkOnAwake = true;

    [Tooltip("Startで自動チェックするかどうか(Awakeと両方ONでもOK)")]
    [SerializeField] private bool checkOnStart = false;

    [Header("タイムアウト設定(秒)")]
    [SerializeField] private float requestTimeoutSeconds = 10f;

    [Header("デバッグ")]
    [Tooltip("ログをConsoleに出すかどうか")]
    [SerializeField] private bool enableDebugLog = true;

    // ==============================
    // 結果通知用イベント
    // ==============================

    [Serializable]
    public class VersionCheckSuccessEvent : UnityEvent<VersionCheckResult> { }

    [Serializable]
    public class VersionCheckFailureEvent : UnityEvent<string> { }

    [Header("イベント")]
    [Tooltip("バージョンチェック成功時(サーバーから正常なレスポンスが返ってきた場合)")]
    [SerializeField] private VersionCheckSuccessEvent onSuccess = new VersionCheckSuccessEvent();

    [Tooltip("バージョンチェック失敗時(通信エラーやJSON解析エラーなど)")]
    [SerializeField] private VersionCheckFailureEvent onFailure = new VersionCheckFailureEvent();

    // ==============================
    // 内部状態
    // ==============================

    /// <summary>現在チェック中かどうか</summary>
    public bool IsChecking { get; private set; }

    /// <summary>最後に取得した結果(成功時のみ)</summary>
    public VersionCheckResult LastResult { get; private set; }

    // ==============================
    // ライフサイクル
    // ==============================

    private void Awake()
    {
        if (checkOnAwake)
        {
            StartVersionCheck();
        }
    }

    private void Start()
    {
        if (checkOnStart && !IsChecking && LastResult == null)
        {
            StartVersionCheck();
        }
    }

    // ==============================
    // 公開API
    // ==============================

    /// <summary>
    /// バージョンチェックを開始する(外部からも呼び出し可能)。
    /// すでにチェック中の場合は無視される。
    /// </summary>
    public void StartVersionCheck()
    {
        if (IsChecking)
        {
            Log("Version check already in progress. Ignoring StartVersionCheck call.");
            return;
        }

        if (string.IsNullOrEmpty(versionApiUrl))
        {
            HandleFailure("Version API URL is not set.");
            return;
        }

        StartCoroutine(VersionCheckCoroutine());
    }

    // ==============================
    // コルーチン本体
    // ==============================

    private IEnumerator VersionCheckCoroutine()
    {
        IsChecking = true;
        LastResult = null;

        Log($"Starting version check... (URL: {versionApiUrl})");

        using (UnityWebRequest request = UnityWebRequest.Get(versionApiUrl))
        {
            // タイムアウト設定(0以下なら無制限)
            if (requestTimeoutSeconds > 0f)
            {
                request.timeout = Mathf.RoundToInt(requestTimeoutSeconds);
            }

            // リクエスト送信
            yield return request.SendWebRequest();

#if UNITY_2020_2_OR_NEWER
            bool hasError = request.result != UnityWebRequest.Result.Success;
#else
            bool hasError = request.isNetworkError || request.isHttpError;
#endif

            if (hasError)
            {
                // 通信エラー
                string errorMessage = $"Version check failed: {request.error}";
                HandleFailure(errorMessage);
            }
            else
            {
                // HTTPステータスコード確認
                long statusCode = request.responseCode;
                if (statusCode < 200 || statusCode >= 300)
                {
                    string errorMessage = $"Version check HTTP error: {statusCode}";
                    HandleFailure(errorMessage);
                }
                else
                {
                    // 正常レスポンス
                    string json = request.downloadHandler.text;
                    Log($"Version check response: {json}");

                    // JSONパース
                    VersionApiResponse response = null;
                    try
                    {
                        response = JsonUtility.FromJson<VersionApiResponse>(json);
                    }
                    catch (Exception e)
                    {
                        HandleFailure($"JSON parse error: {e.Message}");
                        yield break;
                    }

                    if (response == null || string.IsNullOrEmpty(response.version))
                    {
                        HandleFailure("Invalid response: version field is missing.");
                    }
                    else
                    {
                        // バージョン比較
                        VersionCheckResult result = BuildResult(response);
                        LastResult = result;

                        Log($"Current: {result.CurrentVersion}, Latest: {result.LatestVersion}, " +
                            $"IsLatest: {result.IsLatest}, ForceUpdate: {result.ForceUpdate}");

                        // 成功イベント発火
                        onSuccess?.Invoke(result);
                    }
                }
            }
        }

        IsChecking = false;
    }

    // ==============================
    // 結果構築とエラーハンドリング
    // ==============================

    /// <summary>
    /// APIレスポンスからVersionCheckResultを構築し、バージョン比較を行う。
    /// </summary>
    private VersionCheckResult BuildResult(VersionApiResponse response)
    {
        // サーバーから取得した最新バージョン
        string latestVersion = response.version;

        // バージョン文字列を比較するため、System.Versionを使う
        bool parseCurrentOk = Version.TryParse(currentVersion, out Version current);
        bool parseLatestOk = Version.TryParse(latestVersion, out Version latest);

        bool isLatest = true;

        if (parseCurrentOk && parseLatestOk)
        {
            // current < latest なら最新版ではない
            isLatest = current >= latest;
        }
        else
        {
            // パースに失敗した場合は、文字列比較など別のルールにしても良い
            // ここでは「バージョン形式が不正な場合は常に最新ではない」とみなす
            isLatest = false;
        }

        // API側で「強制アップデート」のフラグを持たせる想定
        bool forceUpdate = response.forceUpdate;

        return new VersionCheckResult(
            currentVersion: currentVersion,
            latestVersion: latestVersion,
            isLatest: isLatest,
            forceUpdate: forceUpdate,
            rawResponse: response
        );
    }

    /// <summary>
    /// 失敗時の共通処理
    /// </summary>
    private void HandleFailure(string message)
    {
        Log(message, isError: true);
        onFailure?.Invoke(message);
        IsChecking = false;
    }

    /// <summary>
    /// デバッグログ出力(フラグでON/OFF)
    /// </summary>
    private void Log(string message, bool isError = false)
    {
        if (!enableDebugLog) return;

        if (isError)
        {
            Debug.LogError($"[VersionChecker] {message}");
        }
        else
        {
            Debug.Log($"[VersionChecker] {message}");
        }
    }
}

/// <summary>
/**
 * バージョンAPIのレスポンス形式。
 * サーバー側は、例えば以下のようなJSONを返す想定:
 *
 * {
 *   "version": "1.2.0",
 *   "forceUpdate": true,
 *   "message": "新バージョンがリリースされました!",
 *   "storeUrl": "https://store.example.com/your-game"
 * }
 */
/// </summary>
[Serializable]
public class VersionApiResponse
{
    // 必須フィールド
    public string version;

    // オプションフィールド
    public bool forceUpdate;
    public string message;
    public string storeUrl;
}

/// <summary>
/**
 * バージョンチェック結果をまとめた構造体。
 * UIやゲームロジック側は、この情報だけを見ればOK。
 */
/// </summary>
[Serializable]
public class VersionCheckResult
{
    /// <summary>現在のクライアントバージョン</summary>
    public string CurrentVersion { get; private set; }

    /// <summary>サーバー上の最新バージョン</summary>
    public string LatestVersion { get; private set; }

    /// <summary>現在バージョンが最新かどうか</summary>
    public bool IsLatest { get; private set; }

    /// <summary>強制アップデートが必要かどうか</summary>
    public bool ForceUpdate { get; private set; }

    /// <summary>生のAPIレスポンス(メッセージやストアURLなど)</summary>
    public VersionApiResponse RawResponse { get; private set; }

    public VersionCheckResult(
        string currentVersion,
        string latestVersion,
        bool isLatest,
        bool forceUpdate,
        VersionApiResponse rawResponse)
    {
        CurrentVersion = currentVersion;
        LatestVersion = latestVersion;
        IsLatest = isLatest;
        ForceUpdate = forceUpdate;
        RawResponse = rawResponse;
    }
}

使い方の手順

ここでは、タイトル画面のプレイヤーオブジェクトや専用の「VersionManager」オブジェクトにアタッチして使う例を想定します。

  1. 空のGameObjectを作成する
    シーン(例: TitleScene)上で、
    GameObject > Create Empty から空のオブジェクトを作成し、
    名前を VersionManager などに変更します。
  2. VersionCheckerコンポーネントをアタッチ
    作成したGameObjectを選択し、
    Add Component から VersionChecker を追加します。
    インスペクターで次の項目を設定します。
    • Current Version: 今ビルドしているゲームのバージョン(例: 1.0.0
    • Version Api Url: バージョン情報を返すAPIのURL(ダミーでもOK)
    • Check On Awake: 起動直後にチェックしたい場合はON
    • Request Timeout Seconds: 通信タイムアウト秒数(例: 10
    • Enable Debug Log: 開発中はON、本番ではOFFにしてもOK
  3. 結果を受け取るUI用コンポーネントを作る
    バージョンチェックの結果を見て、UIを切り替えるコンポーネントを別に用意します。
    例えば「最新版ならスタートボタンを表示」「古いならアップデートを促すパネルを表示」といった処理を担当させます。

    例として、簡単なUI制御スクリプトを示します(任意のGameObjectにアタッチして使います)。

    
    using UnityEngine;
    using UnityEngine.UI;
    
    public class VersionCheckUIHandler : MonoBehaviour
    {
        [Header("参照")]
        [SerializeField] private VersionChecker versionChecker;
        [SerializeField] private GameObject latestVersionPanel;
        [SerializeField] private GameObject updateRequiredPanel;
        [SerializeField] private Text messageText;
    
        private void Reset()
        {
            // 同じGameObject上にあるVersionCheckerを自動取得(なければnull)
            if (versionChecker == null)
            {
                versionChecker = GetComponent<VersionChecker>();
            }
        }
    
        private void OnEnable()
        {
            if (versionChecker == null)
            {
                Debug.LogError("[VersionCheckUIHandler] VersionChecker is not assigned.");
                return;
            }
    
            // イベントへ登録
            // インスペクターから設定してもOKですが、コードで登録する例を示しています
            versionChecker
                .GetType()
                .GetField("onSuccess", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?
                .GetValue(versionChecker);
    
            // 実際には、インスペクターの「On Success」「On Failure」に
            // このコンポーネントのメソッドを登録するのが簡単です。
        }
    
        // インスペクターから登録して使う想定のメソッド群
        public void HandleVersionCheckSuccess(VersionCheckResult result)
        {
            if (latestVersionPanel != null)
            {
                latestVersionPanel.SetActive(result.IsLatest);
            }
    
            if (updateRequiredPanel != null)
            {
                updateRequiredPanel.SetActive(!result.IsLatest);
            }
    
            if (messageText != null && result.RawResponse != null)
            {
                // サーバーからのメッセージがあれば優先して表示
                if (!string.IsNullOrEmpty(result.RawResponse.message))
                {
                    messageText.text = result.RawResponse.message;
                }
                else
                {
                    messageText.text = result.IsLatest
                        ? "最新版をご利用いただいています。"
                        : $"新しいバージョン {result.LatestVersion} が利用可能です。";
                }
            }
        }
    
        public void HandleVersionCheckFailure(string errorMessage)
        {
            if (latestVersionPanel != null)
            {
                latestVersionPanel.SetActive(false);
            }
    
            if (updateRequiredPanel != null)
            {
                updateRequiredPanel.SetActive(true);
            }
    
            if (messageText != null)
            {
                messageText.text = "バージョン確認に失敗しました。\nネットワーク接続を確認してください。";
            }
    
            Debug.LogError($"[VersionCheckUIHandler] Version check failed: {errorMessage}");
        }
    }
    

    このように「通信」と「UI表示」を別コンポーネントに分けることで、責務が明確になります。

  4. インスペクターでイベントを接続する
    • VersionManager(VersionCheckerが付いているオブジェクト)を選択
    • On Success イベントに VersionCheckUIHandler を持つオブジェクトをドラッグ&ドロップ
    • 関数選択から VersionCheckUIHandler -> HandleVersionCheckSuccess を選ぶ
    • On Failure イベントにも同様に HandleVersionCheckFailure を登録

    これで、ゲーム起動時に自動でバージョンチェックが走り、結果に応じてUIが切り替わるようになります。

メリットと応用

VersionChecker を単体コンポーネントとして切り出すことで、次のようなメリットがあります。

  • プレハブごとに挙動を変えやすい
    タイトルシーン用、デバッグ用、イベント用など、シーンやビルドターゲットごとに
    Current VersionVersion Api Url を変えたプレハブを用意するだけでOKです。
  • レベルデザイン時に「通信部分」を意識しなくて良い
    レベルデザイナーは、ただ VersionManager プレハブをシーンに置くだけで、
    バージョンチェックが自動で動作します。UIとの接続もインスペクター上のイベントで完結します。
  • テストがしやすい
    疑似サーバーURLを指すプレハブや、あえてエラーを返すモック用URLを指すプレハブを作っておけば、
    通信成功・失敗パターンを簡単にテストできます。
  • Godクラス化を防げる
    通信ロジックをプレイヤー制御やゲーム進行のスクリプトから切り離すことで、
    それぞれのコンポーネントの責務が明確になります。

応用として、例えば「最新版ではない場合に自動でストアURLを開く」という処理を追加することもできます。
その場合も、VersionChecker自体には余計な責務を持たせず、「アップデート誘導」専用コンポーネントとして分けるのがおすすめです。

例えば、こんなコンポーネントを用意して、On Success イベントに登録するだけで実現できます。


using UnityEngine;

/// <summary>
/// バージョンチェック結果を受け取り、最新版でない場合にストアURLを開くコンポーネント。
/// VersionCheckerのOn Successイベントに、このコンポーネントの
/// HandleVersionCheckメソッドを登録して使います。
/// </summary>
public class VersionUpdateRedirector : MonoBehaviour
{
    [SerializeField] private bool openAutomatically = false;

    public void HandleVersionCheck(VersionCheckResult result)
    {
        if (result == null || result.RawResponse == null)
        {
            return;
        }

        // 最新版なら何もしない
        if (result.IsLatest) return;

        string url = result.RawResponse.storeUrl;
        if (string.IsNullOrEmpty(url))
        {
            Debug.LogWarning("[VersionUpdateRedirector] Store URL is not provided in response.");
            return;
        }

        // 強制アップデート、かつ自動で開く設定なら即座にブラウザを開く
        if (result.ForceUpdate && openAutomatically)
        {
            Application.OpenURL(url);
        }
        else
        {
            // ここではログだけ出しておき、実際のボタンからOpenURLを呼ぶなどでもOK
            Debug.Log($"[VersionUpdateRedirector] Update available: {url}");
        }
    }
}

このように、機能ごとに小さなコンポーネントへ分割していくと、
「バージョン確認」「UI表示」「アップデート誘導」といった責務がきれいに分かれ、保守もしやすくなります。
Updateに何でもかんでも書くスタイルから、一歩進んだコンポーネント指向の設計にシフトしていきましょう。