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」オブジェクトにアタッチして使う例を想定します。
-
空のGameObjectを作成する
シーン(例: TitleScene)上で、
GameObject > Create Emptyから空のオブジェクトを作成し、
名前をVersionManagerなどに変更します。 -
VersionCheckerコンポーネントをアタッチ
作成したGameObjectを選択し、
Add ComponentからVersionCheckerを追加します。
インスペクターで次の項目を設定します。Current Version: 今ビルドしているゲームのバージョン(例:1.0.0)Version Api Url: バージョン情報を返すAPIのURL(ダミーでもOK)Check On Awake: 起動直後にチェックしたい場合はONRequest Timeout Seconds: 通信タイムアウト秒数(例:10)Enable Debug Log: 開発中はON、本番ではOFFにしてもOK
-
結果を受け取る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表示」を別コンポーネントに分けることで、責務が明確になります。
-
インスペクターでイベントを接続する
VersionManager(VersionCheckerが付いているオブジェクト)を選択On SuccessイベントにVersionCheckUIHandlerを持つオブジェクトをドラッグ&ドロップ- 関数選択から
VersionCheckUIHandler -> HandleVersionCheckSuccessを選ぶ On Failureイベントにも同様にHandleVersionCheckFailureを登録
これで、ゲーム起動時に自動でバージョンチェックが走り、結果に応じてUIが切り替わるようになります。
メリットと応用
VersionChecker を単体コンポーネントとして切り出すことで、次のようなメリットがあります。
- プレハブごとに挙動を変えやすい
タイトルシーン用、デバッグ用、イベント用など、シーンやビルドターゲットごとに
Current VersionやVersion 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に何でもかんでも書くスタイルから、一歩進んだコンポーネント指向の設計にシフトしていきましょう。
