Search code examples
c#unity-game-engineanimationblenderunity3d-mecanim

Blender to Unity Animation - Jumps In Frames


I decided to try exporting a Blender model in an FBX with animations to Unity. There are two animations, open and close for the top door of this model. Here's the thing - it seems my model likes to jump into the origin of BOTH animations when the model started, ie. if I import the model and the door is open, and I decide to trigger to CLOSE it, it will close properly - but then, when I try to OPEN it, it decides it needs to stay at the SAME origin as the closing door animation first, THEN it opens - basically causing the door to move too far OUTSIDE of the model.

I've also made a point to try leaning on ONE animation in the Mecanim animator control - but it seems while it gets back to the correct place for the door when moving in the opposite direction, it doesn't do it at the same speed, but does so erratically. Finally, when I try to have one door animation transition to the SAME door animation with the opposite speed (ie. door close to door close, one with speed of 1 and one with -1) it still jumps erratically.

Here's the last Mecanim animator setup I attempted:

enter image description here

This was the approaching of trying to use the same animation in the other state, but negative speed instead of positive. It doesn't work like you would expect.

I'm using state triggers to transition between these states in the code below:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class TriggerAnimation : MonoBehaviour {

    public Transform cockPit;
    // Use this for initialization
    void Start () {


    }

    // Update is called once per frame
    void Update () {
        if (Input.GetKeyDown("t"))
        {
            //AnimationPlayableUtilities.PlayClip("CockPit_ExtDoor|Open");
            Debug.Log("I'm being pressed");
            //GetComponent<Animator>().SetTrigger("ExtDoorOpen");
            GetComponent<Animator>().SetTrigger("IntDoorOpen");
            //GetComponent<Animator>().SetTrigger("CockPit_Door|Open");

        }
        if (Input.GetKeyDown("u"))
        {
            //PlayableGraph graph = new PlayableGraph();
            //AnimationPlayableUtilities.PlayAnimatorController(GetComponent<Animator>(), GetComponent<RuntimeAnimatorController>(), out graph);
            Debug.Log("I'm being pressed");
            //GetComponent<Animator>().SetTrigger("ExtDoorClose");
            GetComponent<Animator>().SetTrigger("IntDoorClose");
            //GetComponent<Animator>().SetTrigger("CockPit_Door|Close");

        }
        if (Input.GetKeyDown("x"))
        {
            GetComponent<Animation>().Play("CockPit_IntDoor|Open");
        }
        if (Input.GetKeyDown("z"))
        {
            GetComponent<Animation>().Play("CockPit_IntDoor|Close");
        }
    }
}

Can someone let me know if there's a better way to approach this and how? I've tried jumping between several articles on this, including a demo tutorial on Mecanim for Unity on their website - no dice...


Solution

  • I don't know how your animation clips look like and how your transitions look like but here are a few tips:

    if (Input.GetKeyDown("x"))
    {
        GetComponent<Animation>().Play("CockPit_IntDoor|Open");
    }
    
    if (Input.GetKeyDown("z"))
    {
        GetComponent<Animation>().Play("CockPit_IntDoor|Close");
    }
    

    Animator.Play directly "jumps" into the target state without any transition. But the animation is restarted everytime you hit the key.

    Triggers get stacked so if you press e.g. the open button mutliple times and than press the close button, you will make both (or even multiple) transitions between the two states since a Trigger is only reset, if it is used (or reset actively in a script using Animator.ResetTrigger).

    And another issue I see is that currently you use decoupled if statements ... so it is theoretically possible to press all 4 keys at the same time what might leed to some issues (The Play call would directly jump to the target state but the triggers from the before Settrigger calls would be still there and stacked so it is possible that your animator makes various transitions from the next frame on.) You rather shouls use if-else statements to allow only processing one key press at a time.


    For a simple door with only two states and no complex animations between those I would suggest a different approach:

    1. Dont export animations from Blender only the model

    2. For a rotating door make sure the pivot is on the axis you ant to rotate around

    3. In the animator have only two states e.g. Opened | Closed

    4. Instead of Triggers use a bool e.g. IsOpen

    5. Create your "animations" in the way that both have only exactly 1 keyframe and disable Loop Time in the inspector. Unity interpolates between the two animations automatically so if there is only one keyframe per animation all the interpolation between the values (positions, colors, rotations etc) is handled automatically by Unity itself.

    6. Make your transitions the following way

      • ExitTime -> 1
        We won't use the exit time actually but this fixes a little bug throwing a warning

        Difference in effective length between states is too big. Transition preview will be disabled. UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

        if you let it on 0

      • HasExittime -> false
        This means don't wait until an animation is finished or at a certain frame to make the transition but do it immeditely (an animation with only keyframe has automatcally a default length of 1 second)
      • FixedDuration -> true (we want to configure stuff in seconds)
      • TransitionDuration(s) -> Here you configure now how long you want the transition to take (it also can be different fo opening and closing)
      • TransitionOffset -> 0 (the animation has only 1 keyframe so we don't want to start it in another frame)

      • and finally your two conditions

        • Open -> Close: Conditions: IsOpen == false
        • Close -> Open: conditions: IsOpen == true

    Than later in the code you instead would use

    // Store the component frerence instead of getting it everytime again
    private Animator _animator;
    
    private void Awake()
    {
        _animator = GetComponent<Animator>();
    }
    
    private void Update()
    {
        if (Input.GetKeyDown("t"))
        {
            Debug.Log("t is being pressed");
            _animator.SetBool("IsOpen", true);
        }
        // use if-else in order to process only one of the buttons at a time
        else if (Input.GetKeyDown("u"))
        {
            Debug.Log("u is being pressed");
            _animator.SetBool("IsOpen", false);
        }
        // you still can keep those for jumping to a state directly without the transition
        else if (Input.GetKeyDown("x"))
        {
            _animator.Play("Opened");
        }
        else if (Input.GetKeyDown("z"))
        {
            _animator.Play("Closed");
        }
    }
    

    Now using a Bool value instead of Triggers you don't have to care about stacked calls. I hope this 1 keyframe animations approach fits your requirements.


    If you require having multiple "steps" in your opnening and closing process e.g. because of having some complex unlock animations before the door actually moves, I would recommend to

    • use a state for every step
    • link all of one direction with HasExitTime = true and ExitTime = 1
    • Use the before mentioned setup to interpolate between those states
    • And finally have the two transition conditions between every "step pair" on the way so every animation either transitions to "more open" on IsOpen = true or to "more closed" on IsOpen = false

    For better understanding here is an example how a more complex setup might look like only using such 1 keyframe animations

    enter image description here