ota
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.AddressableAssets.ResourceLocators;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
public class BootstrapUpdater : MonoBehaviour
|
||||
{
|
||||
public const string ContentReadyPrefsKey = "Ota.ContentReady";
|
||||
|
||||
[Header("Host")]
|
||||
[Tooltip("Apache folder that serves version.json and the Addressables catalog. Example: https://kkapi.playpoolstudios.com/ota")]
|
||||
[SerializeField] string otaBaseUrl = "https://kkapi.playpoolstudios.com/ota";
|
||||
[Tooltip("Opened when version.json has no storeUrl. Same role as the login screen Telegram link.")]
|
||||
[SerializeField] string fallbackStoreUrl = "https://t.me/+dfUhy4gmGXk0ZDRl";
|
||||
|
||||
[Header("Content")]
|
||||
[SerializeField] string introSceneAddress = AddressableSceneLoader.IntroScene;
|
||||
[SerializeField] string gameContentLabel = "game-content";
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] TMP_Text statusText;
|
||||
[SerializeField] Slider progressSlider;
|
||||
[SerializeField] TMP_Text progressText;
|
||||
[SerializeField] GameObject retryButton;
|
||||
[SerializeField] GameObject updateButton;
|
||||
|
||||
string pendingStoreUrl = "";
|
||||
bool updateRunning;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetRetryVisible(false);
|
||||
SetUpdateVisible(false);
|
||||
SetProgress(0f, "");
|
||||
if (!updateRunning)
|
||||
StartCoroutine(RunUpdate());
|
||||
}
|
||||
|
||||
public void Retry()
|
||||
{
|
||||
if (updateRunning)
|
||||
return;
|
||||
SetRetryVisible(false);
|
||||
SetUpdateVisible(false);
|
||||
StartCoroutine(RunUpdate());
|
||||
}
|
||||
|
||||
public void OpenStore()
|
||||
{
|
||||
string url = string.IsNullOrWhiteSpace(pendingStoreUrl) ? fallbackStoreUrl : pendingStoreUrl;
|
||||
if (!string.IsNullOrWhiteSpace(url))
|
||||
Application.OpenURL(url.Trim());
|
||||
}
|
||||
|
||||
IEnumerator RunUpdate()
|
||||
{
|
||||
updateRunning = true;
|
||||
SetStatus("Checking for updates");
|
||||
|
||||
OtaVersionInfo version = null;
|
||||
bool versionFetched = false;
|
||||
yield return FetchVersion(info =>
|
||||
{
|
||||
version = info;
|
||||
versionFetched = info != null;
|
||||
});
|
||||
|
||||
if (!versionFetched)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning("OTA version.json was not reachable. Editor play continues into " + introSceneAddress + ".");
|
||||
#else
|
||||
if (PlayerPrefs.GetInt(ContentReadyPrefsKey, 0) != 1)
|
||||
{
|
||||
Fail("Check your connection and try again.");
|
||||
yield break;
|
||||
}
|
||||
SetStatus("Offline. Loading saved game");
|
||||
#endif
|
||||
}
|
||||
else if (OtaVersionInfo.IsClientBelowRequiredVersion(version.minPlayerVersion))
|
||||
{
|
||||
pendingStoreUrl = version.storeUrl;
|
||||
string message = string.IsNullOrWhiteSpace(version.message)
|
||||
? "A new version is required."
|
||||
: version.message.Trim();
|
||||
SetStatus(message);
|
||||
SetUpdateVisible(true);
|
||||
updateRunning = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperationHandle<IResourceLocator> init = Addressables.InitializeAsync(false);
|
||||
yield return init;
|
||||
bool initOk = init.IsValid() && init.Status == AsyncOperationStatus.Succeeded;
|
||||
if (init.IsValid())
|
||||
Addressables.Release(init);
|
||||
if (!initOk)
|
||||
{
|
||||
Fail("Could not start content update.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperationHandle<System.Collections.Generic.List<string>> check = Addressables.CheckForCatalogUpdates(false);
|
||||
yield return check;
|
||||
if (check.Status == AsyncOperationStatus.Succeeded && check.Result != null && check.Result.Count > 0)
|
||||
{
|
||||
SetStatus("Updating catalog");
|
||||
AsyncOperationHandle<System.Collections.Generic.List<IResourceLocator>> catalogs = Addressables.UpdateCatalogs(check.Result, false);
|
||||
yield return catalogs;
|
||||
if (catalogs.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Addressables.Release(catalogs);
|
||||
Addressables.Release(check);
|
||||
Fail("Could not update the content catalog.");
|
||||
yield break;
|
||||
}
|
||||
Addressables.Release(catalogs);
|
||||
}
|
||||
Addressables.Release(check);
|
||||
|
||||
long downloadBytes = 0;
|
||||
AsyncOperationHandle<long> sizeHandle = Addressables.GetDownloadSizeAsync(gameContentLabel);
|
||||
yield return sizeHandle;
|
||||
if (sizeHandle.Status == AsyncOperationStatus.Succeeded)
|
||||
downloadBytes = sizeHandle.Result;
|
||||
Addressables.Release(sizeHandle);
|
||||
|
||||
if (downloadBytes > 0)
|
||||
{
|
||||
AsyncOperationHandle download = Addressables.DownloadDependenciesAsync(gameContentLabel, false);
|
||||
while (!download.IsDone)
|
||||
{
|
||||
DownloadStatus status = download.GetDownloadStatus();
|
||||
float percent = status.TotalBytes > 0
|
||||
? (float)status.DownloadedBytes / status.TotalBytes
|
||||
: download.PercentComplete;
|
||||
SetProgress(percent, FormatMegabytes(status.DownloadedBytes) + " / " + FormatMegabytes(status.TotalBytes));
|
||||
SetStatus("Downloading update " + Mathf.RoundToInt(percent * 100f) + "%");
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (download.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Addressables.Release(download);
|
||||
Fail("Download failed. Check your connection and try again.");
|
||||
yield break;
|
||||
}
|
||||
Addressables.Release(download);
|
||||
}
|
||||
|
||||
PlayerPrefs.SetInt(ContentReadyPrefsKey, 1);
|
||||
PlayerPrefs.Save();
|
||||
SetStatus("Loading");
|
||||
SetProgress(1f, "");
|
||||
yield return AddressableSceneLoader.LoadRoutine(introSceneAddress);
|
||||
updateRunning = false;
|
||||
}
|
||||
|
||||
IEnumerator FetchVersion(Action<OtaVersionInfo> onResult)
|
||||
{
|
||||
string baseUrl = (otaBaseUrl ?? "").Trim().TrimEnd('/');
|
||||
if (string.IsNullOrEmpty(baseUrl))
|
||||
{
|
||||
onResult?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string url = baseUrl + "/version.json";
|
||||
using (var request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 20;
|
||||
yield return request.SendWebRequest();
|
||||
if (request.result != UnityWebRequest.Result.Success)
|
||||
{
|
||||
onResult?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
onResult?.Invoke(JsonUtility.FromJson<OtaVersionInfo>(request.downloadHandler.text));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning("OTA version.json parse failed: " + e.Message);
|
||||
onResult?.Invoke(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Fail(string message)
|
||||
{
|
||||
SetStatus(message);
|
||||
SetRetryVisible(true);
|
||||
updateRunning = false;
|
||||
}
|
||||
|
||||
void SetStatus(string message)
|
||||
{
|
||||
if (statusText != null)
|
||||
statusText.text = message;
|
||||
}
|
||||
|
||||
void SetProgress(float normalized, string label)
|
||||
{
|
||||
if (progressSlider != null)
|
||||
{
|
||||
progressSlider.gameObject.SetActive(true);
|
||||
progressSlider.value = Mathf.Clamp01(normalized);
|
||||
}
|
||||
if (progressText != null)
|
||||
progressText.text = label ?? "";
|
||||
}
|
||||
|
||||
void SetRetryVisible(bool visible)
|
||||
{
|
||||
if (retryButton != null)
|
||||
retryButton.SetActive(visible);
|
||||
}
|
||||
|
||||
void SetUpdateVisible(bool visible)
|
||||
{
|
||||
if (updateButton != null)
|
||||
updateButton.SetActive(visible);
|
||||
}
|
||||
|
||||
static string FormatMegabytes(long bytes)
|
||||
{
|
||||
float mb = bytes / (1024f * 1024f);
|
||||
return mb.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) + " MB";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user