rabidus-test/Assets/SCSM/SciFiShipController/Editor/SSCManagerEditor.cs

2885 lines
163 KiB
C#

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.SceneManagement;
// Sci-Fi Ship Controller. Copyright (c) 2018-2022 SCSM Pty Ltd. All rights reserved.
namespace SciFiShipController
{
[CustomEditor(typeof(SSCManager))]
public class SSCManagerEditor : Editor
{
#region Custom Editor private variables
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 buttonCompactBoldBlue;
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;
private int selectedTabInt = 0;
private GUIStyle searchTextFieldStyle = null;
private GUIStyle searchCancelButtonStyle = null;
private float defaultSearchTextFieldWidth = 200f;
private bool isDebuggingEnabled = false;
private List<ProjectileTemplate> projectileTemplatesList;
private List<BeamTemplate> beamTemplateList;
private List<DestructTemplate> destructTemplateList;
private List<EffectsObjectTemplate> effectsObjectTemplatesList;
// location data variables
private int numLocations = 0;
private int numFilteredLocations = 0;
private int locationDataMoveDownPos = -1;
private int locationDataInsertPos = -1;
private int locationDataDeletePos = -1;
private Vector2 locationScrollPosition = Vector2.zero;
private bool isModifySelectedMode = false;
private float modifySelectedPosY = 0f;
private float modifySelectedAddPosY = 0f;
// path data variables
private int numPaths = 0;
private int numFilteredPaths = 0;
private int pathDataMoveDownPos = -1;
private int pathDataInsertPos = -1;
private int pathDataDeletePos = -1;
private int numPathLocations = 0;
private int pathDataLocationMoveDownPos = -1;
private int pathDataLocationInsertPos = -1;
private int pathDataLocationDeletePos = -1;
private bool pathLocationDataIsSelected = false; // Temp variable
private string sscHelpPDF;
#endregion
#region Custom Editor Sceneview variables
private bool isSceneDirtyRequired = false;
private bool isSetFocusEnabled = false; // has the manager just been enabled and we need to set focus to the sceneview?
private LocationData locationDataItem;
private Vector3 itemHandlePosition = Vector3.zero;
//private Vector3 gizmoPosition = Vector3.zero;
private Quaternion itemHandleRotation = Quaternion.identity;
private Vector3 itemHandleScale = Vector3.one;
private Vector3 controlHandleSnap = Vector3.one * 0.3f;
private Vector3 newControlPoint;
private Color fadedGizmoColour;
private float startFadeDistSqr = 4000000f; // 2km x 2km
private float maxFadeDistSqr = 400000000f;
// Draw Path variables
private GUIStyle distanceLabel;
private Vector3 pointLabelOffset = Vector3.up * 2f;
private Vector3 locationPosition;
private string locationName;
#endregion
#region GUIContent - General
private GUIContent[] tabTexts = { new GUIContent("Locations"), new GUIContent("Paths"), new GUIContent("Options") };
private readonly static GUIContent headerContent = new GUIContent("The Manager is automatically added to " +
"the scene if it doesn't already exist at runtime. It includes projectile and effects object management for all ships along with Location and Path management.");
private readonly static GUIContent debugModeContent = new GUIContent("Debug Mode", "Use this to display pool sizes at runtime in the editor. This can help optimise the min/max pool size values.");
private readonly static GUIContent projectileHeaderContent = new GUIContent("<b>Projectile Templates</b>", "The Projectile Templates that use pooling or DOTS");
private readonly static GUIContent projectilePoolingContent = new GUIContent(" Current Pool Size", "The current size of the projectile pool");
#if SSC_ENTITIES
private readonly static GUIContent projectileDOTSContent = new GUIContent(" Use DOTS is enabled", "The projectiles will be converted to Entities");
#else
private readonly static GUIContent projectileDOTSNotAvailableContent = new GUIContent(" Use DOTS is enabled but DOTS is not configured", "The projectile cannot use DOTS because it is not available in the project");
#endif
private readonly static GUIContent minmaxPoolSizeContent = new GUIContent("Min/Max", "The minimum and maximum size of the pool");
private readonly static GUIContent beamHeaderContent = new GUIContent("<b>Beam Templates</b>", "The Beam Templates that use pooling");
private readonly static GUIContent beamPoolingContent = new GUIContent(" Current Pool Size", "The current size of the beam pool");
private readonly static GUIContent destructHeaderContent = new GUIContent("<b>Destruct Templates</b>", "The Destruct Templates that use pooling");
private readonly static GUIContent destructPoolingContent = new GUIContent(" Current Pool Size", "The current size of the destruct pool");
private readonly static GUIContent effectsHeaderContent = new GUIContent("<b>Effects Templates</b>", "The Effects Object Templates that use pooling");
private readonly static GUIContent effectsPoolingContent = new GUIContent(" Current Pool Size", "The current size of the effects pool");
private readonly static GUIContent noneWithPoolingContent = new GUIContent(" (None with <i>Use Pooling</i> enabled)");
private readonly static GUIContent findBtnContent = new GUIContent("F", "Find in the scene view");
private readonly static GUIContent gizmoBtnContent = new GUIContent("G", "Toggle gizmos on/off in the scene view");
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 selectBtnContent = new GUIContent("S", "Toggle selection on/off in the scene view");
private readonly static GUIContent modifyToggleBtnContent = new GUIContent("M", "Modify an attribute of all selected Locations");
private readonly static GUIContent reversePathBtnContent = new GUIContent("<->", "Reverse the direction of the path");
private readonly static GUIContent filterContent = new GUIContent("<b>Filter</b>", "Filter the items in the list by entering part of the item name");
private readonly static GUIContent modifySelectedPosYContent = new GUIContent(" New Position Y-axis", "The new Position Y value for all selected Locations in this Path");
private readonly static GUIContent modifySelectedAddPosYContent = new GUIContent(" Add Position Y-axis", "Add (or subtract) amount to Position Y value for all selected Locations in this Path");
#endregion
#region GUIContent - Locations
private readonly static GUIContent locationHeaderContent = new GUIContent("To create a Location, move the mouse over scene view and press the + key. " +
"The scene window needs to have the focus. Hold SHIFT key to multi-select Locations.");
private readonly static GUIContent locationDefaultNormalOffsetContent = new GUIContent("Default Normal Offset", "The distance to place a new Location away from the line of sight object");
private readonly static GUIContent locationDataNameContent = new GUIContent("Location Name", "Name of the Location. E.g. Enemy Base, Route Marker 2, Command Post");
private readonly static GUIContent locationDataPositionContent = new GUIContent("Location Position", "World-space coordinates of the Location");
private readonly static GUIContent locationDataIsRadarEnabledContent = new GUIContent("Visible to Radar", "Is this Location visible to the radar system?");
private readonly static GUIContent locationDataRadarBlipSizedContent = new GUIContent("Radar Blip Size", "The relative size of the blip on the radar mini-map.");
private readonly static GUIContent locationDataFactionIdContent = new GUIContent("Faction Id", "The faction or alliance the Location belongs to. This can be used to identify if a Location is friend or foe. Neutral = 0.");
#endregion
#region GUIContent - Paths
private readonly static GUIContent pathDataHeaderContent = new GUIContent("A Path is an ordered list of Locations. Add a new Path, select the Scene view, move the mouse over the " +
" Scene, and press the + key where you want to add Locations. OR Add a Location slot in the Inspector before assigning an existing Location to that slot. " +
" Deleting a Location slot does not delete the Location. There is also a context menu in the scene view. Hold SHIFT key to move all selected Locations in a Path.");
private readonly static GUIContent pathDataNameContent = new GUIContent("Path Name", "Name of the path.");
private readonly static GUIContent pathDataActiveContent = new GUIContent("A", "Make this the Path active for editing in the scene");
private readonly static GUIContent pathDataActivateContent = new GUIContent("Activate for editing", "Make this the Path active for editing in the scene");
private readonly static GUIContent pathDataActivatedContent = new GUIContent("Stop Editing", "Stop editing this Path in the scene");
private readonly static GUIContent pathDataExportJsonContent = new GUIContent("Export Json", "Export the Path to a Json file");
private readonly static GUIContent pathDataSnapToMeshContent = new GUIContent("Snap To Mesh", "Will attempt to position all locations on the Path above the top mesh in the scene.");
private readonly static GUIContent pathDataImportJsonContent = new GUIContent("Import", "Import Path data from a Json file (should be from the same version of Sci-Fi Ship Controller)");
private readonly static GUIContent pathDataTotalDistanceContent = new GUIContent("Total Distance", "The total spline length or distance of the Path");
private readonly static GUIContent pathDataIsClosedCircuitContent = new GUIContent("Closed Circuit", "Is this Path a closed circuit or loop?");
private readonly static GUIContent pathDataLineColourContent = new GUIContent("Line Colour", "The colour of the Path drawn in the scene.");
private readonly static GUIContent pathDataLocationDefaultNormalOffsetContent = new GUIContent("Default Normal Offset", "The distance to place a new Location on the Path away from the line of sight object");
private readonly static GUIContent pathDataLocationSnapMinMaxHeightContent = new GUIContent("Snap Min/Max Height", "The minimum and maximum heights checked when Snap To Mesh is used.");
private readonly static GUIContent pathDataShowPointNumberLabelsInSceneContent = new GUIContent("Display Number Labels", "Display the Location number labels in the scene view");
private readonly static GUIContent pathDataShowPointNameLabelsInSceneContent = new GUIContent("Display Name Labels", "Display the Location name labels in the scene view");
private readonly static GUIContent pathDataShowDistanceLabelsInSceneContent = new GUIContent("Display Distances", "Display cumulative distances in the scene view");
#endregion
#region GUIContent - Options
private readonly static GUIContent optionLocationContent = new GUIContent("<b>Location Options</b>");
private readonly static GUIContent optionIsAutosizeLocationGizmoContent = new GUIContent("Autosize Gizmos", "Autosize Location gizmos in scene view so they are visible a long way away.");
private readonly static GUIContent optionLocationGizmoColourContent = new GUIContent("Gizmo Colour", "Colour of the Location gizmos in the scene view");
private readonly static GUIContent optionFindZoomDistanceContent = new GUIContent("Zoom Distance", "The default zoom distance when using the (F)ind buttons in the inspector, or Zoom content menu in the scene");
private readonly static GUIContent optionPathContent = new GUIContent("<b>Path Options</b>");
private readonly static GUIContent optionPathControlGizmoColourContent = new GUIContent("Control Gizmo Colour", "Colour of the Path tangent control gizmos in the scene view");
private readonly static GUIContent optionPathDisplayResolutionContent = new GUIContent("Display Resolution", "The appromiate length of each Path segment drawn in the scene view");
private readonly static GUIContent optionPathPrecisionContent = new GUIContent("Path Precision", "Determines how precisely the Path distance should be calculated. Keep as low as possible when editing in the scene.");
private readonly static GUIContent optionPathControlOffsetContent = new GUIContent("Default Control Offset", "Default control point offset distance from the Path Location");
//private readonly static GUIContent optionRadarContent = new GUIContent("<b>Radar Options</b>");
#endregion
#region Serialized Properties - Locations
private SerializedProperty locationDataListProp;
private SerializedProperty locationDefaultNormalOffsetProp;
private SerializedProperty locationDataProp;
private SerializedProperty locationDataNameProp;
private SerializedProperty locationDataPositionProp;
private SerializedProperty locationDataSelectedProp;
private SerializedProperty locationDataGUIDHashProp;
private SerializedProperty isLocationDataListExpandedProp;
private SerializedProperty locationDataShowInEditorProp;
private SerializedProperty locationDataShowGizmosInSceneViewProp;
#endregion
#region Serialized Properties - Paths
private SerializedProperty pathDataListProp;
private SerializedProperty pathDataActiveGUIDHashProp;
private SerializedProperty pathDataProp;
private SerializedProperty pathDataNameProp;
private SerializedProperty pathDataGUIDHashProp;
private SerializedProperty isPathDataListExpandedProp;
private SerializedProperty pathDataShowInEditorProp;
private SerializedProperty pathDataShowLocationsInEditorProp;
private SerializedProperty pathDataShowInSceneViewProp;
private SerializedProperty pathDataIsClosedCircuitProp;
private SerializedProperty pathDataLineColourProp;
private SerializedProperty pathDataShowNumberLabelsProp;
private SerializedProperty pathDataShowNameLabelsProp;
private SerializedProperty pathLocationDataListProp;
private SerializedProperty pathLocationDataProp;
private SerializedProperty pathLocationDataLocationProp;
private SerializedProperty pathDataLocationNameProp;
private SerializedProperty pathDataLocationPositionProp;
private SerializedProperty pathDataLocationSelectedProp;
private SerializedProperty pathDataLocationUnassignedProp;
private SerializedProperty pathDataLocationGUIDHashProp;
private SerializedProperty pathDataLocationShowGizmosInSceneViewProp;
private SerializedProperty pathDataLocationShowInEditorProp;
#endregion
#region Serialized Properties - Options
private SerializedProperty isAutosizeLocationGizmoProp;
private SerializedProperty locationGizmoColourProp;
private SerializedProperty findZoomDistanceProp;
private SerializedProperty defaultPathControlGizmoColourProp;
private SerializedProperty pathDisplayResolutionProp;
private SerializedProperty pathPrecisionProp;
private SerializedProperty defaultPathControlOffsetProp;
#endregion
#region Events
public void OnEnable()
{
sscManager = (SSCManager)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) { }
// Reset guistyles to avoid issues - forces reinitialisation of button styles etc
helpBoxRichText = null;
labelFieldRichText = null;
buttonCompact = null;
buttonCompactBoldBlue = null;
foldoutStyleNoLabel = null;
toggleCompactButtonStyleNormal = null;
toggleCompactButtonStyleToggled = null;
searchTextFieldStyle = null;
searchCancelButtonStyle = null;
defaultEditorLabelWidth = 150f; // EditorGUIUtility.labelWidth;
defaultEditorFieldWidth = EditorGUIUtility.fieldWidth;
#region FindProperties
// Locations
locationDataListProp = serializedObject.FindProperty("locationDataList");
locationDefaultNormalOffsetProp = serializedObject.FindProperty("locationDefaultNormalOffset");
isLocationDataListExpandedProp = serializedObject.FindProperty("isLocationDataListExpanded");
// Paths
pathDataListProp = serializedObject.FindProperty("pathDataList");
isPathDataListExpandedProp = serializedObject.FindProperty("isPathDataListExpanded");
pathDataActiveGUIDHashProp = serializedObject.FindProperty("pathDataActiveGUIDHash");
// Options
isAutosizeLocationGizmoProp = serializedObject.FindProperty("isAutosizeLocationGizmo");
locationGizmoColourProp = serializedObject.FindProperty("locationGizmoColour");
findZoomDistanceProp = serializedObject.FindProperty("findZoomDistance");
defaultPathControlGizmoColourProp = serializedObject.FindProperty("defaultPathControlGizmoColour");
defaultPathControlOffsetProp = serializedObject.FindProperty("defaultPathControlOffset");
pathDisplayResolutionProp = serializedObject.FindProperty("pathDisplayResolution");
pathPrecisionProp = serializedObject.FindProperty("pathPrecision");
#endregion
sscHelpPDF = SSCEditorHelper.GetHelpURL();
// Deselect all Locations in the scene view
//SelectAllLocations(false, false);
// Turn off the gizmos for the selected Path Gameobject
Tools.hidden = true;
// Switch to the Move tool
Tools.current = Tool.Move;
if (!EditorApplication.isPlayingOrWillChangePlaymode)
{
// This is a workaround to make the scene active so that
// adding locations works first time without first having to
// select the scene (right click, click the scene window title etc)
isSetFocusEnabled = true;
}
}
/// <summary>
/// Called automatically by Unity when the gameobject loses focus
/// </summary>
private void OnDisable()
{
// Turn on the default scene handles
Tools.hidden = false;
Tools.current = Tool.Move;
}
/// <summary>
/// Called when the gameobject loses focus or Unity Editor enters/exits
/// play mode
/// </summary>
private 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;
}
/// <summary>
/// Gets called automatically 10 times per second
/// Comment out if not required
/// </summary>
private 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 (sscManager != null && sscManager.allowRepaint) { Repaint(); }
}
#endregion
#region Public Static Methods
// Add a menu item so that a SSCManager can be created via the GameObject > 3D Object menu
[MenuItem("GameObject/3D Object/Sci-Fi Ship Controller/SSC Manager")]
public static void CreateSSCManager()
{
SSCManager mgr = SSCManager.GetOrCreateManager();
if (!Application.isPlaying)
{
EditorSceneManager.MarkSceneDirty(mgr.gameObject.scene);
EditorUtility.SetDirty(mgr);
}
}
#endregion
#region Private Member Methods
/// <summary>
/// Add (or subtract) to the y-axis position for all the selected Locations in the Path
/// </summary>
/// <param name="listProp"></param>
/// <param name="searchFilter"></param>
/// <param name="addPositionY"></param>
private void AddPosYSelectList(PathData pathData, float addPositionY)
{
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Path y-axis on selected");
int numInList = pathData == null || pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
for (int idx = 0; idx < numInList; idx++)
{
// Get the PathLocationData class instance from the list
PathLocationData pathLocationData = pathData.pathLocationDataList[idx];
// Get the LocationData class reference from within the PathLocationData class instance
LocationData locationData = pathLocationData.locationData;
if (!locationData.isUnassigned && locationData.selectedInSceneView)
{
Vector3 wsPosition = locationData.position;
wsPosition.y += addPositionY;
locationData.position = wsPosition;
// Update the control points to match the new Y position
Vector3 newControlPoint = pathLocationData.inControlPoint;
newControlPoint.y += addPositionY;
pathLocationData.inControlPoint = newControlPoint;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - newControlPoint).normalized * (wsPosition - pathLocationData.outControlPoint).magnitude);
}
}
if (numInList > 0) { sscManager.RefreshPathDistances(pathData); }
}
/// <summary>
/// Attempt to find a path for the first selected location, and being to edit it
/// </summary>
private void EditLocationPath()
{
if (sscManager != null)
{
int numLocations = sscManager.locationDataList == null ? 0 : sscManager.locationDataList.Count;
int numPaths = sscManager.pathDataList == null ? 0 : sscManager.pathDataList.Count;
// If no paths or no locations, then nothing to edit
if (numPaths > 0 && numLocations > 0)
{
LocationData locationData = null;
for (int locIdx = 0; locIdx < numLocations; locIdx++)
{
locationData = sscManager.locationDataList[locIdx];
int locDataGUIDHash = locationData.guidHash;
if (locationData != null && locationData.selectedInSceneView)
{
// Is this location a member of a path?
for (int pIdx = 0; pIdx < numPaths; pIdx++)
{
PathData pathData = sscManager.pathDataList[pIdx];
if (pathData != null && pathData.pathLocationDataList != null && pathData.pathLocationDataList.Exists(locData => !locData.locationData.isUnassigned && locData.locationData.guidHash == locDataGUIDHash))
{
// Found a path for this selected location
// Set as active path
sscManager.pathDataActiveGUIDHash = pathData.guidHash;
// Make sure we're on the Path tab
selectedTabInt = 1;
// Get out of these loops and exit the function
locIdx = numLocations;
break;
}
}
}
}
}
}
}
/// <summary>
/// Get the Position Y from the first selected locations in the path list of locations
/// </summary>
/// <param name="listProp"></param>
/// <param name="searchFilter"></param>
private float GetPosYSelectList(SerializedProperty listProp)
{
float positionY = 0f;
if (listProp != null)
{
int numInList = listProp.arraySize;
for (int idx = 0; idx < numInList; idx++)
{
// Get the PathLocationData class instance from the list
pathLocationDataProp = listProp.GetArrayElementAtIndex(idx);
// Get the LocationData class reference from within the PathLocationData class instance
pathLocationDataLocationProp = pathLocationDataProp.FindPropertyRelative("locationData");
pathDataLocationPositionProp = pathLocationDataLocationProp.FindPropertyRelative("position");
pathDataLocationSelectedProp = pathLocationDataLocationProp.FindPropertyRelative("selectedInSceneView");
pathDataLocationUnassignedProp = pathLocationDataLocationProp.FindPropertyRelative("isUnassigned");
if (!pathDataLocationUnassignedProp.boolValue && pathDataLocationSelectedProp.boolValue)
{
positionY = pathDataLocationPositionProp.vector3Value.y;
break;
}
}
}
return positionY;
}
/// <summary>
/// Set all the selected Locations in the Path to have a new y-axis position
/// </summary>
/// <param name="listProp"></param>
/// <param name="searchFilter"></param>
/// <param name="newPositionY"></param>
private void SetPosYSelectList(PathData pathData, float newPositionY)
{
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Path y-axis on selected");
int numInList = pathData == null || pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
for (int idx = 0; idx < numInList; idx++)
{
// Get the PathLocationData class instance from the list
PathLocationData pathLocationData = pathData.pathLocationDataList[idx];
// Get the LocationData class reference from within the PathLocationData class instance
LocationData locationData = pathLocationData.locationData;
if (!locationData.isUnassigned && locationData.selectedInSceneView)
{
Vector3 wsPosition = locationData.position;
wsPosition.y = newPositionY;
locationData.position = wsPosition;
// Update the control points to match the new Y position
Vector3 newControlPoint = pathLocationData.inControlPoint;
newControlPoint.y = wsPosition.y;
pathLocationData.inControlPoint = newControlPoint;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - newControlPoint).normalized * (wsPosition - pathLocationData.outControlPoint).magnitude);
}
}
if (numInList > 0) { sscManager.RefreshPathDistances(pathData); }
}
private void SceneGUI(SceneView sv)
{
if (sscManager != null && sscManager.gameObject.activeInHierarchy)
{
isSceneDirtyRequired = false;
Event currentEvent = Event.current;
bool isRightButton = (currentEvent.button == 1);
bool isCtrlKey = currentEvent.control;
bool isShiftKey = currentEvent.shift;
// Make the scene active so that it receives key events correctly
// when the sscmanger is selected
if (isSetFocusEnabled) { sv.Focus(); isSetFocusEnabled = false; }
#region Context Menu
if (currentEvent.type == EventType.MouseDown && isRightButton && Tools.current != Tool.View && Tools.current != Tool.None)
{
// If user has Path tab selected, see if there is an active path
PathData activePath = selectedTabInt == 1 ? sscManager.GetPath(sscManager.pathDataActiveGUIDHash) : null;
bool isEditPath = activePath != null;
GenericMenu menu = new GenericMenu();
menu.AddDisabledItem(new GUIContent(isEditPath ? (string.IsNullOrEmpty(activePath.name) ? "Edit (unnamed) Path" : activePath.name) : "Edit Locations")); // Header
if (isEditPath)
{
menu.AddItem(new GUIContent("Stop Editing Path"), false, () => { sscManager.pathDataActiveGUIDHash = -1; isEditPath = false; activePath = null; } );
}
else
{
menu.AddItem(new GUIContent("Start Editing Path for location"), false, () => { EditLocationPath(); });
}
#region Context Menu - Selection
menu.AddSeparator("");
if (isEditPath)
{
menu.AddItem(new GUIContent("Select All (Active Path)"), false, () => { SelectPathLocations(activePath, true); });
menu.AddItem(new GUIContent("Unselect All (Active Path)"), false, () => { SelectPathLocations(activePath, false); });
}
menu.AddItem(new GUIContent("Select All Locations"), false, () => { SelectAllLocations(true, false); });
menu.AddItem(new GUIContent("Unselect All Locations"), false, () => { SelectAllLocations(false, false); });
#endregion
#region Context Menu - Add Location
menu.AddSeparator("");
if (isEditPath)
{
menu.AddItem(new GUIContent("Insert Before 1st Selected (Active Path)"), false, () =>
{
int _firstSelected = activePath.pathLocationDataList.FindIndex(locData => locData.locationData.selectedInSceneView == true);
if (_firstSelected >= 0)
{
Undo.RecordObject(sscManager, "Insert in Active Path");
LocationData locationData = sscManager.InsertLocationAt(activePath, _firstSelected, true, true);
if (locationData != null) { locationData.selectedInSceneView = true; }
isSceneDirtyRequired = true;
}
}
);
menu.AddItem(new GUIContent("Append Location (Active Path)"), false, () =>
{
Vector3 posWS = Vector3.zero;
if (SSCEditorHelper.GetPositionFromMouse(sv, currentEvent.mousePosition, sscManager.locationDefaultNormalOffset, ref posWS, true))
{
currentEvent.Use();
Undo.RecordObject(sscManager, "Append Path Location");
// Unselect existing path locations so only extended location path is selected afterwards
SelectPathLocations(activePath, false);
LocationData locationData = sscManager.AppendLocation(activePath, posWS, true, true);
if (locationData != null) { locationData.selectedInSceneView = true; }
isSceneDirtyRequired = true;
}
});
menu.AddItem(new GUIContent("Extend Active Path"), false, () =>
{
int numPathPoints = activePath.pathLocationDataList == null ? 0 : activePath.pathLocationDataList.Count;
Vector3 posWS = Vector3.zero;
// If no points in Path add one at the current mouse point
if (numPathPoints < 1)
{
if (SSCEditorHelper.GetPositionFromMouse(sv, currentEvent.mousePosition, sscManager.locationDefaultNormalOffset, ref posWS, true))
{
currentEvent.Use();
Undo.RecordObject(sscManager, "Append Path Location");
LocationData locationData = sscManager.AppendLocation(activePath, posWS, true, true);
if (locationData != null) { locationData.selectedInSceneView = true; }
isSceneDirtyRequired = true;
}
}
else
{
Undo.RecordObject(sscManager, "Extend Active Path");
LocationData locationData = sscManager.AppendLocation(activePath, true, true);
if (locationData != null) { locationData.selectedInSceneView = true; }
isSceneDirtyRequired = true;
}
});
}
else
{
menu.AddItem(new GUIContent("Add Location"), false, () =>
{
Vector3 posWS = Vector3.zero;
if (SSCEditorHelper.GetPositionFromMouse(sv, currentEvent.mousePosition, sscManager.locationDefaultNormalOffset, ref posWS, true))
{
currentEvent.Use();
Undo.RecordObject(sscManager, "Add Location");
LocationData locationData = sscManager.AppendLocation(posWS, true);
if (locationData != null) { locationData.selectedInSceneView = true; }
isSceneDirtyRequired = true;
}
});
}
#endregion
#region Context Menu - Control Points and Snap Location(s)
if (isEditPath)
{
menu.AddItem(new GUIContent("Set Controls to Location Y (Selected)"), false, () =>
{
SetPathControlPointsY(activePath);
isSceneDirtyRequired = true;
});
menu.AddItem(new GUIContent("Snap Locations to Mesh Below (Selected)"), false, () =>
{
sscManager.SnapPathSelectedLocationsToMesh(activePath, Vector3.up, ~0, true);
isSceneDirtyRequired = true;
});
}
#endregion
menu.AddSeparator("");
#region Context Menu - Display
if (isEditPath)
{
menu.AddItem(new GUIContent("Display/Distances"), activePath.showDistancesInScene, () =>
{
activePath.showDistancesInScene = !activePath.showDistancesInScene;
isSceneDirtyRequired = true;
});
menu.AddItem(new GUIContent("Display/Number Labels"), activePath.showPointNumberLabelsInScene, () =>
{
activePath.showPointNumberLabelsInScene = !activePath.showPointNumberLabelsInScene;
if (activePath.showPointNumberLabelsInScene) { activePath.showPointNameLabelsInScene = false; }
isSceneDirtyRequired = true;
});
menu.AddItem(new GUIContent("Display/Name Labels"), activePath.showPointNameLabelsInScene, () =>
{
activePath.showPointNameLabelsInScene = !activePath.showPointNameLabelsInScene;
if (activePath.showPointNameLabelsInScene) { activePath.showPointNumberLabelsInScene = false; }
isSceneDirtyRequired = true;
});
}
#endregion
#region Context Menu - Zoom
menu.AddItem(new GUIContent("Zoom/Selected Locations"), false, () => { ZoomExtent(sv, false, null); });
if (isEditPath)
{
menu.AddItem(new GUIContent("Zoom/Active Path"), false, () => { ZoomExtent(sv, true, activePath); });
}
#endregion
menu.AddSeparator("");
menu.AddItem(new GUIContent("Delete All Selected Locations"), false, () => { DeleteSelectedLocations(); sv.Focus(); });
menu.AddSeparator("");
menu.AddItem(new GUIContent("Allow Scene View Rotation"), false, () => { Tools.current = Tool.View; });
// The Cancel option is not really necessary as use can just click anywhere else. However, it may help some users.
menu.AddItem(new GUIContent("Cancel"), false, () => { });
menu.ShowAsContext();
currentEvent.Use();
}
#endregion
#region Add Locations
// Did the user press "+" key to add a location in the scene?
// Allow for + or = which are mostly the same key (+ is typically shift and '=' key).
else if (currentEvent.type == EventType.KeyUp && (currentEvent.keyCode == KeyCode.Equals || currentEvent.keyCode == KeyCode.KeypadPlus))
{
Vector3 posWS = Vector3.zero;
if (SSCEditorHelper.GetPositionFromMouse(sv, currentEvent.mousePosition, sscManager.locationDefaultNormalOffset, ref posWS, true))
{
currentEvent.Use();
// If user has Path tab selected, see if there is an active path
PathData activePath = selectedTabInt == 1 ? sscManager.GetPath(sscManager.pathDataActiveGUIDHash) : null;
Undo.RecordObject(sscManager, activePath != null ? "Add Path Location" : "Add Location");
// If the user has Path tab selected and there is an active Path, add the new Location to the Path,
// else just create a new Location in the scene.
LocationData locationData = activePath != null ? sscManager.AppendLocation(activePath, posWS, true, true) : sscManager.AppendLocation(posWS, true);
if (locationData != null) { locationData.selectedInSceneView = true; }
}
isSceneDirtyRequired = true;
}
#endregion Add Locations
#region Modify Locations and Active Path in the scene
else
{
#region Locations in the scene
int numLocations = sscManager.locationDataList == null ? 0 : sscManager.locationDataList.Count;
// If cannot find the camera, use the middle of the sceneview (not ideal but better than nothing)
Vector3 svCamPos = sv.camera != null ? sv.camera.transform.position : sv.pivot;
fadedGizmoColour = sscManager.locationGizmoColour;
using (new Handles.DrawingScope(sscManager.locationGizmoColour))
{
for (int lnIdx = 0; lnIdx < numLocations; lnIdx++)
{
locationDataItem = sscManager.locationDataList[lnIdx];
if (locationDataItem != null)
{
if (locationDataItem.showGizmosInSceneView)
{
itemHandlePosition = locationDataItem.position;
// Currently rotation and scale aren't being used.
itemHandleRotation = Quaternion.identity;
itemHandleScale.x = 0f;
itemHandleScale.y = 0f;
itemHandleScale.z = 0f;
// Draw location in the scene that is non-interactable
//if (Event.current.type == EventType.Repaint)
//{
// //Handles.ArrowHandleCap(0, itemHandlePosition, itemHandleRotation, 1f, EventType.Repaint);
// Handles.SphereHandleCap(0, itemHandlePosition, itemHandleRotation, 1f, EventType.Repaint);
//}
// Here is where we would draw any shape that identifies this location type
if (locationDataItem.selectedInSceneView)
{
// Choose which handle to draw based on which Unity tool is selected
if (Tools.current == Tool.Rotate)
{
// Currently not required
}
else if (Tools.current == Tool.Scale)
{
// Currently not required
}
else if (Tools.current == Tool.Move || Tools.current == Tool.Transform)
{
EditorGUI.BeginChangeCheck();
// Draw a movement handle
itemHandlePosition = Handles.PositionHandle(itemHandlePosition, itemHandleRotation);
// Use the position handle to edit the position of the location
if (EditorGUI.EndChangeCheck())
{
isSceneDirtyRequired = true;
if (currentEvent.shift)
{
// Move all selected Locations in the Active Path
if (selectedTabInt == 1)
{
// This could be a little slow
PathData activePath = sscManager.GetPath(sscManager.pathDataActiveGUIDHash);
if (activePath != null)
{
Undo.RecordObject(sscManager, "Move Path Locations");
sscManager.MoveLocations(activePath, locationDataItem, itemHandlePosition, true);
}
}
// Move all selected Locations in the scene
else
{
Undo.RecordObject(sscManager, "Move Selected Locations");
sscManager.MoveLocations(locationDataItem, itemHandlePosition, true);
}
}
else
{
Undo.RecordObject(sscManager, "Move Location");
// WARNING: Refreshing the Distances could get expensive on long Paths..
sscManager.UpdateLocation(locationDataItem, itemHandlePosition, true);
}
}
}
}
float itemHandleSize = sscManager.isAutosizeLocationGizmo ? HandleUtility.GetHandleSize(itemHandlePosition) * 0.1f : 0.5f;
// Determine how far this item is from the scene view camera and set the alpha value of the buttons
float sqrDistToCamera = ((svCamPos.x - itemHandlePosition.x) * (svCamPos.x - itemHandlePosition.x)) + ((svCamPos.y - itemHandlePosition.y) * (svCamPos.y - itemHandlePosition.y)) + ((svCamPos.z - itemHandlePosition.z) * (svCamPos.z - itemHandlePosition.z));
float gizmoAlpha = 1f - (sscManager.locationGizmoColour.a - 0.1f) * ((sqrDistToCamera - startFadeDistSqr) / maxFadeDistSqr);
fadedGizmoColour.a = gizmoAlpha < 0.1f ? 0.1f : gizmoAlpha > 1f ? 1f : gizmoAlpha;
using (new Handles.DrawingScope(fadedGizmoColour))
{
// Allow the user to select/deselect the location
if (Handles.Button(itemHandlePosition, Quaternion.identity, itemHandleSize, itemHandleSize, Handles.SphereHandleCap))
{
if (locationDataItem.selectedInSceneView)
{
// If in multi-select mode or single selected, just unselect the clicked Location
locationDataItem.selectedInSceneView = false;
locationDataItem.showInEditor = false;
}
else
{
if (!isShiftKey)
{
// If not in multi-select mode (Shift key held down), unselect all
SelectAllLocations(false, true);
ExpandList(sscManager.locationDataList, false);
}
// Select the Location clicked on
locationDataItem.selectedInSceneView = true;
locationDataItem.showInEditor = true;
isSceneDirtyRequired = true;
// Hide Unity tools
Tools.hidden = true;
}
isSceneDirtyRequired = true;
}
}
}
}
}
}
#endregion Locations in the scene
#region Edit Tangent Control Points for Active Path in the scene
// NOTE: Path lines are drawn in SSManager.OnDrawGizmosSelected()
using (new Handles.DrawingScope(sscManager.defaultPathControlGizmoColour))
{
PathData pathData = sscManager.GetPath(sscManager.pathDataActiveGUIDHash);
// Only show the tangents for the active Path which has show Gizmos enabled
if (pathData != null && pathData.showGizmosInSceneView)
{
int numPathLocationDatas = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
// We "could" only set this once but it may have issues going in and out of play mode in the editor
if (pathData.showPointNumberLabelsInScene || pathData.showPointNameLabelsInScene || pathData.showDistancesInScene)
{
distanceLabel = new GUIStyle("Box");
distanceLabel.fontSize = 10;
distanceLabel.border = new RectOffset(1, 1, 1, 1);
distanceLabel.onFocused.textColor = UnityEngine.Color.white;
}
fadedGizmoColour = sscManager.defaultPathControlGizmoColour;
for (int pldIdx = 0; pldIdx < numPathLocationDatas; pldIdx++)
{
PathLocationData pathLocationData = pathData.pathLocationDataList[pldIdx];
if (pathLocationData != null && pathLocationData.locationData != null && !pathLocationData.locationData.isUnassigned)
{
float controlHandleSize = sscManager.isAutosizeLocationGizmo ? HandleUtility.GetHandleSize(pathLocationData.inControlPoint) * 0.07f : 0.4f;
locationPosition = pathLocationData.locationData.position;
// Determine how far this Location is from the scene view camera and set the alpha value of the buttons, handles and Control Point lines
float sqrDistToCamera = ((svCamPos.x - locationPosition.x) * (svCamPos.x - locationPosition.x)) + ((svCamPos.y - locationPosition.y) * (svCamPos.y - locationPosition.y)) + ((svCamPos.z - locationPosition.z) * (svCamPos.z - locationPosition.z));
float gizmoAlpha = 1f - (sscManager.defaultPathControlGizmoColour.a - 0.1f) * ((sqrDistToCamera - startFadeDistSqr) / maxFadeDistSqr);
fadedGizmoColour.a = gizmoAlpha < 0.1f ? 0.1f : gizmoAlpha > 1f ? 1f : gizmoAlpha;
using (new Handles.DrawingScope(fadedGizmoColour))
{
#region FreeMove Control Handles
if (Tools.current == Tool.View)
{
EditorGUI.BeginChangeCheck();
#if UNITY_2022_1_OR_NEWER
newControlPoint = Handles.FreeMoveHandle(pathLocationData.inControlPoint, controlHandleSize, controlHandleSnap, Handles.SphereHandleCap);
#else
newControlPoint = Handles.FreeMoveHandle(pathLocationData.inControlPoint, Quaternion.identity, controlHandleSize, controlHandleSnap, Handles.SphereHandleCap);
#endif
if (EditorGUI.EndChangeCheck())
{
isSceneDirtyRequired = true;
UpdatePathControlPoint(pathLocationData, newControlPoint, true);
pathData.isDirty = true;
}
EditorGUI.BeginChangeCheck();
#if UNITY_2022_1_OR_NEWER
newControlPoint = Handles.FreeMoveHandle(pathLocationData.outControlPoint, controlHandleSize, controlHandleSnap, Handles.SphereHandleCap);
#else
newControlPoint = Handles.FreeMoveHandle(pathLocationData.outControlPoint, Quaternion.identity, controlHandleSize, controlHandleSnap, Handles.SphereHandleCap);
#endif
if (EditorGUI.EndChangeCheck())
{
isSceneDirtyRequired = true;
UpdatePathControlPoint(pathLocationData, newControlPoint, false);
pathData.isDirty = true;
}
}
#endregion
#region Position Move Control Handles
else if (Tools.current == Tool.Move || Tools.current == Tool.Transform)
{
// If the Location is selected, turn off the control handles
// Check if in/out controls are enabled before marking scene dirty.
if (pathLocationData.locationData.selectedInSceneView && (pathLocationData.inControlSelectedInSceneView || pathLocationData.outControlSelectedInSceneView))
{
pathLocationData.inControlSelectedInSceneView = false;
pathLocationData.outControlSelectedInSceneView = false;
isSceneDirtyRequired = true;
}
// If the controls are selected, display the movable Position Handles
if (pathLocationData.inControlSelectedInSceneView)
{
EditorGUI.BeginChangeCheck();
newControlPoint = Handles.PositionHandle(pathLocationData.inControlPoint, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
isSceneDirtyRequired = true;
UpdatePathControlPoint(pathLocationData, newControlPoint, true);
pathData.isDirty = true;
}
}
if (pathLocationData.outControlSelectedInSceneView)
{
EditorGUI.BeginChangeCheck();
newControlPoint = Handles.PositionHandle(pathLocationData.outControlPoint, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
isSceneDirtyRequired = true;
UpdatePathControlPoint(pathLocationData, newControlPoint, false);
pathData.isDirty = true;
}
}
// Buttons which let you select or unselect the control points. When selected the above handles appear
if (Handles.Button(pathLocationData.inControlPoint, Quaternion.identity, controlHandleSize, controlHandleSize, Handles.SphereHandleCap))
{
ToggleControl(pathData, pathLocationData, true);
isSceneDirtyRequired = true;
}
if (Handles.Button(pathLocationData.outControlPoint, Quaternion.identity, controlHandleSize, controlHandleSize, Handles.SphereHandleCap))
{
ToggleControl(pathData, pathLocationData, false);
isSceneDirtyRequired = true;
}
}
#endregion
Handles.DrawLine(pathLocationData.inControlPoint, locationPosition);
Handles.DrawLine(pathLocationData.outControlPoint, locationPosition);
}
#region Display labels
if (pathData.showPointNumberLabelsInScene || pathData.showPointNameLabelsInScene || pathData.showDistancesInScene)
{
// Only show the Point labels if they are enabled in Editor AND infront of the scene view camera
// This prevents a ghosted handle being displayed when it is behind the camera.
if (SSCUtils.IsPointInCameraView(SceneView.lastActiveSceneView.camera, locationPosition))
{
if (pathData.showPointNameLabelsInScene) { locationName = string.IsNullOrEmpty(pathLocationData.locationData.name) ? "no name" : pathLocationData.locationData.name.Trim(); }
// Display Point number and Distance labels
if (pathData.showPointNumberLabelsInScene && pathData.showDistancesInScene)
{
if (pldIdx == 0 && pathData.isClosedCircuit)
{
Handles.Label(locationPosition + pointLabelOffset, "Pt: " + (pldIdx + 1).ToString("000") + " Distance: 0.00 [Total " + pathLocationData.distanceCumulative.ToString("0.00") + "]", distanceLabel);
}
else
{
Handles.Label(locationPosition + pointLabelOffset, "Pt: " + (pldIdx + 1).ToString("000") + " Distance: " + pathLocationData.distanceCumulative.ToString("0.00"), distanceLabel);
}
}
// Display Point name and Distance labels
else if (pathData.showPointNameLabelsInScene && pathData.showDistancesInScene)
{
if (pldIdx == 0 && pathData.isClosedCircuit)
{
Handles.Label(locationPosition + pointLabelOffset, locationName + " Distance: 0.00 [Total " + pathLocationData.distanceCumulative.ToString("0.00") + "]", distanceLabel);
}
else
{
Handles.Label(locationPosition + pointLabelOffset, locationName + " Distance: " + pathLocationData.distanceCumulative.ToString("0.00"), distanceLabel);
}
}
else if (pathData.showPointNumberLabelsInScene)
{
Handles.Label(locationPosition + pointLabelOffset, "Pt: " + (pldIdx + 1).ToString("000"), distanceLabel);
}
else if (pathData.showPointNameLabelsInScene)
{
Handles.Label(locationPosition + pointLabelOffset, locationName, distanceLabel);
}
// Just display distance
else if (pldIdx == 0 && pathData.isClosedCircuit)
{
Handles.Label(locationPosition + pointLabelOffset, "Distance: 0.00 [" + pathLocationData.distanceCumulative.ToString("0.00") + "]", distanceLabel);
}
else
{
Handles.Label(locationPosition + pointLabelOffset, "Distance: " + pathLocationData.distanceCumulative.ToString("0.00"), distanceLabel);
}
}
}
#endregion
}
}
}
if (pathData != null && pathData.isDirty) { sscManager.RefreshPathDistances(pathData); }
}
#endregion Edit Tangent Control Points for Active Path in the scene
}
#endregion
if (isSceneDirtyRequired && !Application.isPlaying)
{
isSceneDirtyRequired = false;
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
}
else
{
// Always unhide Unity tools and deselect all Locations when the object is disabled
Tools.hidden = false;
SelectAllLocations(false, true);
}
}
/// <summary>
/// When Control Points can be manipulated with Position Handles, they need to be selected from
/// a button. When they are selected, all other Control points (and positions) on the Path should be unselected.
/// </summary>
/// <param name="pathData"></param>
/// <param name="controlPathLocationData"></param>
/// <param name="isInControl"></param>
private void ToggleControl(PathData pathData, PathLocationData controlPathLocationData, bool isInControl)
{
if (pathData != null && controlPathLocationData != null)
{
int numPathLocationDatas = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
bool isSelected = false;
// Toggle the InControl Point selection
if (isInControl)
{
controlPathLocationData.inControlSelectedInSceneView = !controlPathLocationData.inControlSelectedInSceneView;
if (controlPathLocationData.inControlSelectedInSceneView)
{
controlPathLocationData.outControlSelectedInSceneView = false;
controlPathLocationData.locationData.selectedInSceneView = false;
isSelected = true;
}
}
// Toggle the OutControl Point selection
else
{
controlPathLocationData.outControlSelectedInSceneView = !controlPathLocationData.outControlSelectedInSceneView;
if (controlPathLocationData.outControlSelectedInSceneView)
{
controlPathLocationData.inControlSelectedInSceneView = false;
controlPathLocationData.locationData.selectedInSceneView = false;
isSelected = true;
}
}
// Only unselect all other positions and Control points if the current Control point has been selected
for (int pldIdx = 0; isSelected && pldIdx < numPathLocationDatas; pldIdx++)
{
PathLocationData pathLocationData = pathData.pathLocationDataList[pldIdx];
// Skip the current Control point being toggled
if (pathLocationData != null && pathLocationData.guidHash != controlPathLocationData.guidHash)
{
// Unselect all other Control points
pathLocationData.inControlSelectedInSceneView = false;
pathLocationData.outControlSelectedInSceneView = false;
// Added SSC v1.06. Unselect Location too
if (pathLocationData.locationData != null) { pathLocationData.locationData.selectedInSceneView = false; }
}
}
}
}
/// <summary>
/// Toggle the selected status of list of filtered Locations based on the first item.
/// </summary>
/// <param name="listProp"></param>
/// <param name="searchFilter"></param>
private void ToggleSelectList(SerializedProperty listProp, string searchFilter)
{
if (listProp != null)
{
int numInList = listProp.arraySize;
bool isFirstItem = true;
bool isSelected = false;
for (int idx = 0; idx < numInList; idx++)
{
SerializedProperty itemProp = listProp.GetArrayElementAtIndex(idx);
if (itemProp != null)
{
SerializedProperty nameProp = itemProp.FindPropertyRelative("name");
if (!SSCEditorHelper.IsInSearchFilter(nameProp, searchFilter)) { continue; }
SerializedProperty isSelectedProp = itemProp.FindPropertyRelative("selectedInSceneView");
// Get the selction status of the first item in the list
if (isFirstItem)
{
isSelected = isSelectedProp.boolValue;
isFirstItem = false;
}
// Set selectedInSceneView to the toggled value based on the first item in the list
isSelectedProp.boolValue = !isSelected;
}
}
}
}
/// <summary>
/// Select or Deselect all Locations in the scene view edit mode.
/// If unHideUnityTools is false, Tools are not changed. If you want to hide the Unity Tools,
/// then it must be done outside this method.
/// </summary>
/// <param name="unHideUnityTools"></param>
private void SelectAllLocations(bool isSelected, bool unHideUnityTools)
{
// Avoid situation where sscmanager is destroyed in play mode while it is selected.
if (sscManager != null)
{
int numLocations = sscManager.locationDataList == null ? 0 : sscManager.locationDataList.Count;
LocationData locationData = null;
for (int idx = 0; idx < numLocations; idx++)
{
locationData = sscManager.locationDataList[idx];
if (locationData != null)
{
locationData.selectedInSceneView = isSelected;
}
}
// Added SSC v1.0.6
if (!isSelected)
{
PathData activePath = numLocations > 0 ? sscManager.GetPath(sscManager.pathDataActiveGUIDHash) : null;
DeselectAllControlPoints(activePath);
}
}
// Unhide Unity tools
if (unHideUnityTools) { Tools.hidden = false; }
}
/// <summary>
/// Deselect all Control Points on a given Path
/// </summary>
/// <param name="pathData"></param>
private static void DeselectAllControlPoints(PathData pathData)
{
if (pathData != null)
{
int numPathLocationDatas = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
for (int pldIdx = 0; pldIdx < numPathLocationDatas; pldIdx++)
{
PathLocationData pathLocationData = pathData.pathLocationDataList[pldIdx];
if (pathLocationData != null)
{
// Unselect all other Control points
pathLocationData.inControlSelectedInSceneView = false;
pathLocationData.outControlSelectedInSceneView = false;
}
}
}
}
/// <summary>
/// Select or unselect all the Locations in a Path.
/// Always unselect in/out Controls for a valid Path Location
/// </summary>
/// <param name="pathData"></param>
/// <param name="isSelected"></param>
private void SelectPathLocations(PathData pathData, bool isSelected)
{
if (sscManager != null && pathData != null)
{
int numLocations = pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
PathLocationData pathLocationData = null;
LocationData locationData = null;
for (int idx = 0; idx < numLocations; idx++)
{
pathLocationData = pathData.pathLocationDataList[idx];
locationData = pathLocationData == null ? null : pathLocationData.locationData;
if (locationData != null)
{
locationData.selectedInSceneView = isSelected;
pathLocationData.inControlSelectedInSceneView = false;
pathLocationData.outControlSelectedInSceneView = false;
}
}
}
}
/// <summary>
/// Expand or collapse all locations within the list.
/// If it is a list of Paths, always collapse the Locations in the Path
/// </summary>
/// <param name="listProp"></param>
/// <param name="isExpanded"></param>
private void ExpandList(SerializedProperty listProp, bool isExpanded)
{
if (listProp != null)
{
int numInList = listProp.arraySize;
for (int idx = 0; idx < numInList; idx++)
{
SerializedProperty itemProp = listProp.GetArrayElementAtIndex(idx);
if (itemProp != null)
{
// Locations and Paths have the same showInEditor field.
SerializedProperty showInEditorProp = itemProp.FindPropertyRelative("showInEditor");
if (showInEditorProp != null)
{
showInEditorProp.boolValue = isExpanded;
}
// If this is Path, then it will have a field to show or hide Locations
SerializedProperty showLocationsInEditorProp = itemProp.FindPropertyRelative("showLocationsInEditor");
if (showLocationsInEditorProp != null)
{
showLocationsInEditorProp.boolValue = false;
}
}
}
}
}
/// <summary>
/// Expand or collapse all locations within the list
/// </summary>
/// <param name="locationDataList"></param>
/// <param name="isExpanded"></param>
private void ExpandList(List<LocationData> locationDataList, bool isExpanded)
{
int numInList = locationDataList == null ? 0 : locationDataList.Count;
for (int idx = 0; idx < numInList; idx++)
{
LocationData locationData = locationDataList[idx];
if (locationData != null)
{
locationData.showInEditor = isExpanded;
}
}
}
/// <summary>
/// Toggle all items in the list based on the value of showGizmosInSceneView for the first item.
/// </summary>
/// <param name="listProp"></param>
private void ToggleGizmos(SerializedProperty listProp)
{
if (listProp != null)
{
int numInList = listProp.arraySize;
if (numInList > 0)
{
// Examine the first item in the list
SerializedProperty itemProp = listProp.GetArrayElementAtIndex(0);
if (itemProp != null)
{
SerializedProperty showGizmosInSceneViewProp = itemProp.FindPropertyRelative("showGizmosInSceneView");
if (showGizmosInSceneViewProp != null)
{
bool showGizmos = showGizmosInSceneViewProp.boolValue;
for (int idx = 0; idx < numInList; idx++)
{
itemProp = listProp.GetArrayElementAtIndex(idx);
if (itemProp != null)
{
showGizmosInSceneViewProp = itemProp.FindPropertyRelative("showGizmosInSceneView");
if (showGizmosInSceneViewProp != null) { showGizmosInSceneViewProp.boolValue = !showGizmos; }
}
}
}
}
}
}
}
/// <summary>
/// Delete all selected Locations. Also removes them from an Paths.
/// Prompts user for confirmation.
/// </summary>
private void DeleteSelectedLocations()
{
if (SSCEditorHelper.PromptForDelete("Delete Locations?", "Do you want to delete ALL selected Locations in the scene?\n\nThis will also remove them from any Paths"))
{
sscManager.DeleteSelectedLocations(true);
}
}
/// <summary>
/// Given a location, remove it from all paths in the list supplied.
/// </summary>
/// <param name="pathListProp"></param>
/// <param name="locationGUIDHash"></param>
private void DeleteLocationFromPaths(SerializedProperty pathListProp, int locationGUIDHash, bool refreshPath)
{
if (pathListProp != null)
{
// Loop through all the paths
int numPathsInList = pathListProp.arraySize;
bool isPathAffected = false;
for (int idx = 0; idx < numPathsInList; idx++)
{
// Get the Path
SerializedProperty _pathProp = pathListProp.GetArrayElementAtIndex(idx);
if (_pathProp != null)
{
// Get the list of Location slots in the Path
SerializedProperty _pathLocationDataListProp = _pathProp.FindPropertyRelative("pathLocationDataList");
if (_pathLocationDataListProp != null)
{
isPathAffected = false;
// Loop backwards through all the PathLocationData instances in the Path
for (int lIdx = _pathLocationDataListProp.arraySize - 1; lIdx >= 0; lIdx--)
{
// Get the PathLocationData instance
SerializedProperty _pathLocationDataProp = _pathLocationDataListProp.GetArrayElementAtIndex(lIdx);
if (_pathLocationDataProp != null)
{
SerializedProperty _locationDataProp = _pathLocationDataProp.FindPropertyRelative("locationData");
if (_locationDataProp != null)
{
SerializedProperty _locGUIDHashProp = _locationDataProp.FindPropertyRelative("guidHash");
// If we find a match, remove the Location from the Path
if (_locGUIDHashProp != null && _locGUIDHashProp.intValue == locationGUIDHash)
{
_pathLocationDataListProp.DeleteArrayElementAtIndex(lIdx);
isPathAffected = true;
}
}
}
}
if (isPathAffected)
{
serializedObject.ApplyModifiedProperties();
PathData _pathData = sscManager.GetPath(_pathProp.FindPropertyRelative("guidHash").intValue);
if (_pathData != null)
{
if (refreshPath) { sscManager.RefreshPathDistances(_pathData); }
else { _pathData.isDirty = true; }
}
serializedObject.Update();
}
}
}
}
}
}
/// <summary>
/// When the Location and Path Location lists are updated, especially during an Undo
/// operation, the connect between the Location in the Path and the Location in the
/// Location list can be lost. The method checks for and fixings that issue.
/// </summary>
private void VerifyPathConnections()
{
if (sscManager != null)
{
bool isConnected = false;
bool isCheckConnection = true; // Used to check the first item for broken connection
bool isUpdated = false;
// Loop through all the paths
int numPathsInList = sscManager.pathDataList == null ? 0 : sscManager.pathDataList.Count;
for (int idx = 0; !isConnected && idx < numPathsInList; idx++)
{
PathData _pathData = sscManager.pathDataList[idx];
if (_pathData != null)
{
List<PathLocationData> _pathLocationList = _pathData.pathLocationDataList;
int numLocationsInList = _pathLocationList == null ? 0 : _pathLocationList.Count;
// Loop through all the locations in the Path
for (int lIdx = 0; lIdx < numLocationsInList; lIdx++)
{
// Get the location within the Path
PathLocationData _pathLocation = _pathLocationList[lIdx];
if (_pathLocation != null)
{
// Get the guidHash code for the location within the Path
// Lookup the Location in the full Location list (not the location list in the Path)
LocationData locationDataToAssign = sscManager.locationDataList.Find(l => l.guidHash == _pathLocation.locationData.guidHash);
// Only examine assigned slots
if (locationDataToAssign != null)
{
// Checked and confirmed that first location needs reconnecting.
if (isCheckConnection)
{
// Only do the check once
isCheckConnection = false;
// Is first item check, connected?
if (_pathLocation.locationData == locationDataToAssign)
{
// Assume all are connected
isConnected = true;
//Debug.Log("[DEBUG] " + _pathLocation.name + " is connected");
break;
}
}
if (!isConnected)
{
// If this is the first item to be reconnected, make the sscManager object as dirty in the scene
if (!isUpdated)
{
Undo.RecordObject(sscManager, "Update Path Connections");
isUpdated = true;
}
// Reconnect Path Location with the instance in the Location list
//Debug.Log("[DEBUG] reconnecting " + _pathLocation.locationData.name + " " + Time.realtimeSinceStartup);
_pathLocationList[lIdx].locationData = locationDataToAssign;
}
}
}
}
}
}
}
}
/// <summary>
/// Update the position of an in or out control point. Then reflect this position for the other corresponding in/out control point.
/// </summary>
/// <param name="pathLocationData"></param>
/// <param name="newControlPoint"></param>
/// <param name="isInControl"></param>
private void UpdatePathControlPoint(PathLocationData pathLocationData, Vector3 newControlPoint, bool isInControl)
{
Undo.RecordObject(sscManager, isInControl ? "Change In Control Point" : "Change Out Control Point");
Vector3 wsPosition = pathLocationData.locationData.position;
if (isInControl)
{
pathLocationData.inControlPoint = newControlPoint;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - newControlPoint).normalized * (wsPosition - pathLocationData.outControlPoint).magnitude);
}
else
{
pathLocationData.outControlPoint = newControlPoint;
pathLocationData.inControlPoint = wsPosition + ((wsPosition - newControlPoint).normalized * (wsPosition - pathLocationData.inControlPoint).magnitude);
}
}
/// <summary>
/// Set the control point Y-axis value to the same as the Location
/// for the selected Locations on the Path.
/// </summary>
/// <param name="pathData"></param>
private void SetPathControlPointsY(PathData pathData)
{
int numPathLocationSlots = pathData == null || pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
if (numPathLocationSlots > 0)
{
Undo.RecordObject(sscManager, "Set Control Points Y");
for (int plIdx = 0; plIdx < numPathLocationSlots; plIdx++)
{
PathLocationData pathLocationData = pathData.pathLocationDataList[plIdx];
if (pathLocationData != null && pathLocationData.locationData != null && !pathLocationData.locationData.isUnassigned && pathLocationData.locationData.selectedInSceneView)
{
Vector3 wsPosition = pathLocationData.locationData.position;
Vector3 newControlPoint = pathLocationData.inControlPoint;
newControlPoint.y = wsPosition.y;
pathLocationData.inControlPoint = newControlPoint;
pathLocationData.outControlPoint = wsPosition + ((wsPosition - newControlPoint).normalized * (wsPosition - pathLocationData.outControlPoint).magnitude);
}
}
sscManager.RefreshPathDistances(pathData);
}
}
/// <summary>
/// Dropdown menu callback method used when a location is selected for a path
/// </summary>
/// <param name="obj"></param>
private void UpdatePathLocation(object obj)
{
// The menu data is passed as 3 integers in a Vector3Int
// as Path guidHash, path Location LocationData guidHash, and the selected Location guidHash
// which is being assigned to the location slot on the path.
if (obj != null && obj.GetType() == typeof(Vector3Int))
{
Vector3Int objData = (Vector3Int)obj;
if (sscManager != null && sscManager.pathDataList != null)
{
// Find the path
PathData pathData = sscManager.pathDataList.Find(p => p.guidHash == objData.x);
if (pathData != null)
{
// Look up the guidHash of the LocationData within the PathLocationData (not the hash of the PathLocationData itself)
int pathLocIdx = pathData.pathLocationDataList.FindIndex(l => l.locationData.guidHash == objData.y);
LocationData locationDataToAssign = sscManager.locationDataList.Find(l => l.guidHash == objData.z);
// Replace this existing location reference with a reference to the one being assigned
if (pathLocIdx >= 0 && pathLocIdx < pathData.pathLocationDataList.Count)
{
Undo.RecordObject(sscManager, "Assign Location to Path");
pathData.pathLocationDataList[pathLocIdx].locationData = locationDataToAssign;
// Reset distances to force a new estimated distance
// See ssManager.CalcPathSegments(..) for more details.
pathData.pathLocationDataList[pathLocIdx].distanceFromPreviousLocation = 0f;
pathData.pathLocationDataList[pathLocIdx].distanceCumulative = 0f;
// Set the default tangent control points
pathData.pathLocationDataList[pathLocIdx].inControlPoint = locationDataToAssign.position + Vector3.left;
pathData.pathLocationDataList[pathLocIdx].outControlPoint = locationDataToAssign.position + Vector3.right;
sscManager.RefreshPathDistances(pathData);
}
}
}
}
}
/// <summary>
/// Zoom to the extent of the selected locations or the current active path
/// </summary>
/// <param name="sceneView"></param>
/// <param name="zoomActivePath"></param>
/// <param name="pathData"></param>
private void ZoomExtent(SceneView sceneView, bool zoomActivePath, PathData pathData)
{
if (sceneView != null && sscManager != null)
{
Camera svCamera = sceneView.camera;
if (svCamera != null)
{
List<LocationData> zoomLocations = null;
if (zoomActivePath)
{
// Get all the assigned Locations on the active Path
int numPathLocationSlots = pathData == null || pathData.pathLocationDataList == null ? 0 : pathData.pathLocationDataList.Count;
if (numPathLocationSlots > 0) { zoomLocations = new List<LocationData>(numPathLocationSlots); }
for (int plIdx = 0; plIdx < numPathLocationSlots; plIdx++)
{
PathLocationData pathLocationData = pathData.pathLocationDataList[plIdx];
if (pathLocationData.locationData != null && !pathLocationData.locationData.isUnassigned)
{
zoomLocations.Add(pathLocationData.locationData);
}
}
}
else
{
// Get selected locations
zoomLocations = sscManager.locationDataList.FindAll(l => l.selectedInSceneView == true);
}
int numSelected = zoomLocations == null ? 0 : zoomLocations.Count;
if (numSelected > 0)
{
// calculate the bounds of the Locations
Bounds selectedBounds = new Bounds();
for (int locIdx = 0; locIdx < numSelected; locIdx++)
{
// Override the 0,0,0 default centre of an empty Bounds struct.
if (locIdx == 0) { selectedBounds.center = zoomLocations[locIdx].position; }
selectedBounds.Encapsulate(zoomLocations[locIdx].position);
}
float size = Mathf.Abs(selectedBounds.size.x);
sceneView.size = size < sscManager.findZoomDistance ? sscManager.findZoomDistance : size;
// Place the scene view camera 8m above the position of the Location
Vector3 pivotPosition = selectedBounds.min;
// Raise scene view camera 25% of the height above the selected points
pivotPosition.y = selectedBounds.max.y + (selectedBounds.extents.y * 1.25f);
// Move the scene view camera 20% of the distance infront of the selected points
pivotPosition.z -= selectedBounds.extents.z * 0.2f;
sceneView.pivot = pivotPosition;
//sceneView.LookAt(selectedBounds.center, Quaternion.Euler(0f, 0f, 0f));
sceneView.LookAt(selectedBounds.center);
//SSCEditorHelper.PositionSceneView(pos, size, GetType());
}
}
}
}
#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
EditorGUIUtility.labelWidth = defaultEditorLabelWidth;
EditorGUIUtility.fieldWidth = defaultEditorFieldWidth;
// BUG - this is currently preventing selection of Locations AND moving them in list
VerifyPathConnections();
#endregion
#region Configure Buttons and Styles
// Set up rich text GUIStyles
if (helpBoxRichText == null)
{
helpBoxRichText = new GUIStyle("HelpBox");
helpBoxRichText.richText = true;
}
if (labelFieldRichText == null)
{
labelFieldRichText = new GUIStyle("Label");
labelFieldRichText.richText = true;
}
if (buttonCompact == null)
{
buttonCompact = new GUIStyle("Button");
buttonCompact.fontSize = 10;
}
if (buttonCompactBoldBlue == null)
{
buttonCompactBoldBlue = new GUIStyle("Button");
buttonCompactBoldBlue.fontSize = 10;
buttonCompactBoldBlue.fontStyle = FontStyle.Bold;
buttonCompactBoldBlue.normal.textColor = Color.blue;
}
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;
}
// 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 (searchTextFieldStyle == null)
{
searchTextFieldStyle = new GUIStyle(GUI.skin.FindStyle("ToolbarSeachTextField"));
searchTextFieldStyle.stretchHeight = false;
searchTextFieldStyle.stretchWidth = false;
searchTextFieldStyle.fontSize = 10;
searchTextFieldStyle.fixedHeight = 16f;
searchTextFieldStyle.fixedWidth = defaultSearchTextFieldWidth;
}
if (searchCancelButtonStyle == null)
{
searchCancelButtonStyle = new GUIStyle(GUI.skin.FindStyle("ToolbarSeachCancelButton"));
}
#endregion
// Read in all the properties
serializedObject.Update();
#region Header
GUILayout.BeginVertical("HelpBox");
EditorGUILayout.LabelField("<b>Sci-Fi Ship Controller</b> Version " + ShipControlModule.SSCVersion + " " + ShipControlModule.SSCBetaVersion, labelFieldRichText);
GUILayout.EndVertical();
EditorGUILayout.LabelField(headerContent, helpBoxRichText);
#endregion
#region Debug Mode
// Place above Locations etc so it is not affected by ScrollView when not many Locations.
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
isDebuggingEnabled = EditorGUILayout.Toggle(debugModeContent, isDebuggingEnabled);
if (isDebuggingEnabled && sscManager != null)
{
#region Projectiles
EditorGUILayout.LabelField(projectileHeaderContent, labelFieldRichText);
// Display the size of the current pool for projectiles that use pooling
projectileTemplatesList = sscManager.ProjectileTemplatesList;
int numProjectileTemplates = projectileTemplatesList == null ? 0 : projectileTemplatesList.Count;
if (numProjectileTemplates > 0)
{
foreach (ProjectileTemplate projectileTemplate in projectileTemplatesList)
{
if (projectileTemplate.projectilePrefab != null)
{
EditorGUILayout.LabelField(projectileTemplate.projectilePrefab.name);
if (projectileTemplate.projectilePrefab.usePooling)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(projectilePoolingContent, labelFieldRichText, GUILayout.Width(defaultEditorLabelWidth));
EditorGUILayout.LabelField(projectileTemplate.currentPoolSize.ToString("0000"), GUILayout.MaxWidth(40f));
EditorGUILayout.LabelField(minmaxPoolSizeContent, GUILayout.MaxWidth(55f));
EditorGUILayout.LabelField(projectileTemplate.projectilePrefab.minPoolSize.ToString("0000"), GUILayout.MaxWidth(32f));
EditorGUILayout.LabelField(projectileTemplate.projectilePrefab.maxPoolSize.ToString("0000"), GUILayout.MaxWidth(defaultEditorFieldWidth));
EditorGUILayout.EndHorizontal();
}
else if (projectileTemplate.projectilePrefab.useECS)
{
#if SSC_ENTITIES
EditorGUILayout.LabelField(projectileDOTSContent);
#else
EditorGUILayout.LabelField(projectileDOTSNotAvailableContent);
#endif
}
}
}
}
else
{
EditorGUILayout.LabelField(noneWithPoolingContent, labelFieldRichText);
}
EditorGUILayout.Space();
#endregion
#region Beams
EditorGUILayout.LabelField(beamHeaderContent, labelFieldRichText);
// Display the size of the current pool for beams that use pooling
beamTemplateList = sscManager.BeamTemplatesList;
int numBeamTemplates = beamTemplateList == null ? 0 : beamTemplateList.Count;
if (numBeamTemplates > 0)
{
foreach (BeamTemplate beamTemplate in beamTemplateList)
{
if (beamTemplate.beamPrefab != null && beamTemplate.beamPrefab.usePooling)
{
EditorGUILayout.LabelField(beamTemplate.beamPrefab.name);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(beamPoolingContent, labelFieldRichText, GUILayout.Width(defaultEditorLabelWidth));
EditorGUILayout.LabelField(beamTemplate.currentPoolSize.ToString("0000"), GUILayout.MaxWidth(40f));
EditorGUILayout.LabelField(minmaxPoolSizeContent, GUILayout.MaxWidth(55f));
EditorGUILayout.LabelField(beamTemplate.beamPrefab.minPoolSize.ToString("0000"), GUILayout.MaxWidth(32f));
EditorGUILayout.LabelField(beamTemplate.beamPrefab.maxPoolSize.ToString("0000"), GUILayout.MaxWidth(defaultEditorFieldWidth));
EditorGUILayout.EndHorizontal();
}
}
}
else
{
EditorGUILayout.LabelField(noneWithPoolingContent, labelFieldRichText);
}
EditorGUILayout.Space();
#endregion
#region Destructs
EditorGUILayout.LabelField(destructHeaderContent, labelFieldRichText);
// Display the size of the current pool for destructs that use pooling
destructTemplateList = sscManager.DestructTemplatesList;
int numDestructTemplates = destructTemplateList == null ? 0 : destructTemplateList.Count;
if (numDestructTemplates > 0)
{
foreach (DestructTemplate destructTemplate in destructTemplateList)
{
if (destructTemplate.destructPrefab != null && destructTemplate.destructPrefab.usePooling)
{
EditorGUILayout.LabelField(destructTemplate.destructPrefab.name);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(destructPoolingContent, labelFieldRichText, GUILayout.Width(defaultEditorLabelWidth));
EditorGUILayout.LabelField(destructTemplate.currentPoolSize.ToString("0000"), GUILayout.MaxWidth(40f));
EditorGUILayout.LabelField(minmaxPoolSizeContent, GUILayout.MaxWidth(55f));
EditorGUILayout.LabelField(destructTemplate.destructPrefab.minPoolSize.ToString("0000"), GUILayout.MaxWidth(32f));
EditorGUILayout.LabelField(destructTemplate.destructPrefab.maxPoolSize.ToString("0000"), GUILayout.MaxWidth(defaultEditorFieldWidth));
EditorGUILayout.EndHorizontal();
}
}
}
else
{
EditorGUILayout.LabelField(noneWithPoolingContent, labelFieldRichText);
}
EditorGUILayout.Space();
#endregion
#region Effects
EditorGUILayout.Space();
EditorGUILayout.LabelField(effectsHeaderContent, labelFieldRichText);
// Display the size of the current pool for effects that use pooling
effectsObjectTemplatesList = sscManager.EffectsObjectTemplatesList;
int numEffectObjectTemplates = effectsObjectTemplatesList == null ? 0 : effectsObjectTemplatesList.Count;
if (numEffectObjectTemplates > 0)
{
foreach (EffectsObjectTemplate effectsObjectTemplate in effectsObjectTemplatesList)
{
if (effectsObjectTemplate.effectsObjectPrefab != null && effectsObjectTemplate.effectsObjectPrefab.usePooling)
{
EditorGUILayout.LabelField(effectsObjectTemplate.effectsObjectPrefab.name);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(effectsPoolingContent, labelFieldRichText, GUILayout.Width(defaultEditorLabelWidth));
EditorGUILayout.LabelField(effectsObjectTemplate.currentPoolSize.ToString("0000"), GUILayout.MaxWidth(40f));
EditorGUILayout.LabelField(minmaxPoolSizeContent, GUILayout.MaxWidth(55f));
EditorGUILayout.LabelField(effectsObjectTemplate.effectsObjectPrefab.minPoolSize.ToString("0000"), GUILayout.MaxWidth(32f));
EditorGUILayout.LabelField(effectsObjectTemplate.effectsObjectPrefab.maxPoolSize.ToString("0000"), GUILayout.MaxWidth(defaultEditorFieldWidth));
EditorGUILayout.EndHorizontal();
}
}
}
else
{
EditorGUILayout.LabelField(noneWithPoolingContent, labelFieldRichText);
}
#endregion
}
EditorGUILayout.EndVertical();
#endregion
#region Help Toolbar
GUILayout.BeginHorizontal();
if (GUILayout.Button(SSCEditorHelper.btnTxtGetSupport, buttonCompact)) { Application.OpenURL(SSCEditorHelper.urlGetSupport); }
if (GUILayout.Button(SSCEditorHelper.btnDiscordContent, buttonCompact)) { Application.OpenURL(SSCEditorHelper.urlDiscordChannel); }
if (GUILayout.Button(SSCEditorHelper.btnHelpContent, buttonCompact)) { Application.OpenURL(sscHelpPDF); }
if (GUILayout.Button(SSCEditorHelper.tutorialsURLContent, buttonCompact)) { Application.OpenURL(SSCEditorHelper.urlTutorials); }
EditorGUILayout.EndHorizontal();
#endregion
#region Grid Tab control
//selectedTabInt = -1;
EditorGUI.BeginChangeCheck();
selectedTabInt = GUILayout.SelectionGrid(selectedTabInt, tabTexts, 3, EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck()) { GUI.FocusControl(null); }
#endregion
#region Locations Tab
if (selectedTabInt == 0)
{
if (sscManager.locationDataList == null)
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
sscManager.locationDataList = new List<LocationData>(5);
// Read in the properties
serializedObject.Update();
}
EditorGUILayout.LabelField(locationHeaderContent, helpBoxRichText);
// Reset button variables
locationDataMoveDownPos = -1;
locationDataInsertPos = -1;
locationDataDeletePos = -1;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
#region Gizmos-Add-Remove Location Buttons
numLocations = locationDataListProp.arraySize;
GUILayout.BeginHorizontal();
EditorGUI.indentLevel += 1;
EditorGUIUtility.fieldWidth = 15f;
EditorGUI.BeginChangeCheck();
isLocationDataListExpandedProp.boolValue = EditorGUILayout.Foldout(isLocationDataListExpandedProp.boolValue, "", foldoutStyleNoLabel);
if (EditorGUI.EndChangeCheck())
{
ExpandList(locationDataListProp, isLocationDataListExpandedProp.boolValue);
}
EditorGUI.indentLevel -= 1;
EditorGUIUtility.fieldWidth = defaultEditorFieldWidth;
EditorGUILayout.LabelField("<color=" + txtColourName + "><b>Locations: " + numFilteredLocations + " of " + numLocations + "</b></color>", labelFieldRichText);
if (numLocations > 0)
{
if (GUILayout.Button(gizmoToggleBtnContent, GUILayout.MaxWidth(22f)))
{
ToggleGizmos(locationDataListProp);
}
}
if (GUILayout.Button("+", GUILayout.MaxWidth(30f)))
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Add Location");
sscManager.locationDataList.Add(new LocationData());
// Read in the properties
serializedObject.Update();
numLocations = locationDataListProp.arraySize;
if (numLocations > 0)
{
// Force new location to be serialized in scene
locationDataProp = locationDataListProp.GetArrayElementAtIndex(numLocations - 1);
locationDataShowInEditorProp = locationDataProp.FindPropertyRelative("showInEditor");
locationDataShowInEditorProp.boolValue = !locationDataShowInEditorProp.boolValue;
// Show the new location
locationDataShowInEditorProp.boolValue = true;
// Select new Locations to make them more visible to user
locationDataProp.FindPropertyRelative("selectedInSceneView").boolValue = true;
locationDataProp.FindPropertyRelative("name").stringValue = "new location " + numLocations.ToString();
}
SceneView.RepaintAll();
}
if (GUILayout.Button("-", GUILayout.MaxWidth(30f)))
{
if (numLocations > 0)
{
// Get the last location in the list
locationDataProp = locationDataListProp.GetArrayElementAtIndex(locationDataListProp.arraySize - 1);
// Perform the delete in one place so that we can clean up the paths too.
locationDataDeletePos = locationDataListProp.arraySize - 1;
}
}
GUILayout.EndHorizontal();
#endregion
#region Location Search Filter and filtering buttons
GUILayout.BeginHorizontal();
GUILayout.Label("", GUILayout.Width(2f));
GUILayout.Label(filterContent, labelFieldRichText, GUILayout.Width(50f));
if (sscManager.editorSearchLocationFilter == null) { sscManager.editorSearchLocationFilter = string.Empty; }
sscManager.editorSearchLocationFilter = GUILayout.TextField(sscManager.editorSearchLocationFilter, searchTextFieldStyle);
// Sometimes the search text is reverse search almost seems invisible (small search icon and cancel cannot be seen)
// Not sure how this occurs.
if (GUILayout.Button("", searchCancelButtonStyle))
{
sscManager.editorSearchLocationFilter = string.Empty;
GUI.FocusControl(null);
}
// Force buttons to be right justified
GUILayoutUtility.GetRect(1f, 1f);
// Toggle selection based on the first filtered one in the list
if (GUILayout.Button(selectBtnContent, buttonCompact, GUILayout.MaxWidth(20f)))
{
ToggleSelectList(locationDataListProp, sscManager.editorSearchLocationFilter);
}
GUILayout.EndHorizontal();
// Provide space between previous controls and next ones
GUILayoutUtility.GetRect(1f, 3f);
#endregion
#region General Location Settings
GUILayout.BeginHorizontal();
GUILayout.Label("", GUILayout.Width(2f));
EditorGUILayout.PropertyField(locationDefaultNormalOffsetProp, locationDefaultNormalOffsetContent);
GUILayout.EndHorizontal();
#endregion
EditorGUILayout.EndVertical();
#region Location List
numLocations = locationDataListProp.arraySize;
numFilteredLocations = 0;
locationScrollPosition = EditorGUILayout.BeginScrollView(locationScrollPosition);
for (int idx = 0; idx < numLocations; idx++)
{
#region Get Location Properties (fields) and Filter if required
locationDataProp = locationDataListProp.GetArrayElementAtIndex(idx);
locationDataNameProp = locationDataProp.FindPropertyRelative("name");
// BUG: Debug Mode appear significantly below the end of the location list.
// This is due to the use of BeginScrollView which seems to have a minimum height/view area.
// Check if the list of Locations is being filtered. Ignore Locations not in the filter
if (!SSCEditorHelper.IsInSearchFilter(locationDataNameProp, sscManager.editorSearchLocationFilter)) { continue; }
numFilteredLocations++;
locationDataPositionProp = locationDataProp.FindPropertyRelative("position");
locationDataShowInEditorProp = locationDataProp.FindPropertyRelative("showInEditor");
locationDataShowGizmosInSceneViewProp = locationDataProp.FindPropertyRelative("showGizmosInSceneView");
locationDataSelectedProp = locationDataProp.FindPropertyRelative("selectedInSceneView");
#endregion
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
#region Location Find/Gizmos/Move/Insert/Delete buttons
EditorGUILayout.BeginHorizontal();
EditorGUI.indentLevel += 1;
// A Foldout with no label must have a style fixedWidth of low non-zero value, and have a small (global) fieldWidth.
EditorGUIUtility.fieldWidth = 15f;
locationDataShowInEditorProp.boolValue = EditorGUILayout.Foldout(locationDataShowInEditorProp.boolValue, GUIContent.none, foldoutStyleNoLabel);
EditorGUIUtility.fieldWidth = defaultEditorFieldWidth;
EditorGUI.indentLevel -= 1;
EditorGUILayout.PropertyField(locationDataSelectedProp, GUIContent.none, GUILayout.Width(15f));
GUILayout.Label((idx + 1).ToString("0000") + " " + locationDataNameProp.stringValue, GUILayout.MaxWidth(125f));
// Force remaining buttons to be right justified
GUILayoutUtility.GetRect(1f, 1f);
if (locationDataShowGizmosInSceneViewProp.boolValue)
{
if (GUILayout.Button(findBtnContent, buttonCompact, GUILayout.MaxWidth(20f)))
{
// Move the scene view camera to the selected point
SSCEditorHelper.PositionSceneView(locationDataPositionProp.vector3Value, sscManager.findZoomDistance, this.GetType());
}
}
// Show Gizmos button
if (locationDataShowGizmosInSceneViewProp.boolValue) { if (GUILayout.Button(gizmoBtnContent, toggleCompactButtonStyleToggled, GUILayout.MaxWidth(22f))) { locationDataShowGizmosInSceneViewProp.boolValue = false; } }
else { if (GUILayout.Button(gizmoBtnContent, toggleCompactButtonStyleNormal, GUILayout.MaxWidth(22f))) { locationDataShowGizmosInSceneViewProp.boolValue = true; } }
// Move down button
if (GUILayout.Button("V", buttonCompact, GUILayout.MaxWidth(20f)) && numLocations > 1) { locationDataMoveDownPos = idx; }
// Create duplicate button
if (GUILayout.Button("I", buttonCompact, GUILayout.MaxWidth(20f))) { locationDataInsertPos = idx; }
// Delete button
if (GUILayout.Button("X", buttonCompact, GUILayout.MaxWidth(20f))) { locationDataDeletePos = idx; }
EditorGUILayout.EndHorizontal();
#endregion
if (locationDataShowInEditorProp.boolValue)
{
EditorGUILayout.PropertyField(locationDataNameProp, locationDataNameContent);
EditorGUILayout.PropertyField(locationDataPositionProp, locationDataPositionContent);
EditorGUILayout.PropertyField(locationDataProp.FindPropertyRelative("isRadarEnabled"), locationDataIsRadarEnabledContent);
EditorGUILayout.PropertyField(locationDataProp.FindPropertyRelative("radarBlipSize"), locationDataRadarBlipSizedContent);
EditorGUILayout.PropertyField(locationDataProp.FindPropertyRelative("factionId"), locationDataFactionIdContent);
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndScrollView();
#endregion Location List
#region Move/Remove/Insert Locations
if (locationDataDeletePos >= 0 || locationDataInsertPos >= 0 || locationDataMoveDownPos >= 0)
{
GUI.FocusControl(null);
// Don't permit multiple operations in the same pass
if (locationDataMoveDownPos >= 0)
{
// Update lists directly rather than using MoveArrayElement to avoid issues with
// the Locations within each Path.
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Move Location in List");
LocationData moveLocationData = sscManager.locationDataList[locationDataMoveDownPos];
if (locationDataMoveDownPos < locationDataListProp.arraySize - 1)
{
sscManager.locationDataList.RemoveAt(locationDataMoveDownPos);
sscManager.locationDataList.Insert(locationDataMoveDownPos + 1, moveLocationData);
}
else
{
sscManager.locationDataList.Insert(0, moveLocationData);
sscManager.locationDataList.RemoveAt(sscManager.locationDataList.Count - 1);
}
serializedObject.Update();
locationDataMoveDownPos = -1;
}
else if (locationDataInsertPos >= 0)
{
// Apply property changes before potential list changes
serializedObject.ApplyModifiedProperties();
sscManager.locationDataList.Insert(locationDataInsertPos, new LocationData(sscManager.locationDataList[locationDataInsertPos]));
serializedObject.Update();
// Hide original locationData and unselect it
locationDataListProp.GetArrayElementAtIndex(locationDataInsertPos + 1).FindPropertyRelative("showInEditor").boolValue = false;
locationDataListProp.GetArrayElementAtIndex(locationDataInsertPos + 1).FindPropertyRelative("selectedInSceneView").boolValue = false;
locationDataShowInEditorProp = locationDataListProp.GetArrayElementAtIndex(locationDataInsertPos).FindPropertyRelative("showInEditor");
// Update Location with a unique hashcode
locationDataGUIDHashProp = locationDataListProp.GetArrayElementAtIndex(locationDataInsertPos).FindPropertyRelative("guidHash");
locationDataGUIDHashProp.intValue = SSCMath.GetHashCodeFromGuid();
locationDataNameProp = locationDataListProp.GetArrayElementAtIndex(locationDataInsertPos).FindPropertyRelative("name");
locationDataNameProp.stringValue += " (dup)";
// Force new locationData to be serialized in scene
locationDataShowInEditorProp.boolValue = !locationDataShowInEditorProp.boolValue;
// Show inserted duplicate locationData
locationDataShowInEditorProp.boolValue = true;
locationDataInsertPos = -1;
}
else if (locationDataDeletePos >= 0)
{
// In U2019.4+ DisplayDialog seems to trigger another OnInspectorGUI() and locationDataDeletePos is reset to -1.
int _deleteIndex = locationDataDeletePos;
if (EditorUtility.DisplayDialog("Delete Location " + (_deleteIndex + 1) + "?", "Location " + (_deleteIndex + 1) + " will be deleted\n\nThis action will remove the location from the list and all the paths.", "Delete Now", "Cancel"))
{
DeleteLocationFromPaths(pathDataListProp, locationDataListProp.GetArrayElementAtIndex(_deleteIndex).FindPropertyRelative("guidHash").intValue, true);
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Delete Location");
sscManager.locationDataList.RemoveAt(_deleteIndex);
serializedObject.Update();
}
locationDataDeletePos = -1;
}
SceneView.RepaintAll();
serializedObject.ApplyModifiedProperties();
GUIUtility.ExitGUI();
}
#endregion
}
#endregion Locations Tab
#region Paths Tab
else if (selectedTabInt == 1)
{
if (sscManager.pathDataList == null)
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
sscManager.pathDataList = new List<PathData>(5);
// Read in the properties
serializedObject.Update();
}
EditorGUILayout.LabelField(pathDataHeaderContent, helpBoxRichText);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
#region Add-Remove-Import Path Buttons
numPaths = pathDataListProp.arraySize;
GUILayout.BeginHorizontal();
// Indent the Foldout 7px
GUILayout.Label("", GUILayout.Width(7f));
// Give a 2px gap between Foldout and Label
EditorGUIUtility.fieldWidth = 2f;
EditorGUI.BeginChangeCheck();
isPathDataListExpandedProp.boolValue = EditorGUILayout.Foldout(isPathDataListExpandedProp.boolValue, "", foldoutStyleNoLabel);
if (EditorGUI.EndChangeCheck())
{
ExpandList(pathDataListProp, isPathDataListExpandedProp.boolValue);
}
EditorGUIUtility.fieldWidth = defaultEditorFieldWidth;
EditorGUILayout.LabelField("<color=" + txtColourName + "><b>Paths: " + numFilteredPaths + " of " + numPaths + "</b></color>", labelFieldRichText);
if (GUILayout.Button(pathDataImportJsonContent, GUILayout.MaxWidth(65f)))
{
string importPath = string.Empty, importFileName = string.Empty;
if (SSCEditorHelper.GetFilePathFromUser("Import Path Data", SSCSetup.sscFolder, new string[] { "JSON", "json" }, false, ref importPath, ref importFileName))
{
PathData importedPathData = SSCManager.ImportPathDataFromJson(importPath, importFileName);
if (importedPathData != null)
{
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Import Path");
// Check to see if Locations already exist
numPathLocations = importedPathData.pathLocationDataList == null ? 0 : importedPathData.pathLocationDataList.Count;
for (int pathLocIdx = 0; pathLocIdx < numPathLocations; pathLocIdx++)
{
PathLocationData pathLocationData = importedPathData.pathLocationDataList[pathLocIdx];
if (pathLocationData != null)
{
// If the Location already exists, re-link it to the imported Path
LocationData existingLocationData = sscManager.locationDataList.Find(loc => loc.guidHash == pathLocationData.locationData.guidHash);
if (existingLocationData != null)
{
pathLocationData.locationData = existingLocationData;
}
else
{
// Create new Locations for ones that don't already exist
sscManager.locationDataList.Add(pathLocationData.locationData);
}
}
}
sscManager.pathDataList.Add(importedPathData);
serializedObject.Update();
}
}
}
if (numPaths > 0)
{
if (GUILayout.Button(gizmoToggleBtnContent, GUILayout.MaxWidth(22f)))
{
ToggleGizmos(pathDataListProp);
}
}
if (GUILayout.Button("+", GUILayout.MaxWidth(30f)))
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
GUI.FocusControl(null);
Undo.RecordObject(sscManager, "Add Path");
sscManager.pathDataList.Add(new PathData() { locationDefaultNormalOffset = sscManager.locationDefaultNormalOffset });
// Read in the properties
serializedObject.Update();
numPaths = pathDataListProp.arraySize;
if (numPaths > 0)
{
// Force new path to be serialized in scene
pathDataProp = pathDataListProp.GetArrayElementAtIndex(numPaths - 1);
pathDataShowInEditorProp = pathDataProp.FindPropertyRelative("showInEditor");
pathDataShowInEditorProp.boolValue = !pathDataShowInEditorProp.boolValue;
// Show the new path
pathDataShowInEditorProp.boolValue = true;
// Make it the active path
pathDataActiveGUIDHashProp.intValue = pathDataProp.FindPropertyRelative("guidHash").intValue;
}
SceneView.RepaintAll();
}
if (GUILayout.Button("-", GUILayout.MaxWidth(30f)))
{
if (numPaths > 0)
{
// Get the last path in the list
pathDataProp = pathDataListProp.GetArrayElementAtIndex(pathDataListProp.arraySize - 1);
if (EditorUtility.DisplayDialog("Delete Path?", "Do you wish to delete Path " + (pathDataListProp.arraySize).ToString("00") + "?", "Delete Now", "Cancel"))
{
GUI.FocusControl(null);
pathDataListProp.arraySize -= 1;
serializedObject.ApplyModifiedProperties();
SceneView.RepaintAll();
}
GUIUtility.ExitGUI();
}
}
GUILayout.EndHorizontal();
#endregion
#region Path Search Filter and filtering buttons
GUILayout.BeginHorizontal();
GUILayout.Label("", GUILayout.Width(2f));
GUILayout.Label(filterContent, labelFieldRichText, GUILayout.Width(50f));
if (sscManager.editorSearchPathFilter == null) { sscManager.editorSearchPathFilter = string.Empty; }
sscManager.editorSearchPathFilter = GUILayout.TextField(sscManager.editorSearchPathFilter, searchTextFieldStyle);
// Sometimes the search text is reverse search almost seems invisible (small search icon and cancel cannot be seen)
// Not sure how this occurs.
if (GUILayout.Button("", searchCancelButtonStyle))
{
sscManager.editorSearchPathFilter = string.Empty;
GUI.FocusControl(null);
}
//// Force buttons to be right justified
//GUILayoutUtility.GetRect(1f, 1f);
//// Toggle selection based on the first filtered one in the list
//if (GUILayout.Button(selectBtnContent, buttonCompact, GUILayout.MaxWidth(20f)))
//{
// ToggleSelectList(pathDataListProp, sscManager.editorSearchPathFilter);
//}
GUILayout.EndHorizontal();
// Provide space between previous controls and next ones
GUILayoutUtility.GetRect(1f, 3f);
#endregion
EditorGUILayout.EndVertical();
#region Path List
// Reset Path button variables
pathDataMoveDownPos = -1;
pathDataInsertPos = -1;
pathDataDeletePos = -1;
numPaths = pathDataListProp.arraySize;
numFilteredPaths = 0;
for (int pathIdx = 0; pathIdx < numPaths; pathIdx++)
{
#region Get Path Properties and Filter if required
pathDataProp = pathDataListProp.GetArrayElementAtIndex(pathIdx);
pathDataNameProp = pathDataProp.FindPropertyRelative("name");
// Check if the list of Paths is being filtered. Ignore Locations not in the filter
if (!SSCEditorHelper.IsInSearchFilter(pathDataNameProp, sscManager.editorSearchPathFilter)) { continue; }
numFilteredPaths++;
pathDataGUIDHashProp = pathDataProp.FindPropertyRelative("guidHash");
pathDataShowInEditorProp = pathDataProp.FindPropertyRelative("showInEditor");
pathDataShowLocationsInEditorProp = pathDataProp.FindPropertyRelative("showLocationsInEditor");
pathDataShowInSceneViewProp = pathDataProp.FindPropertyRelative("showGizmosInSceneView");
pathDataIsClosedCircuitProp = pathDataProp.FindPropertyRelative("isClosedCircuit");
pathDataLineColourProp = pathDataProp.FindPropertyRelative("pathLineColour");
pathDataShowNumberLabelsProp = pathDataProp.FindPropertyRelative("showPointNumberLabelsInScene");
pathDataShowNameLabelsProp = pathDataProp.FindPropertyRelative("showPointNameLabelsInScene");
#endregion
GUILayout.BeginVertical(EditorStyles.helpBox);
#region Path Active/Move/Insert/Delete buttons
GUILayout.BeginHorizontal();
GUILayout.Label("", GUILayout.Width(7f));
pathDataShowInEditorProp.boolValue = EditorGUILayout.Foldout(pathDataShowInEditorProp.boolValue, (pathIdx + 1).ToString("000") + " " + pathDataNameProp.stringValue);
#region Active Path
// Bold the "A" text on the button if this is the active layer
if (GUILayout.Button(pathDataActiveContent, pathDataActiveGUIDHashProp.intValue == pathDataGUIDHashProp.intValue ? buttonCompactBoldBlue : buttonCompact, GUILayout.Width(20f)))
{
pathDataActiveGUIDHashProp.intValue = pathDataGUIDHashProp.intValue;
}
#endregion
// Show Gizmos button
if (pathDataShowInSceneViewProp.boolValue) { if (GUILayout.Button(gizmoBtnContent, toggleCompactButtonStyleToggled, GUILayout.MaxWidth(22f))) { pathDataShowInSceneViewProp.boolValue = false; } }
else { if (GUILayout.Button(gizmoBtnContent, toggleCompactButtonStyleNormal, GUILayout.MaxWidth(22f))) { pathDataShowInSceneViewProp.boolValue = true; } }
// Move down button
if (GUILayout.Button("V", buttonCompact, GUILayout.MaxWidth(20f)) && numPaths > 1) { pathDataMoveDownPos = pathIdx; }
// Create duplicate button
if (GUILayout.Button("I", buttonCompact, GUILayout.MaxWidth(20f))) { pathDataInsertPos = pathIdx; }
// Delete button
if (GUILayout.Button("X", buttonCompact, GUILayout.MaxWidth(20f))) { pathDataDeletePos = pathIdx; }
GUILayout.EndHorizontal();
#endregion
if (pathDataShowInEditorProp.boolValue)
{
#region General Path Settings
EditorGUILayout.PropertyField(pathDataNameProp, pathDataNameContent);
GUILayout.BeginHorizontal();
if (pathDataActiveGUIDHashProp.intValue == pathDataGUIDHashProp.intValue)
{
if (GUILayout.Button(pathDataActivatedContent, buttonCompactBoldBlue, GUILayout.Width(defaultEditorLabelWidth - 5f)))
{
pathDataActiveGUIDHashProp.intValue = -1;
}
}
else if (GUILayout.Button(pathDataActivateContent, buttonCompact, GUILayout.Width(defaultEditorLabelWidth - 5f)))
{
pathDataActiveGUIDHashProp.intValue = pathDataGUIDHashProp.intValue;
// Auto-show path in scene
pathDataShowInSceneViewProp.boolValue = true;
// In case where Path name has the focus, disable it so that when user presses +/= key in the scene it doesn't
// just update the path name.
GUI.FocusControl(null);
}
#region PathData Export To Json
if (GUILayout.Button(pathDataExportJsonContent, buttonCompact, GUILayout.Width(100f)))
{
string exportPath = EditorUtility.SaveFilePanel("Save Path Data", "Assets", sscManager.pathDataList[pathIdx].name, "json");
if (SSCManager.SavePathDataAsJson(sscManager.pathDataList[pathIdx], exportPath))
{
// Check if path is in Project folder
if (exportPath.Contains(Application.dataPath))
{
// Get the folder to highlight in the Project folder
string folderPath = "Assets" + System.IO.Path.GetDirectoryName(exportPath).Replace(Application.dataPath, "");
// Get the json file in the Project folder
exportPath = "Assets" + exportPath.Replace(Application.dataPath, "");
AssetDatabase.ImportAsset(exportPath);
SSCEditorHelper.HighlightFolderInProjectWindow(folderPath, true, true);
}
Debug.Log("PathData exported to " + exportPath);
}
GUIUtility.ExitGUI();
}
#endregion
#region Snap To Mesh
if (GUILayout.Button(new GUIContent(pathDataSnapToMeshContent), buttonCompact, GUILayout.Width(100f)))
{
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Path Snap To Mesh");
sscManager.SnapPathToMesh(sscManager.pathDataList[pathIdx], Vector3.up, ~0, true);
serializedObject.Update();
SceneView.RepaintAll();
}
#endregion
GUILayout.EndHorizontal();
EditorGUILayout.LabelField(pathDataTotalDistanceContent, new GUIContent(pathDataProp.FindPropertyRelative("splineTotalDistance").floatValue.ToString("0.00")));
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(pathDataIsClosedCircuitProp, pathDataIsClosedCircuitContent);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
// Potentially adjust tangents when circuit is closed...
sscManager.RefreshPathDistances(sscManager.pathDataList[pathIdx]);
serializedObject.Update();
}
// Don't allow Number and Name labels to be displayed at the same time
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(pathDataShowNumberLabelsProp, pathDataShowPointNumberLabelsInSceneContent);
if (EditorGUI.EndChangeCheck() && pathDataShowNumberLabelsProp.boolValue)
{
pathDataShowNameLabelsProp.boolValue = false;
}
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(pathDataShowNameLabelsProp, pathDataShowPointNameLabelsInSceneContent);
if (EditorGUI.EndChangeCheck() && pathDataShowNameLabelsProp.boolValue)
{
pathDataShowNumberLabelsProp.boolValue = false;
}
EditorGUILayout.PropertyField(pathDataProp.FindPropertyRelative("showDistancesInScene"), pathDataShowDistanceLabelsInSceneContent);
// We store the colour as a vector4 so it can be more portable so use a ColorField rather than a PropertyField.
pathDataLineColourProp.vector4Value = EditorGUILayout.ColorField(pathDataLineColourContent, pathDataLineColourProp.vector4Value);
EditorGUILayout.PropertyField(pathDataProp.FindPropertyRelative("locationDefaultNormalOffset"), pathDataLocationDefaultNormalOffsetContent);
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField(pathDataLocationSnapMinMaxHeightContent, GUILayout.Width(defaultEditorLabelWidth));
EditorGUILayout.PropertyField(pathDataProp.FindPropertyRelative("snapMinHeight"), GUIContent.none);
EditorGUILayout.PropertyField(pathDataProp.FindPropertyRelative("snapMaxHeight"), GUIContent.none);
GUILayout.EndHorizontal();
#endregion
pathLocationDataListProp = pathDataProp.FindPropertyRelative("pathLocationDataList");
if (pathLocationDataListProp != null)
{
#region Add-Remove Path Location slot, Modify Attribute, Reverse Buttons
// Reset path location slot variables
pathDataLocationMoveDownPos = -1;
pathDataLocationInsertPos = -1;
pathDataLocationDeletePos = -1;
numPathLocations = pathLocationDataListProp.arraySize;
GUILayout.BeginHorizontal();
GUILayout.Label("", GUILayout.Width(7f));
pathDataShowLocationsInEditorProp.boolValue = EditorGUILayout.Foldout(pathDataShowLocationsInEditorProp.boolValue, "Path Locations: " + numPathLocations.ToString("000"));
// Toggle modify selected path location attributes
if (GUILayout.Button(modifyToggleBtnContent, GUILayout.MaxWidth(22f)))
{
GUI.FocusControl(null);
isModifySelectedMode = !isModifySelectedMode;
if (isModifySelectedMode)
{
modifySelectedPosY = GetPosYSelectList(pathLocationDataListProp);
}
}
if (GUILayout.Button(reversePathBtnContent, GUILayout.MaxWidth(40f)))
{
GUI.FocusControl(null);
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Reverse Path");
sscManager.ReversePath(sscManager.pathDataList[pathIdx]);
SceneView.RepaintAll();
GUIUtility.ExitGUI();
}
if (GUILayout.Button("+", GUILayout.MaxWidth(30f)))
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Add Path Location slot");
sscManager.pathDataList[pathIdx].pathLocationDataList.Add(new PathLocationData());
// Read in the properties
serializedObject.Update();
numPathLocations = pathLocationDataListProp.arraySize;
if (numPathLocations > 0)
{
// Force new path location slot to be serialized in scene
pathLocationDataProp = pathLocationDataListProp.GetArrayElementAtIndex(numPathLocations - 1);
pathDataShowInEditorProp = pathLocationDataProp.FindPropertyRelative("showInEditor");
pathDataShowInEditorProp.boolValue = !pathDataShowInEditorProp.boolValue;
// Show the new path location slot
pathDataShowInEditorProp.boolValue = true;
pathDataShowLocationsInEditorProp.boolValue = true;
}
SceneView.RepaintAll();
}
if (GUILayout.Button("-", GUILayout.MaxWidth(30f)))
{
if (numPathLocations > 0)
{
pathDataLocationDeletePos = pathLocationDataListProp.arraySize - 1;
}
}
GUILayout.EndHorizontal();
#endregion
#region Modify Selected
if (isModifySelectedMode)
{
GUILayout.BeginHorizontal();
modifySelectedPosY = EditorGUILayout.FloatField(modifySelectedPosYContent, modifySelectedPosY);
if (GUILayout.Button("Change", GUILayout.Width(65f)))
{
SetPosYSelectList(sscManager.pathDataList[pathIdx], modifySelectedPosY);
isModifySelectedMode = false;
GUIUtility.ExitGUI();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
modifySelectedAddPosY = EditorGUILayout.FloatField(modifySelectedAddPosYContent, modifySelectedAddPosY);
if (GUILayout.Button("Change", GUILayout.Width(65f)))
{
AddPosYSelectList(sscManager.pathDataList[pathIdx], modifySelectedAddPosY);
isModifySelectedMode = false;
GUIUtility.ExitGUI();
}
GUILayout.EndHorizontal();
}
#endregion
#region Path Location List
numPathLocations = pathLocationDataListProp.arraySize;
if (pathDataShowLocationsInEditorProp.boolValue)
{
for (int pathLocIdx = 0; pathLocIdx < numPathLocations; pathLocIdx++)
{
#region Get Path Location Data properties (fields)
// Get the PathLocationData class instance from the list
pathLocationDataProp = pathLocationDataListProp.GetArrayElementAtIndex(pathLocIdx);
// Get the LocationData class reference from within the PathLocationData class instance
pathLocationDataLocationProp = pathLocationDataProp.FindPropertyRelative("locationData");
// Get the properties (fields) of the LocationData within the PathLocationData class instance
pathDataLocationNameProp = pathLocationDataLocationProp.FindPropertyRelative("name");
pathDataLocationPositionProp = pathLocationDataLocationProp.FindPropertyRelative("position");
pathDataLocationSelectedProp = pathLocationDataLocationProp.FindPropertyRelative("selectedInSceneView");
pathDataLocationGUIDHashProp = pathLocationDataLocationProp.FindPropertyRelative("guidHash");
pathDataLocationUnassignedProp = pathLocationDataLocationProp.FindPropertyRelative("isUnassigned");
pathDataLocationShowGizmosInSceneViewProp = pathLocationDataLocationProp.FindPropertyRelative("showGizmosInSceneView");
#endregion
GUILayout.BeginHorizontal();
// Find in scene if it is assigned to Location from the SSSManager.locationDataList (not just a member of the Path)
if (GUILayout.Button(findBtnContent, buttonCompact, GUILayout.MaxWidth(20f)) && !pathDataLocationUnassignedProp.boolValue)
{
// Move the scene view camera to the selected point
SSCEditorHelper.PositionSceneView(pathDataLocationPositionProp.vector3Value, sscManager.findZoomDistance, this.GetType());
// Show it in the scene
pathDataLocationShowGizmosInSceneViewProp.boolValue = true;
// Select it in the scene (doesn't unselect other locations)
pathDataLocationSelectedProp.boolValue = true;
}
// When using VerifyPathConnections() for some reason cannot update the Properties.
EditorGUI.BeginChangeCheck();
pathLocationDataIsSelected = EditorGUILayout.Toggle(GUIContent.none, pathDataLocationSelectedProp.boolValue, GUILayout.Width(15f));
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Path Location Selection");
sscManager.pathDataList[pathIdx].pathLocationDataList[pathLocIdx].locationData.selectedInSceneView = pathLocationDataIsSelected;
serializedObject.Update();
SceneView.RepaintAll();
}
if (GUILayout.Button("..", buttonCompact, GUILayout.MaxWidth(20f)))
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
int selectedIdx = sscManager.locationDataList.FindIndex(loc => loc.guidHash == pathDataLocationGUIDHashProp.intValue);
// TODO - filter the drop down list by default (could be 100s or 1000s of locations in the scene).
// Create a drop down list of all the locations
GenericMenu dropdown = new GenericMenu();
for (int i = 0; i < sscManager.locationDataList.Count; i++)
{
// Replace space #/%/& with different chars as Unity treats them as SHIFT/CTRL/ALT in menus.
string _locationName = string.IsNullOrEmpty(sscManager.locationDataList[i].name) ? "No Name" : sscManager.locationDataList[i].name.Replace(" #", "_#").Replace(" &", " &&").Replace(" %", "_%");
dropdown.AddItem(new GUIContent(_locationName), i == selectedIdx, UpdatePathLocation, new Vector3Int(pathDataGUIDHashProp.intValue, pathDataLocationGUIDHashProp.intValue, sscManager.locationDataList[i].guidHash));
}
dropdown.ShowAsContext();
SceneView.RepaintAll();
serializedObject.Update();
}
EditorGUILayout.LabelField(string.IsNullOrEmpty(pathDataLocationNameProp.stringValue) ? "unknown" : pathDataLocationNameProp.stringValue);
// Move down button
if (GUILayout.Button("V", buttonCompact, GUILayout.MaxWidth(20f)) && numPathLocations > 1) { pathDataLocationMoveDownPos = pathLocIdx; }
// Create duplicate button
if (GUILayout.Button("I", buttonCompact, GUILayout.MaxWidth(20f))) { pathDataLocationInsertPos = pathLocIdx; }
if (GUILayout.Button("X", buttonCompact, GUILayout.MaxWidth(20f))) { pathDataLocationDeletePos = pathLocIdx; }
GUILayout.EndHorizontal();
}
}
#endregion Path Location List
#region Move/Remove/Insert Path Location
if (pathDataLocationDeletePos >= 0 || pathDataLocationInsertPos >= 0 || pathDataLocationMoveDownPos >= 0)
{
GUI.FocusControl(null);
// Don't permit multiple operations in the same pass
if (pathDataLocationMoveDownPos >= 0)
{
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Move Location in Path List");
PathLocationData movePathLocationData = sscManager.pathDataList[pathIdx].pathLocationDataList[pathDataLocationMoveDownPos];
if (pathDataLocationMoveDownPos < pathLocationDataListProp.arraySize - 1)
{
sscManager.pathDataList[pathIdx].pathLocationDataList.RemoveAt(pathDataLocationMoveDownPos);
sscManager.pathDataList[pathIdx].pathLocationDataList.Insert(pathDataLocationMoveDownPos + 1, movePathLocationData);
}
else
{
sscManager.pathDataList[pathIdx].pathLocationDataList.Insert(0, movePathLocationData);
sscManager.pathDataList[pathIdx].pathLocationDataList.RemoveAt(sscManager.pathDataList[pathIdx].pathLocationDataList.Count - 1);
}
sscManager.RefreshPathDistances(sscManager.pathDataList[pathIdx]);
serializedObject.Update();
pathDataLocationMoveDownPos = -1;
}
else if (pathDataLocationInsertPos >= 0)
{
// Apply property changes before potential list changes
serializedObject.ApplyModifiedProperties();
// Insert a new default location. The user will replace this with one from the list of Locations
// For locations in a path, there is no advantage of duplicated an existing location slot
Undo.RecordObject(sscManager, "Insert Path Location slot");
sscManager.pathDataList[pathIdx].pathLocationDataList.Insert(pathDataLocationInsertPos, new PathLocationData());
serializedObject.Update();
// Hide original pathDataLocation
pathLocationDataListProp.GetArrayElementAtIndex(pathDataLocationInsertPos + 1).FindPropertyRelative("showInEditor").boolValue = false;
SerializedProperty pathLocationDataInsertedProp = pathLocationDataListProp.GetArrayElementAtIndex(pathDataLocationInsertPos);
pathDataLocationShowInEditorProp = pathLocationDataInsertedProp.FindPropertyRelative("showInEditor");
pathLocationDataInsertedProp.FindPropertyRelative("locationData").FindPropertyRelative("isUnassigned").boolValue = true;
// Force new pathDataLocation to be serialized in scene
pathDataLocationShowInEditorProp.boolValue = !pathDataLocationShowInEditorProp.boolValue;
// Show inserted duplicate pathDataLocation
pathDataLocationShowInEditorProp.boolValue = true;
pathDataLocationInsertPos = -1;
}
else if (pathDataLocationDeletePos >= 0)
{
// In U2019.4+ DisplayDialog seems to trigger another OnInspectorGUI() and pathDataLocationDeletePos is reset to -1.
int _deleteIndex = pathDataLocationDeletePos;
int _pathIndex = pathIdx;
// Get the last Path Location slot in the list
pathLocationDataProp = pathLocationDataListProp.GetArrayElementAtIndex(_deleteIndex);
if (SSCEditorHelper.PromptForDelete("Delete Path Location slot?", "Do you wish to delete Path Location slot " + (_deleteIndex+1).ToString("00") + "?"))
{
pathDataShowLocationsInEditorProp.boolValue = true;
// Change the list rather than use DeleteArrayElementAtIndex which has issues with undo.
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscManager, "Delete Path Location Slot");
sscManager.pathDataList[_pathIndex].pathLocationDataList.RemoveAt(_deleteIndex);
sscManager.RefreshPathDistances(sscManager.pathDataList[_pathIndex]);
pathDataLocationDeletePos = -1;
SceneView.RepaintAll();
GUIUtility.ExitGUI();
//serializedObject.Update();
}
}
SceneView.RepaintAll();
}
#endregion Move/Remove/Insert Path Location
}
}
// 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 (pathDataLocationDeletePos >= 0 ) { GUILayout.BeginVertical(); }
if (pathDataLocationDeletePos >= 0 ) { GUIUtility.ExitGUI(); }
#endif
GUILayout.EndVertical();
}
#endregion Path List
#region Move/Remove/Insert Paths
if (pathDataDeletePos >= 0 || pathDataInsertPos >= 0 || pathDataMoveDownPos >= 0)
{
GUI.FocusControl(null);
// Don't permit multiple operations in the same pass
if (pathDataMoveDownPos >= 0)
{
// Move down one position, or wrap round to start of list
if (pathDataMoveDownPos < pathDataListProp.arraySize - 1)
{
pathDataListProp.MoveArrayElement(pathDataMoveDownPos, pathDataMoveDownPos + 1);
}
else { pathDataListProp.MoveArrayElement(pathDataMoveDownPos, 0); }
pathDataMoveDownPos = -1;
}
else if (pathDataInsertPos >= 0)
{
// Apply property changes before potential list changes
serializedObject.ApplyModifiedProperties();
sscManager.pathDataList.Insert(pathDataInsertPos, new PathData(sscManager.pathDataList[pathDataInsertPos]));
serializedObject.Update();
// Hide original pathData
pathDataListProp.GetArrayElementAtIndex(pathDataInsertPos + 1).FindPropertyRelative("showInEditor").boolValue = false;
pathDataShowInEditorProp = pathDataListProp.GetArrayElementAtIndex(pathDataInsertPos).FindPropertyRelative("showInEditor");
// Update Path with a unique hashcode
pathDataGUIDHashProp = pathDataListProp.GetArrayElementAtIndex(pathDataInsertPos).FindPropertyRelative("guidHash");
pathDataGUIDHashProp.intValue = SSCMath.GetHashCodeFromGuid();
// Force new pathData to be serialized in scene
pathDataShowInEditorProp.boolValue = !pathDataShowInEditorProp.boolValue;
// Show inserted duplicate pathData
pathDataShowInEditorProp.boolValue = true;
pathDataInsertPos = -1;
}
else if (pathDataDeletePos >= 0)
{
// In U2019.4+ DisplayDialog seems to trigger another OnInspectorGUI() and pathDataDeletePos is reset to -1.
int _deleteIndex = pathDataDeletePos;
if (EditorUtility.DisplayDialog("Delete Path " + (_deleteIndex + 1).ToString("00") + "?", "Path " + (_deleteIndex + 1).ToString("00") + " will be deleted\n\nThis action will remove the path from the list and cannot be undone.", "Delete Now", "Cancel"))
{
pathDataListProp.DeleteArrayElementAtIndex(_deleteIndex);
pathDataDeletePos = -1;
}
}
SceneView.RepaintAll();
}
#endregion
}
#endregion Paths Tab
#region Options Tab
else if (selectedTabInt == 2)
{
GUILayout.BeginVertical(EditorStyles.helpBox);
// Location Options
GUILayout.Label(optionLocationContent, labelFieldRichText);
EditorGUILayout.PropertyField(isAutosizeLocationGizmoProp, optionIsAutosizeLocationGizmoContent);
EditorGUILayout.PropertyField(locationGizmoColourProp, optionLocationGizmoColourContent);
EditorGUILayout.PropertyField(findZoomDistanceProp, optionFindZoomDistanceContent);
// Path Options
GUILayout.Label(optionPathContent, labelFieldRichText);
EditorGUILayout.PropertyField(defaultPathControlGizmoColourProp, optionPathControlGizmoColourContent);
EditorGUILayout.PropertyField(pathDisplayResolutionProp, optionPathDisplayResolutionContent);
EditorGUILayout.PropertyField(pathPrecisionProp, optionPathPrecisionContent);
EditorGUILayout.PropertyField(defaultPathControlOffsetProp, optionPathControlOffsetContent);
//// Radar Options
//GUILayout.Label(optionRadarContent, labelFieldRichText);
GUILayout.EndVertical();
}
#endregion
// Apply property changes
serializedObject.ApplyModifiedProperties();
}
#endregion
}
}