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. Join us on Dec 8, 2022, between 7 am & 7 pm EST, in the DOTS Dev Blitz Day 2022 - Q&A forum, Discord, and Unity3D Subreddit to learn more about DOTS directly from the Unity Developers.
    Dismiss Notice
  3. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

Question How to play random animation without code changes?

Discussion in 'Animation' started by Peter77, Jun 27, 2021.

  1. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,314
    I want to implement "some code" that's able to play a random animation for a particular animation state, where the number of animation variations is unknown at the time of writing that code.

    For example, I have an
    Idle
    state that's triggered by code via:
    Code (CSharp):
    1. animator.Play("Idle");
    This
    Idle
    must then be able to trigger a random animation. This allows to add and remove idle variations through the Animator Controller, without code modifications.

    I don't want the (game) code to be aware of how many "Idle variations" there are.

    I don't want:
    Code (CSharp):
    1. var stateName = $"Idle {Random.Range(1, 4)}";
    2. animator.Play(stateName);
    This code falls apart when an idle animation is removed or added, so it's not an acceptable solution.

    I don't want:
    To hook up an Animator Parameter for every animation that might have variations:
    Code (CSharp):
    1. animator.SetInteger("Idle Variation", Random.Range(1, 4));
    2. animator.Play("Idle");
    This again falls apart when the number of animations change. It also introduces another level of complexity that shouldn't be necessary.


    What I do want:
    I want that the Idle state knows how many transitions there are and picks a random one and plays it.

    upload_2021-6-27_10-40-33.png

    The closest I came to it was to implement a
    StateMachineBehaviour
    that holds a list of state names (Idle 1, Idle 2, Idle 3).

    upload_2021-6-27_10-42-36.png

    The problem is that you still need to keep the
    State Names
    array as shown in the screenshot in-sync with the number of actual state transitions.

    Code (CSharp):
    1. public class RandomAnimatorBehaviour : StateMachineBehaviour
    2. {
    3.     [SerializeField] string[] m_StateNames = new string[0];
    4.  
    5.     // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    6.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    7.     {
    8.         var index = UnityEngine.Random.Range(0, m_StateNames.Length);
    9.         var stateName = m_StateNames[index];
    10.  
    11.         animator.Play(stateName, layerIndex);
    12.     }
    13. }
    This again is rather error-prone and shouldn't be needed, since the state must be aware of its transitions. I also don't really want to "animator.Play" again, but rather just substitute the animation variation, because the caller might have called Play, CrossFade, etc.

    So... I guess the question is:
    1. How can I access the "transitions array" from an animator state inside a StateMachineBehaviour callback.
    2. Is there a better way to play a random animation without having to manually keeping an extra list of state names (or similar) in-sync with the Animator Controller?

    I added my test project to this post. If you open SampleScene and press play, the cube changes colors between red, green and blue, which represent different idle variations.
     

    Attached Files:

    Last edited: Jun 27, 2021
    mitaywalle likes this.
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,191
    Animator Controllers don't give you runtime access to that information. In the editor you could cast the RuntimeAnimatorController to a UnityEditor.AnimatorController to get what you want and store that info in a serialized field, but that's a lot of effort for an unreliable hack.
    Yeah, stop using Animator Controllers and use Animancer instead (link in my signature). It lets you directly play whatever animations you want so you could literally just make an
    AnimationClip[]
    and call
    animancer.Play(clips[Random.Range(0, clips.Length)]);
    . This page explains how the random idle system was implemented in Unity's 3D Game Kit and compares it to how much cleaner it can be done in Animancer.
     
  3. electric_jesus

    electric_jesus

    Joined:
    Mar 8, 2015
    Posts:
    35
    I've always used a single [0, 1] random parameter and updated it few times a second. All transitions can check this parameter to select a random branch. This way you can also configure a non even probability distribution, e.g. transition to Idle 1 if Random is less than 0.5 (50%), go to Idle 2 if it less than 0.75 (25%) and go to Idle 3 otherwise (25%).
     
  4. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    204
    Hello everyone.
    Interesting problem, I've brainstormed this problem and here's my solution: https://github.com/mitay-walle/Unity3d-AnimatorRandomBehaviour

    - no need to use Animator.parameter
    - list of nameHashes is updated automaticly at OnBeforeSerialize

    Long story short: Animator.StateMachine with child random animations and StateMachineBehaviour on it, which contains editor-cached nameHashes for each random state and execute Animator.Crossfade(randomHash) at OnStateEnter. There is also some minor checks to not execute Crossfade infinitely.
     
    ritesh_khokhani likes this.
  5. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,191
    StateMachineBehaviours should receive OnValidate events, so you could use that instead of needing ISerializationCallbackReceiver (because the deserialize callback would have a small runtime cost even if the method is empty).