using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; using ABI.CCK.Components; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.XR; using Object = UnityEngine.Object; using Random = System.Random; namespace ABI.CCK.Scripts.Editor { public class CCK_BuildUtility { #region Variables public static PreAvatarBundleEvent PreAvatarBundleEvent = new PreAvatarBundleEvent(); public static PrePropBundleEvent PrePropBundleEvent = new PrePropBundleEvent(); public static PreWorldBundleEvent PreWorldBundleEvent = new PreWorldBundleEvent(); public static string upload_id = ""; #endregion #region Avatar Upload public static async Task BuildAndUploadAvatar(GameObject avatarObject) { ClearLog(); EnforceVRSetting(); CVRAssetInfo assetInfo = avatarObject.GetComponent(); await InitializeAPIAndSetObjectId(assetInfo, "cck/generate/avatar"); SetAssetInfoDirty(assetInfo); GameObject instantiatedAvatar = InstantiateAndUnpackPrefab(avatarObject); if (!HandlePreBuildEvent(PreAvatarBundleEvent.Invoke, instantiatedAvatar)) { Object.DestroyImmediate(instantiatedAvatar); return; } CCK_Tools.CleanEditorOnlyGameObjects(instantiatedAvatar); PrefabUtility.SaveAsPrefabAsset(instantiatedAvatar, !Application.unityVersion.Contains("2021") ? "Assets/ABI.CCK/Resources/Cache/_CVRAvatar.prefab" : $"Assets/ABI.CCK/Resources/Cache/CVRAvatar_{assetInfo.objectId}_{assetInfo.randomNum}.prefab"); AssetBundleBuild assetBundleBuild = new AssetBundleBuild(); assetBundleBuild.assetNames = !Application.unityVersion.Contains("2021") ? new[] { "Assets/ABI.CCK/Resources/Cache/_CVRAvatar.prefab" } : new[] { $"Assets/ABI.CCK/Resources/Cache/CVRAvatar_{assetInfo.objectId}_{assetInfo.randomNum}.prefab" }; upload_id = assetInfo.objectId; EditorPrefs.SetString("m_ABI_uploadId", upload_id); EditorPrefs.SetString("m_ABI_uploadRand", assetInfo.randomNum); assetBundleBuild.assetBundleName = $"cvravatar_{assetInfo.objectId}_{assetInfo.randomNum}.cvravatar"; BuildPipeline.BuildAssetBundles(Application.persistentDataPath, new[] {assetBundleBuild}, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget); Object.DestroyImmediate(instantiatedAvatar); if (!File.Exists($"{Application.persistentDataPath}/cvravatar_{assetInfo.objectId}_{assetInfo.randomNum}.cvravatar")) { Debug.LogError("Error during bundling\nThere has been an error during the bundling process. Please check your console for errors."); EditorUtility.DisplayDialog("Error during bundling", "There has been an error during the bundling process. Please check your console for errors.", "OK"); return; } AssetDatabase.Refresh(); EditorPrefs.SetBool("m_ABI_isBuilding", true); EditorApplication.isPlaying = true; } #endregion #region Spawnable Upload public static async Task BuildAndUploadSpawnable(GameObject spawnableObject) { ClearLog(); EnforceVRSetting(); CVRAssetInfo assetInfo = spawnableObject.GetComponent(); CVRSpawnable spawnable = spawnableObject.GetComponent(); spawnable.spawnableType = CVRSpawnable.SpawnableType.StandaloneSpawnable; EditorUtility.SetDirty(spawnable); await InitializeAPIAndSetObjectId(assetInfo, "cck/generate/spawnable"); SetAssetInfoDirty(assetInfo); GameObject instantiatedSpawnable = InstantiateAndUnpackPrefab(spawnableObject); if (!HandlePreBuildEvent(PrePropBundleEvent.Invoke, instantiatedSpawnable)) { Object.DestroyImmediate(instantiatedSpawnable); return; } CCK_Tools.CleanEditorOnlyGameObjects(instantiatedSpawnable); PrefabUtility.SaveAsPrefabAsset(instantiatedSpawnable, !Application.unityVersion.Contains("2021") ? "Assets/ABI.CCK/Resources/Cache/_CVRSpawnable.prefab" : $"Assets/ABI.CCK/Resources/Cache/CVRSpawnable_{assetInfo.objectId}_{assetInfo.randomNum}.prefab"); AssetBundleBuild assetBundleBuild = new AssetBundleBuild(); assetBundleBuild.assetNames = !Application.unityVersion.Contains("2021") ? new[] { "Assets/ABI.CCK/Resources/Cache/_CVRSpawnable.prefab" } : new[] { $"Assets/ABI.CCK/Resources/Cache/CVRSpawnable_{assetInfo.objectId}_{assetInfo.randomNum}.prefab" }; upload_id = assetInfo.objectId; EditorPrefs.SetString("m_ABI_uploadId", upload_id); EditorPrefs.SetString("m_ABI_uploadRand", assetInfo.randomNum); assetBundleBuild.assetBundleName = $"cvrspawnable_{assetInfo.objectId}_{assetInfo.randomNum}.cvrprop"; BuildPipeline.BuildAssetBundles(Application.persistentDataPath, new[] {assetBundleBuild}, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget); Object.DestroyImmediate(instantiatedSpawnable); if (!File.Exists($"{Application.persistentDataPath}/cvrspawnable_{assetInfo.objectId}_{assetInfo.randomNum}.cvrprop")) { Debug.LogError("Error during bundling\nThere has been an error during the bundling process. Please check your console for errors."); EditorUtility.DisplayDialog("Error during bundling", "There has been an error during the bundling process. Please check your console for errors.", "OK"); return; } AssetDatabase.Refresh(); EditorPrefs.SetBool("m_ABI_isBuilding", true); EditorApplication.isPlaying = true; } #endregion #region World Upload public static async Task BuildAndUploadMapAsset(Scene scene, GameObject descriptor) { ClearLog(); EnforceVRSetting(); SetupNetworkUUIDs(); CVRAssetInfo assetInfo = descriptor.GetComponent(); await InitializeAPIAndSetObjectId(assetInfo, "cck/generate/world"); SetAssetInfoDirty(assetInfo); if (!HandlePreBuildEvent(PreWorldBundleEvent.Invoke, scene)) return; PrefabUtility.SaveAsPrefabAsset(descriptor, "Assets/ABI.CCK/Resources/Cache/_CVRWorld.prefab"); AssetBundleBuild assetBundleBuild = new AssetBundleBuild(); assetBundleBuild.assetNames = new[] {scene.path}; assetBundleBuild.assetBundleName = "bundle.cvrworld"; BuildPipeline.BuildAssetBundles(Application.persistentDataPath, new[] {assetBundleBuild}, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget); if (!File.Exists($"{Application.persistentDataPath}/bundle.cvrworld")) { Debug.LogError("Error during bundling\nThere has been an error during the bundling process. Please check your console for errors."); EditorUtility.DisplayDialog("Error during bundling", "There has been an error during the bundling process. Please check your console for errors.", "OK"); return; } AssetDatabase.Refresh(); EditorPrefs.SetBool("m_ABI_isBuilding", true); EditorApplication.isPlaying = true; } #endregion #region Private Methods private static async Task InitializeAPIAndSetObjectId(CVRAssetInfo assetInfo, string apiEndpoint) { if (string.IsNullOrEmpty(assetInfo.objectId)) { #if UNITY_EDITOR APIConnection.Initialize(EditorPrefs.GetString("m_ABI_Username"), EditorPrefs.GetString("m_ABI_Key")); #endif APIConnection.BaseResponse response = await APIConnection.MakeRequest(apiEndpoint, put: true); if (response != null && response.Data != null) assetInfo.objectId = response.Data.Id.ToString(); else Debug.LogError($"[CCK:BuildUtility] New Guid could not be generated"); } Random rnd = new Random(); assetInfo.randomNum = rnd.Next(11111111, 99999999).ToString(); } private static void SetAssetInfoDirty(Component assetInfo) { EditorUtility.SetDirty(assetInfo); EditorSceneManager.MarkSceneDirty(assetInfo.gameObject.scene); EditorSceneManager.SaveScene(assetInfo.gameObject.scene); } private static GameObject InstantiateAndUnpackPrefab(GameObject original) { GameObject instantiated = Object.Instantiate(original); if (PrefabUtility.IsPartOfNonAssetPrefabInstance(instantiated) && PrefabUtility.IsOutermostPrefabInstanceRoot(instantiated)) PrefabUtility.UnpackPrefabInstance(instantiated, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); // Why would you do this? if (instantiated.CompareTag("EditorOnly")) instantiated.tag = "Untagged"; return instantiated; } private static bool HandlePreBuildEvent(Action preBuildEvent, GameObject instantiatedObject) { try { preBuildEvent.Invoke(instantiatedObject); return true; } catch (Exception ex) { Debug.LogError($"[CCK:BuildUtility] Error occurred during PreBuildEvent: {ex.Message}"); return false; } } private static bool HandlePreBuildEvent(Action preBuildEvent, Scene scene) { try { preBuildEvent.Invoke(scene); return true; } catch (Exception ex) { Debug.LogError($"[CCK:BuildUtility] Error occurred during PreBuildEvent: {ex.Message}"); return false; } } private static void SetupNetworkUUIDs() { CVRInteractable[] interactables = Resources.FindObjectsOfTypeAll(); CVRObjectSync[] objectSyncs = Resources.FindObjectsOfTypeAll(); CVRVideoPlayer[] videoPlayers = Resources.FindObjectsOfTypeAll(); CVRSpawnable[] spawnables = Resources.FindObjectsOfTypeAll(); GameInstanceController[] gameInstances = Resources.FindObjectsOfTypeAll(); GunController[] gunControllers = Resources.FindObjectsOfTypeAll(); List UsedGuids = new List(); foreach (var interactable in interactables) { foreach (var action in interactable.actions) { string guid; do { guid = Guid.NewGuid().ToString(); } while (!string.IsNullOrEmpty(UsedGuids.Find(match => match == guid))); UsedGuids.Add(guid); action.guid = guid; } } foreach (var objectSync in objectSyncs) { string guid; do { guid = Guid.NewGuid().ToString(); } while (!string.IsNullOrEmpty(UsedGuids.Find(match => match == guid))); UsedGuids.Add(guid); var newserializedObject = new SerializedObject(objectSync); newserializedObject.Update(); SerializedProperty _guidProperty = newserializedObject.FindProperty("guid"); _guidProperty.stringValue = guid; newserializedObject.ApplyModifiedProperties(); } foreach (var player in videoPlayers) { Guid res; if (player.playerId == null || !Guid.TryParse(player.playerId, out res)) { string guid; do { guid = Guid.NewGuid().ToString(); } while (!string.IsNullOrEmpty(UsedGuids.Find(match => match == guid))); UsedGuids.Add(guid); player.playerId = guid; } } foreach (var spawnable in spawnables) { string guid; do { guid = Guid.NewGuid().ToString(); } while (!string.IsNullOrEmpty(UsedGuids.Find(match => match == guid))); if (spawnable.preGeneratedInstanceId == "") { UsedGuids.Add(guid); spawnable.preGeneratedInstanceId = "ws~" + guid; } else { UsedGuids.Add(spawnable.preGeneratedInstanceId); } spawnable.spawnableType = CVRSpawnable.SpawnableType.WorldSpawnable; } int i = 0; foreach (GameInstanceController gameInstance in gameInstances) { if (gameInstance.teams.Count == 0) { var team = new Team(); team.name = "Team"; team.color = Color.red; team.playerLimit = 16; team.index = i; gameInstance.teams.Add(team); i++; } else { foreach (var team in gameInstance.teams) { team.index = i; i++; } } } foreach (var gunController in gunControllers) { string guid; do { guid = Guid.NewGuid().ToString(); } while (!string.IsNullOrEmpty(UsedGuids.Find(match => match == guid))); UsedGuids.Add(guid); var newserializedObject = new SerializedObject(gunController); newserializedObject.Update(); SerializedProperty _guidProperty = newserializedObject.FindProperty("referenceID"); _guidProperty.stringValue = guid; newserializedObject.ApplyModifiedProperties(); } } private static void ClearLog() { var assembly = Assembly.GetAssembly(typeof(UnityEditor.Editor)); var type = assembly.GetType("UnityEditor.LogEntries"); var method = type.GetMethod("Clear"); method.Invoke(new object(), null); } private static void EnforceVRSetting() { #if UNITY_2021_1_OR_NEWER PlayerSettings.virtualRealitySupported = true; PlayerSettings.stereoRenderingPath = StereoRenderingPath.Instancing; XRSettings.enabled = false; #else PlayerSettings.virtualRealitySupported = true; PlayerSettings.SetVirtualRealitySDKs(BuildTargetGroup.Standalone, new string[] { "None", "Oculus", "OpenVR", "MockHMD" }); PlayerSettings.stereoRenderingPath = StereoRenderingPath.SinglePass; #endif } #endregion } #region PreBundleEvents [Serializable] public class PreAvatarBundleEvent : UnityEvent { } [Serializable] public class PrePropBundleEvent : UnityEvent { } [Serializable] public class PreWorldBundleEvent : UnityEvent { } #endregion }