Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Random Animation Start Time OnStateEnter?

Discussion in '2D' started by rakkarage, Dec 4, 2015.

  1. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    I have a simple mob animator like this:

    It controls many mobs with identical looping animations that i want to start at different times so not look synced.
    I can use this code to start my mob at a random frame in the idle animation...
    Code (CSharp):
    1. animator.Play("Idle", -1, Random.Range(0f, 1f));
    but this does not help for subsequent idles when returning from attack or walk...
    so i put that code in a StateMachineBehaviour:
    Code (CSharp):
    1. using UnityEngine;
    2. public class StateRandomIdle : StateMachineBehaviour
    3. {
    4.     static int AnimatorIdle = Animator.StringToHash("Idle");
    5.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    6.     {
    7.         animator.Play("Idle", -1, Random.Range(0f, 1f));
    8.     }
    9. }
    but this does not work because each time i call Play it re-triggers the OnStateEnter :(

    Please help.
    Thanks.

    The "random start frame" checkbox for animations in 2dtoolkit was awesome!

     
  2. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,772
    If Attack and Walk don't blend to Idle then you could place your code in the OnStateExit of those two states... Not the nicest solution but the simplest I can think of.

    Edit: Actually no, that won't work because you won't be able to go from Attack -> Walk or Walk -> Attack... Sorry I couldn't help.
     
  3. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    thanks for trying :)
    upload_2015-12-5_11-57-30.png
    this cycle offset seems to be hat i want but there does not seem to be a way to adjust it in code? individually for each instance?

    please & thanks
     
  4. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,772

    Oh yep, that's exactly what you want. You can drive it through code by setting up a Float parameter on your animator, then click the 'parameter' checkbox next to 'Cycle Offset', select the parameter you just created on the animator. From there just use Animator.SetFloat from code.

    Hope that helps.
     
  5. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    woohoo! thanks :)
     
  6. michhessel

    michhessel

    Joined:
    Jan 29, 2016
    Posts:
    1
    After setting the float to the cycle offset in the Animator what should I do?
     
  7. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,772
    You should just use it as normal and the animator will offset the normalised time by that float, i believe.
     
  8. kryzodoze

    kryzodoze

    Joined:
    Nov 6, 2013
    Posts:
    21
    @Antony-Blackett Thanks so much, was trying to figure out how to randomize offsets for a long time before I stumbled across this.
     
    Antony-Blackett likes this.
  9. GuiEdu

    GuiEdu

    Joined:
    Feb 12, 2020
    Posts:
    2
    Thanks, this was exactly what I needed! Except I prefer to have all the logic in code and just attach the behaviour to the animation state. This is what I ended up with and it works perfectly. Keep in mind this will re-trigger `OnStateEnter` and this is why I'm checking the `_hasRandomized` boolean.



    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class RandomizeAnimationCycleOffset : StateMachineBehaviour
    4. {
    5.     bool _hasRandomized;
    6.  
    7.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    8.     {
    9.         if (!_hasRandomized)
    10.         {
    11.             animator.Play(stateInfo.fullPathHash, layerIndex, Random.Range(-0f, 1f));
    12.             _hasRandomized = true;
    13.         }
    14.     }
    15. }
    16.  
    upload_2022-8-25_11-7-44.png

    upload_2022-8-25_11-7-55.png
     
  10. electric_jesus

    electric_jesus

    Joined:
    Mar 8, 2015
    Posts:
    36
    Thank you guys! I've been looking for this feature for a few years now.
    Here is my adaptation of the code above. It takes current transition into account and doesn't break blending. It also randomizes offset not once but every time controller enters the state, using normalizedTime to distinguish self-triggered re-entries from ones initiated by controller. Make sure transitions you want to randomize have zero transitionOffset:

    Code (CSharp):
    1. public class RandomizeAnimationOffset : StateMachineBehaviour {
    2.         public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    3.             // Ignore Mecanim transitions with non-zero transition offset and re-triggered calls
    4.             if (stateInfo.normalizedTime > 0.001f) {
    5.                 return;
    6.             }
    7.      
    8.             var offset = Random.value;
    9.             if (animator.IsInTransition(layerIndex)) {
    10.                 var transition = animator.GetAnimatorTransitionInfo(layerIndex);
    11.  
    12.                 // Re-triggers OnStateEnter on next update
    13.                 animator.CrossFade(stateInfo.fullPathHash, transition.duration, layerIndex, offset, transition.normalizedTime);
    14.                 return;
    15.             }
    16.  
    17.             // Re-triggers OnStateEnter on next update
    18.             animator.Play(stateInfo.fullPathHash, layerIndex, offset);
    19.         }
    20.     }

    Code (CSharp):
    1. public class ControlAnimationOffsetByParameter : StateMachineBehaviour {
    2.         [SerializeField] private string ParameterName;
    3.         private int? _parameterHash;
    4.  
    5.         public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    6.             // Ignore Mecanim transitions with non-zero transition offset and re-triggered calls
    7.             if (stateInfo.normalizedTime > 0.001f) {
    8.                 return;
    9.             }
    10.  
    11.             _parameterHash ??= Animator.StringToHash(ParameterName);
    12.             var offset = animator.GetFloat(_parameterHash.Value);
    13.             if (animator.IsInTransition(layerIndex)) {
    14.                 var transition = animator.GetAnimatorTransitionInfo(layerIndex);
    15.  
    16.                 // Re-triggers OnStateEnter on next update
    17.                 animator.CrossFade(stateInfo.fullPathHash, transition.duration, layerIndex, offset, transition.normalizedTime);
    18.                 return;
    19.             }
    20.  
    21.             // Re-triggers OnStateEnter on next update
    22.             animator.Play(stateInfo.fullPathHash, layerIndex, offset);
    23.         }
    24.     }
    One issue with transitions I couldn't fix is that both Animator.Play and Animator.CrossFade give you no control over interruption source. These transitions are non-interruptable by default. If anybody has any idea on how to make it work with interruption source, please share your implementation.
     
    Last edited: Apr 21, 2023