Search Unity

[Tool] Animating Particle Systems easily!

Discussion in 'Animation' started by FeastSC2, Aug 24, 2018.

  1. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    598
    Hello,

    I made these 2 little scripts to allow to sync both preview of animation clip + particle system without entering playmode.
    There is also an editor script to rename the paths if you change the name/hierarchy of your particle systems.

    I use the Animation component and not the Animator for particle systems because it's more cost effective.
    Creating the animation clips with EasyAnimate will set the animation clips in Legacy mode directly.

    Hopefully it's useful!

    The small problem I can see is that when you're simulating you must be careful not to be in RECORD mode in the animation window, else it will create many keyframes.
    I don't know how to detect when the animation window is in record mode in script, if anyone knows that'd be great.


    1) EasyAnimate script
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. /// <summary>
    6. /// Allows preview of AnimationClip and ParticleSystem to be synced up.
    7. /// Put this component on the topmost of your animated objects.
    8. /// It will also create your animation clips with a click of a button in the specified folder.
    9. /// </summary>
    10.  
    11. // TODO: Stop simulating if we're in record mode in animation window! How to do that?
    12.  
    13. [RequireComponent(typeof(Animation))]
    14. [ExecuteInEditMode]
    15. public class EasyAnimate : MonoBehaviour
    16. {
    17.     public const string ANIMATION_CLIP_SAVE_PATH = "Assets/Animations/EasyAnimations/";
    18.  
    19.     public bool IsSimulating { get; private set; }
    20.  
    21.     public string ClipName = string.Empty;
    22.     private Animation Animation;
    23.     private ParticleSystem Ps;
    24.  
    25.     void OnEnable()
    26.     {
    27.         EditorApplication.update += SimulateAnimation;
    28.     }
    29.  
    30.     void OnDestroy()
    31.     {
    32.         EditorApplication.update -= SimulateAnimation;
    33.     }
    34.  
    35.     public void CreateAnimationClip()
    36.     {
    37.         var path = ANIMATION_CLIP_SAVE_PATH;
    38.         if (ClipName != string.Empty)
    39.         {
    40.             path += ClipName + ".anim";
    41.         }
    42.         else
    43.         {
    44.             path += "new animClip.anim";
    45.         }
    46.  
    47.         path = AssetDatabase.GenerateUniqueAssetPath(path);
    48.  
    49.         var animationClip = new AnimationClip {legacy = true, wrapMode = WrapMode.Loop};
    50.  
    51.         AssetDatabase.CreateAsset(animationClip, path);
    52.         AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
    53.  
    54.         Animation = GetComponent<Animation>();
    55.         AnimationUtility.SetAnimationClips(Animation, new[] {animationClip});
    56.         Animation.clip = animationClip;
    57.  
    58.         EditorApplication.ExecuteMenuItem("Window/Animation/Animation");
    59.     }
    60.  
    61.  
    62.     public void OpenAnimationWindow()
    63.     {
    64.         EditorApplication.ExecuteMenuItem("Window/Animation/Animation");
    65.     }
    66.  
    67.     public void OpenAnimationClipEditor()
    68.     {
    69.         EditorApplication.ExecuteMenuItem("JeyTools/Management/AnimationClipEditor");
    70.     }
    71.  
    72.     public void SelectClip()
    73.     {
    74.         Animation = GetComponent<Animation>();
    75.         if (Animation != null)
    76.         {
    77.             if (Animation.clip != null)
    78.             {
    79.                 var clip = Animation.clip;
    80.                 Selection.activeObject = clip;
    81.                 EditorUtility.FocusProjectWindow();
    82.                 EditorGUIUtility.PingObject(clip);
    83.             }
    84.             else
    85.             {
    86.                 Debug.LogError("No clip on the animation component!");
    87.             }
    88.         }
    89.     }
    90.  
    91.     public WrapMode ClipWrapMode;
    92.  
    93.     void OnValidate()
    94.     {
    95.         if (Animation != null)
    96.         {
    97.             Animation = GetComponent<Animation>();
    98.             if (Animation.clip != null)
    99.                 Animation.clip.wrapMode = ClipWrapMode;
    100.         }
    101.     }
    102.  
    103.     public bool HasClip()
    104.     {
    105.         if (Animation == null) Animation = GetComponent<Animation>();
    106.         return Animation.clip != null;
    107.     }
    108.  
    109.     double StartTime;
    110.  
    111. //    [Button("Start/Stop Simulate", ButtonSizes.Medium)]
    112.     public void SimulateToggle()
    113.     {
    114.         Ps = GetComponent<ParticleSystem>();
    115.         Animation = GetComponent<Animation>();
    116.  
    117.         if (Animation.clip == null)
    118.         {
    119.             Debug.LogError("No animation clip !");
    120.             return;
    121.         }
    122.  
    123.         IsSimulating = !IsSimulating;
    124.  
    125.         StartTime = EditorApplication.timeSinceStartup;
    126.  
    127.         if (Ps)
    128.         {
    129.             Ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
    130.             Ps.time = 0f;
    131.         }
    132.  
    133.         if (IsSimulating == false)
    134.         {
    135.             Animation.clip.SampleAnimation(this.gameObject, 0f);
    136.         }
    137.         else
    138.         {
    139.             if (Ps)
    140.             {
    141.                 Ps.Play(true);
    142.                 Ps.time = 0;
    143.             }
    144.         }
    145.  
    146.         Animation.Rewind();
    147.     }
    148.  
    149.     void SimulateAnimation()
    150.     {
    151.         if (IsSimulating)
    152.         {
    153.             double timeElapsed = EditorApplication.timeSinceStartup - StartTime;
    154.             float time = (float) timeElapsed;
    155.  
    156. //            Debug.Log("timeElapsed: " + timeElapsed);
    157.  
    158.             Animation.clip.SampleAnimation(this.gameObject, time);
    159.         }
    160.     }
    161. }
    162.  
    163. [CustomEditor(typeof(EasyAnimate))]
    164. public class EasyAnimateEditor : Editor
    165. {
    166.     private EasyAnimate Target;
    167.     protected void OnEnable()
    168.     {
    169.         Target = (EasyAnimate)target;
    170.     }
    171.  
    172.     public override void OnInspectorGUI()
    173.     {
    174.         GUI.color = Color.white;
    175.         GUILayout.BeginHorizontal();
    176.         Target.ClipName = EditorGUILayout.TextField("Clip Name", Target.ClipName);
    177.         if (GUILayout.Button("Create", GUILayout.MaxWidth(50)))
    178.         {
    179.             Target.CreateAnimationClip();
    180.         }
    181.         GUILayout.EndHorizontal();
    182.         if (Target.HasClip())
    183.         {
    184.             GUILayout.BeginHorizontal();
    185.             Target.ClipWrapMode = (WrapMode)EditorGUILayout.EnumPopup("Wrap mode", Target.ClipWrapMode);
    186.             if (GUILayout.Button("Select", GUILayout.MaxWidth(50)))
    187.             {
    188.                 Target.SelectClip();
    189.             }
    190.             GUILayout.EndHorizontal();
    191.         }
    192.  
    193.         GUILayout.BeginHorizontal();
    194.         if (GUILayout.Button("Animation Window"))
    195.         {
    196.             Target.OpenAnimationWindow();
    197.         }
    198.  
    199.         if (GUILayout.Button("AnimationClipEditor Window"))
    200.         {
    201.             Target.OpenAnimationClipEditor();
    202.         }
    203.         GUILayout.EndHorizontal();
    204.  
    205.  
    206.         var simuColor = Target.IsSimulating ? Color.red : Color.green;
    207.         GUI.color = simuColor;
    208.         if (!Target.IsSimulating)
    209.         {
    210.             if (GUILayout.Button("Start Simulation"))
    211.             {
    212.                 Target.SimulateToggle();
    213.             }
    214.         }
    215.         else
    216.         {
    217.             if (GUILayout.Button("Stop Simulation"))
    218.             {
    219.                 Target.SimulateToggle();
    220.             }
    221.  
    222.             GUI.color = Color.white;
    223.             var style = new GUIStyle("StatusLog");
    224.             GUILayout.Label("Record -> OFF (Animation Window) when simulating!", style);
    225.         }
    226.     }
    227. }
    228.  
    229. #endif

    2) AnimationClipEditor window (to change the paths of your animation clips easily)
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. public class AnimationClipEditor : EditorWindow
    5. {
    6.     public static AnimationClip Clip;
    7.  
    8.     [MenuItem("JeyTools/Management/AnimationClipEditor")]
    9.     static void ShowWindow()
    10.     {
    11.         LoadSelection();
    12.         EditorWindow.GetWindow<AnimationClipEditor>();
    13.     }
    14.  
    15.     private static void LoadSelection()
    16.     {
    17.         Animation animation = null;
    18.         if (Selection.activeGameObject != null)
    19.             animation = Selection.activeGameObject.GetComponent<Animation>();
    20.         if (Selection.activeObject is AnimationClip)
    21.         {
    22.             Clip = (AnimationClip)Selection.activeObject;
    23.         }
    24.         else if (animation != null)
    25.         {
    26.             Clip = animation.clip;
    27.         }
    28.         else
    29.         {
    30.             Clip = null;
    31.         }
    32.  
    33.     }
    34.  
    35.     void OnSelectionChange()
    36.     {
    37.         LoadSelection();
    38.  
    39.         this.Repaint();
    40.     }
    41.  
    42.     void OnGUI()
    43.     {
    44.         EditorGUILayout.BeginHorizontal();
    45.         Clip = (AnimationClip) EditorGUILayout.ObjectField(
    46.             Clip,
    47.             typeof(AnimationClip), true);
    48.         if (GUILayout.Button("Select animation clip in project"))
    49.         {
    50.             if (Clip != null)
    51.             SelectInProject(Clip);
    52.         }
    53.         EditorGUILayout.EndHorizontal();
    54.  
    55.  
    56.         if (Clip != null)
    57.         {
    58.             DisplayFieldsOfAnimationClip(Clip);
    59.         }
    60.     }
    61.  
    62.     private string[] NewPaths = new string[0];
    63.     private string[] NewPropertyNames = new string[0];
    64.     private EditorCurveBinding[] OldCurveBindings;
    65.  
    66.     void DisplayFieldsOfAnimationClip(AnimationClip _clip)
    67.     {
    68.         var allBindings = AnimationUtility.GetCurveBindings(_clip);
    69.  
    70.         if (IsTheSame(allBindings, OldCurveBindings) == false)
    71.         {
    72.             Reset();
    73.         }
    74.  
    75.         EditorGUILayout.LabelField("■ Set the path from parent (with the animationClip) to the animated GameObject.");
    76.         EditorGUILayout.LabelField("■ Childrens are separated by '/'");
    77.         EditorGUILayout.LabelField("■ Use 'Replace' to change the path/property to something new");
    78.         EditorGUILayout.LabelField("■ Use 'Duplicate' to change to copy an animation curve (can be useful in specific cases)");
    79.         GUILayout.Space(20);
    80.  
    81.         var infoStyle = new GUIStyle("ToolbarButton");
    82.         var color = new Color(0.2f, 0.6f, 0.2f);
    83.         infoStyle.active.textColor = color;
    84.         infoStyle.onActive.textColor = color;
    85.         infoStyle.focused.textColor = color;
    86.         infoStyle.onFocused.textColor = color;
    87.         infoStyle.onNormal.textColor = color;
    88.         infoStyle.normal.textColor = color;
    89.  
    90.         var inputStyle = new GUIStyle("textField");
    91.         var inputColor = new Color(0.78f, 0.33f, 0.33f);
    92.         inputStyle.active.textColor = inputColor;
    93.         inputStyle.onActive.textColor = inputColor;
    94.         inputStyle.focused.textColor = inputColor;
    95.         inputStyle.onFocused.textColor = inputColor;
    96.         inputStyle.onNormal.textColor = inputColor;
    97.         inputStyle.normal.textColor = inputColor;
    98.  
    99.         for (var i = 0; i < allBindings.Length; i++)
    100.         {
    101.             var b = allBindings[i];
    102.             GUILayout.BeginHorizontal();
    103.             EditorGUILayout.LabelField(new GUIContent(b.path, "GameObject Path"), infoStyle, GUILayout.ExpandHeight(false), GUILayout.ExpandWidth(false));
    104.             NewPaths[i] = EditorGUILayout.TextField(NewPaths[i], inputStyle);
    105.             EditorGUILayout.LabelField(new GUIContent(b.propertyName, "Property Name"), infoStyle, GUILayout.ExpandHeight(false), GUILayout.ExpandWidth(false));
    106.             NewPropertyNames[i] = EditorGUILayout.TextField(NewPropertyNames[i], inputStyle);
    107.             if (GUILayout.Button("Replace"))
    108.             {
    109.                 ReplaceNewCurve(_clip, i, b);
    110.             }
    111.             if (GUILayout.Button("Duplicate"))
    112.             {
    113.                 DuplicateCurve(_clip, i, b);
    114.             }
    115.             if (GUILayout.Button("Delete"))
    116.             {
    117.                 DeleteCurve(_clip, i, b);
    118.             }
    119.  
    120.             GUILayout.EndHorizontal();
    121.         }
    122.  
    123.         GUILayout.Space(20);
    124.         GUILayout.BeginHorizontal();
    125.         if (GUILayout.Button("Reset Fields"))
    126.         {
    127.             Reset();
    128.         }
    129.  
    130.         if (GUILayout.Button("Apply All"))
    131.         {
    132.             for (var i = 0; i < allBindings.Length; i++)
    133.             {
    134.                 ReplaceNewCurve(Clip, i, allBindings[i]);
    135.             }
    136.         }
    137.         if (GUILayout.Button("Duplicate All"))
    138.         {
    139.             for (var i = 0; i < allBindings.Length; i++)
    140.             {
    141.                 DuplicateCurve(Clip, i, allBindings[i]);
    142.             }
    143.         }
    144.         GUILayout.EndHorizontal();
    145.     }
    146.  
    147.     private void DuplicateCurve(AnimationClip _clip, int i, EditorCurveBinding b)
    148.     {
    149.         var curve = AnimationUtility.GetEditorCurve(_clip, b);
    150.  
    151.         // Create new curve
    152.         EditorCurveBinding newBind = new EditorCurveBinding();
    153.         newBind = b;
    154.         newBind.path = NewPaths[i];
    155.         newBind.propertyName = NewPropertyNames[i];
    156.  
    157.         if (b.path == newBind.path && b.propertyName == newBind.propertyName)
    158.         {
    159.             newBind.path += "x";
    160.             Debug.LogError("Can't duplicate a curve if the path and property names are the same as the original. Adding x to path.");
    161.         }
    162.         AnimationUtility.SetEditorCurve(_clip, newBind, curve);
    163.  
    164.         EditorUtility.SetDirty(_clip);
    165.     }
    166.  
    167.     private void ReplaceNewCurve(AnimationClip _clip, int i, EditorCurveBinding b)
    168.     {
    169.         var curve = AnimationUtility.GetEditorCurve(_clip, b);
    170.  
    171.         // Remove the curve
    172.         AnimationUtility.SetEditorCurve(_clip, b, null);
    173.  
    174.         // Create new curve
    175.         EditorCurveBinding newBind = new EditorCurveBinding();
    176.         newBind = b;
    177.         newBind.path = NewPaths[i];
    178.         newBind.propertyName = NewPropertyNames[i];
    179.         AnimationUtility.SetEditorCurve(_clip, newBind, curve);
    180.  
    181.         EditorUtility.SetDirty(_clip);
    182.     }
    183.  
    184.     private void DeleteCurve(AnimationClip _clip, int i, EditorCurveBinding b)
    185.     {
    186.         var curve = AnimationUtility.GetEditorCurve(_clip, b);
    187.         // Remove the curve
    188.         AnimationUtility.SetEditorCurve(_clip, b, null);
    189.     }
    190.  
    191.     private void SelectInProject(UnityEngine.Object _object)
    192.     {
    193.         Selection.activeObject = _object;
    194.         EditorUtility.FocusProjectWindow();
    195.         EditorGUIUtility.PingObject(_object);
    196.         //        EditorUtility.RevealInFinder(AssetDatabase.GetAssetPath(_object));
    197.     }
    198.  
    199.     private void Reset()
    200.     {
    201.         if (Clip != null)
    202.         {
    203.             var allBindings = AnimationUtility.GetCurveBindings(Clip);
    204.             OldCurveBindings = allBindings;
    205.             NewPaths = new string[allBindings.Length];
    206.             NewPropertyNames = new string[allBindings.Length];
    207.  
    208.             for (var i = 0; i < allBindings.Length; i++)
    209.             {
    210.                 NewPaths[i] = allBindings[i].path;
    211.                 NewPropertyNames[i] = allBindings[i].propertyName;
    212.             }
    213.         }
    214.     }
    215.  
    216.     bool IsTheSame(EditorCurveBinding[] _one, EditorCurveBinding[] _two)
    217.     {
    218.         if (_one == null || _two == null) return false;
    219.         if (_one.Length != _two.Length) return false;
    220.         for (int i = 0; i < _one.Length; i++)
    221.         {
    222.             if (_one[i].path != _two[i].path) return false;
    223.             if (_one[i].propertyName != _two[i].propertyName) return false;
    224.         }
    225.  
    226.         return true;
    227.     }
    228. }