2D Animator Help

Hi guys!
I’m working on a personal game project (it’s for school as well) and I’m just getting the hang of Unity because this is the first time I’m working with it. I’ve made sprite sheets for my player and I finally found how to link the animations with a blend tree so that I don’t need to have the animations jumbled together with all the transitions. Although the blend tree made it somewhat similar, my animations are really laggy (they’re linked with up, down, left, right for movement and one sprite for an idle frame). They’re laggy in the sense that I move the character up, and it will show the up animation and then alternate back to idle and then back to the up animation. This is also a huge problem with moving in any direction because it lags and loops each action twice before changing to the other keypress that has been requested. Any suggestions how to fix this? Please help!

It’s probably just a problem with how the blending is set up. Can you show us pictures of your blend tree and its inspector view(s)?

A few walking animations in Mecanim states connected with transitions isn’t “jumbled”, by the way. Don’t shy away from using them, I bet you’ll run into problems down the road trying to do everything in a blend tree. Here’s some pretty typical controllers for reasonably complex actors from Google:

It’s just how it is. If the visualization wasn’t there, it would only be harder to keep track of all the states, but it will still be just as complex.

1 Like

Wow. I wasn’t thinking that it’d be like that. Okay, well I can actually show you what’s going on because I’ve made a GIF of what’s going on. Also I can show what the blend tree and the actual animator page looks like. To be honest this is probably the one thing that would confuse me most (aside from the tagging editor, don’t get me started on that) because of how many different ways you can animate using it. Anyways. Here is the animator and blend tree, plus the GIF.

2932461--216881--Problem3.gif 2932461--216882--screen-shot-2017-01-15-at-11-24-22-pm.png 2932461--216883--screen-shot-2017-01-15-at-11-25-07-pm.png

A few thoughts:

In the first configuration, how are your transitions set up? I have a suspicion that “Has Exit Time” may be checked in them-- this creates an additional exit condition from the current state that triggers after a certain point in the animation. If the transition into and out of a walking state has exit time as a condition, then it will switch back and forth regularly as seen in the first image. If any are checked, you’ll want to remove ‘Has Exit Time’ from all of your transitions that you don’t want to proceed automatically after the animation reaches a certain point.

In the second configuration, what does the Base layer look like? Transitions into blend trees are as important as any other transitions, but I can’t be sure of how you have it set up. I’m assuming this is a separate attempt from the first image. I’ve seen 2D blend trees used for this purpose before. It’s a pretty good approach, and I would probably go with this one. I hope I’m not being confusing about this. It’s ok to use a blend tree for this sort of application, I just didn’t want you to try to do ALL of your animation states in a blend tree or in nested trees.

For either configuration, I’d recommend using two float parameters with x- and y-speed passed in rather than having one bool/trigger for each direction. Change your transition conditions to check for greater than or less than an x-speed and a y-speed, depending on the animation in question, or for a blend tree, use the x- and y-speed as the two input axes. I think you’ll find this more flexible, and you’ll have a lot less scripting to do if you let the Animator handle that logic for you.

For the third configuration, I don’t think you should have the four directions all come from any state, otherwise they will be able to transition from other things unexpectedly, like attack animations. You’ll have to add more logic in your scripts to work around that. If using individual states for each direction, you might as well just add transitions between each one. You could probably exclude the Up>Down and Down>Up states as well as those ones for Left/Right, and just pass through the idle state if going from Up to Down, etc.

As necessary as they seem to be, those animator graphs are just crazy complicated. There’s got to be an easier way than that.

Oh, I’m really sorry about how I presented this. While showing these three types of media, I meant to say that it’s the result of all three being put together. So, the blend tree is actually connected to idle and that’s what the last picture is, the base layer. So, I did have exit time turned off for input for the triggers for each direction, but turning off exit time gives me an mini message that says that it will ignore the method because it isn’t valid. So, I do like the approach of x & y speed instead of my trigger/bool attempt. Also, implementing the x and y speed would be done through the script, right? (While it’s being added/subtracted for the move script it will update to those variables in the Animator.) I might’ve made it more confusing for myself if I did implement the blend tree and the transition together, I’m not sure.

It’s really just a visualization of how complicated state machines can be for things like animation, no? If you look away, it doesn’t seem so bad.

I’m joking of course, but my point is that if some logic is controlled by a state machine, and you reach a certain number of states and transition conditions, then the state machine will be complex anyway, whether you have a picture of it or not. Maybe a behavior tree-based animator could simplify things, but only for some cases. I don’t think I’ll have more than one controller in my project with that many animation states, most will be simpler I’m guessing. I suppose you could make heavier use of sub-state machines to clean it up, visually.

@Andrew_Buiko

I’m having a hard time seeing how you used all three of those pictures in one controller. I see how the last two are connected, but not the first one.

Try this: On a blank controller, create a default state for Idle, and a blend tree called “Walking”. Then create four transitions to Walking (if any one of the transitions’ condition is met, it will transition to Walking, like an OR operation). Create two float parameters for x- and y-speed. Set the transition conditions for one to be x-speed > 0.1, for another, x-speed < -0.1. The other two should be the same, but for y-speed. Uncheck “Has Exit Time” for all four, and reduce the transition time to 0 for all four.

Create one transition back from Walking toward Idle, and give it four conditions: x-speed < 0.1, x-speed > -0.1, etc. Basically opposite from before, but all conditions should be on one transition this time (all conditions on a transition must be met for it to trigger, like an AND operation). This way it will transition to the blend tree whenever speed is non-zero (outside a dead-area), then transition back to idle when speed is 0 again (inside the dead-area). Uncheck “Has Exit Time”, and reduce the transition time to 0.

After that, try setting up the 2D blend tree based on x-speed and y-speed instead of the four bools, and see how it works out. This is all you really need to make this sort of setup work.

@Hyblademin
Alright, so your example did work out well. I ended up switching it out to be the default controller. Although, My script is kinda screwing up when it’s interacting with the controller. This is my script currently, it won’t set the float values to 0 when I am not pressing down on any keys.

using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour
{
    private SpriteRenderer spriteRenderer;
    public Sprite Character_Back, Character_Front, Character_Left, Character_Right;
    public float speed = 1.5f;

    Animator animator;

    void Start ()
    {
        animator = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        if (spriteRenderer.sprite == null)
            spriteRenderer.sprite = Character_Front;
    }

    void Update ()
    {
        if (Input.GetKey (KeyCode.LeftArrow)) {
            transform.position += Vector3.left * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Left;
            animator.SetFloat ("X-Speed", -speed);
        }
               
        if (Input.GetKey (KeyCode.RightArrow))
        {
            transform.position += Vector3.right * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Right;
            animator.SetFloat ("X-Speed", speed);
        }   

        if (Input.GetKey (KeyCode.UpArrow))
        {
            transform.position += Vector3.up * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Back;
            animator.SetFloat ("Y-Speed", speed);
        }
       
        if (Input.GetKey (KeyCode.DownArrow))
        {
            transform.position += Vector3.down * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Front;
            animator.SetFloat ("Y-Speed", -speed);
        }
        using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour
{
    private SpriteRenderer spriteRenderer;
    public Sprite Character_Back, Character_Front, Character_Left, Character_Right;
    public float speed = 1.5f;

    Animator animator;

    void Start ()
    {
        animator = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        if (spriteRenderer.sprite == null)
            spriteRenderer.sprite = Character_Front;
    }

    void Update ()
    {
        if (Input.GetKey (KeyCode.LeftArrow)) {
            transform.position += Vector3.left * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Left;
            animator.SetFloat ("X-Speed", -speed);
        }
        else
        {
            animator.SetFloat ("Y-Speed", 0);
            animator.SetFloat ("X-Speed", 0);
        }
               
        if (Input.GetKey (KeyCode.RightArrow))
        {
            transform.position += Vector3.right * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Right;
            animator.SetFloat ("X-Speed", speed);
        }   
        else
        {
            animator.SetFloat ("Y-Speed", 0);
            animator.SetFloat ("X-Speed", 0);
        }
        if (Input.GetKey (KeyCode.UpArrow))
        {
            transform.position += Vector3.up * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Back;
            animator.SetFloat ("Y-Speed", speed);
        }
        else
        {
            animator.SetFloat ("Y-Speed", 0);
            animator.SetFloat ("X-Speed", 0);
        }
        if (Input.GetKey (KeyCode.DownArrow))
        {
            transform.position += Vector3.down * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Front;
            animator.SetFloat ("Y-Speed", -speed);
        }
        
            animator.SetFloat ("Y-Speed", 0);
            animator.SetFloat ("X-Speed", 0);
    }
}

    }
}

Syntactically I think this should be correct, but running it doesn’t help the desired outcome. I tried commenting out setting the speeds to 0 so that the sprite can stay in one location and not change the sprite whenever I’m not pressing on the key. What should be changed to make it work correctly?

I see two scripts called ‘Move’ in there. Which one are you using?

You’re missing one else in that second script, it seems, but I wouldn’t do it quite like that anyway. Otherwise, since the DownArrow check is the last one, it gets the final say-- if down is not pressed, the speeds would be set to zero no matter what. If it is pressed, the y-speed will be set to speed, no matter what. There are other similar precedence issues with x-speed.

Maybe something more like this:

void Update ()
    {
        if (Input.GetKey (KeyCode.LeftArrow)) {
            transform.position += Vector3.left * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Left;
            animator.SetFloat ("X-Speed", -speed);
        }
        else if (Input.GetKey (KeyCode.RightArrow))
        {
            transform.position += Vector3.right * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Right;
            animator.SetFloat ("X-Speed", speed);
        } 
        else
        {
            animator.SetFloat ("X-Speed", 0);
        }

        if (Input.GetKey (KeyCode.UpArrow))
        {
            transform.position += Vector3.up * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Back;
            animator.SetFloat ("Y-Speed", speed);
        }
        else if (Input.GetKey (KeyCode.DownArrow))
        {
            transform.position += Vector3.down * speed * Time.deltaTime;
            spriteRenderer.sprite = Character_Front;
            animator.SetFloat ("Y-Speed", -speed);
        }
        else
        {
            animator.SetFloat ("Y-Speed", 0);
        }
}

Notice where I combined the left/right if blocks into an if-else if block, then added an else statement so that if neither are pressed, it will set the x-speed to 0. I did the same for up/down. Note that this will cause left to override right, and up to override down. I assumed that would be acceptable.

By the way, if you’re using a Rigidbody (if you aren’t, you will probably want to), you can just set x-speed to Rigidbody2D.velocity.x and y-speed to Rigidbody2D.velocity.y just one time in Update() or LateUpdate(). It’s definitely easier that way.

Oh man. Thank you so much, @Hyblademin ! I was able to completely work it out! It all works flawlessly and I’m so glad that it all worked out. I’ve been working on this for like two months, so thank you so much for the help. I really appreciate it!

1 Like

Hey @Hyblademin !
I’m sorry if this came out of nowhere, but I wanted to ask a question regarding to more help with my game. I’ve been trying to figure out some things online, and I can’t seem to find it anywhere, even with a forum being put up. I wanted to ask if it’s possible to set a multi-idle state with a character from the game. I can’t seem to see any sort of solution, and since you worked with me to help solve the previous problem with the animator I was wondering if you could help out with this. Also, I’ve asked online about setting colliders and actually making it work, for some reason I can’t get it to work either and in this forum I think I’m not really getting the effect that I want. Please let me know if you could help out, I’d appreciate it a lot!
Thanks so much,
Andrew