Search Unity

  1. Get all the Unite Berlin 2018 news on the blog.
    Dismiss Notice
  2. Unity 2018.2 has arrived! Read about it here.
    Dismiss Notice
  3. We're looking for your feedback on the platforms you use and how you use them. Let us know!
    Dismiss Notice
  4. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  5. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  6. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  7. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  8. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Extending StateMachineBehaviours

Discussion in 'Animation' started by JamesB, Aug 15, 2017.

  1. JamesB

    JamesB

    Unity Technologies

    Joined:
    Feb 21, 2012
    Posts:
    79
    Hey guys, I've been working on a project which has involved a lot of StateMachineBehaviours and requires a lot of linking to things in the scene. As such I have made an extension to StateMachineBehaviour which helps with that. The you create a normal MonoBehaviour which has all your scene references and in it's Start function it calls Initialise. Each of the StateMachineBehaviours you create should inherit from SceneLinkedSMB and use the MonoBehaviour you created as the generic type. So for example:

    Code (csharp):
    1. public class MyMonoBehaviour : MonoBehaviour
    2. {
    3.     public Rigidbody someSceneRigidbody;
    4.     public Animator animator;
    5.  
    6.     void Start ()
    7.     {
    8.         SceneLinkedSMB<MyMonoBehaviour>.Initialise (animator, this);
    9.     }
    10. }
    Then your StateMachineBehaviours would look like:

    Code (csharp):
    1. public class MySceneLinkedSMB : SceneLinkedSMB<MyMonoBehaviour>
    2. {
    3.     public override void OnSLStatePostEnter (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    4.     {
    5.         m_MonoBehaviour.someSceneRigidbody.isKinematic = true;
    6.     }
    7. }
    The SceneLinkedSMB also contains much more granular function calls so you can easily control exactly when things happen. The script itself contains an explanation of each of its functions.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.Animations;
    3.  
    4. public class SceneLinkedSMB<TMonoBehaviour> : SealedSMB
    5.     where TMonoBehaviour : MonoBehaviour
    6. {
    7.     protected TMonoBehaviour m_MonoBehaviour;
    8.    
    9.     bool m_FirstFrameHappened;
    10.     bool m_LastFrameHappened;
    11.  
    12.     public static void Initialise (Animator animator, TMonoBehaviour monoBehaviour)
    13.     {
    14.         SceneLinkedSMB<TMonoBehaviour>[] sceneLinkedSMBs = animator.GetBehaviours<SceneLinkedSMB<TMonoBehaviour>>();
    15.  
    16.         for (int i = 0; i < sceneLinkedSMBs.Length; i++)
    17.         {
    18.             sceneLinkedSMBs[i].InternalInitialise(animator, monoBehaviour);
    19.         }
    20.     }
    21.  
    22.     protected void InternalInitialise (Animator animator, TMonoBehaviour monoBehaviour)
    23.     {
    24.         m_MonoBehaviour = monoBehaviour;
    25.         OnStart (animator);
    26.     }
    27.  
    28.     public sealed override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
    29.     {
    30.         m_FirstFrameHappened = false;
    31.  
    32.         OnSLStateEnter(animator, stateInfo, layerIndex);
    33.         OnSLStateEnter (animator, stateInfo, layerIndex, controller);
    34.     }
    35.  
    36.     public sealed override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
    37.     {
    38.         if (animator.IsInTransition(layerIndex) && animator.GetNextAnimatorStateInfo(layerIndex).fullPathHash == stateInfo.fullPathHash)
    39.         {
    40.             OnSLTransitionToStateUpdate(animator, stateInfo, layerIndex);
    41.             OnSLTransitionToStateUpdate(animator, stateInfo, layerIndex, controller);
    42.         }
    43.  
    44.         if (!animator.IsInTransition(layerIndex) && !m_FirstFrameHappened)
    45.         {
    46.             m_FirstFrameHappened = true;
    47.  
    48.             OnSLStatePostEnter(animator, stateInfo, layerIndex);
    49.             OnSLStatePostEnter(animator, stateInfo, layerIndex, controller);
    50.         }
    51.  
    52.         if (!animator.IsInTransition(layerIndex))
    53.         {
    54.             OnSLStateNoTransitionUpdate(animator, stateInfo, layerIndex);
    55.             OnSLStateNoTransitionUpdate(animator, stateInfo, layerIndex, controller);
    56.         }
    57.        
    58.         if (animator.IsInTransition(layerIndex) && !m_LastFrameHappened && m_FirstFrameHappened)
    59.         {
    60.             m_LastFrameHappened = true;
    61.  
    62.             OnSLStatePreExit(animator, stateInfo, layerIndex);
    63.             OnSLStatePreExit(animator, stateInfo, layerIndex, controller);
    64.         }
    65.  
    66.         if (animator.IsInTransition(layerIndex) && animator.GetCurrentAnimatorStateInfo(layerIndex).fullPathHash == stateInfo.fullPathHash)
    67.         {
    68.             OnSLTransitionFromStateUpdate(animator, stateInfo, layerIndex);
    69.             OnSLTransitionFromStateUpdate(animator, stateInfo, layerIndex, controller);
    70.         }
    71.     }
    72.  
    73.     public sealed override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
    74.     {
    75.         m_LastFrameHappened = false;
    76.  
    77.         OnSLStateExit(animator, stateInfo, layerIndex);
    78.         OnSLStateExit(animator, stateInfo, layerIndex, controller);
    79.     }
    80.  
    81.     /// <summary>
    82.     /// Called by a MonoBehaviour in the scene during its Start function.
    83.     /// </summary>
    84.     public virtual void OnStart(Animator animator) { }
    85.  
    86.     /// <summary>
    87.     /// Called before Updates when execution of the state first starts (on transition to the state).
    88.     /// </summary>
    89.     public virtual void OnSLStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    90.    
    91.     /// <summary>
    92.     /// Called after OnSLStateEnter every frame during transition to the state.
    93.     /// </summary>
    94.     public virtual void OnSLTransitionToStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    95.  
    96.     /// <summary>
    97.     /// Called on the first frame after the transition to the state has finished.
    98.     /// </summary>
    99.     public virtual void OnSLStatePostEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    100.  
    101.     /// <summary>
    102.     /// Called every frame when the state is not being transitioned to or from.
    103.     /// </summary>
    104.     public virtual void OnSLStateNoTransitionUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    105.  
    106.     /// <summary>
    107.     /// Called on the first frame after the transition from the state has started.  Note that if the transition has a duration of less than a frame, this will not be called.
    108.     /// </summary>
    109.     public virtual void OnSLStatePreExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    110.  
    111.     /// <summary>
    112.     /// Called after OnSLStatePreExit every frame during transition to the state.
    113.     /// </summary>
    114.     public virtual void OnSLTransitionFromStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    115.  
    116.     /// <summary>
    117.     /// Called after Updates when execution of the state first finshes (after transition from the state).
    118.     /// </summary>
    119.     public virtual void OnSLStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    120.  
    121.     /// <summary>
    122.     /// Called before Updates when execution of the state first starts (on transition to the state).
    123.     /// </summary>
    124.     public virtual void OnSLStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    125.  
    126.     /// <summary>
    127.     /// Called after OnSLStateEnter every frame during transition to the state.
    128.     /// </summary>
    129.     public virtual void OnSLTransitionToStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    130.  
    131.     /// <summary>
    132.     /// Called on the first frame after the transition to the state has finished.
    133.     /// </summary>
    134.     public virtual void OnSLStatePostEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    135.  
    136.     /// <summary>
    137.     /// Called every frame when the state is not being transitioned to or from.
    138.     /// </summary>
    139.     public virtual void OnSLStateNoTransitionUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    140.  
    141.     /// <summary>
    142.     /// Called on the first frame after the transition from the state has started.  Note that if the transition has a duration of less than a frame, this will not be called.
    143.     /// </summary>
    144.     public virtual void OnSLStatePreExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    145.  
    146.     /// <summary>
    147.     /// Called after OnSLStatePreExit every frame during transition to the state.
    148.     /// </summary>
    149.     public virtual void OnSLTransitionFromStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    150.  
    151.     /// <summary>
    152.     /// Called after Updates when execution of the state first finshes (after transition from the state).
    153.     /// </summary>
    154.     public virtual void OnSLStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller) { }
    155. }
    156.  
    157.  
    158. public abstract class SealedSMB : StateMachineBehaviour
    159. {
    160.     public sealed override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    161.  
    162.     public sealed override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    163.  
    164.     public sealed override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    165. }
    I hope you find this useful.
     
    Mecanim-Dev likes this.
  2. NoiseFloorDev

    NoiseFloorDev

    Joined:
    May 13, 2017
    Posts:
    102
    FYI, it's actually a bit more hazardous than that. It's not just if the transition's duration is less than a frame, it's if the transition finishes in less than a frame. This means that you can have a transition much longer than one frame, and still not always receive this callback.

    If you have a 200ms transition, and a gameplay skip (a hard drive spinning up, plugs in a joystick and driver installation kicks in, or anything else that might cause a deltaTime hitch) causes Time.deltaTime to be 300, you can skip right over the transition and OnSLStatePreExit won't be called. I tested this with a script that forces a gameplay skip by calling Thread.Sleep, and was able to skip over the PreExit call. That could lead to games ending up in an unintended state.

    (Some awkward workarounds that could have unwanted side-effects: use manual Animator updates, and if Time.deltaTime is too big, break it apart and call update multiple times so no one update can be too big, or use animate physics.)

    OnTransitionEnter and OnTransitionExit (and maybe OnTransitionInterrupted) callbacks would help this sort of thing a lot.
     
  3. Ylisar

    Ylisar

    Joined:
    Jan 27, 2014
    Posts:
    6
    We also noticed this problem. It seems impossible to get the association between a StateMachineBehaviour & its owning SM node which makes it rather troublesome for StateMachineBehaviours to get some decent composition going without extra book-keeping. I went another approach though: a behaviour whose only purpose is to give the parent animator enough context to trigger the correct callbacks & give us a set of the active behaviours ( OnStateEnter & OnStateExit is enough as long as it's guaranteed that such a behaviour exist on all nodes ).

    One of the upside is that we don't have to do extra work inside OnStateUpdate. It also gives the same correctness guarantees as OnStateEnter, so hopefully doesn't suffer from the delta > transition problem. The downside however is that it strongly relies on the fact that OnStateEnter is being called in the same order on behaviours as they show up on the nodes inside of the editor. This seems to be the case at the moment however.
     
  4. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,573
    It should always be the case, the order is:
    1. current state
    2. interrupted state(should only occur on the frame that the transition was interrupted)
    3. next state
     
  5. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    391
    Is there a way to know which state you're coming from using the SceneLinkedSMB?

    Something like a PreviousStateHash? Since not every state has to have a SceneLinkedSMB on it, it seems hard to know reliably in which state the SMB was.
     
  6. JamesB

    JamesB

    Unity Technologies

    Joined:
    Feb 21, 2012
    Posts:
    79
    In the animator there are two states that are recorded at any one time: the current state and the next state. When transitioning from one state to another, the current state is the one being transitioned from and the next state is the one being transitioned to. This means in a normal OnStateUpdate call if you record the current state as a different state to the one the SMB is on then the state machine is in transition to the SMB's state and the current state is the "previous" state you are looking for. From there it is simply a matter of recording it.
     
    FeastSC2 likes this.
  7. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    391
    The problem with this is that a StateMachineBehaviour is only aware of whichever state you put the script on. That means OnStateEnter, Update and Exit are called yes but in order to know the previous animation state you'd need to put a script on every state and you would probably also need to make a GetComponent call from the script to update a state manager.

    My goal is quite simple: to put a single animator script that listens to when you enter/update/exit all the states of the entire Animator. If the AI behaviour is constrained to a single state like how I've seen most people use it in combination with the Animator, it makes the AI quite simple. Luckily all one needs to make a more complex AI is to know exactly when an animation state is being entered/updated/exitted and then you can hook that up in any complex system that you want.

    I just found out that putting the script not on states but on the "ground" of the animator makes the script understand every state present in 1 layer of the Animator.

    Here's a gif to show what the "ground" of the animator means, I'm selecting with my mouse the ground and the Air state in this gif.
    https://i.imgur.com/r4oPaEQ.gifv

    Here's the AnimatorListener that I'm using. It allows to know about every state in 1 layer,
    the only problem I still have is that I can't have access to other animator layers with that same script:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. // Put this on the topmost animation layer to get access to every animation played.
    5. // Listens to any animation entered in the state machine.
    6.  
    7. public class AnimatorListener : StateMachineBehaviour
    8. {
    9.     public int LastVisual { get; private set; }
    10.     public int CurrentVisual { get; private set; }
    11.  
    12.     /// <summary>
    13.     /// DO NOT REGISTER ON AWAKE, IT ONLY WORKS IN START DUE TO INHERITING FROM STATEMACHINEBEHAVIOUR
    14.     /// </summary>
    15.     public Action<Animator, AnimatorStateInfo, int> OnEnterState;
    16.     /// <summary>
    17.     /// DO NOT REGISTER ON AWAKE, IT ONLY WORKS IN START DUE TO INHERITING FROM STATEMACHINEBEHAVIOUR
    18.     /// </summary>
    19.     public Action<Animator, AnimatorStateInfo, int> OnUpdateState;
    20.     /// <summary>
    21.     /// DO NOT REGISTER ON AWAKE, IT ONLY WORKS IN START DUE TO INHERITING FROM STATEMACHINEBEHAVIOUR
    22.     /// </summary>
    23.     public Action<Animator, AnimatorStateInfo, int> OnExitState;
    24.  
    25.     void Awake()
    26.     {
    27.         LastVisual = -1;
    28.         CurrentVisual = -1;
    29.     }
    30.  
    31.     public sealed override void OnStateEnter(Animator _animator, AnimatorStateInfo _animatorStateInfo, int _layerIndex)
    32.     {
    33.         if (OnEnterState != null)
    34.         {
    35.             OnEnterState.Invoke(_animator, _animatorStateInfo, _layerIndex);
    36.         }
    37.  
    38.         LastVisual = CurrentVisual;
    39.         CurrentVisual = _animatorStateInfo.shortNameHash;
    40.     }
    41.  
    42.     public sealed override void OnStateUpdate(Animator _animator, AnimatorStateInfo _animatorStateInfo, int _layerIndex)
    43.     {
    44.         if (OnUpdateState != null)
    45.             OnUpdateState.Invoke(_animator, _animatorStateInfo, _layerIndex);
    46.     }
    47.  
    48.     public sealed override void OnStateExit(Animator _animator, AnimatorStateInfo _animatorStateInfo, int _layerIndex)
    49.     {
    50.         if (OnExitState != null)
    51.             OnExitState.Invoke(_animator, _animatorStateInfo, _layerIndex);
    52.     }
    53. }
    Please correct me if I'm wrong. And can I listen to all the states of all the layers in an Animator with a single script?
     
    Last edited: Aug 7, 2018
  8. JamesB

    JamesB

    Unity Technologies

    Joined:
    Feb 21, 2012
    Posts:
    79
    When you are adding the script the to the "ground" of the animator window you are adding it to the statemachine currently being displayed. This can be on any sub statemachines that have been created or the parent statemachine belonging to the layer (as in your case). I don't think you can share one instance across multiple layers. However, you can share one instance across ALL animators using this attribute:
    https://docs.unity3d.com/ScriptReference/SharedBetweenAnimatorsAttribute.html
    I recommend caution when using this though. It will make sure that only one instance of the SMB is created and it will be the instance that all animators reference. This might be what you want, it might not be. Note you will still need to add the script wherever you want to use it (each statemachine on each animator).

    I hope this helps.
     
    FeastSC2 likes this.
  9. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    391
    It's interesting but it's not what I need, however you don't have to mind it if there's no solution, I'm quite content with what I have already. It was more of a curiosity.

    Thanks James.
     
  10. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,573
    you need at least to put your script on every layer's top most statemachine. In this case all sub state and sub statemachine inherit the script from their parent statemachine.

    Don't use SharedBetweenAnimators in this case because you do have some fields/properties in your script that aren't static and they would have the same value for all your animators which won't work since each animator can be in a different state
     
    FeastSC2 likes this.
  11. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    391
    I want to extend state machine behaviours so that they hold data of event informations.

    For example, all my attack animations would need the event "ActivateHitbox" "Deactivate Hitbox". So to create them faster than in the normal FBX importer I decided to store the information on a State within the Animator state itself.

    This also solves the problem where I don't want to lose my events every time I reimport an animation.

    upload_2018-8-14_1-17-55.png

    With an monobehaviour editor script I then read these behaviours and write the events in the read-only fbx.

    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using Sirenix.OdinInspector;
    5. using UnityEditor;
    6. using UnityEngine;
    7. using Object = UnityEngine.Object;
    8.  
    9. [RequireComponent(typeof(UnitEvents))]
    10. [RequireComponent(typeof(Animator))]
    11. public class AnimatorEventBaker : MonoBehaviour
    12. {
    13.     // When can exit
    14.     // When can cancel/exit early
    15.     // When can flip direction based on input x
    16.     // When activate hitbox
    17.     // When deactivate hitbox
    18.  
    19.     // Question how to go back to idle properly and give ample time to make another strike.
    20.  
    21.     public UnitEvents UnitEvents;
    22.  
    23.     public static bool ShowName = true;
    24.  
    25.     public AnimState[] States;
    26.  
    27.     [Button("Get from Animator")]
    28.     private void GetEventsFromAnimator()
    29.     {
    30.         UnitEvents = GetComponent<UnitEvents>();
    31.         var clips = AnimationUtility.GetAnimationClips(gameObject);
    32.         var animator = GetComponent<Animator>();
    33.         var behaviours = animator.GetBehaviours<AnimatorEvent>();
    34.  
    35.         var states = new List<AnimState>();
    36.  
    37.         for (var i = 0; i < behaviours.Length; i++)
    38.         {
    39.             behaviours[i].UnitEvents = UnitEvents;
    40.             states.Add(behaviours[i].State);
    41.             Debug.Log("state found: " + behaviours[i].State.Name);
    42.             Debug.Log("State event: " + behaviours[i].UnitEvents);
    43.  
    44. //            EditorUtility.SetDirty(behaviours[i]);
    45.         }
    46.  
    47.         foreach (var c in clips)
    48.         {
    49.             EditorUtility.SetDirty(c);
    50.             Debug.Log("clip: " + c.name);
    51.         }
    52.  
    53.         AssetDatabase.SaveAssets();
    54.         AssetDatabase.Refresh();
    55.  
    56.         states = states.OrderBy(x => x.Name).ToList();
    57.  
    58.         States = states.ToArray();
    59.     }
    60.  
    61.     public void Bake()
    62.     {
    63.         GetEventsFromAnimator();
    64.         WriteEvents();
    65.     }
    66.  
    67.     [Button("Bake")]
    68.     private void WriteEvents()
    69.     {
    70.         // Get the clips
    71.         var clips = AnimationUtility.GetAnimationClips(gameObject);
    72.  
    73.         for (int i = 0; i < clips.Length; i++)
    74.         {
    75.             var currentClip = clips[i];
    76.  
    77.             foreach (var state in States)
    78.             {
    79.                 if (currentClip.name == state.Name)
    80.                 {
    81.                     var events = new List<AnimationEvent>();
    82.                     foreach (var ev in state.Events)
    83.                     {
    84.                         var target = ev.Target;
    85.                         if (target == null) target = state.BaseTarget;
    86.                         events.Add(CreateAnimationEvent(currentClip, target, ev.FunctionName, ev.NormalizedTime, ev.Int));
    87.                     }
    88.  
    89.                     foreach (var ev in state.AttackEvents)
    90.                     {
    91.                         var target = (GameObject)state.BaseTarget;
    92.                         var crap = target.GetComponent<Player>();
    93.                         events.Add(CreateAnimationEvent(currentClip, crap, ev.FunctionName.ToString(), ev.NormalizedTime, ev.Nr));
    94.                     }
    95.  
    96.                     AnimationUtility.SetAnimationEvents(currentClip, events.ToArray());
    97.                     EditorUtility.SetDirty(currentClip);
    98.                 }
    99.  
    100.             }
    101.         }
    102.  
    103.  
    104.         AssetDatabase.SaveAssets();
    105.         AssetDatabase.Refresh();
    106.  
    107.  
    108.         Debug.Log("Bake finished");
    109.     }
    110.  
    111.     [Button("Clear All")]
    112.     private void CleanAllAnimatorEvents()
    113.     {
    114.         // Get the clips
    115.         var clips = AnimationUtility.GetAnimationClips(gameObject);
    116.  
    117.         for (int i = 0; i < clips.Length; i++)
    118.         {
    119.             var currentClip = clips[i];
    120.  
    121.             CleanEventsFromClip(currentClip);
    122.         }
    123.     }
    124.  
    125.     private void CleanEventsFromClip(AnimationClip _clip)
    126.     {
    127.         AnimationUtility.SetAnimationEvents(_clip, new AnimationEvent[] { });
    128.     }
    129.  
    130.     private AnimationEvent CreateAnimationEvent(AnimationClip _clip, Object _object, string _functionName,
    131.         float _normalizedTime, int _int = 0)
    132.     {
    133.         float time2Trigger = _normalizedTime * _clip.length;
    134.         AnimationEvent anEvent = new AnimationEvent
    135.         {
    136.             functionName = _functionName,
    137.             objectReferenceParameter = _object,
    138.             time = time2Trigger,
    139.             intParameter = _int
    140.         };
    141.  
    142.         return anEvent;
    143.     }
    144. }
    145. #endif

    The only problem I have is that my information is not being stored properly. Whenever I exit Unity, my events are not saved. How can I keep this information in the animation clips after quitting Unity? Why are the FBX animation clips read-only?
     
  12. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,573
    because if you modify them, on the next import your modification will be overriden.

    So you have two options:

    1 . you can duplicate your animation clips to edit them, I wouldn't recommend this if you know that you will edit again your animation.
    2. you can embedded the animation events in the model importer, like you do when you import an animation but from script.
    see https://docs.unity3d.com/ScriptReference/ModelImporterClipAnimation-events.html
    and https://docs.unity3d.com/ScriptReference/ModelImporter-clipAnimations.html
     
    FeastSC2 likes this.