using System; using UnityEditor; using UnityEditor.Splines; using UnityEngine; using UnityEngine.Splines; class SplineInstantiateGizmoDrawer { [DrawGizmo(GizmoType.Selected | GizmoType.Active)] static void DrawSplineInstantiateGizmos(SplineInstantiate scr, GizmoType gizmoType) { var instances = scr.instances; foreach(var instance in instances) { var pos = instance.transform.position; Handles.color = Color.red; Handles.DrawAAPolyLine(3f,new []{ pos, pos + 0.25f * instance.transform.right }); Handles.color = Color.green; Handles.DrawAAPolyLine(3f,new []{pos, pos + 0.25f * instance.transform.up}); Handles.color = Color.blue; Handles.DrawAAPolyLine(3f,new []{pos, pos + 0.25f * instance.transform.forward}); } } } [CustomPropertyDrawer (typeof(SplineInstantiate.InstantiableItem))] class InstantiableItemDrawer : PropertyDrawer { static readonly string k_ProbabilityTooltip = L10n.Tr("Probability for that element to appear."); public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return EditorGUIUtility.singleLineHeight; } public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) { var prefabProperty = property.FindPropertyRelative("prefab"); var probaProperty = property.FindPropertyRelative("probability"); var headerLine = ReserveSpace(EditorGUIUtility.singleLineHeight, ref rect); using(new SplineInstantiateEditor.LabelWidthScope(0f)) EditorGUI.ObjectField(ReserveLineSpace(headerLine.width - 100, ref headerLine), prefabProperty, new GUIContent("")); ReserveLineSpace(10, ref headerLine); EditorGUI.LabelField(ReserveLineSpace(15, ref headerLine), new GUIContent("%", k_ProbabilityTooltip)); probaProperty.floatValue = EditorGUI.FloatField(ReserveLineSpace(60, ref headerLine), probaProperty.floatValue); } static Rect ReserveSpace(float height, ref Rect total) { Rect current = total; current.height = height; total.y += height; return current; } static Rect ReserveLineSpace(float width, ref Rect total) { Rect current = total; current.width = width; total.x += width; return current; } } [CustomPropertyDrawer (typeof(SplineInstantiate.AlignAxis))] class ItemAxisDrawer : PropertyDrawer { static int s_LastUpAxis; public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) { var enumValue = property.intValue; if(property.name == "m_Up") { property.intValue = (int)( (SplineInstantiate.AlignAxis)EditorGUI.EnumPopup(rect, label, (SplineInstantiate.AlignAxis)enumValue)); s_LastUpAxis = property.intValue; } else { property.intValue = (int)((SplineInstantiate.AlignAxis)EditorGUI.EnumPopup(rect, label, (SplineInstantiate.AlignAxis)enumValue, (item) => { int axisItem = (int)(SplineInstantiate.AlignAxis)item; return !(axisItem == s_LastUpAxis || axisItem == (s_LastUpAxis + 3) % 6); })); } } } [CustomEditor(typeof(SplineInstantiate),false)] [CanEditMultipleObjects] class SplineInstantiateEditor : SplineComponentEditor { enum SpawnType { Exact, Random } SerializedProperty m_SplineContainer; SerializedProperty m_ItemsToInstantiate; SerializedProperty m_InstantiateMethod; SerializedProperty m_Space; SerializedProperty m_UpAxis; SerializedProperty m_ForwardAxis; SerializedProperty m_Spacing; SerializedProperty m_PositionOffset; SerializedProperty m_RotationOffset; SerializedProperty m_ScaleOffset; SerializedProperty m_AutoRefresh; static readonly string[] k_SpacingTypesLabels = new [] { L10n.Tr("Count"), L10n.Tr("Spacing (Spline)"), L10n.Tr("Spacing (Linear)") }; static readonly string k_Helpbox = L10n.Tr("Instantiated Objects need a SplineContainer target to be created."); //Setup Section static readonly string k_Setup = L10n.Tr("Instantiated Object Setup"); static readonly string k_ObjectUp = L10n.Tr("Up Axis"); static readonly string k_ObjectUpTooltip = L10n.Tr("Object axis to use as Up Direction when instantiating on the Spline (default is Y)."); static readonly string k_ObjectForward = L10n.Tr("Forward Axis"); static readonly string k_ObjectForwardTooltip = L10n.Tr("Object axis to use as Forward Direction when instantiating on the Spline (default is Z)."); static readonly string k_AlignTo = L10n.Tr("Align To"); static readonly string k_AlignToTooltip = L10n.Tr("Define the space to use to orientate the instantiated object."); static readonly string k_Instantiation = L10n.Tr("Instantiation"); static readonly string k_Method = L10n.Tr("Instantiate Method"); static readonly string k_MethodTooltip = L10n.Tr("How instances are generated along the spline."); static readonly string k_Max = L10n.Tr("Max"); static readonly string k_Min = L10n.Tr("Min"); SpawnType m_SpacingType; //Offsets static readonly string k_Offset = L10n.Tr("Offsets"); static readonly string k_PositionOffset = L10n.Tr("Position Offset"); static readonly string k_PositionOffsetTooltip = L10n.Tr("Whether or not to use a position offset."); static readonly string k_RotationOffset = L10n.Tr("Rotation Offset"); static readonly string k_RotationOffsetTooltip = L10n.Tr("Whether or not to use a rotation offset."); static readonly string k_ScaleOffset = L10n.Tr("Scale Offset"); static readonly string k_ScaleOffsetTooltip = L10n.Tr("Whether or not to use a scale offset."); //Generation static readonly string k_Generation = L10n.Tr("Generation"); static readonly string k_AutoRefresh = L10n.Tr("Auto Refresh Generation"); static readonly string k_AutoRefreshTooltip = L10n.Tr("Automatically refresh the instances when the spline or the values are changed."); static readonly string k_Randomize = L10n.Tr("Randomize"); static readonly string k_RandomizeTooltip = L10n.Tr("Compute a new randomization of the instances along the spline."); static readonly string k_Regenerate = L10n.Tr("Regenerate"); static readonly string k_RegenerateTooltip = L10n.Tr("Regenerate the instances along the spline (only available when the content is not automatically refreshed)."); static readonly string k_Clear = L10n.Tr("Clear"); static readonly string k_ClearTooltip = L10n.Tr("Clear the instances along the spline (only available when the content is not automatically refreshed)."); bool m_PositionFoldout; bool m_RotationFoldout; bool m_ScaleFoldout; enum OffsetType { Exact, Random }; protected override void OnEnable() { base.OnEnable(); m_SplineContainer = serializedObject.FindProperty("m_Container"); m_ItemsToInstantiate = serializedObject.FindProperty("m_ItemsToInstantiate"); m_InstantiateMethod = serializedObject.FindProperty("m_Method"); m_Space = serializedObject.FindProperty("m_Space"); m_UpAxis = serializedObject.FindProperty("m_Up"); m_ForwardAxis = serializedObject.FindProperty("m_Forward"); m_Spacing = serializedObject.FindProperty("m_Spacing"); m_PositionOffset = serializedObject.FindProperty("m_PositionOffset"); m_RotationOffset = serializedObject.FindProperty("m_RotationOffset"); m_ScaleOffset = serializedObject.FindProperty("m_ScaleOffset"); m_AutoRefresh = serializedObject.FindProperty("m_AutoRefresh"); m_SpacingType = m_Spacing.vector2Value.x == m_Spacing.vector2Value.y ? SpawnType.Exact : SpawnType.Random; EditorSplineUtility.afterSplineWasModified += OnSplineModified; } void OnDisable() { EditorSplineUtility.afterSplineWasModified -= OnSplineModified; } void OnSplineModified(Spline spline) { if (EditorApplication.isPlayingOrWillChangePlaymode) return; ( (SplineInstantiate)target ).SetSplineDirty(spline); } public override void OnInspectorGUI() { serializedObject.Update(); var splineInstantiate = ((SplineInstantiate)target); var dirtyInstances = false; var updateInstances = false; EditorGUILayout.PropertyField(m_SplineContainer); if(m_SplineContainer.objectReferenceValue == null) EditorGUILayout.HelpBox(k_Helpbox, MessageType.Warning); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_ItemsToInstantiate); dirtyInstances = EditorGUI.EndChangeCheck(); DoSetupSection(); dirtyInstances |= DoInstantiateSection(); updateInstances |= DisplayOffsets(); HorizontalLine( Color.grey ); EditorGUILayout.LabelField(k_Generation, EditorStyles.boldLabel); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(m_AutoRefresh, new GUIContent(k_AutoRefresh, k_AutoRefreshTooltip)); EditorGUI.indentLevel--; serializedObject.ApplyModifiedProperties(); EditorGUILayout.Separator(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); if(GUILayout.Button(new GUIContent(k_Randomize, k_RandomizeTooltip), GUILayout.MaxWidth(100f))) splineInstantiate.Randomize(); if(GUILayout.Button(new GUIContent(k_Regenerate, k_RegenerateTooltip), GUILayout.MaxWidth(100f))) updateInstances = true; if(GUILayout.Button(new GUIContent(k_Clear, k_ClearTooltip), GUILayout.MaxWidth(100f))) splineInstantiate.Clear(); EditorGUILayout.Space(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Separator(); if(dirtyInstances) splineInstantiate.SetDirty(); if(updateInstances) splineInstantiate.UpdateInstances(); } void DoSetupSection() { HorizontalLine(Color.grey); EditorGUILayout.LabelField(k_Setup, EditorStyles.boldLabel); GUILayout.Space(5f); EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_UpAxis, new GUIContent(k_ObjectUp, k_ObjectUpTooltip)); EditorGUILayout.PropertyField(m_ForwardAxis, new GUIContent(k_ObjectForward, k_ObjectForwardTooltip)); if(EditorGUI.EndChangeCheck()) { //Insuring axis integrity if(m_ForwardAxis.intValue == m_UpAxis.intValue || m_ForwardAxis.intValue == ( m_UpAxis.intValue + 3 ) % 6) m_ForwardAxis.intValue = ( m_ForwardAxis.intValue + 1 ) % 6; } EditorGUILayout.PropertyField(m_Space, new GUIContent(k_AlignTo, k_AlignToTooltip)); EditorGUI.indentLevel--; } bool DoInstantiateSection() { var dirty = false; Vector2 spacingV2 = m_Spacing.vector2Value; HorizontalLine( Color.grey ); EditorGUILayout.LabelField(k_Instantiation, EditorStyles.boldLabel); EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_InstantiateMethod, new GUIContent(k_Method, k_MethodTooltip), EditorStyles.boldFont ); if(EditorGUI.EndChangeCheck()) { if(m_SpacingType == SpawnType.Random && m_InstantiateMethod.intValue == (int)SplineInstantiate.Method.LinearDistance) m_Spacing.vector2Value = new Vector2(spacingV2.x, float.NaN); dirty = true; } EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(new GUIContent(k_SpacingTypesLabels[m_InstantiateMethod.intValue])); EditorGUI.indentLevel--; GUILayout.Space(2f); EditorGUI.BeginChangeCheck(); float spacingX = m_Spacing.vector2Value.x; var isExact = m_SpacingType == SpawnType.Exact; if(isExact || m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance) { using(new LabelWidthScope(30f)) spacingX = (SplineInstantiate.Method)m_InstantiateMethod.intValue == SplineInstantiate.Method.InstanceCount ? EditorGUILayout.IntField(new GUIContent(isExact ? L10n.Tr("Dist") : k_Min), (int)m_Spacing.vector2Value.x, GUILayout.MinWidth(50f)) : EditorGUILayout.FloatField(new GUIContent(isExact ? L10n.Tr("Dist") : k_Min), m_Spacing.vector2Value.x, GUILayout.MinWidth(50f)); } if(isExact) { spacingV2 = new Vector2(spacingX, spacingX); } else if(m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance) { using(new LabelWidthScope(30f)) { var spacingY = (SplineInstantiate.Method)m_InstantiateMethod.intValue == SplineInstantiate.Method.InstanceCount ? EditorGUILayout.IntField(new GUIContent(k_Max), (int)m_Spacing.vector2Value.y, GUILayout.MinWidth(50f)) : EditorGUILayout.FloatField(new GUIContent(k_Max), m_Spacing.vector2Value.y, GUILayout.MinWidth(50f)); if(spacingX > m_Spacing.vector2Value.y) spacingY = spacingX; else if(spacingY < m_Spacing.vector2Value.x) spacingX = spacingY; spacingV2 = new Vector2(spacingX, spacingY); } } if(EditorGUI.EndChangeCheck()) m_Spacing.vector2Value = spacingV2; EditorGUI.BeginChangeCheck(); if(m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance) m_SpacingType = (SpawnType)EditorGUILayout.EnumPopup(m_SpacingType, GUILayout.MinWidth(30f)); else m_SpacingType = (SpawnType)EditorGUILayout.Popup(m_SpacingType == SpawnType.Exact ? 0 : 1, new []{"Exact", "Auto"}, GUILayout.MinWidth(30f)); if(EditorGUI.EndChangeCheck()) { if(m_SpacingType == SpawnType.Exact) m_Spacing.vector2Value = new Vector2(spacingV2.x, spacingV2.x); else if(m_InstantiateMethod.intValue == (int)SplineInstantiate.Method.LinearDistance) m_Spacing.vector2Value = new Vector2(spacingV2.x, float.NaN); dirty = true; } EditorGUILayout.EndHorizontal(); return dirty; } bool DoOffsetProperties( SerializedProperty offsetProperty, GUIContent content, bool foldoutValue, out bool newFoldoutValue) { bool changed = false; newFoldoutValue = foldoutValue; EditorGUILayout.BeginHorizontal(); using(new LabelWidthScope(0f)) { var setupProperty = offsetProperty.FindPropertyRelative("setup"); var setup = (SplineInstantiate.Vector3Offset.Setup)setupProperty.intValue; var hasOffset = ( setup & SplineInstantiate.Vector3Offset.Setup.HasOffset ) != 0; EditorGUI.BeginChangeCheck(); hasOffset = EditorGUILayout.Toggle(hasOffset, GUILayout.MaxWidth(20f)); if(EditorGUI.EndChangeCheck()) { if(hasOffset) setup |= SplineInstantiate.Vector3Offset.Setup.HasOffset; else setup &= ~SplineInstantiate.Vector3Offset.Setup.HasOffset; setupProperty.intValue = (int)setup; changed = true; } EditorGUILayout.Space(10f); using(new EditorGUI.DisabledScope(!hasOffset)) { newFoldoutValue = Foldout(foldoutValue, content, hasOffset) && hasOffset; GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); if(newFoldoutValue) { EditorGUILayout.BeginHorizontal(); var hasCustomSpace = ( setup & SplineInstantiate.Vector3Offset.Setup.HasCustomSpace ) != 0; EditorGUI.BeginChangeCheck(); var space = m_Space.intValue < 1 ? "Spline Element" : m_Space.intValue == 1 ? "Spline Object" : "World"; hasCustomSpace = EditorGUILayout.Toggle(new GUIContent("Override space", L10n.Tr("Override current space (" + space + ")")), hasCustomSpace); if(EditorGUI.EndChangeCheck()) { if(hasCustomSpace) setup |= SplineInstantiate.Vector3Offset.Setup.HasCustomSpace; else setup &= ~SplineInstantiate.Vector3Offset.Setup.HasCustomSpace; setupProperty.intValue = (int)setup; changed = true; } var spaceProperty = offsetProperty.FindPropertyRelative("space"); using(new EditorGUI.DisabledScope(!hasCustomSpace)) { var type = (SplineInstantiate.OffsetSpace)spaceProperty.intValue; EditorGUI.BeginChangeCheck(); type = (SplineInstantiate.OffsetSpace)EditorGUILayout.EnumPopup(type); if(EditorGUI.EndChangeCheck()) { spaceProperty.intValue = (int)type; changed = true; } } EditorGUILayout.EndHorizontal(); var minProperty = offsetProperty.FindPropertyRelative("min"); var maxProperty = offsetProperty.FindPropertyRelative("max"); var minPropertyValue = minProperty.vector3Value; var maxPropertyValue = maxProperty.vector3Value; float min, max; SerializedProperty randomProperty; for(int i = 0; i < 3; i++) { string label = i == 0 ? "X" : i == 1 ? "Y" : "Z"; EditorGUILayout.BeginHorizontal(); using(new LabelWidthScope(30f)) EditorGUILayout.LabelField(label); randomProperty = offsetProperty.FindPropertyRelative("random"+label); GUILayout.FlexibleSpace(); if(randomProperty.boolValue) { EditorGUI.BeginChangeCheck(); using(new LabelWidthScope(30f)) { min = EditorGUILayout.FloatField("from", minPropertyValue[i], GUILayout.MinWidth(95f), GUILayout.MaxWidth(95f)); max = EditorGUILayout.FloatField(" to", maxPropertyValue[i], GUILayout.MinWidth(95f), GUILayout.MaxWidth(95f)); } if(EditorGUI.EndChangeCheck()) { if(min > maxPropertyValue[i]) maxPropertyValue[i] = min; if(max < minPropertyValue[i]) minPropertyValue[i] = max; minPropertyValue[i] = min; maxPropertyValue[i] = max; minProperty.vector3Value = minPropertyValue; maxProperty.vector3Value = maxPropertyValue; changed = true; } } else { EditorGUI.BeginChangeCheck(); using(new LabelWidthScope(30f)) min = EditorGUILayout.FloatField("is ", minPropertyValue[i], GUILayout.MinWidth(193f), GUILayout.MaxWidth(193f)); if(EditorGUI.EndChangeCheck()) { minPropertyValue[i] = min; if(min > maxPropertyValue[i]) maxPropertyValue[i] = min; minProperty.vector3Value = minPropertyValue; maxProperty.vector3Value = maxPropertyValue; changed = true; } } EditorGUI.BeginChangeCheck(); var isOffsetRandom = randomProperty.boolValue ? OffsetType.Random : OffsetType.Exact; using(new LabelWidthScope(0f)) isOffsetRandom = (OffsetType)EditorGUILayout.EnumPopup(isOffsetRandom,GUILayout.MinWidth(100f), GUILayout.MaxWidth(200f)); if(EditorGUI.EndChangeCheck()) { randomProperty.boolValue = isOffsetRandom == OffsetType.Random; changed = true; } EditorGUILayout.EndHorizontal(); } } } } return changed; } bool DisplayOffsets() { HorizontalLine( Color.grey ); var updateNeeded = DoOffsetProperties(m_PositionOffset, new GUIContent(k_PositionOffset, k_PositionOffsetTooltip), m_PositionFoldout, out m_PositionFoldout); updateNeeded |= DoOffsetProperties(m_RotationOffset, new GUIContent(k_RotationOffset, k_RotationOffsetTooltip), m_RotationFoldout, out m_RotationFoldout); updateNeeded |= DoOffsetProperties(m_ScaleOffset, new GUIContent(k_ScaleOffset, k_ScaleOffsetTooltip), m_ScaleFoldout, out m_ScaleFoldout); return updateNeeded; } }