#if UNITY_EDITOR using System.IO; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Build; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEngine; public static class OtaAddressablesSetup { const string RemoteLoadUrl = "https://kkapi.playpoolstudios.com/ota/[BuildTarget]"; const string GameContentLabel = "game-content"; const string BootstrapScenePath = "Assets/Scenes/Bootstrap.unity"; static readonly string[] RemoteScenePaths = { "Assets/Scenes/Intro.unity", "Assets/Scenes/MainMenu.unity", "Assets/Scenes/Game.unity" }; [MenuItem("Tools/OTA/Configure Addressables")] public static void Configure() { AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.GetSettings(true); settings.BuildRemoteCatalog = true; settings.DisableCatalogUpdateOnStartup = true; settings.OverridePlayerVersion = ""; string profileId = settings.activeProfileId; settings.profileSettings.SetValue(profileId, AddressableAssetSettings.kRemoteBuildPath, "ServerData/[BuildTarget]"); settings.profileSettings.SetValue(profileId, AddressableAssetSettings.kRemoteLoadPath, RemoteLoadUrl); settings.RemoteCatalogBuildPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteBuildPath); settings.RemoteCatalogLoadPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteLoadPath); for (int i = 0; i < settings.DataBuilders.Count; i++) { if (settings.GetDataBuilder(i) != null && settings.GetDataBuilder(i).Name.Contains("Asset Database")) { settings.ActivePlayModeDataBuilderIndex = i; break; } } AddressableAssetGroup local = GetOrCreateGroup(settings, "Local_Bootstrap"); AddressableAssetGroup remote = GetOrCreateGroup(settings, "Remote_Game"); ConfigureGroup(settings, local, true); ConfigureGroup(settings, remote, false); MarkScene(settings, local, BootstrapScenePath, "Bootstrap", false); for (int i = 0; i < RemoteScenePaths.Length; i++) { string path = RemoteScenePaths[i]; string address = Path.GetFileNameWithoutExtension(path); MarkScene(settings, remote, path, address, true); } settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true, true); AssetDatabase.SaveAssets(); var scenes = new EditorBuildSettingsScene[1]; scenes[0] = new EditorBuildSettingsScene(BootstrapScenePath, true); EditorBuildSettings.scenes = scenes; Debug.Log("OTA Addressables configured. Remote load path: " + RemoteLoadUrl); } [MenuItem("Tools/OTA/Build Content")] public static void BuildContent() { Configure(); AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result); if (!string.IsNullOrEmpty(result.Error)) { Debug.LogError("OTA content build failed: " + result.Error); return; } string statePath = ContentUpdateScript.GetContentStateDataPath(false); Debug.Log("OTA content build finished. Keep this file for later patches: " + statePath); } [MenuItem("Tools/OTA/Build Content Update")] public static void BuildContentUpdate() { AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null) { Debug.LogError("Addressables settings are missing. Run Tools/OTA/Configure Addressables first."); return; } string statePath = ContentUpdateScript.GetContentStateDataPath(false); if (!File.Exists(statePath)) { Debug.LogError("Missing content state at " + statePath + ". Run Tools/OTA/Build Content from the player build you shipped."); return; } ContentUpdateScript.BuildContentUpdate(settings, statePath); Debug.Log("OTA content update built. Upload ServerData and keep " + statePath); } static AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings, string name) { AddressableAssetGroup group = settings.FindGroup(name); if (group != null) return group; return settings.CreateGroup(name, false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema)); } static void ConfigureGroup(AddressableAssetSettings settings, AddressableAssetGroup group, bool local) { BundledAssetGroupSchema bundle = group.GetSchema(); if (bundle == null) bundle = group.AddSchema(); if (local) { bundle.BuildPath.SetVariableByName(settings, AddressableAssetSettings.kLocalBuildPath); bundle.LoadPath.SetVariableByName(settings, AddressableAssetSettings.kLocalLoadPath); } else { bundle.BuildPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteBuildPath); bundle.LoadPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteLoadPath); } bundle.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; bundle.UseAssetBundleCrc = true; bundle.UseAssetBundleCache = true; ContentUpdateGroupSchema update = group.GetSchema(); if (update == null) update = group.AddSchema(); update.StaticContent = local; } static void MarkScene(AddressableAssetSettings settings, AddressableAssetGroup group, string assetPath, string address, bool gameContent) { string guid = AssetDatabase.AssetPathToGUID(assetPath); if (string.IsNullOrEmpty(guid)) { Debug.LogWarning("OTA scene missing, skipped: " + assetPath); return; } AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, group, false, false); entry.address = address; if (gameContent) entry.SetLabel(GameContentLabel, true, true, true); } } #endif