Unityを触り始めた頃は、つい「とりあえず全部を1つのUpdateに書く」実装をしてしまいがちですよね。入力処理、移動処理、UI更新、セーブデータ、ネットワーク通信…すべてが1つのスクリプトに詰め込まれていくと、だんだんと何を触るにも壊れそうなGodクラスが出来上がってしまいます。

特に「起動時にサーバーへバージョン確認をする」といった処理は、ついGameManagerやメインのシーン制御スクリプトに直書きしがちです。しかし、こうした処理こそ独立したコンポーネントに切り出しておくと、ゲームの構造がとてもスッキリします。

この記事では、起動時にHTTPリクエストを送り、最新版があるかどうかをチェックして通知するためのコンポーネント「VersionChecker」を実装していきます。ゲーム本体のロジックからバージョン確認処理を分離し、プレハブに載せて再利用しやすい形にしておきましょう。

【Unity】起動時にサクッとバージョンチェック!「VersionChecker」コンポーネント

コンポーネントの概要

  • ゲーム起動時(Awake/Start)にサーバーへHTTPリクエストを送る
  • 「現在のアプリバージョン」と「サーバー上の最新バージョン」を比較する
  • 最新版がある場合は、イベントやログで通知する(UI連携しやすい設計)
  • エラー時(タイムアウト・サーバーダウンなど)もハンドリングする

Unity6標準の UnityWebRequest とコルーチンを使った、シンプルかつ拡張しやすい実装にしていきます。


前提: サーバー側のレスポンス形式(想定)

この記事では、サーバーから次のようなJSONが返ってくる想定で進めます。


{
  "latestVersion": "1.2.0",
  "requiredVersion": "1.0.0",
  "message": "新しいバージョンが利用可能です。",
  "downloadUrl": "https://example.com/download"
}
  • latestVersion : サーバーが持っている最新のバージョン
  • requiredVersion : このバージョン未満は起動不可、などに使える必須バージョン(任意)
  • message : ユーザーに見せるメッセージなど(任意)
  • downloadUrl : ダウンロードページのURL(任意)

もちろん、サーバー実装によってJSON構造は変わってもかまいません。その場合は、この記事のJSONパース部分を書き換えればOKです。


VersionCheckerの設計方針

  • 責務は「バージョン情報の取得と比較」のみに限定する
  • UI表示やシーン遷移などは、イベントコールバックで外部に通知する
  • インスペクターからURLやタイムアウト時間を設定できるようにする
  • テストしやすいよう、Unity側からも簡単に手動トリガーできるメソッドを用意する

フルコード


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

namespace VersionCheckSample
{
    /// <summary>
    /// 起動時にサーバーへHTTPリクエストを送り、
    /// 最新バージョンがあるかどうかをチェックするコンポーネント。
    /// 
    /// 役割は「バージョン情報の取得と比較」に限定し、
    /// UI表示やシーン遷移は外部のコンポーネントに任せる設計にしています。
    /// </summary>
    public class VersionChecker : MonoBehaviour
    {
        // ====== 設定項目(インスペクターから編集)======

        [Header("接続先設定")]
        [Tooltip("バージョン情報を返すAPIのURL(GET)")]
        [SerializeField] private string versionApiUrl = "https://example.com/api/version";

        [Tooltip("リクエストのタイムアウト秒数")]
        [SerializeField] private int timeoutSeconds = 10;

        [Header("バージョン情報設定")]
        [Tooltip("現在のアプリバージョン。Application.version を使う場合は空にしておく")]
        [SerializeField] private string currentVersionOverride = "";

        [Tooltip("起動時に自動でチェックを実行するか")]
        [SerializeField] private bool checkOnStart = true;

        [Header("ログ出力設定")]
        [Tooltip("チェック結果をDebug.Logで出力するか")]
        [SerializeField] private bool enableDebugLog = true;


        // ====== コールバック(外部とつなぐためのイベント)======

        /// <summary>
        /// 正常にサーバーから情報を取得できたときに呼ばれるイベント。
        /// </summary>
        public event Action<VersionCheckResult> OnVersionCheckSucceeded;

        /// <summary>
        /// 通信エラーやJSONパースエラーなどが発生した場合に呼ばれるイベント。
        /// </summary>
        public event Action<string> OnVersionCheckFailed;


        // ====== 内部で使うデータ構造 ======

        /// <summary>
        /// サーバーから返ってくるJSONをパースするためのクラス。
        /// JsonUtilityで扱えるよう、フィールド名はJSONと一致させます。
        /// </summary>
        [Serializable]
        private class VersionApiResponse
        {
            public string latestVersion;
            public string requiredVersion;
            public string message;
            public string downloadUrl;
        }

        /// <summary>
        /// 外部に通知するための結果情報。
        /// UIやゲームロジックから使いやすいようにまとめています。
        /// </summary>
        public class VersionCheckResult
        {
            /// <summary>現在のアプリバージョン</summary>
            public string CurrentVersion { get; }

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

            /// <summary>サーバーからのメッセージ(任意)</summary>
            public string Message { get; }

            /// <summary>ダウンロードURL(任意)</summary>
            public string DownloadUrl { get; }

            /// <summary>アップデートが存在するかどうか</summary>
            public bool HasUpdate { get; }

            /// <summary>必須バージョンを満たしているかどうか</summary>
            public bool IsRequiredVersionSatisfied { get; }

            public VersionCheckResult(
                string currentVersion,
                string latestVersion,
                string message,
                string downloadUrl,
                bool hasUpdate,
                bool isRequiredVersionSatisfied)
            {
                CurrentVersion = currentVersion;
                LatestVersion = latestVersion;
                Message = message;
                DownloadUrl = downloadUrl;
                HasUpdate = hasUpdate;
                IsRequiredVersionSatisfied = isRequiredVersionSatisfied;
            }
        }


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

        private void Start()
        {
            if (checkOnStart)
            {
                StartVersionCheck();
            }
        }


        // ====== 公開メソッド ======

        /// <summary>
        /// バージョンチェックを開始します。
        /// 外部のスクリプトやボタンからも呼び出せるようにpublicにしています。
        /// </summary>
        public void StartVersionCheck()
        {
            if (string.IsNullOrEmpty(versionApiUrl))
            {
                HandleError("VersionChecker: versionApiUrl が設定されていません。");
                return;
            }

            StartCoroutine(CheckVersionCoroutine());
        }


        // ====== メイン処理(コルーチン)======

        /// <summary>
        /// サーバーへHTTPリクエストを送り、JSONを取得して解析するコルーチン。
        /// </summary>
        private IEnumerator CheckVersionCoroutine()
        {
            using (UnityWebRequest request = UnityWebRequest.Get(versionApiUrl))
            {
                // タイムアウト設定
                request.timeout = timeoutSeconds;

#if UNITY_2020_2_OR_NEWER
                yield return request.SendWebRequest();
#else
                yield return request.Send();
#endif

                // 通信エラーのチェック
#if UNITY_2020_2_OR_NEWER
                if (request.result == UnityWebRequest.Result.ConnectionError ||
                    request.result == UnityWebRequest.Result.ProtocolError)
                {
                    HandleError($"VersionChecker: 通信エラー - {request.error}");
                    yield break;
                }
#else
                if (request.isNetworkError || request.isHttpError)
                {
                    HandleError($"VersionChecker: 通信エラー - {request.error}");
                    yield break;
                }
#endif

                // レスポンスボディ(JSON文字列)を取得
                string json = request.downloadHandler.text;
                if (enableDebugLog)
                {
                    Debug.Log($"[VersionChecker] Response JSON: {json}");
                }

                // JSONをパース
                VersionApiResponse apiResponse;
                try
                {
                    apiResponse = JsonUtility.FromJson<VersionApiResponse>(json);
                }
                catch (Exception e)
                {
                    HandleError($"VersionChecker: JSONパースに失敗しました。{e.Message}");
                    yield break;
                }

                if (apiResponse == null)
                {
                    HandleError("VersionChecker: JSONパース結果がnullです。レスポンス形式を確認してください。");
                    yield break;
                }

                // 現在のバージョンを決定(Override が空なら Application.version を使う)
                string currentVersion = string.IsNullOrEmpty(currentVersionOverride)
                    ? Application.version
                    : currentVersionOverride;

                string latestVersion = string.IsNullOrEmpty(apiResponse.latestVersion)
                    ? currentVersion // latestVersion が空なら同じとみなす
                    : apiResponse.latestVersion;

                string requiredVersion = string.IsNullOrEmpty(apiResponse.requiredVersion)
                    ? currentVersion // requiredVersion が空なら同じとみなす
                    : apiResponse.requiredVersion;

                // バージョン比較
                bool hasUpdate = IsNewerVersion(latestVersion, currentVersion);
                bool isRequiredSatisfied = !IsNewerVersion(requiredVersion, currentVersion); 
                // requiredVersion > currentVersion なら満たしていない

                var result = new VersionCheckResult(
                    currentVersion,
                    latestVersion,
                    apiResponse.message,
                    apiResponse.downloadUrl,
                    hasUpdate,
                    isRequiredSatisfied
                );

                if (enableDebugLog)
                {
                    Debug.Log($"[VersionChecker] Current: {result.CurrentVersion}, Latest: {result.LatestVersion}, " +
                              $"HasUpdate: {result.HasUpdate}, RequiredOK: {result.IsRequiredVersionSatisfied}");
                }

                // イベントで外部へ通知
                OnVersionCheckSucceeded?.Invoke(result);
            }
        }


        // ====== バージョン比較ロジック ======

        /// <summary>
        /// v1 が v2 より新しいかどうかを判定します。
        /// 例: "1.2.0" と "1.1.9" のようなドット区切りの数値を比較します。
        /// </summary>
        /// <param name="v1">比較対象1(例: サーバーの最新バージョン)</param>
        /// <param name="v2">比較対象2(例: 現在のアプリバージョン)</param>
        /// <returns>v1 が v2 より新しければ true</returns>
        private bool IsNewerVersion(string v1, string v2)
        {
            // null や空文字は 0.0.0 とみなす
            if (string.IsNullOrEmpty(v1)) v1 = "0.0.0";
            if (string.IsNullOrEmpty(v2)) v2 = "0.0.0";

            string[] v1Parts = v1.Split('.');
            string[] v2Parts = v2.Split('.');

            int length = Mathf.Max(v1Parts.Length, v2Parts.Length);

            for (int i = 0; i < length; i++)
            {
                int v1Num = (i < v1Parts.Length && int.TryParse(v1Parts[i], out var n1)) ? n1 : 0;
                int v2Num = (i < v2Parts.Length && int.TryParse(v2Parts[i], out var n2)) ? n2 : 0;

                if (v1Num > v2Num) return true;   // v1 の方が新しい
                if (v1Num < v2Num) return false;  // v2 の方が新しい
            }

            // すべて同じなら新しくはない
            return false;
        }


        // ====== エラーハンドリング ======

        /// <summary>
        /// エラー発生時の共通処理。
        /// ログ出力とイベント通知を行います。
        /// </summary>
        private void HandleError(string message)
        {
            if (enableDebugLog)
            {
                Debug.LogWarning(message);
            }

            OnVersionCheckFailed?.Invoke(message);
        }
    }
}

使い方の手順

手順① 空のGameObjectにアタッチする

  1. UnityのHierarchyで 「VersionChecker」用の空のGameObject を作成します。
    例: System/VersionChecker という名前にしておくとわかりやすいです。
  2. 上記コードを VersionChecker.cs として保存し、そのGameObjectにアタッチします。

手順② インスペクターで設定する

  • Version Api Url: バージョン情報を返すAPIのURL(例: https://example.com/api/version
  • Timeout Seconds: 通信のタイムアウト秒数(例: 10)
  • Current Version Override: 空にしておくと Application.version を使用します。
    開発中にテストしたい場合は「1.0.0」「1.2.3」など任意の文字列を入れてもOKです。
  • Check On Start: 起動時に自動でチェックしたい場合はON
  • Enable Debug Log: ログを見ながら動作確認したい場合はON

手順③ UIやゲームロジックと連携する

VersionChecker自体は「チェックして結果をイベントで通知する」だけなので、UIとは別のコンポーネントでつなぎます。例えば、プレイヤーにアップデートを促すポップアップを出したい場合は、次のようなコンポーネントを用意します。


using UnityEngine;
using UnityEngine.UI;
using VersionCheckSample;

public class VersionCheckUI : MonoBehaviour
{
    [SerializeField] private VersionChecker versionChecker;
    [SerializeField] private GameObject updatePanel; // アップデート通知用のパネル
    [SerializeField] private Text messageText;       // メッセージ表示用のText
    [SerializeField] private Button openUrlButton;   // ダウンロードURLを開くボタン

    private string downloadUrl;

    private void Awake()
    {
        if (versionChecker == null)
        {
            versionChecker = FindObjectOfType<VersionChecker>();
        }

        if (versionChecker != null)
        {
            versionChecker.OnVersionCheckSucceeded += HandleVersionCheckSucceeded;
            versionChecker.OnVersionCheckFailed += HandleVersionCheckFailed;
        }

        if (updatePanel != null)
        {
            updatePanel.SetActive(false);
        }

        if (openUrlButton != null)
        {
            openUrlButton.onClick.AddListener(OpenDownloadUrl);
        }
    }

    private void OnDestroy()
    {
        if (versionChecker != null)
        {
            versionChecker.OnVersionCheckSucceeded -= HandleVersionCheckSucceeded;
            versionChecker.OnVersionCheckFailed -= HandleVersionCheckFailed;
        }
    }

    private void HandleVersionCheckSucceeded(VersionChecker.VersionCheckResult result)
    {
        // 必須バージョンを満たしていない場合は、強制アップデートのメッセージにするなど
        if (!result.IsRequiredVersionSatisfied)
        {
            ShowUpdatePanel(
                $"このバージョン({result.CurrentVersion})はサポート対象外です。\n" +
                $"最新バージョン({result.LatestVersion})をインストールしてください。",
                result.DownloadUrl);
            return;
        }

        // 任意アップデート(最新があれば通知)
        if (result.HasUpdate)
        {
            string msg = string.IsNullOrEmpty(result.Message)
                ? $"新しいバージョン({result.LatestVersion})が利用可能です。"
                : result.Message;

            ShowUpdatePanel(msg, result.DownloadUrl);
        }
        else
        {
            Debug.Log("[VersionCheckUI] すでに最新バージョンです。");
        }
    }

    private void HandleVersionCheckFailed(string error)
    {
        Debug.LogWarning($"[VersionCheckUI] バージョンチェックに失敗しました: {error}");
        // 必要に応じて、ユーザーに「あとで再試行してください」などのUIを表示してもよい
    }

    private void ShowUpdatePanel(string msg, string url)
    {
        downloadUrl = url;

        if (updatePanel != null)
        {
            updatePanel.SetActive(true);
        }

        if (messageText != null)
        {
            messageText.text = msg;
        }

        if (openUrlButton != null)
        {
            openUrlButton.gameObject.SetActive(!string.IsNullOrEmpty(url));
        }
    }

    private void OpenDownloadUrl()
    {
        if (!string.IsNullOrEmpty(downloadUrl))
        {
            Application.OpenURL(downloadUrl);
        }
    }
}

このように、VersionCheckerはネットワークとバージョン比較だけVersionCheckUIはUIだけと責務を分けておくと、どちらかを差し替えたりテストしたりするのがとても楽になります。

手順④ 具体的な使用例

  • プレイヤー用のタイトルシーン
    タイトル画面のシーンに VersionCheckerVersionCheckUI を配置しておき、起動時に自動チェック。
    最新版がある場合のみ「アップデートがあります」ポップアップを表示するようにします。
  • 運営用ビルド(社内テスト版)
    QAチーム向けのビルドで currentVersionOverride を使い分けることで、
    サーバー側のバージョンAPIをテスト環境/本番環境で切り替えながら動作確認ができます。
  • 敵AIや動く床などとは完全に分離
    バージョンチェックはゲームプレイとは無関係なので、プレイヤーや敵、動く床のスクリプトには一切書きません。
    それぞれはあくまで「移動」「攻撃」「挙動」に集中させ、バージョン確認はこのコンポーネントに任せる、というコンポーネント指向の設計になります。

メリットと応用

メリット① プレハブ化でどのプロジェクトにも持っていける

VersionCheckerは1つのGameObjectに完結しているので、プレハブにしておけば別プロジェクトにも簡単に持ち運べます。URLなどの設定だけ変えれば、どのゲームでも同じ仕組みを再利用できます。

メリット② Godクラス化を防ぎ、責務が明確になる

GameManagerに「入力処理」「シーン管理」「セーブ」「ネットワーク」「バージョンチェック」…と詰め込むのではなく、VersionCheckerはバージョンチェックだけに責務を限定しています。
これにより、コードの見通しがよくなり、バグの切り分けやテストもしやすくなります。

メリット③ レベルデザインやチーム開発が楽になる

  • レベルデザイナーは「タイトルシーンにVersionCheckerプレハブを1つ置く」だけでバージョンチェックを有効化できる
  • UI担当は VersionCheckUI だけを編集すれば見た目や文言を変えられる
  • サーバー担当はJSONの中身を増やしても、Unity側はパースクラスを少し拡張するだけで対応できる

応用: 一定時間ごとに再チェックする改造案

例えば、ロビー画面に長時間いるプレイヤーに対して、一定時間ごとにバージョンチェックを再実行したい場合は、次のようなメソッドをVersionCheckerに追加できます。


/// <summary>
/// 一定間隔でバージョンチェックを繰り返し実行する。
/// 例: ロビー画面で、10分おきに最新バージョンを確認したい場合など。
/// </summary>
/// <param name="intervalSeconds">チェック間隔(秒)</param>
public void StartPeriodicCheck(float intervalSeconds)
{
    StopAllCoroutines(); // 既存のチェックを止めたい場合
    StartCoroutine(PeriodicCheckCoroutine(intervalSeconds));
}

private IEnumerator PeriodicCheckCoroutine(float intervalSeconds)
{
    while (true)
    {
        StartVersionCheck();
        yield return new WaitForSeconds(intervalSeconds);
    }
}

このように、小さな責務のコンポーネントにしておくと、あとから「定期チェックしたい」「タイトル以外のシーンでも使いたい」といった要望にも柔軟に対応できます。
バージョン確認に限らず、ネットワーク系やセーブ系なども、同じ発想で小さなコンポーネントに分割していくと、Unityプロジェクトの保守性がぐっと上がりますね。