// ---------------------------------------------------------------------------- // // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH // // // Unity Editor Utils // // developer@exitgames.com // ---------------------------------------------------------------------------- #pragma warning disable 618 // Deprecation warnings #if UNITY_2017_4_OR_NEWER #define SUPPORTED_UNITY #endif #if UNITY_EDITOR namespace Photon.Realtime { using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using System.IO; using System.Text; using UnityEngine.Networking; [InitializeOnLoad] public static class PhotonEditorUtils { /// Stores a flag which tells Editor scripts if the PhotonEditor.OnProjectChanged got called since initialization. /// If not, the AssetDatabase is likely not usable yet and instances of ScriptableObject can't be loaded. public static bool ProjectChangedWasCalled; /// True if the ChatClient of the Photon Chat API is available. If so, the editor may (e.g.) show additional options in settings. public static bool HasChat; /// True if the VoiceClient of the Photon Voice API is available. If so, the editor may (e.g.) show additional options in settings. public static bool HasVoice; /// True if PUN is in the project. public static bool HasPun; /// True if Photon Fusion is available in the project (and enabled). public static bool HasFusion; /// True if the PhotonEditorUtils checked the available products / APIs. If so, the editor may (e.g.) show additional options in settings. public static bool HasCheckedProducts; static PhotonEditorUtils() { HasVoice = Type.GetType("Photon.Voice.VoiceClient, Assembly-CSharp") != null || Type.GetType("Photon.Voice.VoiceClient, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Voice.VoiceClient, PhotonVoice.API") != null; HasChat = Type.GetType("Photon.Chat.ChatClient, Assembly-CSharp") != null || Type.GetType("Photon.Chat.ChatClient, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Chat.ChatClient, PhotonChat") != null; HasPun = Type.GetType("Photon.Pun.PhotonNetwork, Assembly-CSharp") != null || Type.GetType("Photon.Pun.PhotonNetwork, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Pun.PhotonNetwork, PhotonUnityNetworking") != null; #if FUSION_WEAVER HasFusion = true; #endif PhotonEditorUtils.HasCheckedProducts = true; if (EditorPrefs.HasKey("DisablePun") && EditorPrefs.GetBool("DisablePun")) { HasPun = false; } if (HasPun) { // MOUNTING SYMBOLS #if !PHOTON_UNITY_NETWORKING AddScriptingDefineSymbolToAllBuildTargetGroups("PHOTON_UNITY_NETWORKING"); #endif #if !PUN_2_0_OR_NEWER AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_0_OR_NEWER"); #endif #if !PUN_2_OR_NEWER AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_OR_NEWER"); #endif #if !PUN_2_19_OR_NEWER AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_19_OR_NEWER"); #endif } } /// /// Adds a given scripting define symbol to all build target groups /// You can see all scripting define symbols ( not the internal ones, only the one for this project), in the PlayerSettings inspector /// /// Define symbol. public static void AddScriptingDefineSymbolToAllBuildTargetGroups(string defineSymbol) { foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget))) { BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target); if (group == BuildTargetGroup.Unknown) { continue; } var defineSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';').Select(d => d.Trim()).ToList(); if (!defineSymbols.Contains(defineSymbol)) { defineSymbols.Add(defineSymbol); try { PlayerSettings.SetScriptingDefineSymbolsForGroup(group, string.Join(";", defineSymbols.ToArray())); } catch (Exception e) { Debug.Log("Could not set Photon " + defineSymbol + " defines for build target: " + target + " group: " + group + " " + e); } } } } /// /// Removes PUN2's Script Define Symbols from project /// public static void CleanUpPunDefineSymbols() { foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget))) { BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target); if (group == BuildTargetGroup.Unknown) { continue; } var defineSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(group) .Split(';') .Select(d => d.Trim()) .ToList(); List newDefineSymbols = new List(); foreach (var symbol in defineSymbols) { if ("PHOTON_UNITY_NETWORKING".Equals(symbol) || symbol.StartsWith("PUN_2_")) { continue; } newDefineSymbols.Add(symbol); } try { PlayerSettings.SetScriptingDefineSymbolsForGroup(group, string.Join(";", newDefineSymbols.ToArray())); } catch (Exception e) { Debug.LogErrorFormat("Could not set clean up PUN2's define symbols for build target: {0} group: {1}, {2}", target, group, e); } } } /// /// Gets the parent directory of a path. Recursive Function, will return null if parentName not found /// /// The parent directory /// Path. /// Parent name. public static string GetParent(string path, string parentName) { var dir = new DirectoryInfo(path); if (dir.Parent == null) { return null; } if (string.IsNullOrEmpty(parentName)) { return dir.Parent.FullName; } if (dir.Parent.Name == parentName) { return dir.Parent.FullName; } return GetParent(dir.Parent.FullName, parentName); } /// /// Check if a GameObject is a prefab asset or part of a prefab asset, as opposed to an instance in the scene hierarchy /// /// true, if a prefab asset or part of it, false otherwise. /// The GameObject to check public static bool IsPrefab(GameObject go) { #if UNITY_2021_2_OR_NEWER return UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(go) != null || EditorUtility.IsPersistent(go); #elif UNITY_2018_3_OR_NEWER return UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(go) != null || EditorUtility.IsPersistent(go); #else return EditorUtility.IsPersistent(go); #endif } //https://forum.unity.com/threads/using-unitywebrequest-in-editor-tools.397466/#post-4485181 public static void StartCoroutine(System.Collections.IEnumerator update) { EditorApplication.CallbackFunction closureCallback = null; closureCallback = () => { try { if (update.MoveNext() == false) { EditorApplication.update -= closureCallback; } } catch (Exception ex) { Debug.LogException(ex); EditorApplication.update -= closureCallback; } }; EditorApplication.update += closureCallback; } public static System.Collections.IEnumerator HttpPost(string url, Dictionary headers, byte[] payload, Action successCallback, Action errorCallback) { using (UnityWebRequest w = new UnityWebRequest(url, "POST")) { if (payload != null) { w.uploadHandler = new UploadHandlerRaw(payload); } w.downloadHandler = new DownloadHandlerBuffer(); if (headers != null) { foreach (var header in headers) { w.SetRequestHeader(header.Key, header.Value); } } #if UNITY_2017_2_OR_NEWER yield return w.SendWebRequest(); #else yield return w.Send(); #endif while (w.isDone == false) yield return null; #if UNITY_2020_2_OR_NEWER if (w.result == UnityWebRequest.Result.ProtocolError || w.result == UnityWebRequest.Result.ConnectionError || w.result == UnityWebRequest.Result.DataProcessingError) #elif UNITY_2017_1_OR_NEWER if (w.isNetworkError || w.isHttpError) #endif { if (errorCallback != null) { errorCallback(w.error); } } else { if (successCallback != null) { successCallback(w.downloadHandler.text); } } } } /// /// Creates a Foldout using a toggle with (GUIStyle)"Foldout") and a separate label. This is a workaround for 2019.3 foldout arrows not working. /// /// /// /// Returns the new isExpanded value. public static bool Foldout(this SerializedProperty isExpanded, GUIContent label) { var rect = EditorGUILayout.GetControlRect(); bool newvalue = EditorGUI.Toggle(new Rect(rect) { xMin = rect.xMin + 2 }, GUIContent.none, isExpanded.boolValue, (GUIStyle)"Foldout"); EditorGUI.LabelField(new Rect(rect) { xMin = rect.xMin + 15 }, label); if (newvalue != isExpanded.boolValue) { isExpanded.boolValue = newvalue; isExpanded.serializedObject.ApplyModifiedProperties(); } return newvalue; } /// /// Creates a Foldout using a toggle with (GUIStyle)"Foldout") and a separate label. This is a workaround for 2019.3 foldout arrows not working. /// /// /// /// Returns the new isExpanded value. public static bool Foldout(this bool isExpanded, GUIContent label) { var rect = EditorGUILayout.GetControlRect(); bool newvalue = EditorGUI.Toggle(new Rect(rect) { xMin = rect.xMin + 2 }, GUIContent.none, isExpanded, (GUIStyle)"Foldout"); EditorGUI.LabelField(new Rect(rect) { xMin = rect.xMin + 15 }, label); return newvalue; } } public class CleanUpDefinesOnPunDelete : UnityEditor.AssetModificationProcessor { public static AssetDeleteResult OnWillDeleteAsset(string assetPath, RemoveAssetOptions rao) { if ("Assets/Photon/PhotonUnityNetworking".Equals(assetPath)) { PhotonEditorUtils.CleanUpPunDefineSymbols(); } return AssetDeleteResult.DidNotDelete; } } } #endif