/// <summary> /// Asynchronously builds an AAB at the specified path. /// </summary> /// <param name="aabFilePath">The AAB output file path.</param> /// <param name="assetPackConfig">Asset packs to include in the AAB.</param> /// <param name="onSuccess"> /// Callback that fires with the final aab file location, when the bundle creation succeeds. /// </param> public void CreateBundleAsync(string aabFilePath, AssetPackConfig assetPackConfig, PostBuildCallback onSuccess) { // Copy the AssetPackConfig before leaving the main thread in case the original is modified later. var copiedAssetPackConfig = SerializationHelper.DeepCopy(assetPackConfig); _createBundleAsyncOnSuccess = onSuccess; StartCreateBundleAsync(() => { try { CreateBundle(aabFilePath, copiedAssetPackConfig); } catch (ThreadAbortException ex) { if (!_canceled) { // Unexpected ThreadAbortException. DisplayBuildError("Exception", ex.ToString()); } } catch (Exception ex) { // Catch and display exceptions since they may otherwise be undetected on a background thread. DisplayBuildError("Exception", ex.ToString()); } }); }
/// <summary> /// Save the specified <see cref="AssetPackConfig"/> config to disk. /// </summary> public static void SaveConfig( AssetPackConfig assetPackConfig, string configurationFilePath = SerializationHelper.ConfigurationFilePath) { Debug.LogFormat("Saving {0}", configurationFilePath); var config = SerializationHelper.Serialize(assetPackConfig); var jsonText = JsonUtility.ToJson(config); File.WriteAllText(configurationFilePath, jsonText); }
private static void CreateBundleAsync( AppBundleBuilder appBundleBuilder, string aabFilePath, AssetPackConfig assetPackConfig, bool runOnDevice) { var callback = runOnDevice ? (AppBundleBuilder.PostBuildCallback)RunBundle : EditorUtility.RevealInFinder; appBundleBuilder.CreateBundleAsync(aabFilePath, assetPackConfig, callback); }
private static void CreateBundleAsync( AppBundleBuilder appBundleBuilder, AppBundleBuildSettings buildSettings, AssetPackConfig assetPackConfig) { var callback = buildSettings.runOnDevice ? (AppBundleBuilder.PostBuildCallback)RunBundle : EditorUtility.RevealInFinder; appBundleBuilder.CreateBundleAsync(buildSettings.aabFilePath, assetPackConfig, callback); }
public static SerializableAssetPackConfig Serialize(AssetPackConfig assetPackConfig) { var config = new SerializableAssetPackConfig { DefaultTextureCompressionFormat = assetPackConfig.DefaultTextureCompressionFormat }; foreach (var assetPackEntry in assetPackConfig.AssetPacks) { var name = assetPackEntry.Key; var assetPack = assetPackEntry.Value; if (assetPack.AssetBundleFilePath != null) { var assetBundle = new SerializableMultiTargetingAssetBundle { name = name, DeliveryMode = assetPack.DeliveryMode }; assetBundle.assetBundles.Add(new SerializableAssetBundle { path = assetPack.AssetBundleFilePath, TextureCompressionFormat = TextureCompressionFormat.Default }); config.assetBundles.Add(assetBundle); } if (assetPack.CompressionFormatToAssetBundleFilePath != null) { var assetBundle = new SerializableMultiTargetingAssetBundle { name = name, DeliveryMode = assetPack.DeliveryMode }; foreach (var compressionEntry in assetPack.CompressionFormatToAssetBundleFilePath) { assetBundle.assetBundles.Add(new SerializableAssetBundle { path = compressionEntry.Value, TextureCompressionFormat = compressionEntry.Key }); } config.assetBundles.Add(assetBundle); } if (assetPack.AssetPackDirectoryPath != null) { config.assetPacks.Add(new SerializableAssetPack { name = name, DeliveryMode = assetPack.DeliveryMode, path = assetPack.AssetPackDirectoryPath }); } } return(config); }
/// <summary> /// Creates an AssetPackConfig from the specified SerializableAssetPackConfig, validating its fields /// in the process. Note: AssetBundle names are interpreted from the AssetBundle path rather than /// the specified names. /// </summary> public static AssetPackConfig Deserialize(SerializableAssetPackConfig config) { var assetPackConfig = new AssetPackConfig { DefaultTextureCompressionFormat = config.DefaultTextureCompressionFormat, SplitBaseModuleAssets = config.splitBaseModuleAssets }; foreach (var multiTargetingAssetBundle in config.assetBundles) { var assetBundles = multiTargetingAssetBundle.assetBundles; if (assetBundles.Count == 0) { continue; } // TODO: consider checking the folder name for "#tcf". if (assetBundles.Count == 1 && assetBundles[0].TextureCompressionFormat == TextureCompressionFormat.Default ) { assetPackConfig.AddAssetBundle(assetBundles[0].path, multiTargetingAssetBundle.DeliveryMode); continue; } var dictionaryTextureCompression = assetBundles .ToDictionary(item => item.TextureCompressionFormat, item => item.path); if (dictionaryTextureCompression.Count != 0) { assetPackConfig.AddAssetBundles(dictionaryTextureCompression, multiTargetingAssetBundle.DeliveryMode); } } foreach (var pack in config.assetPacks) { assetPackConfig.AddAssetsFolder(pack.name, pack.path, pack.DeliveryMode); } foreach (var pack in config.targetedAssetPacks) { var compressionFormatToAssetPackDirectoryPath = pack.paths .ToDictionary(item => item.TextureCompressionFormat, item => item.path); if (compressionFormatToAssetPackDirectoryPath.Count != 0) { assetPackConfig.AddAssetsFolders( pack.name, compressionFormatToAssetPackDirectoryPath, pack.DeliveryMode); } } return(assetPackConfig); }
private static void AddAssetFilesExampleToConfig(AssetPackConfig assetPackConfig, string packName, string fileContents) { var assetsDirectory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); assetsDirectory.Create(); File.WriteAllLines(Path.Combine(assetsDirectory.FullName, packName + ".txt"), new[] { fileContents }); assetPackConfig.AddAssetsFolder( packName, assetsDirectory.FullName, AssetPackDeliveryMode.OnDemand); }
/// <summary> /// Builds an Android App Bundle given the specified <see cref="BuildPlayerOptions"/>. /// </summary> /// <returns>True if the build succeeded, false if it failed or was cancelled.</returns> public static bool Build(BuildPlayerOptions buildPlayerOptions, AssetPackConfig assetPackConfig) { var buildSettings = new AppBundleBuildSettings { requirePrerequisiteChecks = true, runOnDevice = false }; var appBundleBuilder = CreateAppBundleBuilder(); return(Build(appBundleBuilder, buildPlayerOptions, assetPackConfig, buildSettings)); }
/** * Create the Play Asset Delivery configuration. */ public static AssetPackConfig CreateAssetPacks() { Debug.LogFormat("[{0}.{1}] path={2}", nameof(AssetPackBuilder), nameof(CreateAssetPacks), Addressables.BuildPath); AssetPackConfig assetPackConfig = new AssetPackConfig(); var bundles = GetBundles(Addressables.BuildPath); foreach (var bundle in bundles) { assetPackConfig.AssetPacks.Add(bundle.Name, bundle.CreateAssetPack()); } return(assetPackConfig); }
private static void AddAssetFilesPackToConfig(AssetPackConfig assetPackConfig) { var assetsDirectory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); assetsDirectory.Create(); var subdirectory = assetsDirectory.CreateSubdirectory("subdirectory"); File.WriteAllLines(Path.Combine(assetsDirectory.FullName, "file.txt"), new[] { "root file" }); File.WriteAllLines(Path.Combine(subdirectory.FullName, "file.txt"), new[] { "subdirectory file" }); assetPackConfig.AddAssetsFolder( AssetFilesPackName, assetsDirectory.FullName, AssetPackDeliveryMode.OnDemand); }
private static bool Build(AppBundleBuilder appBundleBuilder, BuildPlayerOptions buildPlayerOptions, AssetPackConfig assetPackConfig, AppBundleBuildSettings buildSettings) { if (buildSettings.requirePrerequisiteChecks && !appBundleBuilder.Initialize(new BuildToolLogger())) { return(false); } buildSettings.aabFilePath = buildPlayerOptions.locationPathName; Debug.LogFormat("Building app bundle: {0}", buildSettings.aabFilePath); buildSettings.androidPlayerFilePath = appBundleBuilder.BuildAndroidPlayer(buildPlayerOptions); if (buildSettings.androidPlayerFilePath == null) { return(false); } if (!File.Exists(buildSettings.androidPlayerFilePath)) { // If the build is canceled late, sometimes the build "succeeds" but the file is missing. // Since this may be intentional, don't display an onscreen error dialog. However, just // in case the build wasn't canceled, print a warning instead of silently failing. Debug.LogWarningFormat( "The Android Player file \"{0}\"is missing, possibly because of a late build cancellation.", buildSettings.androidPlayerFilePath); return(false); } if (IsBatchMode) { return(appBundleBuilder.CreateBundle( buildSettings.aabFilePath, buildSettings.androidPlayerFilePath, assetPackConfig)); } #if UNITY_2018_3_OR_NEWER CreateBundleAsync(appBundleBuilder, buildSettings, assetPackConfig); #else var task = new AppBundlePostBuildTask { buildSettings = buildSettings, serializableAssetPackConfig = SerializationHelper.Serialize(assetPackConfig), workingDirectoryPath = appBundleBuilder.WorkingDirectoryPath }; PostBuildRunner.RunTask(task); #endif return(true); }
/// <summary> /// Builds an Android App Bundle to a temp directory and then runs it on device. /// </summary> /// <param name="assetPackConfig">The asset pack configuration to use when building.</param> public static void BuildAndRun(AssetPackConfig assetPackConfig) { var appBundleBuilder = CreateAppBundleBuilder(); if (!appBundleBuilder.Initialize(new BuildToolLogger())) { return; } var tempOutputFilePath = Path.Combine(appBundleBuilder.WorkingDirectoryPath, "temp.aab"); var buildSettings = new AppBundleBuildSettings { buildPlayerOptions = AndroidBuildHelper.CreateBuildPlayerOptions(tempOutputFilePath), assetPackConfig = assetPackConfig, runOnDevice = true }; Build(appBundleBuilder, buildSettings); }
/// <summary> /// Builds an Android App Bundle given the specified configuration. /// </summary> public static bool Build( BuildPlayerOptions buildPlayerOptions, AssetPackConfig assetPackConfig, bool forceSynchronousBuild) { var appBundleBuilder = CreateAppBundleBuilder(); if (!appBundleBuilder.Initialize(new BuildToolLogger())) { return(false); } var buildSettings = new AppBundleBuildSettings { buildPlayerOptions = buildPlayerOptions, assetPackConfig = assetPackConfig ?? new AssetPackConfig(), forceSynchronousBuild = forceSynchronousBuild }; return(Build(appBundleBuilder, buildSettings)); }
/// <summary> /// Asynchronously builds an AAB at the specified path. /// </summary> /// <param name="aabFilePath">Path to the AAB file that should be built.</param> /// <param name="assetPackConfig">Indicates asset packs to include in the AAB.</param> /// <param name="onSuccess"> /// Callback that fires with the final aab file location, when the bundle creation succeeds. /// </param> public void CreateBundleAsync(string aabFilePath, AssetPackConfig assetPackConfig, PostBuildCallback onSuccess) { _progressBarWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); EditorApplication.update += HandleUpdate; _createBundleAsyncOnSuccess = onSuccess; _backgroundThread = new Thread(() => { try { CreateBundle(aabFilePath, assetPackConfig); } catch (Exception ex) { // Catch and display exceptions since they may otherwise be undetected on a background thread. DisplayBuildError("Exception", ex.ToString()); throw; } }); _backgroundThread.Name = "AppBundle"; _backgroundThread.Start(); }
private void CreateBundleInternal( TaskCompletionSource <AndroidBuildReport> taskCompletionSource, string aabFilePath, AssetPackConfig assetPackConfig, CompressionOptions compressionOptions, AndroidBuildReport androidBuildReport) { try { var errorMessage = CreateBundle(aabFilePath, assetPackConfig, compressionOptions); if (errorMessage == null) { taskCompletionSource.SetResult(androidBuildReport); } else { // Already logged. taskCompletionSource.SetException(new AndroidBuildException(errorMessage, androidBuildReport)); } } catch (ThreadAbortException ex) { if (_canceled) { taskCompletionSource.SetCanceled(); } else { // Unexpected ThreadAbortException. taskCompletionSource.SetException(new AndroidBuildException(ex, androidBuildReport)); DisplayBuildError("Exception", ex.ToString()); } } catch (Exception ex) { taskCompletionSource.SetException(new AndroidBuildException(ex, androidBuildReport)); DisplayBuildError("Exception", ex.ToString()); } }
/** * Create the Play Asset Delivery configuration. */ public static AssetPackConfig CreateAssetPacks(TextureCompressionFormat textureCompressionFormat, string buildPath = null) { if (string.IsNullOrEmpty(buildPath)) { buildPath = GetLocalBuildPath(); } Debug.LogFormat("[{0}.{1}] path={2}", nameof(AssetPackBuilder), nameof(CreateAssetPacks), buildPath); AssetPackConfig assetPackConfig = new AssetPackConfig { DefaultTextureCompressionFormat = textureCompressionFormat }; if (!Directory.Exists(buildPath)) { return(null); } var bundles = GetBundles(buildPath); if (Directory.Exists(BuildPath)) { Directory.Delete(BuildPath, true); } Directory.CreateDirectory(BuildPath); foreach (var bundle in bundles) { string targetPath = Path.Combine(BuildPath, bundle.Name); Directory.CreateDirectory(targetPath); string bundlePath = Path.Combine(targetPath, Path.GetFileNameWithoutExtension(bundle.Bundle)); File.Copy(bundle.Bundle, bundlePath); assetPackConfig.AssetPacks.Add(bundle.Name, bundle.CreateAssetPack(textureCompressionFormat, bundlePath)); } WriteAssetPackConfig(bundles); AssetPackConfigSerializer.SaveConfig(assetPackConfig); return(assetPackConfig); }
/// <summary> /// Synchronously builds an Android App Bundle at the specified path using the specified Android Player. /// </summary> /// <returns>True if the build succeeded, false if it failed or was cancelled.</returns> public bool CreateBundle(string aabFilePath, string androidPlayerFilePath, AssetPackConfig assetPackConfig) { if (_buildStatus != BuildStatus.Running) { throw new Exception("Unexpected call to CreateBundle() with status: " + _buildStatus); } var moduleDirectoryList = new List <DirectoryInfo>(); var workingDirectory = new DirectoryInfo(_workingDirectoryPath); var configParams = new BundletoolHelper.BuildBundleConfigParams { defaultTcfSuffix = TextureTargetingTools.GetBundleToolTextureCompressionFormatName( assetPackConfig.DefaultTextureCompressionFormat), minSdkVersion = _minSdkVersion }; // Create asset pack module directories. var index = 0; var assetPacks = assetPackConfig.DeliveredAssetPacks; foreach (var entry in assetPacks) { DisplayProgress( string.Format("Processing asset pack {0} of {1}", index + 1, assetPacks.Count), Mathf.Lerp(0.1f, ProgressCreateBaseModule, (float)index / assetPacks.Count)); index++; var assetPackName = entry.Key; var assetPack = entry.Value; configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetBundleFilePath != null; configParams.containsInstallTimeAssetPack |= assetPack.DeliveryMode == AssetPackDeliveryMode.InstallTime; var assetPackDirectoryInfo = workingDirectory.CreateSubdirectory(assetPackName); if (!CreateAssetPackModule(assetPackName, assetPack, assetPackDirectoryInfo)) { return(false); } moduleDirectoryList.Add(assetPackDirectoryInfo); } // Create base module directory. var baseDirectory = workingDirectory.CreateSubdirectory(AndroidAppBundle.BaseModuleName); IList <string> bundleMetadata; if (!CreateBaseModule(baseDirectory, androidPlayerFilePath, out bundleMetadata)) { return(false); } moduleDirectoryList.Add(baseDirectory); // Create a ZIP file for each module directory. var moduleFiles = new List <string>(); var numModules = moduleDirectoryList.Count; for (var i = 0; i < numModules; i++) { if (numModules == 1) { DisplayProgress("Processing base module", ProgressProcessModules); } else { DisplayProgress( string.Format("Processing module {0} of {1}", i + 1, numModules), Mathf.Lerp(ProgressProcessModules, ProgressRunBundletool, (float)i / numModules)); } var moduleDirectoryInfo = moduleDirectoryList[i]; var destinationDirectoryInfo = GetDestinationSubdirectory(moduleDirectoryInfo); // Create ZIP file path, for example /path/to/files/base becomes /path/to/files/base/base.zip var zipFilePath = Path.Combine(moduleDirectoryInfo.FullName, moduleDirectoryInfo.Name + ".zip"); var zipErrorMessage = _zipUtils.CreateZipFile(zipFilePath, destinationDirectoryInfo.FullName, "."); if (zipErrorMessage != null) { DisplayBuildError("Zip creation", zipErrorMessage); return(false); } moduleFiles.Add(zipFilePath); } // If the .aab file exists, EditorUtility.SaveFilePanel() has already prompted for whether to overwrite. // Therefore, prevent Bundletool from throwing an IllegalArgumentException that "File already exists." File.Delete(aabFilePath); DisplayProgress("Running bundletool", ProgressRunBundletool); var buildBundleErrorMessage = _bundletool.BuildBundle(aabFilePath, moduleFiles, bundleMetadata, configParams); if (buildBundleErrorMessage != null) { DisplayBuildError("bundletool", buildBundleErrorMessage); return(false); } DisplayProgress("Signing bundle", 0.9f); var signingErrorMessage = _apkSigner.SignZip(aabFilePath); if (signingErrorMessage != null) { DisplayBuildError("Signing", signingErrorMessage); return(false); } Debug.LogFormat("Finished building app bundle: {0}", aabFilePath); _finishedAabFilePath = aabFilePath; _buildStatus = BuildStatus.Succeeding; return(true); }
/// <summary> /// Synchronously builds an AAB given the specified options and existing Android Player on disk. /// </summary> /// <returns>An error message if there was an error, or null if successful.</returns> public string CreateBundle(string aabFilePath, AssetPackConfig assetPackConfig) { if (_buildStatus != BuildStatus.Running) { throw new Exception("Unexpected call to CreateBundle() with status: " + _buildStatus); } var moduleDirectoryList = new List <DirectoryInfo>(); var workingDirectory = new DirectoryInfo(_workingDirectoryPath); var configParams = new BundletoolHelper.BuildBundleConfigParams { defaultTcfSuffix = TextureTargetingTools.GetBundleToolTextureCompressionFormatName( assetPackConfig.DefaultTextureCompressionFormat), minSdkVersion = _minSdkVersion }; // Create asset pack module directories. var index = 0; var assetPacks = assetPackConfig.DeliveredAssetPacks; foreach (var entry in assetPacks) { DisplayProgress( string.Format("Processing asset pack {0} of {1}", index + 1, assetPacks.Count), Mathf.Lerp(0.1f, ProgressCreateBaseModule, (float)index / assetPacks.Count)); index++; var assetPackName = entry.Key; var assetPack = entry.Value; configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetBundleFilePath != null; configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetPackDirectoryPath != null; configParams.containsInstallTimeAssetPack |= assetPack.DeliveryMode == AssetPackDeliveryMode.InstallTime; var assetPackDirectoryInfo = workingDirectory.CreateSubdirectory(assetPackName); var assetPackErrorMessage = CreateAssetPackModule(assetPackName, assetPack, assetPackDirectoryInfo); if (assetPackErrorMessage != null) { // Already displayed the error. return(assetPackErrorMessage); } moduleDirectoryList.Add(assetPackDirectoryInfo); } // Create base module directory. var baseDirectory = workingDirectory.CreateSubdirectory(AndroidAppBundle.BaseModuleName); IList <string> bundleMetadata; var baseErrorMessage = CreateBaseModule(baseDirectory, out bundleMetadata); if (baseErrorMessage != null) { // Already displayed the error. return(baseErrorMessage); } moduleDirectoryList.Add(baseDirectory); // Create a ZIP file for each module directory. var moduleFiles = new List <string>(); var numModules = moduleDirectoryList.Count; for (var i = 0; i < numModules; i++) { if (numModules == 1) { DisplayProgress("Processing base module", ProgressProcessModules); } else { DisplayProgress( string.Format("Processing module {0} of {1}", i + 1, numModules), Mathf.Lerp(ProgressProcessModules, ProgressRunBundletool, (float)i / numModules)); } var moduleDirectoryInfo = moduleDirectoryList[i]; var destinationDirectoryInfo = GetDestinationSubdirectory(moduleDirectoryInfo); // Create ZIP file path, for example /path/to/files/base becomes /path/to/files/base/base.zip var zipFilePath = Path.Combine(moduleDirectoryInfo.FullName, moduleDirectoryInfo.Name + ".zip"); var zipErrorMessage = _zipUtils.CreateZipFile(zipFilePath, destinationDirectoryInfo.FullName, "."); if (zipErrorMessage != null) { return(DisplayBuildError("Zip creation", zipErrorMessage)); } moduleFiles.Add(zipFilePath); } DisplayProgress("Running bundletool", ProgressRunBundletool); var buildBundleErrorMessage = _bundletool.BuildBundle(aabFilePath, moduleFiles, bundleMetadata, configParams); if (buildBundleErrorMessage != null) { return(DisplayBuildError("Bundletool", buildBundleErrorMessage)); } // Only sign the .aab if a custom keystore is configured. if (_jarSigner.UseCustomKeystore) { DisplayProgress("Signing bundle", 0.9f); var signingErrorMessage = _jarSigner.Sign(aabFilePath); if (signingErrorMessage != null) { return(DisplayBuildError("Signing", signingErrorMessage)); } } else { Debug.LogFormat("Skipped signing since a Custom Keystore isn't configured in Android Player Settings"); } MoveSymbolsZipFile(aabFilePath); Debug.LogFormat("Finished building app bundle: {0}", aabFilePath); _finishedAabFilePath = aabFilePath; _buildStatus = BuildStatus.Succeeding; return(null); }
/// <summary> /// Returns a deep copy of the specified <see cref="AssetPackConfig"/>. /// </summary> /// <param name="assetPackConfig">The AssetPackConfig to copy.</param> /// <returns>A new copy of the original AssetPackConfig.</returns> public static AssetPackConfig DeepCopy(AssetPackConfig assetPackConfig) { return(Deserialize(Serialize(assetPackConfig))); }
static void BuildRTAssets_AssetPacks_Scripted() { // Save the current setting MobileTextureSubtarget originalSetting = EditorUserBuildSettings.androidBuildSubtarget; // Clean out any old data DeleteTargetDirectory(streamingName); DeleteTargetDirectory(assetPacksName); // Build the AssetBundles, both in ETC2 and ASTC texture formats BuildAssetBundles(assetPacksName, astcSuffix, MobileTextureSubtarget.ASTC); BuildAssetBundles(assetPacksName, "", MobileTextureSubtarget.ETC2); AssetDatabase.Refresh(); // Copy our discrete test image asset into a new directory // which will be used for the 'discrete' asset pack source string discreteFileName = "Discrete1.jpg"; string discretePackName = "discretepack"; string discretePath = Path.Combine(Path.GetTempPath(), discretePackName); Directory.CreateDirectory(discretePath); string destPath = Path.Combine(discretePath, discreteFileName); if (File.Exists(destPath)) { File.Delete(destPath); } string sourcePath = Path.Combine(Application.dataPath, "Images"); sourcePath = Path.Combine(sourcePath, discreteFileName); File.Copy(sourcePath, destPath); Debug.Log("Copied discrete file to : " + destPath); // Create an AssetPackConfig and start creating asset packs AssetPackConfig assetPackConfig = new AssetPackConfig(); // Create asset packs using AssetBundles string assetBundlePath = Path.Combine(Application.dataPath, assetPacksName); // Add the default ETC2 bundles assetPackConfig.AddAssetBundle(Path.Combine(assetBundlePath, "installtime"), AssetPackDeliveryMode.InstallTime); assetPackConfig.AddAssetBundle(Path.Combine(assetBundlePath, "fastfollow"), AssetPackDeliveryMode.FastFollow); assetPackConfig.AddAssetBundle(Path.Combine(assetBundlePath, "ondemand"), AssetPackDeliveryMode.OnDemand); // Add the ASTC bundles assetBundlePath += astcSuffix; assetPackConfig.AddAssetBundle(Path.Combine(assetBundlePath, "installtime"), AssetPackDeliveryMode.InstallTime); assetPackConfig.AddAssetBundle(Path.Combine(assetBundlePath, "fastfollow"), AssetPackDeliveryMode.FastFollow); assetPackConfig.AddAssetBundle(Path.Combine(assetBundlePath, "ondemand"), AssetPackDeliveryMode.OnDemand); // Create an asset pack from our discrete directory assetPackConfig.AddAssetsFolder(discretePackName, discretePath, AssetPackDeliveryMode.OnDemand); // Configures the build system to use the newly created // assetPackConfig when calling Google > Build and Run or // Google > Build Android App Bundle. AssetPackConfigSerializer.SaveConfig(assetPackConfig); EditorUserBuildSettings.androidBuildSubtarget = originalSetting; }