Search Unity

How to add and delete Transitions in script?

Discussion in 'Animation' started by Raidenwins, Jan 26, 2015.

  1. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    132
    So I have the following situation that cannot be easily handled with a Mecanim State Transition chart: I have a high number of animations for the same character in my game, let's say about 50. Let's call them Attack 1, Attack 2, ....Attack 50, just for illustration purposes. So far I am able to easily add them as States to an Animation Controller and add Transitions for them from my Idle state. So far so good, I can perform each animation individually and that works just fine.

    Here comes the problem though. Next, I want to be able to perform multiple animations one after another, as soon as the previous one ends, with just one button click. For example, when the player hits the A button, I want the character, who is already in Idle state, to do Attack 1 --> Attack 2 --> Attack 3 --> Idle. I figured out I could do that by creating a Transition from Idle to Attack 1 with a Boolean parameter that is set to True when the player hits a certain button, then another Exit Time Transition from from Attack 1 to Attack 2, then the same (Exit Time) from Attack 2 to Attack 3, and finally from Attack 3 to Idle. Basically I created a Transition loop (in the image below Attack 1 is "punching_2", Attack 2 is "roundhouse_kick_2 0", and Attack 3 is "leg_sweep_2 0", and the Transitions in the loop are highlighted in Blue):



    While that works fine, it's a problem when you have a lot of animations and need to be able to perform pretty much every permutation of length 4 or less. For example, what if I need to perform the following animation combinations?

    • Attack 1 --> Attack 2
    • Attack 2 --> Attack 1
    • Attack 1 --> Attack 2 --> Attack 3
    • Attack 1 --> Attack 3 --> Attack 2
    • Attack 2 --> Attack 1 --> Attack 3
    • Attack 2 --> Attack 3 --> Attack 1
    • Attack 3 --> Attack 1 --> Attack 2
    • Attack 3 --> Attack 2 --> Attack 1
    • Attack 1 --> Attack 2 --> Attack 3 --> Attack 4
    • etc. etc. etc.
    I think you see where I am going with this. If I were to attempt to create a State Transition loop like the one in the image above for every single combination in the Animator Controller editor, things would get out of hand, and flat out impossible to keep track of, very fast.

    So here comes my question: is there a way to add and delete Transitions programmatically so I can create loops like the one above for the particular animation combination I need on the fly? Let's say I want to perform the combination Idle --> Attack 20 --> Attack 34 --> Attack 9 --> Attack 11 --> Idle. I would then create the necessary Transition between the desired States in script and run them upon a button hit. After that I would delete the Transitions.

    If that is not is not possible, how else can I handle the situation I described above?
     
  2. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Raidenwins likes this.
  3. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    132
  4. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    132
    What about adding Transitions with the AddTransition method of the StateMachine class? It'd be something like this:

    1. StateMachine m_stateMachine = m_controller.GetLayerStateMachine(AnimatorController.lastActiveLayerIndex);
    2. State selectedState = Selection.activeObject as State;
    3. State idleState = m_stateMachine.FindState(idleName);
    4. Transition tin = m_stateMachine.AddTransition(idleState, selectedState);
    5. tin.SetConditionEvent(0, "GeneralState");
    6. tin.SetConditionMode(0, TransitionConditionMode.Equals);
    7. tin.SetEventTreshold(0, 42);
    8. tin.AddCondition();
    9. tin.SetConditionEvent(1, "Random");
    10. tin.SetConditionMode(1, TransitionConditionMode.Less);
    11. tin.SetEventTreshold(1, 0.5f);
    12. Transition tout = m_stateMachine.AddTransition(selectedState, idleState);
    Can this work, or this is perhaps legacy animation code? I stumbled upon it in the following thread:

    http://forum.unity3d.com/threads/create-mecanim-states-and-transitions-by-script.188544/
     
    M-Elwy likes this.
  5. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Yes you can do it like this too but this is only available in the editor.

    Also please take note that before 5.0 the state machine API was all internal and in 5.0 we did rewrite the state machine API to make it public.

    Please keep in mind that if you upgrade to 5.0 later on you may need to update your script.
     
  6. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    132
    Well, I ended up with a slightly different approach. It is definitely not ideal, but it works. It's not ideal for two reasons: first, it uses co-routines. I know some people scoff at them in disdain, but this was the best way I could find to reliably wait until an animation has finished before playing the next one. The problem was that the sequence of animations is triggered upon a button click in my Update method (which executes every frame) and you have to use a combination of Boolean flags and animation state checks to see if an animation has ended or not, which I actually tried, but for whatever reason the state was always the previous one (Idle) and not the one corresponding to the animation being played.

    The second reason why it's not ideal is because I ended up hard-coding the length of each animation. I know that's a hack, but again, I could not find a good way to obtain the length of an animation. I tried the code below, but it didn't work. Just like above, the problem was that when checking for which animation state the character was in, it always came back as the previous one. Maybe in Unity 5.0 there'll be a better way to do this:

    public float GetAnimationLength (int animationNameHash)
    {
    // The length of the animation state.
    float animationLength = 0.0f;

    // Not obtain the animator component of the player.
    Animator animator = player.GetComponent<Animator> ();
    if (animator != null)
    {
    // Get the current animation state.
    AnimatorStateInfo animationState = animator.GetCurrentAnimatorStateInfo (0);

    if (animationState.nameHash != animationNameHash)
    {
    animationLength = -1;
    }

    // Now get the the clips in the current animation state. The first
    // clip in the array is the one corresponding to the desired
    // animation state.
    AnimationInfo[] animationInfo = animator.GetCurrentAnimationClipState (0);
    if (animationInfo.Length == 0)
    {
    throw new UnityException ("There are no clips associated with animation " + animationNameHash);
    }

    AnimationClip animationClip = animationInfo [0].clip;

    // Finally, get the length of the desired animation state.
    //animationLength = animationClip.length * animationState.normalizedTime;
    animationLength = animationClip.length;
    }
    }
    catch (UnityException unityEx)
    {
    throw new UnityException ("Could not get length for animation " + animationNameHash + ". Error is: " + unityEx.ToString ());
    }

    return animationLength;
    }
    So here is the solution I came up with, in case someone is interested. If somebody can tell me a reliable way to find an animation's length, that would be awesome:

    1. In my Mecanim State Transition diagram, I added transitions to all the animations from Any State, with a parameter of type Trigger.
    2. I added the method below for playing the animations, stored in a List, in a sequence:

    public IEnumerator PlayAll ()
    {
    // Not obtain the animator component of the player.
    Animator animator = player.GetComponent<Animator> ();
    if (animator != null)
    {
    float anmationLength = 0.0f;
    foreach (String animParameter in AnimParameters)
    {
    animator.SetTrigger (animationParameter);
    // Get the length of the current animation.
    // GetAnimationLength just has the hard-coded lengths
    // of all animations.
    anmationLength = GetAnimationLength (animParameter);
    yield return new WaitForSeconds (anmationLength);
    } // End foreach

    animator.Play ("bouncing_fight_idle_1", 0, float.NegativeInfinity);
    }
    }

    3. And this is how I call the method above:

    void Update ()
    {
    if (Input.GetKeyDown ([some button code here]))
    {
    StartCoroutine(PlayAll);
    }
    }


    Btw, how can one format code in this editor so it'd look nicer?

     
    Last edited: Feb 4, 2015
  7. hotozi

    hotozi

    Joined:
    Nov 19, 2012
    Posts:
    5
    Best thing would be to ''CrossFade'' for 0.1sec than ''Play it'' after the crossfade delay!!