2D 8 Directional Movement and Animation

Hi,

I’m looking to create a topdown shooter like Jackel fro NES.

I really enjoyed these types of NES games, as your character faces the same direction you are moving in, which adds challenge because you’re dealing with positioning and shooting at the same time.

So, I am trying to implement an 8 direction movement system. Currently I am utilizing Input.GetAxis(“Horizontal”) and Input.GetAxis(“Vertical”), then using animator.SetTrigger(“direction_facing”) to set the animation based on the Vector2 of the two axis. So I am using the animator/triggers to set the character direction/sprite. Hopefully that makes sense. Attached is a picture of what the 8 different animation/positions look like.

So, the biggest challenge I am facing is when I am using the WASD keys, If I am say holding W/A together the character will move Up/Left and the character will use the appropriate Sprite/Animation. But, as soon as I release W/A, because there’s a microsecond between when they are being hold, the animation will temporarily snap to Left or Up. Wondering how I can ensure the animation stays on the diagonal at the end of the button presses.

Thoughts?

2950881--218771--8directional.jpg

1 Like

Just wanted to add that I’ve done some googling, and found a conversation here: xna - 8 directional movement - Maintain vertical position - Game Development Stack Exchange

This may end up being a solution, but does anyone know of any Unity specific solutions? I’ve tried searching the forum and using the keywords I provided wasn’t able to find any examples. Any help, or nudges in the right direction would be greatly appreciated.

Cheers!

Is there a better forum for me to ask this question? Or perhaps I’m not clearly explaining what I’m seeking to achieve or my efforts made so far?

Hard to tell without seeing how you’re polling for input, but assuming you’re using Mecanim to drive the sprite switching, could you put a small exit time on the diagonal animations?

yes, it hard to say without seeing the moving skript. I whould make it with delayed boolean (perhaps). Check if one direction button was up and one other is down. Then set boolean (that prevents the direction switch) on and start timer (for some ms) to disable it.

Hey, appreciate folks getting back to me. So I’ve tried two different methods, and I’ll post each in a post here.

The method below uses 8 different animations for each direction, and uses SetTrigger to initiate them.
I’ll posted a video as well, to show that at the last second the character jerks around. I think some kind of counter/timer may work as mentioned above but have no idea how to implement it. Method 2, which I’ll post below, uses Blend Tree, but still suffers from the same fundamental issues.

Method #1:

using UnityEngine;
using System.Collections;

public class PlayerMovement2 : MonoBehaviour {
    Animator anim;
    public float speed = 5f;
    private Vector2 movement;


    // Use this for initialization
    void Start () {
        anim = GetComponent<Animator>();
        //GetComponent<Rigidbody2D>().velocity = movement;
    }
   
    // Update is called once per frame
    void FixedUpdate () {
        float inputX = Input.GetAxisRaw("Horizontal");
        float inputY = Input.GetAxisRaw("Vertical");
        movement = new Vector2(inputX, inputY);

        //Diagonals

        if (inputX != 0 && inputY != 0) {
            if (movement.y == 1 && movement.x == -1)
            {
                anim.SetTrigger("move_up_left");
            }

            if (movement.y == 1 && movement.x == 1)
            {
                anim.SetTrigger("move_up_right");
            }

            if (movement.y == -1 && movement.x == -1)
            {
                anim.SetTrigger("move_down_left");
            }

            if (movement.y == -1 && movement.x == 1)
            {
                anim.SetTrigger("move_down_right");
            }
        }

        else
        {

            //left/right/up/down
            if (movement.x == -1)
            {
                anim.SetTrigger("move_left");
            }

            if (movement.x == 1)
            {
                anim.SetTrigger("move_right");
            }


            if (movement.y == 1)
            {
                anim.SetTrigger("move_up");
            }


            if (movement.y == -1)
            {
                anim.SetTrigger("move_down");
            }
        }




        transform.Translate(movement * speed * Time.deltaTime);
    }

   
}

So I was also reading the conversation here: Animator state machine for top-down 2d games with non-rotational sprite animation - Unity Engine - Unity Discussions

But they’re only focusing on facing 4 directions rather than 8 so are not facing the same challenges I am here. I am struggling to find a way to set a timer, or a temp variable, to give it a few milliseconds to account for the ms between each button being released.

I actually tried to use a timer to fix the issues, but ran into issues as well. Here is the timer code:

using UnityEngine;
using System.Collections;

public class PlayerMovement2 : MonoBehaviour {
Animator anim;
public float speed = 5f;
private Vector2 movement;
float counter;


// Use this for initialization
void Start () {
    anim = GetComponent<Animator>();
    counter = 0f;
    //GetComponent<Rigidbody2D>().velocity = movement;
}

// Update is called once per frame
void FixedUpdate()
{
    float inputX = Input.GetAxisRaw("Horizontal");
    float inputY = Input.GetAxisRaw("Vertical");




    movement = new Vector2(inputX, inputY);



    if(Input.GetAxisRaw("Horizontal") != 0 && Input.GetAxisRaw("Vertical") != 0)
    {
        if (movement.y == 1 && movement.x == -1)
        {
            anim.SetTrigger("move_up_left");
            counter = .1f;
        }

        if (movement.y == 1 && movement.x == 1)
        {
            anim.SetTrigger("move_up_right");
            counter = .1f;
        }

        if (movement.y == -1 && movement.x == -1)
        {
            anim.SetTrigger("move_down_left");
            counter = .1f;
        }

        if (movement.y == -1 && movement.x == 1)
        {
            anim.SetTrigger("move_down_right");
            counter = .1f;
        }

    }

    else if(counter <= 0)
    {
        if (movement.x == -1)
        {
            anim.SetTrigger("move_left");
        }

        if (movement.x == 1)
        {
            anim.SetTrigger("move_right");
        }


        if (movement.y == 1)
        {
            anim.SetTrigger("move_up");
        }


        if (movement.y == -1)
        {
            anim.SetTrigger("move_down");
        }

        counter = 0;
    }

    counter -= Time.deltaTime;
    Debug.Log(counter);

    transform.Translate(movement * speed * Time.deltaTime);

}
}

Back using the original code I’ve posted, I’m also seeing a lag on the animation and input.

ok, I don’t think it will be perfect :eyes:, try something like this (adjust delay). I am not sure about startcoroutine, perhaps you will need one second boolean, to start it only once ?

using UnityEngine;
using System.Collections;

public class PlayeMovement2 : MonoBehaviour {
    Animator anim;
    public float speed = 5f;
    private Vector2 movement;

    bool isDiagonal = false;
    public float delay = 0.1f;

    // Use this for initialization
    void Start () {
        anim = GetComponent<Animator>();
        //GetComponent<Rigidbody2D>().velocity = movement;
    }

    // Update is called once per frame
    void FixedUpdate () {
        float inputX = Input.GetAxisRaw("Horizontal");
        float inputY = Input.GetAxisRaw("Vertical");
        movement = new Vector2(inputX, inputY);

        //Diagonals

        if (inputX != 0 && inputY != 0) {
            isDiagonal = true;
            if (movement.y == 1 && movement.x == -1) {
                anim.SetTrigger("move_up_left");
            }

            if (movement.y == 1 && movement.x == 1) {
                anim.SetTrigger("move_up_right");
            }

            if (movement.y == -1 && movement.x == -1) {
                anim.SetTrigger("move_down_left");
            }

            if (movement.y == -1 && movement.x == 1) {
                anim.SetTrigger("move_down_right");
            }
        } else {

            if (isDiagonal) {
                StartCoroutine (NoMoreDiagonal ());
            } else {

                //left/right/up/down
                if (movement.x == -1) {
                    anim.SetTrigger("move_left");
                }

                if (movement.x == 1) {
                    anim.SetTrigger("move_right");
                }


                if (movement.y == 1) {
                    anim.SetTrigger("move_up");
                }


                if (movement.y == -1) {
                    anim.SetTrigger("move_down");
                }
            }
        }
       
        transform.Translate(movement * speed * Time.deltaTime);
    }

    IEnumerator NoMoreDiagonal () {
        yield return new WaitForSeconds (delay);
        isDiagonal = false;
    }

}
1 Like

Appreciate the suggestion Vakbaka… I’ll need to look into how StartCoroutine works… as that may be a key to solving this challenge. I think this is an improvement over the code I posted earlier, and it feels more consistent when finishing the animations on diagonal. But I am still having issues. One challenge is that the character’s animation will lag behind if you have a quick amount of input.

I’ve posted a video below showing another NES game (Guerilla Wars) that uses a similar 8 direction movement system, lag free, and very accurate on diagonal animation. At first I thought maybe this accuracy was because of NES controllers, but even on a keyboard, side by side, you can see the difference. Any suggestions? Perhaps I should be using a combination of update and fixed update?

Fixed! So I wanted to say thank you to everyone who contributed - especially to vakabaka who provided some code to get me going. I think your code has allowed me to have the character facing diagonal more consistently. If possible, I’m still interested in vakabaka explaining some of the code and how the NoMoreDiagonal method works. I’m not too familar with IEnumerator, yield, StartCoroutine, or how WaitForSeconds works.

To deal with the animation lag, it was something rather simple that I had overlooked (I’m still quite new to Unity, and even newer to using animation). In the animation, “Fixed Duration” is ticked by default, and the animations were playing out, even if the next animation was queued. Right now I literally have 8 animations of one single sprite each, though now I have the groundwork laid I will likely have actual animations of a few sprites for each direction.

Any pitfalls I might want to consider before adding in additional sprites to each animation?

I see there’s a “Interruption Source” box, that allows you to select “Next State” as an option… so I imagine that may be a better choice than just unticking Fixed Duration, but I’ll cross that bridge when I get there.

Thank you again for pitching in ideas, questions, and code. Excited to be part of this community and hopefully this is the first step into being able to make some awesome projects.

Cheers,
Brodus

Updated video after working code/animation:

p.s. Just wanted to add that with the Fixed Duration unchecked, one of my other original scripts works well (on par I believe, and very similar, to vakabaka’s script). Any suggestions on why one might be better than the other performance wise? Or any issues I might run into?

using UnityEngine;
using System.Collections;

public class PlayerMovement2 : MonoBehaviour {
Animator anim;
public float speed = 5f;
private Vector2 movement;
float counter;


// Use this for initialization
void Start () {
    anim = GetComponent<Animator>();
    counter = 0f;
    //GetComponent<Rigidbody2D>().velocity = movement;
}

// Update is called once per frame
void FixedUpdate()
{
    float inputX = Input.GetAxisRaw("Horizontal");
    float inputY = Input.GetAxisRaw("Vertical");




    movement = new Vector2(inputX, inputY);



    if(Input.GetAxisRaw("Horizontal") != 0 && Input.GetAxisRaw("Vertical") != 0)
    {
        if (movement.y == 1 && movement.x == -1)
        {
            anim.SetTrigger("move_up_left");
            counter = .1f;
        }

        if (movement.y == 1 && movement.x == 1)
        {
            anim.SetTrigger("move_up_right");
            counter = .1f;
        }

        if (movement.y == -1 && movement.x == -1)
        {
            anim.SetTrigger("move_down_left");
            counter = .1f;
        }

        if (movement.y == -1 && movement.x == 1)
        {
            anim.SetTrigger("move_down_right");
            counter = .1f;
        }

    }

    else if(counter <= 0)
    {
        if (movement.x == -1)
        {
            anim.SetTrigger("move_left");
        }

        if (movement.x == 1)
        {
            anim.SetTrigger("move_right");
        }


        if (movement.y == 1)
        {
            anim.SetTrigger("move_up");
        }


        if (movement.y == -1)
        {
            anim.SetTrigger("move_down");
        }

        counter = 0;
    }

    counter -= Time.deltaTime;
    Debug.Log(counter);

    transform.Translate(movement * speed * Time.deltaTime);

}
}

yes, we have did the same work :smile: I think this whould be right code with coroutine. Just as example, because your code is almost similar.

using UnityEngine;
using System.Collections;

public class PlayeMovement2 : MonoBehaviour {
    Animator anim;
    public float speed = 5f;
    private Vector2 movement;

    bool isDiagonal = false;
    bool noDelayStarted = false;
    public float delay = 0.05f;

    // Use this for initialization
    void Start () {
        anim = GetComponent<Animator>();
        //GetComponent<Rigidbody2D>().velocity = movement;
    }

    // Update is called once per frame
    void Update () {
        float inputX = Input.GetAxisRaw("Horizontal");
        float inputY = Input.GetAxisRaw("Vertical");
        movement = new Vector2(inputX, inputY);

        //Diagonals

        if (inputX != 0 && inputY != 0) {
            isDiagonal = true;
            if (movement.y == 1 && movement.x == -1) {
                anim.SetTrigger("move_up_left");
            }

            if (movement.y == 1 && movement.x == 1) {
                anim.SetTrigger("move_up_right");
            }

            if (movement.y == -1 && movement.x == -1) {
                anim.SetTrigger("move_down_left");
            }

            if (movement.y == -1 && movement.x == 1) {
                anim.SetTrigger("move_down_right");
            }
        } else {

            if (isDiagonal && !noDelayStarted) {
                StartCoroutine (NoMoreDiagonal ());
                noDelayStarted = true;
            } else {

                //left/right/up/down
                if (movement.x == -1) {
                    anim.SetTrigger("move_left");
                }

                if (movement.x == 1) {
                    anim.SetTrigger("move_right");
                }


                if (movement.y == 1) {
                    anim.SetTrigger("move_up");
                }


                if (movement.y == -1) {
                    anim.SetTrigger("move_down");
                }
            }
        }
           
        transform.Translate(movement * speed * Time.deltaTime);
    }

    IEnumerator NoMoreDiagonal () {
        yield return new WaitForSeconds (delay);
        isDiagonal = false;
        noDelayStarted = false;
    }
}
2 Likes

Would you be able to explain the benefit of using Coroutine over the approach that I took? Thank you for updating your code. Also, why add NoDelayStart? Cheers!

Coroutines are easy. They are just functions, wich can be paused.

At first you make the function:

IEnumerator + your function

Enumerator NoMoreDiagonal () {
        yield return new WaitForSeconds (delay);
        isDiagonal = false;
        noDelayStarted = false;
    }

later in the code you can call it:
StartCoroutine (functionsName ())

StartCoroutine (NoMoreDiagonal ());

The function will be started and is doing its job like ordinary funktion, befor the line

yield return new

now the function is paused, but your script is working forward.
Then you can choose at what time the function should be continued:

WaitForSeconds (delay);

The are different possibility for this: WaitFor Seconds, EndOfFrame, FixedUpdate.

NoDelayStarted: I am not sure with this, perhaps the script whould start the “NoMoreDiagonals” more as only once. Every 0.02 sec (default fixed time) the coroutine whould be called, because isDiagonal is still true for delay time. So, I added boolean for only one call. I am not programmer, so i dont know, how it will work if the same coroutine was started few times :eyes:

explain the benefit: I dont know :wink: At beginn, I have used -Time.deltaTime for delays too. Now I am using coroutine, because it easy to change boolean with it. If my scripts are not working, then I switch all boolen to public and can see better what is wrong with it.

And you can use Update() for transform.Translate moving. Should be more smooth in this case.
Use FixedUpdate() for physics.

1 Like

Greatly appreciate you getting back to me vakabaka with such a detailed and well explained response. I think Coroutine works well, also because I wonder if Time.deltaTime could end up fluctuating depending on the power the machine that is running the game. This may not be an issue in a small program, but could be an issue in other situations. Thanks again!