using System; using System.IO; using System.Collections.Generic; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Build; using UnityEditor.AddressableAssets.Settings; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEngine; using UnityEngine.AddressableAssets; #if UNITY_2021_2_OR_NEWER /// /// Maintains Addresssables build data when processing a player build. /// public class AddressablesPlayerBuildProcessor : BuildPlayerProcessor { /// /// Functor to override Addressables build when building Player. /// /// /// Functor is invoked where Addressables settings state to build Addressables content when performing a Player build. /// /// Available in Unity 2021.2 or later. /// public static Func BuildAddressablesOverride { get; set; } /// /// Returns the player build processor callback order. /// public override int callbackOrder { get { return 1; } } [InitializeOnLoadMethod] private static void CleanTemporaryPlayerBuildData() { RemovePlayerBuildLinkXML(AddressableAssetSettingsDefaultObject.Settings); } internal static void RemovePlayerBuildLinkXML(AddressableAssetSettings settings) { string linkProjectPath = GetLinkPath(settings, false); string guid = AssetDatabase.AssetPathToGUID(linkProjectPath); if (!string.IsNullOrEmpty(guid)) AssetDatabase.DeleteAsset(linkProjectPath); else if (File.Exists(linkProjectPath)) File.Delete(linkProjectPath); DirectoryUtility.DeleteDirectory(Path.GetDirectoryName(linkProjectPath)); } private static string GetLinkPath(AddressableAssetSettings settings, bool createFolder) { string folderPath; if (settings == null) folderPath = "Assets/Addressables_Temp"; else folderPath = settings.ConfigFolder; if (createFolder && !Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); AssetDatabase.ImportAsset(folderPath); } return Path.Combine(folderPath, "link.xml");; } /// /// Invoked before performing a Player build. Maintains building Addressables step and processing Addressables build data. /// /// public override void PrepareForBuild(BuildPlayerContext buildPlayerContext) { var settings = AddressableAssetSettingsDefaultObject.Settings; PrepareForPlayerbuild(settings, buildPlayerContext, ShouldBuildAddressablesForPlayerBuild(settings)); } internal static void PrepareForPlayerbuild(AddressableAssetSettings settings, BuildPlayerContext buildPlayerContext, bool buildAddressables) { if (settings != null && buildAddressables) { AddressablesPlayerBuildResult result; if (BuildAddressablesOverride != null) { try { result = BuildAddressablesOverride.Invoke(settings); } catch(Exception e) { result = new AddressablesPlayerBuildResult(); result.Error = "Exception in BuildAddressablesOverride: " + e; } } else AddressableAssetSettings.BuildPlayerContent(out result); if (result != null && !string.IsNullOrEmpty(result.Error)) Debug.LogError($"Failed to build Addressables content, content not included in Player Build. \"{result.Error}\""); } if (buildPlayerContext != null) { var streamingAssetValues = GetStreamingAssetPaths(); foreach (KeyValuePair streamingAssetValue in streamingAssetValues) { buildPlayerContext.AddAdditionalPathToStreamingAssets(streamingAssetValue.Key, streamingAssetValue.Value); } } string buildPath = Addressables.BuildPath + "/AddressablesLink/link.xml"; if (File.Exists(buildPath)) { string projectPath = GetLinkPath(settings, true); File.Copy(buildPath, projectPath, true); AssetDatabase.ImportAsset(projectPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.DontDownloadFromCacheServer); } } internal static bool ShouldBuildAddressablesForPlayerBuild(AddressableAssetSettings settings) { if (settings == null) return false; switch (settings.BuildAddressablesWithPlayerBuild) { case AddressableAssetSettings.PlayerBuildOption.DoNotBuildWithPlayer: return false; case AddressableAssetSettings.PlayerBuildOption.BuildWithPlayer: break; case AddressableAssetSettings.PlayerBuildOption.PreferencesValue: if (!EditorPrefs.GetBool(AddressablesPreferences.kBuildAddressablesWithPlayerBuildKey, true)) return false; break; } return true; } /// /// Gets a list of StreamingAsset files managed through Addressables, and relative path in StreamingAssets. /// internal static List> GetStreamingAssetPaths() { List> pairs = new List>(1); if (Directory.Exists(Addressables.BuildPath)) pairs.Add(new KeyValuePair(Addressables.BuildPath, "aa")); return pairs; } } #else /// /// Maintains Addresssables build data when processing a player build. /// public class AddressablesPlayerBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport { /// /// Returns the player build processor callback order. /// public int callbackOrder { get { return 1; } } /// /// Restores temporary data created as part of a build. /// /// Stores temporary player build data. public void OnPostprocessBuild(BuildReport report) { CleanTemporaryPlayerBuildData(); } [InitializeOnLoadMethod] internal static void CleanTemporaryPlayerBuildData() { if (Directory.Exists(Addressables.PlayerBuildDataPath)) { DirectoryUtility.DirectoryMove(Addressables.PlayerBuildDataPath, Addressables.BuildPath); DirectoryUtility.DeleteDirectory(Application.streamingAssetsPath, onlyIfEmpty: true); } } /// /// Initializes temporary build data. /// /// Contains build data information. public void OnPreprocessBuild(BuildReport report) { CopyTemporaryPlayerBuildData(); } internal static void CopyTemporaryPlayerBuildData() { if (Directory.Exists(Addressables.BuildPath)) { if (Directory.Exists(Addressables.PlayerBuildDataPath)) { Debug.LogWarning($"Found and deleting directory \"{Addressables.PlayerBuildDataPath}\", directory is managed through Addressables."); DirectoryUtility.DeleteDirectory(Addressables.PlayerBuildDataPath, false); } string parentDir = Path.GetDirectoryName(Addressables.PlayerBuildDataPath); if (!string.IsNullOrEmpty(parentDir) && !Directory.Exists(parentDir)) Directory.CreateDirectory(parentDir); Directory.Move(Addressables.BuildPath, Addressables.PlayerBuildDataPath ); } } } #endif