first commit
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineAnimate))]
|
||||
[CanEditMultipleObjects]
|
||||
class SplineAnimateEditor : UnityEditor.Editor
|
||||
{
|
||||
List<VisualElement> m_Roots = new ();
|
||||
List<Slider> m_ProgressSliders = new ();
|
||||
List<FloatField> m_ElapsedTimeFields = new ();
|
||||
List<EnumField> m_ObjectForwardFields = new ();
|
||||
List<EnumField> m_ObjectUpFields = new ();
|
||||
|
||||
SerializedProperty m_MethodProperty;
|
||||
SerializedProperty m_ObjectForwardProperty;
|
||||
SerializedProperty m_ObjectUpProperty;
|
||||
SerializedProperty m_StartOffsetProperty;
|
||||
SerializedObject m_TransformSO;
|
||||
|
||||
SplineAnimate m_SplineAnimate;
|
||||
|
||||
const string k_UxmlPath = "Packages/com.unity.splines/Editor/Resources/UI/UXML/splineanimate-inspector.uxml";
|
||||
static VisualTreeAsset s_TreeAsset;
|
||||
static StyleSheet s_ThemeStyleSheet;
|
||||
|
||||
SplineAnimate[] m_Components;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_SplineAnimate = target as SplineAnimate;
|
||||
if (m_SplineAnimate == null)
|
||||
return;
|
||||
|
||||
m_SplineAnimate.Updated += OnSplineAnimateUpdated;
|
||||
|
||||
try {
|
||||
m_MethodProperty = serializedObject.FindProperty("m_Method");
|
||||
m_ObjectForwardProperty = serializedObject.FindProperty("m_ObjectForwardAxis");
|
||||
m_ObjectUpProperty = serializedObject.FindProperty("m_ObjectUpAxis");
|
||||
m_StartOffsetProperty = serializedObject.FindProperty("m_StartOffset");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_TransformSO = new SerializedObject(m_SplineAnimate.transform);
|
||||
m_Components = targets.Select(x => x as SplineAnimate).Where(y => y != null).ToArray();
|
||||
|
||||
foreach (var animate in m_Components)
|
||||
{
|
||||
if (animate.Container != null)
|
||||
animate.RecalculateAnimationParameters();
|
||||
}
|
||||
|
||||
m_Roots.Clear();
|
||||
m_ObjectForwardFields.Clear();
|
||||
m_ObjectUpFields.Clear();
|
||||
m_ProgressSliders.Clear();
|
||||
m_ElapsedTimeFields.Clear();
|
||||
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
Spline.Changed += OnSplineChange;
|
||||
SplineContainer.SplineAdded += OnContainerSplineSetModified;
|
||||
SplineContainer.SplineRemoved += OnContainerSplineSetModified;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if(m_SplineAnimate != null)
|
||||
m_SplineAnimate.Updated -= OnSplineAnimateUpdated;
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
foreach (var animate in m_Components)
|
||||
{
|
||||
if (animate.Container != null)
|
||||
{
|
||||
animate.RecalculateAnimationParameters();
|
||||
animate.Restart(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
Spline.Changed -= OnSplineChange;
|
||||
SplineContainer.SplineAdded -= OnContainerSplineSetModified;
|
||||
SplineContainer.SplineRemoved -= OnContainerSplineSetModified;
|
||||
}
|
||||
|
||||
void OnEditorUpdate()
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
if (m_SplineAnimate.Container != null && m_SplineAnimate.IsPlaying)
|
||||
{
|
||||
m_SplineAnimate.Update();
|
||||
RefreshProgressFields();
|
||||
}
|
||||
}
|
||||
else if(m_SplineAnimate.IsPlaying)
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnSplineChange(Spline spline, int knotIndex, SplineModification modificationType)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var animate in m_Components)
|
||||
{
|
||||
if (animate.Container != null && animate.Container.Splines.Contains(spline))
|
||||
animate.RecalculateAnimationParameters();
|
||||
}
|
||||
}
|
||||
|
||||
void OnContainerSplineSetModified(SplineContainer container, int spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var animate in m_Components)
|
||||
{
|
||||
if (animate.Container == container)
|
||||
animate.RecalculateAnimationParameters();
|
||||
}
|
||||
}
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = new VisualElement();
|
||||
|
||||
if (s_TreeAsset == null)
|
||||
s_TreeAsset = (VisualTreeAsset)AssetDatabase.LoadAssetAtPath(k_UxmlPath, typeof(VisualTreeAsset));
|
||||
s_TreeAsset.CloneTree(root);
|
||||
|
||||
if (s_ThemeStyleSheet == null)
|
||||
s_ThemeStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"Packages/com.unity.splines/Editor/Stylesheets/SplineAnimateInspector{(EditorGUIUtility.isProSkin ? "Dark" : "Light")}.uss");
|
||||
|
||||
root.styleSheets.Add(s_ThemeStyleSheet);
|
||||
|
||||
var methodField = root.Q<PropertyField>("method");
|
||||
methodField.RegisterValueChangeCallback((_) => { RefreshMethodParamFields((SplineAnimate.Method)m_MethodProperty.enumValueIndex); });
|
||||
RefreshMethodParamFields((SplineAnimate.Method)m_MethodProperty.enumValueIndex);
|
||||
|
||||
var objectForwardField = root.Q<EnumField>("object-forward");
|
||||
objectForwardField.RegisterValueChangedCallback((evt) => OnObjectAxisFieldChange(evt, m_ObjectForwardProperty, m_ObjectUpProperty));
|
||||
|
||||
var objectUpField = root.Q<EnumField>("object-up");
|
||||
objectUpField.RegisterValueChangedCallback((evt) => OnObjectAxisFieldChange(evt, m_ObjectUpProperty, m_ObjectForwardProperty));
|
||||
|
||||
var playButton = root.Q<Button>("play");
|
||||
playButton.SetEnabled(!EditorApplication.isPlaying);
|
||||
playButton.clicked += OnPlayClicked;
|
||||
|
||||
var pauseButton = root.Q<Button>("pause");
|
||||
pauseButton.SetEnabled(!EditorApplication.isPlaying);
|
||||
pauseButton.clicked += OnPauseClicked;
|
||||
|
||||
var resetButton = root.Q<Button>("reset");
|
||||
resetButton.SetEnabled(!EditorApplication.isPlaying);
|
||||
resetButton.clicked += OnResetClicked;
|
||||
|
||||
var progressSlider = root.Q<Slider>("normalized-progress");
|
||||
progressSlider.SetEnabled(!EditorApplication.isPlaying);
|
||||
progressSlider.RegisterValueChangedCallback((evt) => OnProgressSliderChange(evt.newValue));
|
||||
|
||||
var elapsedTimeField = root.Q<FloatField>("elapsed-time");
|
||||
elapsedTimeField.SetEnabled(!EditorApplication.isPlaying);
|
||||
elapsedTimeField.RegisterValueChangedCallback((evt) => OnElapsedTimeFieldChange(evt.newValue));
|
||||
|
||||
var startOffsetField = root.Q<PropertyField>("start-offset");
|
||||
startOffsetField.RegisterValueChangeCallback((evt) =>
|
||||
{
|
||||
m_SplineAnimate.StartOffset = m_StartOffsetProperty.floatValue;
|
||||
if (!EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
m_SplineAnimate.Restart(false);
|
||||
OnElapsedTimeFieldChange(elapsedTimeField.value);
|
||||
}
|
||||
});
|
||||
|
||||
m_Roots.Add(root);
|
||||
m_ProgressSliders.Add(progressSlider);
|
||||
m_ElapsedTimeFields.Add(elapsedTimeField);
|
||||
|
||||
m_ObjectForwardFields.Add(objectForwardField);
|
||||
m_ObjectUpFields.Add(objectUpField);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void RefreshMethodParamFields(SplineAnimate.Method method)
|
||||
{
|
||||
foreach (var root in m_Roots)
|
||||
{
|
||||
|
||||
var durationField = root.Q<PropertyField>("duration");
|
||||
var maxSpeedField = root.Q<PropertyField>("max-speed");
|
||||
|
||||
if (method == (int)SplineAnimate.Method.Time)
|
||||
{
|
||||
durationField.style.display = DisplayStyle.Flex;
|
||||
maxSpeedField.style.display = DisplayStyle.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
durationField.style.display = DisplayStyle.None;
|
||||
maxSpeedField.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshProgressFields()
|
||||
{
|
||||
for (int i = 0; i < m_ProgressSliders.Count && i < m_ElapsedTimeFields.Count; ++i)
|
||||
{
|
||||
var progressSlider = m_ProgressSliders[i];
|
||||
var elapsedTimeField = m_ElapsedTimeFields[i];
|
||||
if (progressSlider == null || elapsedTimeField == null)
|
||||
continue;
|
||||
|
||||
progressSlider.SetValueWithoutNotify(m_SplineAnimate.GetLoopInterpolation(false));
|
||||
elapsedTimeField.SetValueWithoutNotify(m_SplineAnimate.ElapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
void OnProgressSliderChange(float progress)
|
||||
{
|
||||
m_SplineAnimate.Pause();
|
||||
m_SplineAnimate.NormalizedTime = progress;
|
||||
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnElapsedTimeFieldChange(float elapsedTime)
|
||||
{
|
||||
m_SplineAnimate.Pause();
|
||||
m_SplineAnimate.ElapsedTime = elapsedTime;
|
||||
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnObjectAxisFieldChange(ChangeEvent<Enum> changeEvent, SerializedProperty axisProp, SerializedProperty otherAxisProp)
|
||||
{
|
||||
if (changeEvent.newValue == null)
|
||||
return;
|
||||
|
||||
|
||||
var newValue = (SplineAnimate.AlignAxis)changeEvent.newValue;
|
||||
var previousValue = (SplineAnimate.AlignAxis)changeEvent.previousValue;
|
||||
|
||||
// Swap axes if the picked value matches that of the other axis field
|
||||
if (newValue == (SplineAnimate.AlignAxis)otherAxisProp.enumValueIndex)
|
||||
{
|
||||
otherAxisProp.enumValueIndex = (int)previousValue;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
// Prevent the user from configuring object's forward and up as opposite axes
|
||||
if (((int) newValue) % 3 == otherAxisProp.enumValueIndex % 3)
|
||||
{
|
||||
axisProp.enumValueIndex = (int)previousValue;
|
||||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
foreach (var objectForwardField in m_ObjectForwardFields)
|
||||
objectForwardField.SetValueWithoutNotify((SplineComponent.AlignAxis)m_ObjectForwardProperty.enumValueIndex);
|
||||
foreach (var objectUpField in m_ObjectUpFields)
|
||||
objectUpField.SetValueWithoutNotify((SplineComponent.AlignAxis)m_ObjectUpProperty.enumValueIndex);
|
||||
}
|
||||
|
||||
void OnPlayClicked()
|
||||
{
|
||||
if (!m_SplineAnimate.IsPlaying)
|
||||
{
|
||||
m_SplineAnimate.RecalculateAnimationParameters();
|
||||
if (m_SplineAnimate.NormalizedTime == 1f)
|
||||
m_SplineAnimate.Restart(true);
|
||||
else
|
||||
m_SplineAnimate.Play();
|
||||
}
|
||||
}
|
||||
|
||||
void OnPauseClicked()
|
||||
{
|
||||
m_SplineAnimate.Pause();
|
||||
}
|
||||
|
||||
void OnResetClicked()
|
||||
{
|
||||
m_SplineAnimate.RecalculateAnimationParameters();
|
||||
m_SplineAnimate.Restart(false);
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnSplineAnimateUpdated(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (m_SplineAnimate == null)
|
||||
return;
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
m_TransformSO.Update();
|
||||
|
||||
var localPosition = position;
|
||||
var localRotation = rotation;
|
||||
if (m_SplineAnimate.transform.parent != null)
|
||||
{
|
||||
localPosition = m_SplineAnimate.transform.parent.worldToLocalMatrix.MultiplyPoint3x4(position);
|
||||
localRotation = Quaternion.Inverse(m_SplineAnimate.transform.parent.rotation) * localRotation;
|
||||
}
|
||||
|
||||
m_TransformSO.FindProperty("m_LocalPosition").vector3Value = localPosition;
|
||||
m_TransformSO.FindProperty("m_LocalRotation").quaternionValue = localRotation;
|
||||
|
||||
m_TransformSO.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.Selected | GizmoType.Active)]
|
||||
static void DrawSplineAnimateGizmos(SplineAnimate splineAnimate, GizmoType gizmoType)
|
||||
{
|
||||
if (splineAnimate.Container == null)
|
||||
return;
|
||||
|
||||
const float k_OffsetGizmoSize = 0.15f;
|
||||
splineAnimate.Container.Evaluate(splineAnimate.StartOffsetT, out var offsetPos, out var forward, out var up);
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
using (new Handles.DrawingScope(Handles.elementColor))
|
||||
#else
|
||||
using (new Handles.DrawingScope(SplineHandleUtility.knotColor))
|
||||
#endif
|
||||
if (Vector3.Magnitude(forward) <= Mathf.Epsilon)
|
||||
{
|
||||
if (splineAnimate.StartOffsetT < 1f)
|
||||
forward = splineAnimate.Container.EvaluateTangent(Mathf.Min(1f, splineAnimate.StartOffsetT + 0.01f));
|
||||
else
|
||||
forward = splineAnimate.Container.EvaluateTangent(splineAnimate.StartOffsetT - 0.01f);
|
||||
|
||||
}
|
||||
Handles.ConeHandleCap(-1, offsetPos, Quaternion.LookRotation(Vector3.Normalize(forward), up), k_OffsetGizmoSize * HandleUtility.GetHandleSize(offsetPos), EventType.Repaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7292ebc78c5d44eaa80048f8fd7d5fe6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
class SplineComponentEditor : Editor
|
||||
{
|
||||
static GUIStyle s_FoldoutStyle;
|
||||
|
||||
internal static readonly string k_Helpbox = L10n.Tr("Instantiated Objects need a SplineContainer target to be created.");
|
||||
|
||||
protected bool Foldout(bool foldout, GUIContent content)
|
||||
{
|
||||
return Foldout(foldout, content, false);
|
||||
}
|
||||
|
||||
public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick)
|
||||
{
|
||||
if (s_FoldoutStyle == null)
|
||||
{
|
||||
s_FoldoutStyle = new GUIStyle(EditorStyles.foldout);
|
||||
s_FoldoutStyle.fontStyle = FontStyle.Bold;
|
||||
}
|
||||
|
||||
return EditorGUILayout.Foldout(foldout, content, toggleOnLabelClick, s_FoldoutStyle);
|
||||
}
|
||||
|
||||
internal struct LabelWidthScope : IDisposable
|
||||
{
|
||||
float previousWidth;
|
||||
|
||||
public LabelWidthScope(float width)
|
||||
{
|
||||
previousWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = width;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUIUtility.labelWidth = previousWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9103eb7dcd041455886438d75625d19c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
// Multi-object selection is not supported
|
||||
[CustomEditor(typeof(SplineContainer))]
|
||||
class SplineContainerEditor : UnityEditor.Editor
|
||||
{
|
||||
SerializedProperty m_SplineProperty;
|
||||
SerializedProperty splinesProperty => m_SplineProperty ??= serializedObject.FindProperty("m_Splines");
|
||||
|
||||
static GUIStyle s_HelpLabelStyle;
|
||||
static GUIStyle HelpLabelStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_HelpLabelStyle == null)
|
||||
{
|
||||
s_HelpLabelStyle = new GUIStyle(EditorStyles.helpBox);
|
||||
s_HelpLabelStyle.padding = new RectOffset(2, 2, 2, 2);
|
||||
}
|
||||
|
||||
return s_HelpLabelStyle;
|
||||
}
|
||||
}
|
||||
|
||||
static GUIContent m_HelpLabelContent;
|
||||
|
||||
const string k_HelpBoxIconPath = "SplineEditMode-Info";
|
||||
static GUIContent m_HelpLabelContentIcon;
|
||||
|
||||
const string k_ComponentMessage = "Use the Spline Edit Mode in the Scene Tools Overlay to edit this Spline.";
|
||||
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
m_HelpLabelContent = EditorGUIUtility.TrTextContent(k_ComponentMessage);
|
||||
m_HelpLabelContentIcon = new GUIContent(PathIcons.GetIcon(k_HelpBoxIconPath));
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
||||
}
|
||||
|
||||
void UndoRedoPerformed()
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var container = t as SplineContainer;
|
||||
if (container != null)
|
||||
{
|
||||
container.ClearCaches();
|
||||
foreach (var spline in container.Splines)
|
||||
spline.SetDirty(SplineModification.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// [SPLB-132] Reverting to custom helpbox as the default helpbox style as a trouble to handle custom icons
|
||||
// when using a screen with PixelPerPoints different than 1. This is done in trunk by setting the
|
||||
// Texture2d.pixelsPerPoints which is an internal property than cannot be access from here.
|
||||
EditorGUILayout.BeginHorizontal(HelpLabelStyle);
|
||||
EditorGUIUtility.SetIconSize(new Vector2(32f, 32f));
|
||||
EditorGUILayout.LabelField(m_HelpLabelContentIcon,
|
||||
GUILayout.Width(34), GUILayout.MinHeight(34), GUILayout.ExpandHeight(true));
|
||||
EditorGUIUtility.SetIconSize(Vector2.zero);
|
||||
EditorGUILayout.LabelField(m_HelpLabelContent,
|
||||
new GUIStyle(EditorStyles.label){wordWrap = HelpLabelStyle.wordWrap, fontSize = HelpLabelStyle.fontSize, padding = new RectOffset(-2, 0, 0, 0)},
|
||||
GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
SplineReorderableList.Get(splinesProperty).DoLayoutList();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
bool HasFrameBounds()
|
||||
{
|
||||
foreach (var o in targets)
|
||||
{
|
||||
var target = (SplineContainer) o;
|
||||
foreach (var spline in target.Splines)
|
||||
if (spline.Count > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Bounds OnGetFrameBounds()
|
||||
{
|
||||
List<SplineInfo> splines = new List<SplineInfo>();
|
||||
EditorSplineUtility.GetSplinesFromTargets(targets, splines);
|
||||
return EditorSplineUtility.GetBounds(splines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b17d23c06da64c139386c53a0b59281
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,304 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.Splines.ExtrusionShapes;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineExtrude))]
|
||||
[CanEditMultipleObjects]
|
||||
class SplineExtrudeEditor : SplineComponentEditor
|
||||
{
|
||||
SerializedProperty m_Container;
|
||||
SerializedProperty m_RebuildOnSplineChange;
|
||||
SerializedProperty m_RebuildFrequency;
|
||||
SerializedProperty m_SegmentsPerUnit;
|
||||
SerializedProperty m_Capped;
|
||||
SerializedProperty m_Radius;
|
||||
SerializedProperty m_Range;
|
||||
SerializedProperty m_Shape;
|
||||
SerializedProperty m_UpdateColliders;
|
||||
SerializedProperty m_FlipNormals;
|
||||
|
||||
static readonly GUIContent k_RangeContent = new GUIContent(L10n.Tr("Range"), L10n.Tr("The section of the Spline to extrude."));
|
||||
static readonly GUIContent k_AdvancedContent = new GUIContent(L10n.Tr("Advanced"), L10n.Tr("Advanced Spline Extrude settings."));
|
||||
static readonly GUIContent k_PercentageContent = new GUIContent(L10n.Tr("Percentage"), L10n.Tr("The section of the Spline to extrude in percentages."));
|
||||
static readonly GUIContent k_ShapeContent = new GUIContent(L10n.Tr("Shape Extrude"), L10n.Tr("Shape Extrude settings."));
|
||||
static readonly GUIContent k_ShapeSettings = EditorGUIUtility.TrTextContent("Settings");
|
||||
static readonly GUIContent k_GeometryContent = new GUIContent(L10n.Tr("Geometry"), L10n.Tr("Mesh Geometry settings."));
|
||||
|
||||
static readonly string k_SourceSplineContainer = L10n.Tr("Source Spline Container");
|
||||
static readonly string k_CapEnds = L10n.Tr("Cap Ends");
|
||||
static readonly string k_AutoRefreshGeneration = L10n.Tr("Auto Refresh Generation");
|
||||
static readonly string k_To = L10n.Tr("to");
|
||||
static readonly string k_From = L10n.Tr("from");
|
||||
|
||||
SplineExtrude[] m_Components;
|
||||
|
||||
bool m_AnyMissingMesh;
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
m_Container = serializedObject.FindProperty("m_Container");
|
||||
m_RebuildOnSplineChange = serializedObject.FindProperty("m_RebuildOnSplineChange");
|
||||
m_RebuildFrequency = serializedObject.FindProperty("m_RebuildFrequency");
|
||||
m_SegmentsPerUnit = serializedObject.FindProperty("m_SegmentsPerUnit");
|
||||
m_Capped = serializedObject.FindProperty("m_Capped");
|
||||
m_Radius = serializedObject.FindProperty("m_Radius");
|
||||
m_Range = serializedObject.FindProperty("m_Range");
|
||||
m_UpdateColliders = serializedObject.FindProperty("m_UpdateColliders");
|
||||
m_Shape = serializedObject.FindProperty("m_Shape");
|
||||
m_FlipNormals = serializedObject.FindProperty("m_FlipNormals");
|
||||
|
||||
m_Components = targets.Select(x => x as SplineExtrude).Where(y => y != null).ToArray();
|
||||
m_AnyMissingMesh = false;
|
||||
|
||||
EditorSplineUtility.AfterSplineWasModified += OnSplineModified;
|
||||
SplineContainer.SplineAdded += OnContainerSplineSetModified;
|
||||
SplineContainer.SplineRemoved += OnContainerSplineSetModified;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
EditorSplineUtility.AfterSplineWasModified -= OnSplineModified;
|
||||
SplineContainer.SplineAdded -= OnContainerSplineSetModified;
|
||||
SplineContainer.SplineRemoved -= OnContainerSplineSetModified;
|
||||
}
|
||||
|
||||
void OnSplineModified(Spline spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var extrude in m_Components)
|
||||
{
|
||||
if (extrude.Container != null && extrude.Splines.Contains(spline))
|
||||
extrude.Rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
void OnContainerSplineSetModified(SplineContainer container, int spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var extrude in m_Components)
|
||||
{
|
||||
if (extrude.Container == container)
|
||||
extrude.Rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
void SetShapeType(ShapeType type)
|
||||
{
|
||||
foreach (var extrude in m_Components)
|
||||
{
|
||||
if (ShapeTypeUtility.GetShapeType(extrude.Shape) == type)
|
||||
continue;
|
||||
|
||||
Undo.RecordObject(extrude, "Set Extrude Shape");
|
||||
|
||||
extrude.Shape = ShapeTypeUtility.CreateShape(type);
|
||||
m_Shape.isExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CanCapEnds()
|
||||
{
|
||||
foreach (var extrude in m_Components)
|
||||
{
|
||||
if (!extrude.CanCapEnds)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetRebuildOnSplineChange(bool value)
|
||||
{
|
||||
foreach (var extrude in m_Components)
|
||||
{
|
||||
Undo.RecordObject(extrude, "Set Rebuild on Spline Change.");
|
||||
|
||||
extrude.RebuildOnSplineChange = value;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateMeshAssets(SplineExtrude[] components)
|
||||
{
|
||||
foreach (var extrude in components)
|
||||
{
|
||||
if (!extrude.TryGetComponent<MeshFilter>(out var filter) || filter.sharedMesh == null)
|
||||
filter.sharedMesh = extrude.CreateMeshAsset();
|
||||
}
|
||||
|
||||
m_AnyMissingMesh = false;
|
||||
}
|
||||
|
||||
void Rebuild()
|
||||
{
|
||||
foreach (var extrude in m_Components)
|
||||
extrude.Rebuild();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
m_AnyMissingMesh = m_Components.Any(x => x.TryGetComponent<MeshFilter>(out var filter) && filter.sharedMesh == null);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(m_Container, new GUIContent(k_SourceSplineContainer, m_Container.tooltip));
|
||||
|
||||
if (m_Container.objectReferenceValue == null)
|
||||
EditorGUILayout.HelpBox(k_Helpbox, MessageType.Warning);
|
||||
|
||||
// shape section
|
||||
m_Shape.isExpanded = Foldout(m_Shape.isExpanded, k_ShapeContent, true);
|
||||
|
||||
if (m_Shape.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.showMixedValue = m_Shape.hasMultipleDifferentValues;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var shapeType = ShapeTypeUtility.GetShapeType(m_Shape.managedReferenceValue);
|
||||
shapeType = (ShapeType)EditorGUILayout.EnumPopup(L10n.Tr("Type"), shapeType);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetShapeType(shapeType);
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
if (m_Shape.hasVisibleChildren)
|
||||
EditorGUILayout.PropertyField(m_Shape, k_ShapeSettings, true);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// https://unityeditordesignsystem.unity.com/patterns/content-organization recommends 8px spacing for
|
||||
// vertical groups. padding already adds 4 so just nudge that up for a total of 8
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// geometry section
|
||||
m_Radius.isExpanded = Foldout(m_Radius.isExpanded, k_GeometryContent, true);
|
||||
|
||||
if (m_Radius.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_RebuildOnSplineChange, new GUIContent(k_AutoRefreshGeneration, m_RebuildOnSplineChange.tooltip));
|
||||
if (m_RebuildOnSplineChange.boolValue)
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(!m_RebuildOnSplineChange.boolValue);
|
||||
using (new LabelWidthScope(80f))
|
||||
EditorGUILayout.PropertyField(m_RebuildFrequency, new GUIContent() { text = L10n.Tr("Frequency") });
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button(new GUIContent(L10n.Tr("Regenerate"))))
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck() && !m_RebuildOnSplineChange.boolValue)
|
||||
{
|
||||
// This is needed to set m_RebuildRequested to the appropriate value.
|
||||
SetRebuildOnSplineChange(m_RebuildOnSplineChange.boolValue);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (m_AnyMissingMesh)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel(" ");
|
||||
if (GUILayout.Button("Create Mesh Asset"))
|
||||
CreateMeshAssets(m_Components);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_Radius);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_Radius.floatValue = Mathf.Clamp(m_Radius.floatValue, .00001f, 1000f);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_SegmentsPerUnit);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_SegmentsPerUnit.floatValue = Mathf.Clamp(m_SegmentsPerUnit.floatValue, .00001f, 4096f);
|
||||
|
||||
var canCapEnds = CanCapEnds();
|
||||
using (new EditorGUI.DisabledScope(!canCapEnds))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_Capped, new GUIContent(k_CapEnds, m_Capped.tooltip));
|
||||
if (m_Capped.boolValue && !canCapEnds)
|
||||
m_Capped.boolValue = false;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_FlipNormals);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// advanced section
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
m_Range.isExpanded = Foldout(m_Range.isExpanded, k_AdvancedContent, true);
|
||||
|
||||
if (m_Range.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.showMixedValue = m_Range.hasMultipleDifferentValues;
|
||||
var range = m_Range.vector2Value;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.MinMaxSlider(k_RangeContent, ref range.x, ref range.y, 0f, 1f);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_Range.vector2Value = range;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel(k_PercentageContent);
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newRange = new Vector2(range.x, range.y);
|
||||
using (new LabelWidthScope(30f))
|
||||
newRange.x = EditorGUILayout.FloatField(k_From, range.x * 100f) / 100f;
|
||||
|
||||
using (new LabelWidthScope(15f))
|
||||
newRange.y = EditorGUILayout.FloatField(k_To, range.y * 100f) / 100f;
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
newRange.x = Mathf.Min(Mathf.Clamp(newRange.x, 0f, 1f), range.y);
|
||||
newRange.y = Mathf.Max(newRange.x, Mathf.Clamp(newRange.y, 0f, 1f));
|
||||
m_Range.vector2Value = newRange;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
EditorGUILayout.PropertyField(m_UpdateColliders);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
foreach (var extrude in m_Components)
|
||||
extrude.Rebuild();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ae5aa24d97048439e76e9392765c364
|
||||
timeCreated: 1637689699
|
||||
@@ -0,0 +1,661 @@
|
||||
using System.Linq;
|
||||
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(nameof(SplineInstantiate.InstantiableItem.Prefab));
|
||||
var probaProperty = property.FindPropertyRelative(nameof(SplineInstantiate.InstantiableItem.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_Seed;
|
||||
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)")
|
||||
};
|
||||
|
||||
//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_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_Seed = L10n.Tr("Randomization Seed");
|
||||
static readonly string k_SeedTooltip = L10n.Tr("Value used to initialize the pseudorandom number generator of the instances.");
|
||||
|
||||
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.");
|
||||
static readonly string k_Clear = L10n.Tr("Clear");
|
||||
static readonly string k_ClearTooltip = L10n.Tr("Clear the instances along the spline.");
|
||||
static readonly string k_Bake = L10n.Tr("Bake Instances");
|
||||
static readonly string k_BakeTooltip = L10n.Tr("Bake the instances in the SceneView for custom edition and destroy that SplineInstantiate component.");
|
||||
|
||||
bool m_PositionFoldout;
|
||||
bool m_RotationFoldout;
|
||||
bool m_ScaleFoldout;
|
||||
|
||||
enum OffsetType
|
||||
{
|
||||
Exact,
|
||||
Random
|
||||
};
|
||||
|
||||
SplineInstantiate[] m_Components;
|
||||
|
||||
SplineInstantiate[] components
|
||||
{
|
||||
get
|
||||
{
|
||||
//in case of multiple selection where some objects do not have a SplineInstantiate component, m_Components might be null
|
||||
if (m_Components == null)
|
||||
m_Components = targets.Select(x => x as SplineInstantiate).Where(y => y != null).ToArray();
|
||||
|
||||
return m_Components;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
Spline.Changed += OnSplineChanged;
|
||||
EditorSplineUtility.AfterSplineWasModified += OnSplineModified;
|
||||
SplineContainer.SplineAdded += OnContainerSplineSetModified;
|
||||
SplineContainer.SplineRemoved += OnContainerSplineSetModified;
|
||||
}
|
||||
|
||||
bool Initialize()
|
||||
{
|
||||
if (m_Components != null && m_Components.Length > 0)
|
||||
return true;
|
||||
|
||||
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_Seed = serializedObject.FindProperty("m_Seed");
|
||||
|
||||
m_AutoRefresh = serializedObject.FindProperty("m_AutoRefresh");
|
||||
|
||||
if (m_Spacing != null)
|
||||
m_SpacingType = Mathf.Approximately(m_Spacing.vector2Value.x, m_Spacing.vector2Value.y) ? SpawnType.Exact : SpawnType.Random;
|
||||
else
|
||||
m_SpacingType = SpawnType.Exact;
|
||||
|
||||
m_Components = targets.Select(x => x as SplineInstantiate).Where(y => y != null).ToArray();
|
||||
|
||||
return m_Components != null && m_Components.Length > 0;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_Components = null;
|
||||
|
||||
Spline.Changed -= OnSplineChanged;
|
||||
EditorSplineUtility.AfterSplineWasModified -= OnSplineModified;
|
||||
SplineContainer.SplineAdded -= OnContainerSplineSetModified;
|
||||
SplineContainer.SplineRemoved -= OnContainerSplineSetModified;
|
||||
}
|
||||
|
||||
void OnSplineModified(Spline spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var instantiate in components)
|
||||
{
|
||||
if(instantiate == null)
|
||||
continue;
|
||||
if (instantiate.Container != null && instantiate.Container.Splines.Contains(spline))
|
||||
instantiate.SetSplineDirty(spline);
|
||||
}
|
||||
}
|
||||
|
||||
void OnSplineChanged(Spline spline, int knotIndex, SplineModification modification)
|
||||
{
|
||||
OnSplineModified(spline);
|
||||
}
|
||||
|
||||
void OnContainerSplineSetModified(SplineContainer container, int spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var instantiate in components)
|
||||
{
|
||||
if (instantiate.Container == container)
|
||||
instantiate.UpdateInstances();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if(!Initialize())
|
||||
return;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
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();
|
||||
|
||||
EditorGUILayout.LabelField(k_Generation, EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_Seed, new GUIContent(k_Seed, k_SeedTooltip));
|
||||
var newSeed = EditorGUI.EndChangeCheck();
|
||||
dirtyInstances |= newSeed;
|
||||
updateInstances |= newSeed;
|
||||
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)))
|
||||
{
|
||||
Undo.SetCurrentGroupName("Change SplineInstantiate Seed");
|
||||
var group = Undo.GetCurrentGroup();
|
||||
foreach (var splineInstantiate in m_Components)
|
||||
{
|
||||
Undo.RecordObject(splineInstantiate, $"Change SplineInstantiate Seed for {splineInstantiate.gameObject.name}");
|
||||
splineInstantiate.Randomize();
|
||||
}
|
||||
Undo.CollapseUndoOperations(group);
|
||||
|
||||
updateInstances = true;
|
||||
}
|
||||
|
||||
var isInstancesCountGreaterThanZero = false;
|
||||
foreach (var splineInstantiate in m_Components)
|
||||
{
|
||||
if (splineInstantiate.instances.Count > 0)
|
||||
{
|
||||
isInstancesCountGreaterThanZero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent(k_Regenerate, k_RegenerateTooltip), GUILayout.MaxWidth(100f)))
|
||||
updateInstances = true;
|
||||
|
||||
GUI.enabled = isInstancesCountGreaterThanZero;
|
||||
if (GUILayout.Button(new GUIContent(k_Clear, k_ClearTooltip), GUILayout.MaxWidth(100f)))
|
||||
{
|
||||
Undo.SetCurrentGroupName("Clear SplineInstantiate");
|
||||
var group = Undo.GetCurrentGroup();
|
||||
foreach (var splineInstantiate in m_Components)
|
||||
{
|
||||
Undo.RecordObject(splineInstantiate, $"Clear SplineInstantiate for {splineInstantiate.gameObject.name}");
|
||||
splineInstantiate.Clear();
|
||||
}
|
||||
Undo.CollapseUndoOperations(group);
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent(k_Bake, k_BakeTooltip), GUILayout.MaxWidth(100f)))
|
||||
{
|
||||
foreach (var splineInstantiate in m_Components)
|
||||
BakeInstances(splineInstantiate);
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Separator();
|
||||
|
||||
foreach (var splineInstantiate in m_Components)
|
||||
{
|
||||
if (dirtyInstances)
|
||||
splineInstantiate.SetDirty();
|
||||
|
||||
if (updateInstances)
|
||||
splineInstantiate.UpdateInstances();
|
||||
}
|
||||
|
||||
if (dirtyInstances || updateInstances)
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void DoSetupSection()
|
||||
{
|
||||
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;
|
||||
|
||||
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 ? string.Empty : 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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bake the instances into the scene and destroy this SplineInstantiate component.
|
||||
/// Making changes to the spline after baking will not affect the instances anymore.
|
||||
/// </summary>
|
||||
void BakeInstances(SplineInstantiate splineInstantiate)
|
||||
{
|
||||
Undo.SetCurrentGroupName("Baking SplineInstantiate instances");
|
||||
var group = Undo.GetCurrentGroup();
|
||||
|
||||
splineInstantiate.UpdateInstances();
|
||||
for (int i = 0; i < splineInstantiate.instances.Count; ++i)
|
||||
{
|
||||
var newInstance = splineInstantiate.instances[i];
|
||||
newInstance.name = "Instance-" + i;
|
||||
newInstance.hideFlags = HideFlags.None;
|
||||
newInstance.transform.SetParent(splineInstantiate.gameObject.transform, true);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(newInstance, "Baking instance");
|
||||
}
|
||||
|
||||
splineInstantiate.instances.Clear();
|
||||
if(splineInstantiate.InstancesRoot != null)
|
||||
Undo.DestroyObjectImmediate(splineInstantiate.InstancesRoot);
|
||||
|
||||
Undo.DestroyObjectImmediate(splineInstantiate);
|
||||
|
||||
Undo.CollapseUndoOperations(group);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c574a2f698921d1458f6dfba4b5e1229
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user