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(SSCMovingPlatform))]
public class SSCMovingPlatformEditor : Editor
{
#region Static Strings
#endregion
#region Custom Editor private variables
private SSCMovingPlatform sscMovingPlatform;
private bool isStylesInitialised = false;
private bool isSceneModified = false;
// Formatting and style variables
private string txtColourName = "Black";
private Color defaultTextColour = Color.black;
private string labelText;
private GUIStyle labelFieldRichText;
private GUIStyle headingFieldRichText;
private GUIStyle helpBoxRichText;
private GUIStyle buttonCompact;
private GUIStyle foldoutStyleNoLabel;
//private Color separatorColor = new Color();
private float defaultEditorLabelWidth = 0f;
private float defaultEditorFieldWidth = 0f;
private bool isClipPlaying = false;
private int positionMoveDownPos = -1;
private int positionInsertPos = -1;
private int positionDeletePos = -1;
#endregion
#region GUIContent - Headers
private readonly static GUIContent headerContent = new GUIContent("This module enables you to control the moment and rotation of a platform");
#endregion
#region GUIContent - General
private readonly static GUIContent resetBtnContent = new GUIContent("R", "Reset to default setting");
private readonly static GUIContent initialiseOnAwakeContent = new GUIContent(" Initialise on Awake", "If enabled, Initialise() will be called as soon as Awake() runs. This should be disabled if you want to control when the SCC Moving Platform is enabled through code.");
private readonly static GUIContent moveContent = new GUIContent(" Move", "Does the platform move?");
private readonly static GUIContent useRelativePositionsContent = new GUIContent(" Relative Positions", "Use positions relative to the initial gameobject position, rather than absolute world space positions.");
private readonly static GUIContent averageMoveSpeedContent = new GUIContent(" Average Move Speed", "Average movement speed of the platform in metres per second");
private readonly static GUIContent startWaitTimeContent = new GUIContent(" Start Wait Time", "The time the platform waits at the first position");
private readonly static GUIContent endWaitTimeContent = new GUIContent(" End Wait Time", "The time the platform waits at the last position");
private readonly static GUIContent movementProfileContent = new GUIContent(" Movement Profile", "The *profile* of the platform's movement. Use this to make the movement more or less smooth.");
private readonly static GUIContent smoothStartTimeContent = new GUIContent(" Smooth Start Time", "The maximum time it takes the platform to come to resume normal speed when smooth start is used with StartPlatform().");
private readonly static GUIContent smoothStopTimeContent = new GUIContent(" Smooth Stop Time", "The maximum time it takes the platform to come to a stop when smooth stop is used with StopPlatform().");
private readonly static GUIContent rotateContent = new GUIContent(" Rotate", "Does the platform rotate?");
private readonly static GUIContent startingRotationContent = new GUIContent(" Starting Rotation", "The starting rotation of the platform in degrees");
private readonly static GUIContent rotationAxisContent = new GUIContent(" Rotation Axis", "The axis of rotation of the platform.");
private readonly static GUIContent rotationSpeedContent = new GUIContent(" Rotation Speed", "The rotational speed of the platform in degrees per second.");
private readonly static GUIContent moveUpdateTypeContent = new GUIContent(" Move Update Type", "The update loop or timing to use for moving or rotating the platform");
#endregion
#region GUIContent - Audio
private readonly static GUIContent audioSourceContent = new GUIContent(" Audio Source", "The audio source containing the clip to play for the platform. Must be a child of the platform gameobject.");
private readonly static GUIContent audioNewContent = new GUIContent("New", "Create a new child audio source on the platform");
private readonly static GUIContent audioListenContent = new GUIContent("L", "Listen to the clip play once. Press again to stop (any clip) playing.");
private readonly static GUIContent audioOverallVolumeContent = new GUIContent(" Overall Audio Volume", "Overall volume of sound for the platform");
private readonly static GUIContent audioInTransitClipContent = new GUIContent(" In Transit Sound", "The sound that is played while the platform is moving");
private readonly static GUIContent audioArrivedStartClipContent = new GUIContent(" Arrived Start Sound", "The sound that is played when the platform arrives at the first position. This does not play when it is first initialised.");
private readonly static GUIContent audioArrivedEndClipContent = new GUIContent(" Arrived End Sound", "The sound that is played when the platform arrives at the last position");
private readonly static GUIContent audioArrivedStartVolumeContent = new GUIContent(" Arrived Start Volume", "The relative volume the arrived start audio clip is played");
private readonly static GUIContent audioArrivedEndVolumeContent = new GUIContent(" Arrived End Volume", "The relative volume the arrived end audio clip is played");
//private static GUIContent audioPlayBtnContent = null;
#endregion
#region GUIContent - Events
private readonly static GUIContent onArriveStartContent = new GUIContent(" On Arrive Start", "These are triggered by a moving platform when it arrives at the start position");
private readonly static GUIContent onArriveEndContent = new GUIContent(" On Arrive End", "These are triggered by a moving platform when it arrives at the end position");
private readonly static GUIContent onDepartStartContent = new GUIContent(" On Depart Start", "These are triggered by a moving platform when it departs from the start position");
private readonly static GUIContent onDepartEndContent = new GUIContent(" On Depart End", "These are triggered by a moving platform when it departs from the end position");
#endregion
#region Serialized Properties - General
private SerializedProperty initialiseOnAwakeProp;
private SerializedProperty moveProp;
private SerializedProperty useRelativePositionsProp;
private SerializedProperty averageMoveSpeedProp;
private SerializedProperty startWaitTimeProp;
private SerializedProperty endWaitTimeProp;
private SerializedProperty movementProfileProp;
private SerializedProperty smoothStartTimeProp;
private SerializedProperty smoothStopTimeProp;
private SerializedProperty rotateProp;
private SerializedProperty startingRotationProp;
private SerializedProperty rotationAxisProp;
private SerializedProperty rotationSpeedProp;
private SerializedProperty positionListProp;
private SerializedProperty positionProp;
private SerializedProperty isPositionListExpandedProp;
private SerializedProperty moveUpdateTypeProp;
#endregion
#region Serialized Properties - Audio
private SerializedProperty platformAudioProp;
private SerializedProperty overallAudioVolumeProp;
private SerializedProperty inTransitAudioClipProp;
private SerializedProperty audioArrivedStartClipProp;
private SerializedProperty audioArrivedEndClipProp;
private SerializedProperty audioArrivedStartVolumeProp;
private SerializedProperty audioArrivedEndVolumeProp;
#endregion
#region Serialized Properties - Events
private SerializedProperty onArriveStartProp;
private SerializedProperty onArriveEndProp;
private SerializedProperty onDepartStartProp;
private SerializedProperty onDepartEndProp;
#endregion
#region Events
public void OnEnable()
{
sscMovingPlatform = (SSCMovingPlatform)target;
defaultEditorLabelWidth = 175f;
defaultEditorFieldWidth = EditorGUIUtility.fieldWidth;
//separatorColor = EditorGUIUtility.isProSkin ? new Color(0.2f, 0.2f, 0.2f, 2f) : Color.grey;
// 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) { }
#region Find Properties - General
initialiseOnAwakeProp = serializedObject.FindProperty("initialiseOnAwake");
moveProp = serializedObject.FindProperty("move");
useRelativePositionsProp = serializedObject.FindProperty("useRelativePositions");
averageMoveSpeedProp = serializedObject.FindProperty("averageMoveSpeed");
startWaitTimeProp = serializedObject.FindProperty("startWaitTime");
endWaitTimeProp = serializedObject.FindProperty("endWaitTime");
movementProfileProp = serializedObject.FindProperty("movementProfile");
smoothStartTimeProp = serializedObject.FindProperty("smoothStartTime");
smoothStopTimeProp = serializedObject.FindProperty("smoothStopTime");
rotateProp = serializedObject.FindProperty("rotate");
startingRotationProp = serializedObject.FindProperty("startingRotation");
rotationAxisProp = serializedObject.FindProperty("rotationAxis");
rotationSpeedProp = serializedObject.FindProperty("rotationSpeed");
moveUpdateTypeProp = serializedObject.FindProperty("moveUpdateType");
positionListProp = serializedObject.FindProperty("positions");
//isPositionListExpandedProp = serializedObject.FindProperty("isPositionListExpanded");
#endregion
#region Find Properties - Audio
platformAudioProp = serializedObject.FindProperty("platformAudio");
overallAudioVolumeProp = serializedObject.FindProperty("overallAudioVolume");
inTransitAudioClipProp = serializedObject.FindProperty("inTransitAudioClip");
audioArrivedStartClipProp = serializedObject.FindProperty("audioArrivedStartClip");
audioArrivedEndClipProp = serializedObject.FindProperty("audioArrivedEndClip");
audioArrivedStartVolumeProp = serializedObject.FindProperty("audioArrivedStartVolume");
audioArrivedEndVolumeProp = serializedObject.FindProperty("audioArrivedEndVolume");
#endregion
#region Find Properties - Events
onArriveStartProp = serializedObject.FindProperty("onArriveStart");
onArriveEndProp = serializedObject.FindProperty("onArriveEnd");
onDepartStartProp = serializedObject.FindProperty("onDepartStart");
onDepartEndProp = serializedObject.FindProperty("onDepartEnd");
#endregion
#region Find Buttons
//audioPlayBtnContent = EditorGUIUtility.TrIconContent("PlayButton", "Play");
//audioPlayBtnContent = EditorGUIUtility.TrIconContent("preAudioAutoPlayOff", "L");
#endregion
isClipPlaying = false;
}
///
/// Called when the gameobject loses focus or Unity Editor enters/exits play mode
///
private void OnDisable()
{
if (isClipPlaying)
{
SSCEditorHelper.StopAllAudioClips();
isClipPlaying = false;
}
}
#endregion
#region Private Methods
private void DrawAudioClip(SerializedProperty audioClip, GUIContent audioClipContent)
{
GUILayout.BeginHorizontal();
#if UNITY_2019_1_OR_NEWER
EditorGUILayout.LabelField(audioClipContent, GUILayout.Width(defaultEditorLabelWidth - 23f));
#else
EditorGUILayout.LabelField(audioClipContent, GUILayout.Width(defaultEditorLabelWidth - 28f));
#endif
if (GUILayout.Button(audioListenContent, buttonCompact, GUILayout.MaxWidth(20f)))
{
if (isClipPlaying)
{
SSCEditorHelper.StopAllAudioClips();
isClipPlaying = false;
}
else if (audioClip.objectReferenceValue != null)
{
SSCEditorHelper.PlayAudioClip((AudioClip)audioClip.objectReferenceValue, 0, false);
isClipPlaying = true;
}
}
EditorGUILayout.PropertyField(audioClip, GUIContent.none);
GUILayout.EndHorizontal();
}
#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()
{
//DrawDefaultInspector();
#region Initialise
EditorGUIUtility.labelWidth = defaultEditorLabelWidth;
EditorGUIUtility.fieldWidth = defaultEditorFieldWidth;
isSceneModified = false;
#endregion
#region Configure Buttons and Styles
// Set up rich text GUIStyles
if (!isStylesInitialised)
{
helpBoxRichText = new GUIStyle("HelpBox");
helpBoxRichText.richText = true;
labelFieldRichText = new GUIStyle("Label");
labelFieldRichText.richText = true;
headingFieldRichText = new GUIStyle(UnityEditor.EditorStyles.miniLabel);
headingFieldRichText.richText = true;
headingFieldRichText.normal.textColor = helpBoxRichText.normal.textColor;
// Overide default styles
EditorStyles.foldout.fontStyle = FontStyle.Bold;
// 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;
buttonCompact = new GUIStyle("Button");
buttonCompact.fontSize = 10;
isStylesInitialised = true;
}
#endregion
// Read in all the properties
serializedObject.Update();
#region Header Info and Buttons
EditorGUILayout.LabelField(headerContent, helpBoxRichText);
#endregion
#region General Settings
GUILayout.BeginVertical("HelpBox");
EditorGUILayout.PropertyField(initialiseOnAwakeProp, initialiseOnAwakeContent);
EditorGUILayout.PropertyField(moveProp, moveContent);
EditorGUILayout.PropertyField(useRelativePositionsProp, useRelativePositionsContent);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(averageMoveSpeedProp, averageMoveSpeedContent);
if (EditorGUI.EndChangeCheck() && Application.isPlaying)
{
sscMovingPlatform.UpdateAverageMoveSpeed(averageMoveSpeedProp.floatValue);
}
EditorGUILayout.PropertyField(startWaitTimeProp, startWaitTimeContent);
EditorGUILayout.PropertyField(endWaitTimeProp, endWaitTimeContent);
GUILayout.BeginHorizontal();
#if UNITY_2019_1_OR_NEWER
EditorGUILayout.LabelField(movementProfileContent, GUILayout.Width(defaultEditorLabelWidth - 23f));
#else
EditorGUILayout.LabelField(movementProfileContent, GUILayout.Width(defaultEditorLabelWidth - 28f));
#endif
if (GUILayout.Button(resetBtnContent, buttonCompact, GUILayout.MaxWidth(20f)))
{
movementProfileProp.animationCurveValue = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
}
EditorGUILayout.PropertyField(movementProfileProp, GUIContent.none);
GUILayout.EndHorizontal();
EditorGUILayout.PropertyField(smoothStartTimeProp, smoothStartTimeContent);
EditorGUILayout.PropertyField(smoothStopTimeProp, smoothStopTimeContent);
EditorGUILayout.PropertyField(rotateProp, rotateContent);
EditorGUILayout.PropertyField(startingRotationProp, startingRotationContent);
EditorGUILayout.PropertyField(rotationAxisProp, rotationAxisContent);
EditorGUILayout.PropertyField(rotationSpeedProp, rotationSpeedContent);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(moveUpdateTypeProp, moveUpdateTypeContent);
if (EditorGUI.EndChangeCheck() && EditorApplication.isPlaying)
{
sscMovingPlatform.SetMoveUpdateType((SSCMovingPlatform.MoveUpdateType)moveUpdateTypeProp.intValue);
}
GUILayout.EndVertical();
#endregion
#region Audio Settings
GUILayout.BeginVertical("HelpBox");
//EditorGUI.BeginChangeCheck();
GUILayout.BeginHorizontal();
#if UNITY_2019_1_OR_NEWER
EditorGUILayout.LabelField(audioSourceContent, GUILayout.Width(defaultEditorLabelWidth - 53f));
#else
EditorGUILayout.LabelField(audioSourceContent, GUILayout.Width(defaultEditorLabelWidth - 58f));
#endif
if (GUILayout.Button(audioNewContent, buttonCompact, GUILayout.Width(50f)) && platformAudioProp.objectReferenceValue == null)
{
serializedObject.ApplyModifiedProperties();
Undo.SetCurrentGroupName("New Audio Source");
int undoGroup = UnityEditor.Undo.GetCurrentGroup();
Undo.RecordObject(sscMovingPlatform, string.Empty);
GameObject audioGameObject = new GameObject("Audio");
if (audioGameObject != null)
{
Undo.RegisterCreatedObjectUndo(audioGameObject, string.Empty);
AudioSource _newAudioSource = Undo.AddComponent(audioGameObject, typeof(AudioSource)) as AudioSource;
_newAudioSource.playOnAwake = false;
_newAudioSource.maxDistance = 25f;
_newAudioSource.spatialBlend = 1f;
_newAudioSource.loop = true;
_newAudioSource.volume = overallAudioVolumeProp.floatValue;
sscMovingPlatform.platformAudio = _newAudioSource;
audioGameObject.transform.SetParent(sscMovingPlatform.transform, false);
}
Undo.CollapseUndoOperations(undoGroup);
// Should be non-scene objects but is required to force being set as dirty
EditorUtility.SetDirty(sscMovingPlatform);
GUIUtility.ExitGUI();
}
EditorGUILayout.PropertyField(platformAudioProp, GUIContent.none);
GUILayout.EndHorizontal();
EditorGUILayout.PropertyField(overallAudioVolumeProp, audioOverallVolumeContent);
DrawAudioClip(inTransitAudioClipProp, audioInTransitClipContent);
DrawAudioClip(audioArrivedStartClipProp, audioArrivedStartClipContent);
EditorGUILayout.PropertyField(audioArrivedStartVolumeProp, audioArrivedStartVolumeContent);
DrawAudioClip(audioArrivedEndClipProp, audioArrivedEndClipContent);
EditorGUILayout.PropertyField(audioArrivedEndVolumeProp, audioArrivedEndVolumeContent);
GUILayout.EndVertical();
#endregion
#region Event Settings
EditorGUILayout.BeginVertical("HelpBox");
EditorGUILayout.PropertyField(onArriveStartProp, onArriveStartContent);
EditorGUILayout.PropertyField(onArriveEndProp, onArriveEndContent);
EditorGUILayout.PropertyField(onDepartStartProp, onDepartStartContent);
EditorGUILayout.PropertyField(onDepartEndProp, onDepartEndContent);
EditorGUILayout.EndVertical();
#endregion
#region Positions
GUILayout.BeginVertical("HelpBox");
#region Check if Positions is null or less than 2 items
// Checking the property for being NULL doesn't check if the list is actually null.
if (sscMovingPlatform.positions == null)
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
sscMovingPlatform.positions = new List(new Vector3[] { Vector3.zero, Vector3.forward * 5f });
isSceneModified = true;
// Read in the properties
serializedObject.Update();
}
else if (sscMovingPlatform.positions.Count < 2)
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
if (sscMovingPlatform.positions.Count == 1)
{
sscMovingPlatform.positions.Add(Vector3.forward * 5f);
}
else
{
sscMovingPlatform.positions.Add(Vector3.zero);
sscMovingPlatform.positions.Add(Vector3.forward * 5f);
}
isSceneModified = true;
// Read in the properties
serializedObject.Update();
}
#endregion
#region Add-Remove AnimActions
int numPositions = positionListProp.arraySize;
// Reset button variables
positionMoveDownPos = -1;
positionInsertPos = -1;
positionDeletePos = -1;
GUILayout.BeginHorizontal();
//SSCEditorHelper.DrawSSCFoldout(isPositionListExpandedProp, foldoutStyleNoLabel, defaultEditorFieldWidth);
EditorGUILayout.LabelField(" Positions: " + numPositions.ToString("00") + "", labelFieldRichText);
if (GUILayout.Button("+", GUILayout.MaxWidth(30f)))
{
// Apply property changes
serializedObject.ApplyModifiedProperties();
Undo.RecordObject(sscMovingPlatform, "Add Move Position");
sscMovingPlatform.positions.Add(new Vector3());
isSceneModified = true;
// Read in the properties
serializedObject.Update();
numPositions = positionListProp.arraySize;
if (numPositions > 0)
{
// Force new position to be serialized in scene
positionProp = positionListProp.GetArrayElementAtIndex(numPositions - 1);
positionProp.vector3Value *= 2f;
positionProp.vector3Value *= 0.5f;
}
}
if (GUILayout.Button("-", GUILayout.MaxWidth(30f)))
{
if (numPositions > 2) { positionDeletePos = positionListProp.arraySize - 1; }
}
GUILayout.EndHorizontal();
#endregion
#region Position List members
numPositions = positionListProp.arraySize;
GUILayoutUtility.GetRect(1f, 2f);
//if (isPositionListExpandedProp.boolValue)
{
for (int pIdx = 0; pIdx < numPositions; pIdx++)
{
positionProp = positionListProp.GetArrayElementAtIndex(pIdx);
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField(" Pos " + (pIdx + 1).ToString("000"), GUILayout.MaxWidth(55f));
EditorGUILayout.PropertyField(positionProp, GUIContent.none);
#region Move/Insert/Delete buttons
// Move down button
if (GUILayout.Button("V", buttonCompact, GUILayout.MaxWidth(20f)) && numPositions > 2) { positionMoveDownPos = pIdx; }
// Create duplicate button
if (GUILayout.Button("I", buttonCompact, GUILayout.MaxWidth(20f))) { positionInsertPos = pIdx; }
// Delete button
if (GUILayout.Button("X", buttonCompact, GUILayout.MaxWidth(20f))) { positionDeletePos = pIdx; }
#endregion
GUILayout.EndHorizontal();
}
}
#endregion
GUILayout.EndVertical();
#region Move / Insert / Delete Position
if (positionDeletePos >= 0 || positionInsertPos >= 0 || positionMoveDownPos >= 0)
{
GUI.FocusControl(null);
// Don't permit multiple operations in the same pass
if (positionMoveDownPos >= 0)
{
// Move down one position, or wrap round to start of list
if (positionMoveDownPos < positionListProp.arraySize - 1)
{
positionListProp.MoveArrayElement(positionMoveDownPos, positionMoveDownPos + 1);
}
else { positionListProp.MoveArrayElement(positionMoveDownPos, 0); }
positionMoveDownPos = -1;
}
else if (positionInsertPos >= 0)
{
// NOTE: Undo doesn't work with Insert.
// Apply property changes before potential list changes
serializedObject.ApplyModifiedProperties();
Vector3 originalPosition = sscMovingPlatform.positions[positionInsertPos];
sscMovingPlatform.positions.Insert(positionInsertPos, new Vector3(originalPosition.x, originalPosition.y, originalPosition.z));
// Read all properties from the MovingPlatform
serializedObject.Update();
positionProp = positionListProp.GetArrayElementAtIndex(positionInsertPos);
// Force new position to be serialized in scene
positionProp.vector3Value *= 2f;
positionProp.vector3Value *= 0.5f;
positionInsertPos = -1;
isSceneModified = true;
}
else if (positionDeletePos >= 0 && numPositions > 2)
{
// In U2019.4+ DisplayDialog seems to trigger another OnInspectorGUI() and DeletePos is reset to -1.
int _deleteIndex = positionDeletePos;
if (EditorUtility.DisplayDialog("Delete Position " + (positionDeletePos + 1) + "?", "Position " + (positionDeletePos + 1).ToString("00") + " will be deleted\n\nThis action will remove the Position from the list and cannot be undone.", "Delete Now", "Cancel"))
{
positionListProp.DeleteArrayElementAtIndex(_deleteIndex);
positionDeletePos = -1;
}
}
#if UNITY_2019_3_OR_NEWER
serializedObject.ApplyModifiedProperties();
// In U2019.4+ avoid: EndLayoutGroup: BeginLayoutGroup must be called first.
if (!Application.isPlaying)
{
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
GUIUtility.ExitGUI();
#endif
}
#endregion
#endregion
// Apply property changes
serializedObject.ApplyModifiedProperties();
#region Mark Scene Dirty if required
if (isSceneModified && !Application.isPlaying)
{
isSceneModified = false;
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
#endregion
}
#endregion
}
}