using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; namespace MetaCity.BundleKit.Editor { public enum MetacityBundleType { Prefab, Scene, Avatar, SpawnPoints } public class MetacityBundle { public MetacityBundleType BundleType; public AssetBundleBuild BundleBuild; public bool NeedUpload; } public enum PlayMode { SinglePlayer, MultiPlayer } public enum WebUrl { LocalTesting, MetacityInt, U3D } [Serializable] public class BuildBundleConfig { public PlayMode playMode; public MetacityBundleType curType; public string bundleName; public bool needBuild; } [System.Serializable] public class BuildMainConfig { public PlayMode mPlayMode; public WebUrl mWebUrl; public bool mCustomize = false; public List bundleConfigs = new List(); private BuildBundleConfig TryGetConfig(string bundleName, MetacityBundleType bundleType, bool createIfNotExists = false) { if (bundleConfigs != null) { foreach (var bundle in bundleConfigs) { if (bundle.bundleName == bundleName && bundle.curType == bundleType) { return bundle; } } } BuildBundleConfig ret = null; if (createIfNotExists) { if (bundleConfigs == null) { bundleConfigs = new List(); } ret = new BuildBundleConfig { bundleName = bundleName, curType = bundleType, needBuild = true }; } return ret; } private void ReCollectBundleByType(List newConfigs, List bundleSources, MetacityBundleType curType) { foreach (var sceneName in bundleSources) { var newConfig = TryGetConfig(sceneName, curType); if (newConfig == null) { newConfig = new BuildBundleConfig { bundleName = sceneName, curType = curType, needBuild = true, }; } newConfigs.Add(newConfig); } } public void RefreshBundles(List allScenes, List allPrefabs) { var newConfigs = new List(); ReCollectBundleByType(newConfigs, allScenes, MetacityBundleType.Scene); ReCollectBundleByType(newConfigs, allPrefabs, MetacityBundleType.Prefab); bundleConfigs = newConfigs; } public bool NeedBuildBundle(string bundleName, MetacityBundleType bundleType) { return TryGetConfig(bundleName, bundleType)?.needBuild ?? true; } public void ToggleNeedBuildArtifact(string bundleName) { if (bundleConfigs == null) { bundleConfigs = new List(); } var curConfig = TryGetConfig(bundleName, MetacityBundleType.Prefab, true); curConfig.needBuild = !curConfig.needBuild; } public void ToggleSceneBundleConfig(string bundleName) { if (bundleConfigs == null) { bundleConfigs = new List(); } var curConfig = TryGetConfig(bundleName, MetacityBundleType.Scene, true); curConfig.needBuild = !curConfig.needBuild; curConfig.playMode=mPlayMode; } public void SetPlayMode(PlayMode playMode) { mPlayMode=playMode; foreach (var bundle in bundleConfigs) { if (bundle.curType == MetacityBundleType.Scene) { bundle.playMode=playMode; } } } public void SetWebUrl(WebUrl weburl) { mWebUrl= weburl; } public void PrepareSave(List allScenes, List allAssets) { RefreshBundles(allScenes, allAssets); } private List GetSkippedBundles(MetacityBundleType curType) { var bundleNames = new List(); if (bundleConfigs != null) { foreach (var bundleConfig in bundleConfigs) { if (bundleConfig.curType == curType && !bundleConfig.needBuild) { bundleNames.Add(bundleConfig.bundleName); } } } return bundleNames; } private List GetAllBundles(MetacityBundleType curType) { var bundles = new List(); if (bundleConfigs != null) { foreach (var bundleConfig in bundleConfigs) { if (bundleConfig.curType == curType) { bundles.Add(bundleConfig); } } } return bundles; } public List skipAssets => GetSkippedBundles(MetacityBundleType.Prefab); public List allSceneBundles => GetAllBundles(MetacityBundleType.Scene); public List allAssetBundles => GetAllBundles(MetacityBundleType.Prefab); } public static class BuildEntry { private static readonly List AllBundleBuilds = new List(); private static void PrepareBuild() { AllBundleBuilds.Clear(); var buildPath = Constants.BundleFolderPath; if (Directory.Exists(buildPath)) { DirectoryInfo di = new DirectoryInfo(buildPath); foreach (FileInfo file in di.GetFiles()) { file.Delete(); } foreach (DirectoryInfo dir in di.GetDirectories()) { dir.Delete(true); } Directory.Delete(buildPath); } Directory.CreateDirectory(buildPath); } /** * Build all user generated contents into Bundles according to the following rules: * (1) Each scene inside the Assets/Game folder will be packed into one scene bundle named as its scene name in lower cases * * (2) User should put all the sharable assets (e.g., a prefab that can be bought and used by other users) into the Assets/Exports folder. * Specifically, a specific prefab and all its dependent assets should be put into one child folder in Assets/Exports. Then they will be * packed into one asset bundle named as the folder name in lower cases * * (3) All the referenced assets (e.g., a prefab from a bundle that shared from other users) are automatically downloaded into the Assets/Imports folder and * user should not modify this folder manually at all if they want to remain the reference link. These assets won't be uploaded to the * cloud as dependent bundles of the user scene. On the contrary, the user scene will dependent on the shared bundle directly instead. * * (4) Bundle dependencies will be correctly processed in the pack progress and stored on the cloud. So user doesn't need to handle it by themselves * * (5) Bundle hashes will be considered automatically when uploading the bundle and updating its content on the cloud. If it doesn't change after * a new build, nothing will be changed on the cloud. * * (6) By default, user can use the builtin pack process which will generate bundles following the above rules. However, they can also customize * it by turning on "Customize Build" and toggling off check boxes before bundles to build in the BundleMainWindow. Bundles with unchecked boxes will * skip the next build and won't be uploaded to the cloud at all * */ public static List DoBuild(PlayMode playMode,List sceneConfigs, List skipPrefabs) { PrepareBuild(); SpawnPositionCollector.CollectSpawnPositionBundle(AllBundleBuilds); PrefabCollector.CollectPrefabBundles(AllBundleBuilds, skipPrefabs); SceneCollector.CollectSceneBundles(AllBundleBuilds, sceneConfigs); var platform = EditorUserBuildSettings.activeBuildTarget; AssetBundleManifest manifest = null; try { var allBundleBuilds = AllBundleBuilds.Select(item => item.BundleBuild); manifest = BuildPipeline.BuildAssetBundles(Constants.BundleFolderPath, allBundleBuilds.ToArray(), BuildAssetBundleOptions.None, platform); Debug.Log($"Build succeeded. Manifest {manifest.name}"); } catch (Exception e) { Debug.LogError($"Build bundle error for {platform.ToString()}: {e.Message}"); } if (manifest != null) { var catalogs = CatalogUtilities.CreateCatalog(playMode,platform, manifest, AllBundleBuilds); var catalogCollection = new BuildCatalogCollection { catalogs = catalogs.ToArray() }; var writer = new StreamWriter(Constants.BundleLocalCatalogPath, false); writer.WriteLine(JsonUtility.ToJson(catalogCollection)); writer.Close(); return catalogs; } Debug.LogError($"nothing is built"); return null; } public static List LoadLastBuiltCatalog() { var catalogReader = new StreamReader(Constants.BundleLocalCatalogPath); try { var catalogCollection = JsonUtility.FromJson(catalogReader.ReadToEnd()); return catalogCollection.catalogs.ToList(); } catch (System.Exception) { Debug.Log($"Cannot load last build catalog from {Constants.BundleLocalCatalogPath}!"); } return new List(); } } }