/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Editor.Managers { using Opsive.Shared.Utility; using Opsive.UltimateCharacterController.Editor.Inspectors.Utility; using Opsive.UltimateCharacterController.Utility; using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; /// /// The MainManagerWindow is an editor window which contains all of the sub managers. This window draws the high level menu options and draws /// the selected sub manager. /// [InitializeOnLoad] public class MainManagerWindow : EditorWindow { private float c_MenuWidth = 120; public float MenuWidth { get { return c_MenuWidth; } } private Manager[] m_Managers; private string[] m_ManagerNames; private Vector2 m_MenuScrollPosition; private int m_MenuSelection; // Unity's serialization doesn't support abstract classes so serialize the data separately. private Serialization[] m_ManagerData; private UnityEngine.Networking.UnityWebRequest m_UpdateCheckRequest; private DateTime m_LastUpdateCheck = DateTime.MinValue; public string LatestVersion { get { return EditorPrefs.GetString("Opsive.UltimateCharacterController.Editor.LatestVersion", AssetInfo.Version); } set { EditorPrefs.SetString("Opsive.UltimateCharacterController.Editor.LatestVersion", value); } } private DateTime LastUpdateCheck { get { try { // Don't read from editor prefs if it isn't necessary. if (m_LastUpdateCheck != DateTime.MinValue) { return m_LastUpdateCheck; } m_LastUpdateCheck = DateTime.Parse(EditorPrefs.GetString("Opsive.UltimateCharacterController.Editor.LastUpdateCheck", "1/1/1971 00:00:01"), System.Globalization.CultureInfo.InvariantCulture); } catch (Exception /*e*/) { m_LastUpdateCheck = DateTime.UtcNow; } return m_LastUpdateCheck; } set { m_LastUpdateCheck = value; EditorPrefs.SetString("Opsive.UltimateCharacterController.Editor.LastUpdateCheck", m_LastUpdateCheck.ToString(System.Globalization.CultureInfo.InvariantCulture)); } } private GUIStyle m_MenuBackground; private GUIStyle MenuBackground { get { #if UNITY_2019_3_OR_NEWER if (m_MenuBackground == null) { m_MenuBackground = new GUIStyle(EditorStyles.label); // The left, top, and bottom background border should extend to prevent it from being seen. var overflow = m_MenuBackground.overflow; overflow.left = overflow.top = overflow.bottom = 3; m_MenuBackground.overflow = overflow; var border = m_MenuBackground.border; border.left = border.right = 10; m_MenuBackground.border = border; } #else if (m_MenuBackground == null) { m_MenuBackground = new GUIStyle(EditorStyles.textArea); // The left, top, and bottom background border should extend to prevent it from being seen. var overflow = m_MenuBackground.overflow; overflow.left = overflow.top = overflow.bottom = 3; m_MenuBackground.overflow = overflow; } #endif return m_MenuBackground; } } private GUIStyle m_MenuButton; private GUIStyle MenuButton { get { #if UNITY_2019_3_OR_NEWER if (m_MenuButton == null) { m_MenuButton = new GUIStyle(EditorStyles.label); m_MenuButton.fontSize = 13; m_MenuButton.alignment = TextAnchor.MiddleRight; } #else if (m_MenuButton == null) { m_MenuButton = new GUIStyle(EditorStyles.toolbarButton); m_MenuButton.active.background = m_MenuButton.normal.background = null; m_MenuButton.fontSize = 13; m_MenuButton.alignment = TextAnchor.MiddleRight; var padding = m_MenuBackground.padding; padding.left = 0; padding.right = 2; m_MenuBackground.padding = padding; } #endif return m_MenuButton; } } private GUIStyle m_SelectedMenuButton; private GUIStyle SelectedMenuButton { get { if (m_SelectedMenuButton == null) { m_SelectedMenuButton = new GUIStyle(MenuButton); #if !UNITY_2019_3_OR_NEWER var overflow = m_SelectedMenuButton.overflow; overflow.top = overflow.bottom = 4; #endif } if (m_SelectedMenuButton.active.background == null) { #if UNITY_2018_1_OR_NEWER var background = new Texture2D(1, 1, TextureFormat.RGBA32, false); #else var background = new Texture2D(1, 1, TextureFormat.RGBA32, false, true); #endif background.SetPixel(0, 0, EditorGUIUtility.isProSkin ? new Color(0.243f, 0.373f, 0.588f) : new Color(0.247f, 0.494f, 0.871f)); background.Apply(); m_SelectedMenuButton.active.background = m_SelectedMenuButton.normal.background = background; } return m_SelectedMenuButton; } } private GUIStyle m_ManagerTitle; private GUIStyle ManagerTitle { get { if (m_ManagerTitle == null) { m_ManagerTitle = new GUIStyle(InspectorStyles.CenterBoldLabel); m_ManagerTitle.fontSize = 16; m_ManagerTitle.alignment = TextAnchor.MiddleLeft; } return m_ManagerTitle; } } /// /// Perform editor checks as soon as the scripts are done compiling. /// static MainManagerWindow() { EditorApplication.update += EditorStartup; } /// /// Initializes the Main Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Main Manager", false, 1)] public static MainManagerWindow ShowWindow() { var window = EditorWindow.GetWindow(false, "Character Manager"); window.minSize = new Vector2(680, 550); return window; } /// /// Show the project settings dialogues. /// private static void UpdateProjectSettings() { if (EditorUtility.DisplayDialog("Update Input Manager?", "Do you want to update the Input Manager?\n\n" + "If you have already updated the Input Manager or are using custom inputs you can select No.", "Yes", "No")) { Utility.UnityInputBuilder.UpdateInputManager(); } if (EditorUtility.DisplayDialog("Update Layers?", "Do you want to update the project layers?\n\n" + "If you have already updated the layers or are using custom layers you can select No.", "Yes", "No")) { SetupManager.UpdateLayers(); } EditorPrefs.SetBool("Opsive.UltimateCharacterController.Editor.UpdateProject", false); } /// /// Initializes the Main Manager and shows the Character Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Character Manager", false, 11)] public static void ShowCharacterManagerWindow() { var window = ShowWindow(); window.Open(typeof(CharacterManager)); } /// /// Initializes the Main Manager and shows the Item Type Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Item Type Manager", false, 12)] public static void ShowItemTypeManagerWindow() { var window = ShowWindow(); window.Open(typeof(ItemTypeManager)); } /// /// Initializes the Main Manager and shows the Item Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Item Manager", false, 13)] public static void ShowItemManagerWindow() { var window = ShowWindow(); window.Open(typeof(ItemManager)); } /// /// Initializes the Main Manager and shows the Item Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Object Manager", false, 14)] public static void ShowObjectManagerWindow() { var window = ShowWindow(); window.Open(typeof(ObjectManager)); } /// /// Initializes the Main Manager and shows the Integrations Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Integrations Manager", false, 25)] public static void ShowIntegrationsManagerWindow() { var window = ShowWindow(); window.Open(typeof(IntegrationsManager)); } /// /// Initializes the Main Manager and shows the Add-Ons Manager. /// [MenuItem("Tools/Opsive/Ultimate Character Controller/Add-Ons Manager", false, 26)] public static void ShowAddOnsManagerWindow() { var window = ShowWindow(); window.Open(typeof(AddOnsManager)); } /// /// Show the editor window if it hasn't been shown before and also setup. /// private static void EditorStartup() { if (EditorApplication.isCompiling) { return; } if (!EditorPrefs.GetBool("Opsive.UltimateCharacterController.Editor.MainManagerShown", false)) { EditorPrefs.SetBool("Opsive.UltimateCharacterController.Editor.MainManagerShown", true); ShowWindow(); } if (!EditorPrefs.HasKey("Opsive.UltimateCharacterController.Editor.UpdateProject") || EditorPrefs.GetBool("Opsive.UltimateCharacterController.Editor.UpdateProject", true)) { EditorUtility.DisplayDialog("Project Settings Setup", "Thank you for purchasing the " + AssetInfo.Name +".\n\n" + "This wizard will ask two questions related to updating your project.", "OK"); UpdateProjectSettings(); } EditorApplication.update -= EditorStartup; } /// /// Updates the inspector. /// private void OnInspectorUpdate() { UpdateCheck(); } /// /// Is an update available? /// /// True if an update is available. private bool UpdateCheck() { if (m_UpdateCheckRequest != null && m_UpdateCheckRequest.isDone) { if (string.IsNullOrEmpty(m_UpdateCheckRequest.error)) { LatestVersion = m_UpdateCheckRequest.downloadHandler.text; } m_UpdateCheckRequest = null; return false; } if (m_UpdateCheckRequest == null && DateTime.Compare(LastUpdateCheck.AddDays(1), DateTime.UtcNow) < 0) { var url = string.Format("https://opsive.com/asset/UpdateCheck.php?asset=UltimateCharacterController&type={0}&version={1}&unityversion={2}&devplatform={3}&targetplatform={4}", AssetInfo.Name.Replace(" ", ""), AssetInfo.Version, Application.unityVersion, Application.platform, EditorUserBuildSettings.activeBuildTarget); m_UpdateCheckRequest = UnityEngine.Networking.UnityWebRequest.Get(url); m_UpdateCheckRequest.SendWebRequest(); LastUpdateCheck = DateTime.UtcNow; } return m_UpdateCheckRequest != null; } /// /// The window has been enabled. /// private void OnEnable() { DeserializeManagers(); BuildManagerItems(); } /// /// Draws the Main Manager. /// private void OnGUI() { // Draw the menu. OnMenuGUI(); EditorGUI.BeginChangeCheck(); // Draw the manager. OnManagerGUI(); // Use a custom serialization for any changes since Unity's serialization doesn't support abstract inheritance. if (EditorGUI.EndChangeCheck()) { SerializeManagers(); } } /// /// Draws the menu UI. /// private void OnMenuGUI() { GUILayout.BeginArea(new Rect(0, 0, c_MenuWidth, position.height), MenuBackground); m_MenuScrollPosition = GUILayout.BeginScrollView(m_MenuScrollPosition); GUILayout.BeginVertical(); GUILayout.Space(32); for (int i = 0; i < m_Managers.Length; ++i) { if (GUILayout.Button(m_ManagerNames[i], (i == m_MenuSelection ? SelectedMenuButton : MenuButton), GUILayout.Height(32))) { m_MenuSelection = i; } } GUILayout.EndVertical(); GUILayout.EndScrollView(); GUILayout.EndArea(); } /// /// Builds the array which contains all of the IManager objects. /// private void BuildManagerItems() { var managers = new List(); var managerIndexes = new List(); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; ++i) { var assemblyTypes = assemblies[i].GetTypes(); for (int j = 0; j < assemblyTypes.Length; ++j) { // Must implement Manager. if (!typeof(Manager).IsAssignableFrom(assemblyTypes[j])) { continue; } // Ignore abstract classes. if (assemblyTypes[j].IsAbstract) { continue; } // A valid manager class. managers.Add(assemblyTypes[j]); var index = managerIndexes.Count; if (assemblyTypes[j].GetCustomAttributes(typeof(OrderedEditorItem), true).Length > 0) { var item = assemblyTypes[j].GetCustomAttributes(typeof(OrderedEditorItem), true)[0] as OrderedEditorItem; index = item.Index; } managerIndexes.Add(index); } } // Do not reinitialize the managers if they are already initialized and there aren't any changes. if (m_Managers != null && m_Managers.Length == managers.Count) { return; } // All of the manager types have been found. Sort by the index. var managerTypes = managers.ToArray(); Array.Sort(managerIndexes.ToArray(), managerTypes); m_Managers = new Manager[managers.Count]; m_ManagerNames = new string[managers.Count]; // The manager types have been found and sorted. Add them to the list. for (int i = 0; i < managerTypes.Length; ++i) { m_Managers[i] = Activator.CreateInstance(managerTypes[i]) as Manager; m_Managers[i].Initialize(this); var name = InspectorUtility.SplitCamelCase(managerTypes[i].Name); if (managers[i].GetCustomAttributes(typeof(OrderedEditorItem), true).Length > 0) { var item = managerTypes[i].GetCustomAttributes(typeof(OrderedEditorItem), true)[0] as OrderedEditorItem; name = item.Name; } m_ManagerNames[i] = name; } SerializeManagers(); } /// /// Draws the manager UI. /// private void OnManagerGUI() { GUILayout.BeginArea(new Rect(c_MenuWidth + 2, 0, position.width - c_MenuWidth, position.height)); GUILayout.Space(4); GUILayout.Label(m_ManagerNames[m_MenuSelection], ManagerTitle); GUILayout.Space(2); m_Managers[m_MenuSelection].OnGUI(); GUILayout.EndArea(); } /// /// Opens the specified manager. /// /// The type of manager to open. public void Open(Type managerType) { for (int i = 0; i < m_Managers.Length; ++i) { if (m_Managers[i].GetType() == managerType) { m_MenuSelection = i; break; } } } /// /// Serializes the data for each manager. /// private void SerializeManagers() { m_ManagerData = new Serialization[m_Managers.Length]; for (int i = 0; i < m_Managers.Length; ++i) { var serializedValue = new Serialization(); serializedValue.Serialize(m_Managers[i], true, MemberVisibility.Public); m_ManagerData[i] = serializedValue; } } /// /// Deserializes the data for each manager. /// private void DeserializeManagers() { if (m_ManagerData != null) { m_Managers = new Manager[m_ManagerData.Length]; for (int i = 0; i < m_ManagerData.Length; ++i) { m_Managers[i] = m_ManagerData[i].DeserializeFields(MemberVisibility.Public) as Manager; // The object will be null if the class doesn't exist anymore. if (m_Managers[i] == null) { continue; } m_Managers[i].Initialize(this); } } } } /// /// Attribute which specifies the name and ordering of the editor items. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class OrderedEditorItem : Attribute { private string m_Name; private int m_Index; public string Name { get { return m_Name; } } public int Index { get { return m_Index; } } public OrderedEditorItem(string name, int index) { m_Name = name; m_Index = index; } } }