using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEditor.SceneManagement; // Sci-Fi Ship Controller. Copyright (c) 2018-2022 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { [CustomEditor(typeof(ShipDockingStation))] public class ShipDockingStationEditor : Editor { #region Enumerations #endregion #region Custom Editor private variables private ShipDockingStation shipDockingStation; private SSCManager sscManager; // Formatting and style variables private string txtColourName = "Black"; private Color defaultTextColour = Color.black; private string labelText; private GUIStyle labelFieldRichText; private GUIStyle helpBoxRichText; private GUIStyle buttonCompact; private GUIStyle foldoutStyleNoLabel; private static GUIStyle toggleCompactButtonStyleNormal = null; // Small Toggle button. e.g. G(izmo) on/off private static GUIStyle toggleCompactButtonStyleToggled = null; private float defaultEditorLabelWidth = 0f; private float defaultEditorFieldWidth = 0f; // Similar to isSceneDirtyRequired (SceneView variabled) but used for Inspector modifications. private bool isSceneModified = false; private int dockingPointMoveDownPos = -1; private int dockingPointInsertPos = -1; private int dockingPointDeletePos = -1; #endregion #region SceneView Variables private bool isSceneDirtyRequired = false; private Quaternion sceneViewTrfmRotation = Quaternion.identity; private ShipDockingPoint dockingPointComponent = null; private Vector3 componentHandlePosition = Vector3.zero; //private Vector3 gizmoPosition = Vector3.zero; private Quaternion componentHandleRotation = Quaternion.identity; private float relativeHandleSize = 1f; private Color fadedGizmoColour; #endregion #region GUIContent General private readonly static GUIContent headerContent = new GUIContent("This module enables you to dock other ships with this gameobject or Ship"); private readonly static GUIContent[] tabTexts = { new GUIContent("General"), new GUIContent("Events") }; private readonly static GUIContent initialiseOnAwakeContent = new GUIContent("Initialise on Awake", "If enabled, Initialise() will be called as soon as Awake() runs. " + "This should be disabled if you are instantiating the Ship or ShipDockingStation through code and using the Docking API methods."); private readonly static GUIContent gizmoToggleBtnContent = new GUIContent("G", "Toggle gizmos and visualisations on/off for all items in the scene view"); private readonly static GUIContent gizmoBtnContent = new GUIContent("G", "Toggle gizmos on/off in the scene view"); private readonly static GUIContent gizmoFindBtnContent = new GUIContent("F", "Find (select) in the scene view."); private readonly static GUIContent resetBtnContent = new GUIContent("R", "Reset"); private readonly static GUIContent dockingPointExportJsonContent = new GUIContent("Export Json", "Export Docking Points to a json file"); private readonly static GUIContent dockingPointImportJsonContent = new GUIContent("Import Json", "Import Docking Points from a json file"); #endregion #region GUIContent Events private readonly static GUIContent onPreUndockContent = new GUIContent("", "Methods that get called immediately before Undock or UndockDelayed are executed. WARNING: Do NOT call UndockShip from this event."); private readonly static GUIContent onPostDockedContent = new GUIContent("", "Methods that get called immediately after docking is complete. WARNING: Do NOT call DockShip from this event."); #endregion #region GUIContent Ship Docking Point private readonly static GUIContent dkgPtRelativePositionContent = new GUIContent("Relative Position", "Local position relative to the ShipDockingStation tranform position"); private readonly static GUIContent dkgPtRelativeRotationContent = new GUIContent("Relative Rotation", "Docking Point rotation relative, in degrees, to the ShipDockingStation rotation"); private readonly static GUIContent dkgPtEntryPathContent = new GUIContent("Docking Entry Path", "The optional Path (stored as a guidHash) which identifies the entry path a ship can take to dock at this docking point"); private readonly static GUIContent dkgPtExitPathContent = new GUIContent("Undocking Exit Path", "The optional Path (stored as a guidHash) which identifies the exit path a ship can take to depart from this docking point"); private readonly static GUIContent dkgPtHoverHeightContent = new GUIContent("Hover Height", "This is the optimum height above the docking point in the relative up direction, a ship hovers before arriving or departing"); private readonly static GUIContent dkgPtDockedShipContent = new GUIContent("Assigned Ship", "The Ship in the scene that is currently docked, docking or undocking with the Ship Docking Point. This assigned ship requires a Ship Docking component."); #endregion #region Serialized Properties - General private SerializedProperty selectedTabIntProp; private SerializedProperty dkgPtListProp; private SerializedProperty dkgPtProp; private SerializedProperty dkgPtShowInEditorProp; private SerializedProperty dkgPtEntryPathGUIDHashProp; private SerializedProperty dkgPtExitPathGUIDHashProp; private SerializedProperty dkgPtDockedShipProp; private SerializedProperty dkgPtSelectedInSceneViewProp; private SerializedProperty dkgPtShowGizmosInSceneViewProp; private SerializedProperty isDockingPointListExpandedProp; private SerializedProperty initialiseOnAwakeProp; #endregion #region Serialized Properties - Events private SerializedProperty onPreUndockProp; private SerializedProperty onPostDockedProp; #endregion #region Events public void OnEnable() { shipDockingStation = (ShipDockingStation)target; // Only use if require scene view interaction #if UNITY_2019_1_OR_NEWER SceneView.duringSceneGui -= SceneGUI; SceneView.duringSceneGui += SceneGUI; #else SceneView.onSceneGUIDelegate -= SceneGUI; SceneView.onSceneGUIDelegate += SceneGUI; #endif // Used in Richtext labels if (EditorGUIUtility.isProSkin) { txtColourName = "White"; defaultTextColour = new Color(180f / 255f, 180f / 255f, 180f / 255f, 1f); } // Keep compiler happy - can remove this later if it isn't required if (defaultTextColour.a > 0f) { } defaultEditorLabelWidth = 150f; // EditorGUIUtility.labelWidth; defaultEditorFieldWidth = EditorGUIUtility.fieldWidth; #region Find Properties - General selectedTabIntProp = serializedObject.FindProperty("selectedTabInt"); initialiseOnAwakeProp = serializedObject.FindProperty("initialiseOnAwake"); #endregion #region Find Properties - Events onPreUndockProp = serializedObject.FindProperty("onPreUndock"); onPostDockedProp = serializedObject.FindProperty("onPostDocked"); #endregion // Reset GUIStyles toggleCompactButtonStyleNormal = null; toggleCompactButtonStyleToggled = null; foldoutStyleNoLabel = null; if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); } if (shipDockingStation != null && shipDockingStation.shipDockingPointList != null) { // If a docking point is marked as selected, turn off the transform tools if (shipDockingStation.shipDockingPointList.Exists(dp => dp.selectedInSceneView == true)) { Tools.hidden = true; } } } /// /// Called when the gameobject loses focus or Unity Editor enters/exits /// play mode /// void OnDestroy() { #if UNITY_2019_1_OR_NEWER SceneView.duringSceneGui -= SceneGUI; #else SceneView.onSceneGUIDelegate -= SceneGUI; #endif // Always unhide Unity tools when losing focus on this gameObject Tools.hidden = false; } /// /// Gets called automatically 10 times per second /// Comment out if not required /// void OnInspectorUpdate() { // OnInspectorGUI() only registers events when the mouse is positioned over the custom editor window // This code forces OnInspectorGUI() to run every frame, so it registers events even when the mouse // is positioned over the scene view if (shipDockingStation.allowRepaint) { Repaint(); } } #endregion #region Private Methods // /// Draw the toolbar using the supplied array of tab text. /// /// private void DrawToolBar(GUIContent[] tabGUIContent) { int prevTab = selectedTabIntProp.intValue; // Show a toolbar to allow the user to switch between viewing different areas selectedTabIntProp.intValue = GUILayout.Toolbar(selectedTabIntProp.intValue, tabGUIContent); // When switching tabs, disable focus on previous control if (prevTab != selectedTabIntProp.intValue) { GUI.FocusControl(null); } } /// /// Draw gizmos and editable handles in the scene view /// /// private void SceneGUI(SceneView sv) { if (shipDockingStation != null && shipDockingStation.gameObject.activeInHierarchy) { isSceneDirtyRequired = false; // Get the rotation of the gameobject / transform in the scene sceneViewTrfmRotation = Quaternion.LookRotation(shipDockingStation.transform.forward, shipDockingStation.transform.up); Vector3 localScale = shipDockingStation.transform.localScale; using (new Handles.DrawingScope(shipDockingStation.dockingPointGizmoColour)) { int numDockingPoints = shipDockingStation.shipDockingPointList == null ? 0 : shipDockingStation.shipDockingPointList.Count; for (int dpi = 0; dpi < numDockingPoints; dpi++) { dockingPointComponent = shipDockingStation.shipDockingPointList[dpi]; fadedGizmoColour = shipDockingStation.dockingPointGizmoColour; // If this is not the selected Docking Point, show it a little more transparent if (!dockingPointComponent.selectedInSceneView) { fadedGizmoColour.a *= 0.65f; if (fadedGizmoColour.a < 0.1f) { fadedGizmoColour.a = shipDockingStation.dockingPointGizmoColour.a; } } if (dockingPointComponent.showGizmosInSceneView) { // Get component handle position componentHandlePosition = shipDockingStation.transform.TransformPoint(new Vector3(dockingPointComponent.relativePosition.x / localScale.x, dockingPointComponent.relativePosition.y / localScale.y, dockingPointComponent.relativePosition.z / localScale.z)); relativeHandleSize = HandleUtility.GetHandleSize(componentHandlePosition); // Get component handle rotation componentHandleRotation = shipDockingStation.transform.rotation * Quaternion.Euler(dockingPointComponent.relativeRotation); // Draw point in the scene that is non-interactable if (Event.current.type == EventType.Repaint) { using (new Handles.DrawingScope(fadedGizmoColour)) { // Forwards direction of the ship docking point Handles.ArrowHandleCap(GUIUtility.GetControlID(FocusType.Passive), componentHandlePosition, componentHandleRotation, 1.25f * relativeHandleSize, EventType.Repaint); // Draw the up direction with a sphere on top of a line Quaternion upRotation = componentHandleRotation * Quaternion.Euler(270f, 0f, 0f); Handles.DrawLine(componentHandlePosition, componentHandlePosition + upRotation * (Vector3.forward * relativeHandleSize * 1.25f)); Handles.SphereHandleCap(GUIUtility.GetControlID(FocusType.Passive), componentHandlePosition + (upRotation * (Vector3.forward * relativeHandleSize * 1.25f)), upRotation, 0.3f * relativeHandleSize, EventType.Repaint); } } if (dockingPointComponent.selectedInSceneView) { // Choose which handle to draw based on which Unity tool is selected if (Tools.current == Tool.Rotate) { EditorGUI.BeginChangeCheck(); // Draw a rotation handle componentHandleRotation = Handles.RotationHandle(componentHandleRotation, componentHandlePosition); // Use the rotation handle to edit the docking point direction if (EditorGUI.EndChangeCheck()) { isSceneDirtyRequired = true; Undo.RecordObject(shipDockingStation, "Rotate Docking Point"); dockingPointComponent.relativeRotation = (Quaternion.Inverse(shipDockingStation.transform.rotation) * componentHandleRotation).eulerAngles; } } #if UNITY_2017_3_OR_NEWER else if (Tools.current == Tool.Move || Tools.current == Tool.Transform) #else else if (Tools.current == Tool.Move) #endif { EditorGUI.BeginChangeCheck(); // Draw a movement handle componentHandlePosition = Handles.PositionHandle(componentHandlePosition, sceneViewTrfmRotation); // Use the position handle to edit the position of the weapon if (EditorGUI.EndChangeCheck()) { isSceneDirtyRequired = true; Undo.RecordObject(shipDockingStation, "Move Docking Point"); //dockingPointComponent.relativePosition = shipDockingStation.transform.InverseTransformPoint(componentHandlePosition); dockingPointComponent.relativePosition = shipDockingStation.transform.InverseTransformPoint(new Vector3(componentHandlePosition.x * localScale.x, componentHandlePosition.y * localScale.y, componentHandlePosition.z * localScale.z)); } } } // If the docking point is not selected it will be faded, else it will be the original colour. using (new Handles.DrawingScope(fadedGizmoColour)) { // Allow the user to select/deselect the docking point in the scene view if (Handles.Button(componentHandlePosition, Quaternion.identity, 0.5f * relativeHandleSize, 0.25f * relativeHandleSize, Handles.SphereHandleCap)) { if (dockingPointComponent.selectedInSceneView) { DeselectAllComponents(); dockingPointComponent.showInEditor = false; } else { DeselectAllComponents(); shipDockingStation.isDockingPointListExpanded = false; ExpandList(shipDockingStation.shipDockingPointList, false); dockingPointComponent.selectedInSceneView = true; dockingPointComponent.showInEditor = true; isSceneDirtyRequired = true; // Hide Unity tools Tools.hidden = true; } } } } } } if (isSceneDirtyRequired && !Application.isPlaying) { isSceneDirtyRequired = false; EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); } } else { // Always unhide Unity tools and deselect all components when the object is disabled Tools.hidden = false; DeselectAllComponents(); } } /// /// Expand (show) or collapse (hide) all items in a list in the editor /// /// /// /// private void ExpandList(List componentList, bool isExpanded) { int numComponents = componentList == null ? 0 : componentList.Count; if (numComponents > 0) { System.Type compType = typeof(T); if (compType == typeof(ShipDockingPoint)) { for (int cpi = 0; cpi < numComponents; cpi++) { (componentList[cpi] as ShipDockingPoint).showInEditor = isExpanded; } } } } /// /// Deselect all components in the scene view edit mode, and unhides the Unity tools /// private void DeselectAllComponents() { // Set all components to not be selected if (shipDockingStation != null) { int numDockingPoints = shipDockingStation.shipDockingPointList == null ? 0 : shipDockingStation.shipDockingPointList.Count; for (int dpi = 0; dpi < numDockingPoints; dpi++) { shipDockingStation.shipDockingPointList[dpi].selectedInSceneView = false; } } // Unhide Unity tools Tools.hidden = false; } /// /// Toggle on/off the gizmos and visualisations of a list of components based /// on the state of the first component in the list. /// /// /// private void ToggleGizmos(List componentList) { int numComponents = componentList == null ? 0 : componentList.Count; if (numComponents > 0) { System.Type compType = typeof(T); if (compType == typeof(ShipDockingPoint)) { // Examine the first component bool showGizmos = !(componentList[0] as ShipDockingPoint).showGizmosInSceneView; // Toggle gizmos and visualisations to opposite of first member for (int cpi = 0; cpi < numComponents; cpi++) { (componentList[cpi] as ShipDockingPoint).showGizmosInSceneView = showGizmos; } // When no Gizmos are shown , ensure we can see the standard Unity tools. // This allows the user to move the ShipDockingStation gameobject if (!showGizmos) { Tools.hidden = false; } } SceneView.RepaintAll(); } } /// /// Draw a (F)ind button which will select the item in the scene view. /// Automatically show Gizmo in scene view if it is currently disabled /// /// /// /// /// /// private void SelectItemInSceneViewButton(List componentList, SerializedProperty showInEditorProp, SerializedProperty selectedInSceneViewProp, SerializedProperty showGizmoInSceneViewProp) { if (GUILayout.Button(gizmoFindBtnContent, buttonCompact, GUILayout.MaxWidth(20f))) { serializedObject.ApplyModifiedProperties(); DeselectAllComponents(); ExpandList(componentList, false); serializedObject.Update(); selectedInSceneViewProp.boolValue = true; showInEditorProp.boolValue = true; showGizmoInSceneViewProp.boolValue = true; // Hide Unity tools Tools.hidden = true; } } /// /// Display the path name and give option to select one from the scene (from SSCManager) /// /// /// /// /// private void DrawPathInspector(SerializedProperty propPathGUIDHash, int shipDockingPointIndex, bool isEntryPath, GUIContent guiContent) { GUILayout.BeginHorizontal(); #if UNITY_2019_3_OR_NEWER EditorGUILayout.LabelField(guiContent, GUILayout.Width(defaultEditorLabelWidth - 25f)); #else EditorGUILayout.LabelField(guiContent, GUILayout.Width(defaultEditorLabelWidth - 29f)); #endif PathData pathData = sscManager.GetPath(propPathGUIDHash.intValue); int selectedIdx = sscManager.pathDataList.FindIndex(path => path.guidHash == propPathGUIDHash.intValue); if (GUILayout.Button("..", buttonCompact, GUILayout.MaxWidth(20f))) { // Apply property changes serializedObject.ApplyModifiedProperties(); // Create a drop down list of all the paths GenericMenu dropdown = new GenericMenu(); for (int i = 0; i < sscManager.pathDataList.Count; i++) { // Replace space #/%/& with different chars as Unity treats them as SHIFT/CTRL/ALT in menus. string _pathName = string.IsNullOrEmpty(sscManager.pathDataList[i].name) ? "No Name" : sscManager.pathDataList[i].name.Replace(" #", "_#").Replace(" &", " &&").Replace(" %", "_%"); dropdown.AddItem(new GUIContent(_pathName), i == selectedIdx, UpdatePath, new Vector3Int(shipDockingPointIndex, isEntryPath ? 0 : 1, sscManager.pathDataList[i].guidHash)); } dropdown.ShowAsContext(); SceneView.RepaintAll(); serializedObject.Update(); } EditorGUILayout.LabelField(pathData != null ? (string.IsNullOrEmpty(pathData.name) ? "No Name" : pathData.name) : "No Path"); // Clear the current path setting if (GUILayout.Button("X", buttonCompact, GUILayout.MaxWidth(20f))) { propPathGUIDHash.intValue = 0; } GUILayout.EndHorizontal(); } /// /// Dropdown menu callback method used when a entry or exit path is selected /// /// private void UpdatePath(object obj) { // The menu data is passed as 3 integers in a Vector3Int // The index of the docking point in the list // The selected Path guidHash // which is being assigned to the location slot on the path. if (obj != null && obj.GetType() == typeof(Vector3Int)) { Vector3Int objData = (Vector3Int)obj; bool isEntryPath = objData.y == 0; if (shipDockingStation != null && shipDockingStation.shipDockingPointList != null) { Undo.RecordObject(shipDockingStation, "Set " + (isEntryPath ? "Entry" : "Exit") + " Path "); if (isEntryPath) { shipDockingStation.shipDockingPointList[objData.x].guidHashEntryPath = objData.z; } else { shipDockingStation.shipDockingPointList[objData.x].guidHashExitPath = objData.z; } } } } #endregion #region OnInspectorGUI // This function overides what is normally seen in the inspector window // This allows stuff like buttons to be drawn there public override void OnInspectorGUI() { #region Initialise shipDockingStation.allowRepaint = false; isSceneModified = false; EditorGUIUtility.labelWidth = defaultEditorLabelWidth; EditorGUIUtility.fieldWidth = defaultEditorFieldWidth; #endregion #region Configure Buttons and Styles // Set up rich text GUIStyles helpBoxRichText = new GUIStyle("HelpBox"); helpBoxRichText.richText = true; labelFieldRichText = new GUIStyle("Label"); labelFieldRichText.richText = true; buttonCompact = new GUIStyle("Button"); buttonCompact.fontSize = 10; // Set up the toggle buttons styles if (toggleCompactButtonStyleNormal == null) { // Create a new button or else will effect the Button style for other buttons too toggleCompactButtonStyleNormal = new GUIStyle("Button"); toggleCompactButtonStyleNormal.fontSize = 10; toggleCompactButtonStyleToggled = new GUIStyle(toggleCompactButtonStyleNormal); toggleCompactButtonStyleNormal.fontStyle = FontStyle.Normal; toggleCompactButtonStyleToggled.fontStyle = FontStyle.Bold; toggleCompactButtonStyleToggled.normal.background = toggleCompactButtonStyleToggled.active.background; } if (foldoutStyleNoLabel == null) { // When using a no-label foldout, don't forget to set the global // EditorGUIUtility.fieldWidth to a small value like 15, then back // to the original afterward. foldoutStyleNoLabel = new GUIStyle(EditorStyles.foldout); foldoutStyleNoLabel.fixedWidth = 0.01f; } #endregion // Read in all the properties serializedObject.Update(); #region Header Info and Buttons SSCEditorHelper.SSCVersionHeader(labelFieldRichText); EditorGUILayout.LabelField(headerContent, helpBoxRichText); DrawToolBar(tabTexts); #endregion #region General Properties if (selectedTabIntProp.intValue == 0) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.PropertyField(initialiseOnAwakeProp, initialiseOnAwakeContent); EditorGUILayout.EndVertical(); #region Import and Export Docking Points as json GUILayout.BeginHorizontal(); if (GUILayout.Button(dockingPointExportJsonContent, buttonCompact, GUILayout.Width(100f))) { string exportPath = EditorUtility.SaveFilePanel("Save Docking Points", "Assets", shipDockingStation.name + "_docking_points", "json"); if (shipDockingStation.SaveDockingPointDataAsJson(exportPath)) { // Check if path is in Project folder if (exportPath.Contains(Application.dataPath)) { // Get the folder to highlight in the Project folder string folderPath = SSCEditorHelper.GetAssetFolderFromFilePath(exportPath); // Get the json file in the Project folder exportPath = "Assets" + exportPath.Replace(Application.dataPath, ""); AssetDatabase.ImportAsset(exportPath); SSCEditorHelper.HighlightFolderInProjectWindow(folderPath, true, true); } Debug.Log("ShipDockingStation docking points exported to " + exportPath); } GUIUtility.ExitGUI(); } if (GUILayout.Button(dockingPointImportJsonContent, buttonCompact, GUILayout.Width(100f))) { string importPath = string.Empty, importFileName = string.Empty; if (SSCEditorHelper.GetFilePathFromUser("Import Docking Point Data", SSCSetup.sscFolder, new string[] { "JSON", "json" }, false, ref importPath, ref importFileName)) { List importedDockingPointList = shipDockingStation.ImportDockingPointDataFromJson(importPath, importFileName); int numImportedDockingPoints = importedDockingPointList == null ? 0 : importedDockingPointList.Count; if (numImportedDockingPoints > 0) { serializedObject.ApplyModifiedProperties(); Undo.RecordObject(shipDockingStation, "Import Docking Points"); if (shipDockingStation.shipDockingPointList != null) { shipDockingStation.shipDockingPointList.Clear(); } shipDockingStation.shipDockingPointList.AddRange(importedDockingPointList); serializedObject.Update(); } } GUIUtility.ExitGUI(); } GUILayout.EndHorizontal(); #endregion #region Docking Points // Checking the property for being NULL doesn't check if the list is actually null. if (shipDockingStation.shipDockingPointList == null) { // Apply property changes serializedObject.ApplyModifiedProperties(); shipDockingStation.shipDockingPointList = new List(10); isSceneModified = true; // Read in the properties serializedObject.Update(); } dkgPtListProp = serializedObject.FindProperty("shipDockingPointList"); int numDockingPoints = dkgPtListProp.arraySize; #region Add-Remove Docking Points and Gizmos Buttons // Reset button variables dockingPointMoveDownPos = -1; dockingPointInsertPos = -1; dockingPointDeletePos = -1; GUILayout.BeginHorizontal(); EditorGUI.indentLevel += 1; EditorGUIUtility.fieldWidth = 15f; isDockingPointListExpandedProp = serializedObject.FindProperty("isDockingPointListExpanded"); EditorGUI.BeginChangeCheck(); isDockingPointListExpandedProp.boolValue = EditorGUILayout.Foldout(isDockingPointListExpandedProp.boolValue, "", foldoutStyleNoLabel); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); ExpandList(shipDockingStation.shipDockingPointList, isDockingPointListExpandedProp.boolValue); // Read in the properties serializedObject.Update(); } EditorGUI.indentLevel -= 1; EditorGUIUtility.fieldWidth = defaultEditorFieldWidth; EditorGUILayout.LabelField("Ship Docking Points: " + numDockingPoints.ToString("00") + "", labelFieldRichText); if (numDockingPoints > 0) { if (GUILayout.Button(gizmoToggleBtnContent, GUILayout.MaxWidth(22f))) { serializedObject.ApplyModifiedProperties(); ToggleGizmos(shipDockingStation.shipDockingPointList); isSceneModified = true; serializedObject.Update(); } } if (GUILayout.Button("+", GUILayout.MaxWidth(30f))) { // Apply property changes serializedObject.ApplyModifiedProperties(); Undo.RecordObject(shipDockingStation, "Add Docking Point"); shipDockingStation.shipDockingPointList.Add(new ShipDockingPoint()); DeselectAllComponents(); ExpandList(shipDockingStation.shipDockingPointList, false); isSceneModified = true; // Read in the properties serializedObject.Update(); numDockingPoints = dkgPtListProp.arraySize; if (numDockingPoints > 0) { // Force new docking point to be serialized in scene dkgPtProp = dkgPtListProp.GetArrayElementAtIndex(numDockingPoints - 1); dkgPtShowInEditorProp = dkgPtProp.FindPropertyRelative("showInEditor"); dkgPtShowInEditorProp.boolValue = !dkgPtShowInEditorProp.boolValue; // Show the new docking point and select it in the scnee dkgPtShowInEditorProp.boolValue = true; dkgPtProp.FindPropertyRelative("showGizmosInSceneView").boolValue = true; dkgPtProp.FindPropertyRelative("selectedInSceneView").boolValue = true; Tools.hidden = true; } } if (GUILayout.Button("-", GUILayout.MaxWidth(30f))) { if (numDockingPoints > 0) { dockingPointDeletePos = dkgPtListProp.arraySize - 1; } } GUILayout.EndHorizontal(); #endregion #region Ship Docking Point List numDockingPoints = dkgPtListProp.arraySize; for (int dpIdx = 0; dpIdx < numDockingPoints; dpIdx++) { GUILayout.BeginVertical(EditorStyles.helpBox); dkgPtProp = dkgPtListProp.GetArrayElementAtIndex(dpIdx); dkgPtShowInEditorProp = dkgPtProp.FindPropertyRelative("showInEditor"); dkgPtShowGizmosInSceneViewProp = dkgPtProp.FindPropertyRelative("showGizmosInSceneView"); dkgPtSelectedInSceneViewProp = dkgPtProp.FindPropertyRelative("selectedInSceneView"); dkgPtEntryPathGUIDHashProp = dkgPtProp.FindPropertyRelative("guidHashEntryPath"); dkgPtExitPathGUIDHashProp = dkgPtProp.FindPropertyRelative("guidHashExitPath"); dkgPtDockedShipProp = dkgPtProp.FindPropertyRelative("dockedShip"); #region Docking Point Find/Move/Insert/Delete buttons GUILayout.BeginHorizontal(); EditorGUI.indentLevel += 1; dkgPtShowInEditorProp.boolValue = EditorGUILayout.Foldout(dkgPtShowInEditorProp.boolValue, "Ship Docking Point " + (dpIdx + 1).ToString("00")); EditorGUI.indentLevel -= 1; // Find (select) in the scene SelectItemInSceneViewButton(shipDockingStation.shipDockingPointList, dkgPtShowInEditorProp, dkgPtSelectedInSceneViewProp, dkgPtShowGizmosInSceneViewProp); // Show Gizmos button if (dkgPtShowGizmosInSceneViewProp.boolValue) { // Turn gizmos off if (GUILayout.Button(gizmoBtnContent, toggleCompactButtonStyleToggled, GUILayout.MaxWidth(22f))) { dkgPtShowGizmosInSceneViewProp.boolValue = false; // If it was selected, unselect it when turning gizmos off in the scene if (dkgPtSelectedInSceneViewProp.boolValue) { dkgPtSelectedInSceneViewProp.boolValue = false; Tools.hidden = false; } } } else { if (GUILayout.Button(gizmoBtnContent, toggleCompactButtonStyleNormal, GUILayout.MaxWidth(22f))) { dkgPtShowGizmosInSceneViewProp.boolValue = true; } } // Move down button if (GUILayout.Button("V", buttonCompact, GUILayout.MaxWidth(20f)) && numDockingPoints > 1) { dockingPointMoveDownPos = dpIdx; } // Create duplicate button if (GUILayout.Button("I", buttonCompact, GUILayout.MaxWidth(20f))) { dockingPointInsertPos = dpIdx; } // Delete button if (GUILayout.Button("X", buttonCompact, GUILayout.MaxWidth(20f))) { dockingPointDeletePos = dpIdx; } GUILayout.EndHorizontal(); #endregion if (dkgPtShowInEditorProp.boolValue) { EditorGUILayout.PropertyField(dkgPtProp.FindPropertyRelative("relativePosition"), dkgPtRelativePositionContent); GUILayout.BeginHorizontal(); #if UNITY_2019_3_OR_NEWER EditorGUILayout.LabelField(dkgPtRelativeRotationContent, GUILayout.Width(defaultEditorLabelWidth - 25f)); #else EditorGUILayout.LabelField(dkgPtRelativeRotationContent, GUILayout.Width(defaultEditorLabelWidth - 29f)); #endif if (GUILayout.Button(resetBtnContent, buttonCompact, GUILayout.MaxWidth(20f))) { dkgPtProp.FindPropertyRelative("relativeRotation").vector3Value = Vector3.zero; } EditorGUILayout.PropertyField(dkgPtProp.FindPropertyRelative("relativeRotation"), GUIContent.none); GUILayout.EndHorizontal(); DrawPathInspector(dkgPtEntryPathGUIDHashProp, dpIdx, true, dkgPtEntryPathContent); DrawPathInspector(dkgPtExitPathGUIDHashProp, dpIdx, false, dkgPtExitPathContent); EditorGUILayout.PropertyField(dkgPtProp.FindPropertyRelative("hoverHeight"), dkgPtHoverHeightContent); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(dkgPtDockedShipProp, dkgPtDockedShipContent); if (EditorGUI.EndChangeCheck() && dkgPtDockedShipProp.objectReferenceValue != null && EditorUtility.IsPersistent(dkgPtDockedShipProp.objectReferenceValue)) { Debug.LogWarning("ShipDockingStation - only ships in the scene can be assigned a docking point"); dkgPtDockedShipProp.objectReferenceValue = null; } } // There is a bug here in 2019.4 and 2020.1 where the DisplayDialog causes formatting issues. // EndLayoutGroup: BeginLayoutGroup must be called first. // No 2019_4 define but earlier versions still seem to work. //#if UNITY_2019_3_OR_NEWER //if (dockingPointDeletePos >=0 ) { GUILayout.BeginVertical(); } //#endif GUILayout.EndVertical(); } #endregion #region Move/Insert/Delete Docking Points if (dockingPointDeletePos >= 0 || dockingPointInsertPos >= 0 || dockingPointMoveDownPos >= 0) { GUI.FocusControl(null); // Don't permit multiple operations in the same pass if (dockingPointMoveDownPos >= 0) { // Move down one position, or wrap round to start of list if (dockingPointMoveDownPos < dkgPtListProp.arraySize - 1) { dkgPtListProp.MoveArrayElement(dockingPointMoveDownPos, dockingPointMoveDownPos + 1); } else { dkgPtListProp.MoveArrayElement(dockingPointMoveDownPos, 0); } dockingPointMoveDownPos = -1; } else if (dockingPointInsertPos >= 0) { // NOTE: Undo doesn't work with Insert. // Apply property changes before potential list changes serializedObject.ApplyModifiedProperties(); shipDockingStation.shipDockingPointList.Insert(dockingPointInsertPos, new ShipDockingPoint(shipDockingStation.shipDockingPointList[dockingPointInsertPos])); // Read all properties from the ShipDockingStation serializedObject.Update(); // Hide original dockingPoint dkgPtListProp.GetArrayElementAtIndex(dockingPointInsertPos + 1).FindPropertyRelative("showInEditor").boolValue = false; dkgPtShowInEditorProp = dkgPtListProp.GetArrayElementAtIndex(dockingPointInsertPos).FindPropertyRelative("showInEditor"); // Force new dockingPoint to be serialized in scene dkgPtShowInEditorProp.boolValue = !dkgPtShowInEditorProp.boolValue; // Show inserted duplicate dockingPoint dkgPtShowInEditorProp.boolValue = true; // Ensure we can see the inserted docking point in the scene view dkgPtListProp.GetArrayElementAtIndex(dockingPointInsertPos).FindPropertyRelative("showGizmosInSceneView").boolValue = true; // Select the inserted docking point serializedObject.ApplyModifiedProperties(); DeselectAllComponents(); shipDockingStation.shipDockingPointList[dockingPointInsertPos].selectedInSceneView = true; Tools.hidden = true; serializedObject.Update(); dockingPointInsertPos = -1; isSceneModified = true; } else if (dockingPointDeletePos >= 0) { // In U2019.4+ DisplayDialog seems to trigger another OnInspectorGUI() and DeletePos is reset to -1. int _deleteIndex = dockingPointDeletePos; if (EditorUtility.DisplayDialog("Delete Ship Docking Point " + (dockingPointDeletePos + 1) + "?", "Docking Point " + (dockingPointDeletePos + 1).ToString("00") + " will be deleted\n\nThis action will remove the docking point from the list and cannot be undone.", "Delete Now", "Cancel")) { // If this docking point was selected, turn tools back on if (dkgPtListProp.GetArrayElementAtIndex(_deleteIndex).FindPropertyRelative("selectedInSceneView").boolValue) { Tools.hidden = false; } dkgPtListProp.DeleteArrayElementAtIndex(_deleteIndex); dockingPointDeletePos = -1; } } #if UNITY_2019_3_OR_NEWER serializedObject.ApplyModifiedProperties(); // In U2019.4+ avoid: EndLayoutGroup: BeginLayoutGroup must be called first. if (!Application.isPlaying) { EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); } GUIUtility.ExitGUI(); #endif } #endregion #endregion } #endregion #region Events else { // Label workaroud for content or tooltip not working with UnityEvents property drawer EditorGUILayout.LabelField(onPreUndockContent, GUILayout.MaxHeight(5f)); EditorGUILayout.PropertyField(onPreUndockProp); EditorGUILayout.LabelField(onPostDockedContent, GUILayout.MaxHeight(5f)); EditorGUILayout.PropertyField(onPostDockedProp); } #endregion // Apply property changes serializedObject.ApplyModifiedProperties(); #region Mark Scene Dirty if required if (isSceneModified && !Application.isPlaying) { isSceneModified = false; EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); } #endregion shipDockingStation.allowRepaint = true; } #endregion } }