4
votes

I am new to unity and C# so would appreciate any help.

I have made my sprite jump which works fine, however, the only animation which will play is the landing animation. The takeoff animation won't play and the sprite stays in an idle position whilst jumping until the velocity goes below 0 then the landing animation plays.

What am I doing wrong? I am hoping to achieve a takeoff animation playing when the player jumps up and then move straight into a landing animation when it falls.

Here is my code:

using UnityEngine;

public class Player : MonoBehaviour
{
    private Rigidbody2D myRigidbody;

    private Animator myAnimator;

    [SerializeField]
    private float movementSpeed;

    private bool facingRight;

    private bool attack;

    private bool slide;

    [SerializeField]
    private Transform[] groundPoints;

    [SerializeField]
    private float groundRadius;

    [SerializeField]
    private LayerMask whatIsGround;

    private bool isGrounded;

    private bool jump;

    private bool airControl;

    [SerializeField]
    private float jumpForce;

    // Use this for initialization
    void Start()
    {
        facingRight = true;
        myRigidbody = GetComponent<Rigidbody2D>();
        myAnimator = GetComponent<Animator>();
    }

    void Update()
    {
        HandleInput();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        float horizontal = Input.GetAxis("Horizontal");

        HandleMovement(horizontal);

        isGrounded = IsGrounded();

        Flip(horizontal);

        HandleAttacks();

        HandleLayers();

        ResetValues();
    }

    private void HandleMovement(float horizontal)
    {
        if (myRigidbody.velocity.y < 0)
        {
            myAnimator.SetBool("land", true);
        }

        if (!myAnimator.GetBool("slide") && !this.myAnimator.GetCurrentAnimatorStateInfo(0).IsTag("Attack")&&(isGrounded || airControl))
        {
            myRigidbody.velocity = new Vector2(horizontal * movementSpeed, myRigidbody.velocity.y);

        }
        if (isGrounded && jump)
        {
            isGrounded = false;
            myRigidbody.AddForce(new Vector2(0, jumpForce));
            myAnimator.SetTrigger("jump");

        }
        if (slide && !this.myAnimator.GetCurrentAnimatorStateInfo(0).IsName("Slide"))
        {
            myAnimator.SetBool("slide", true);
        }
        else if (!this.myAnimator.GetCurrentAnimatorStateInfo(0).IsName("Slide"))
        {
            myAnimator.SetBool("slide", false);
        }
        myAnimator.SetFloat("speed", Mathf.Abs(horizontal));
    }

    private void HandleAttacks()
    {
        if (attack && !this.myAnimator.GetCurrentAnimatorStateInfo(0).IsTag("Attack"))
        {
            myAnimator.SetTrigger("attack");
            myRigidbody.velocity = Vector2.zero;
        }

    }

    private void HandleInput()
    {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            jump = true;
        }
        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            attack = true;
        }

        if (Input.GetKeyDown(KeyCode.LeftControl))
        {
            slide = true;
        }
    }

    private void Flip(float horizontal)
    {
        if (horizontal > 0 && !facingRight || horizontal < 0 && facingRight)
        {
            facingRight = !facingRight;

            Vector3 theScale = transform.localScale;

            theScale.x *= -1;

            transform.localScale = theScale;
        }
    }

    private void ResetValues()
    {
        attack = false;
        slide = false;
        jump = false;
    }

    private bool IsGrounded()
    {
        if (myRigidbody.velocity.y <= 0)
        {
            foreach (Transform point in groundPoints)
            {
                Collider2D[] colliders = Physics2D.OverlapCircleAll(point.position, groundRadius, whatIsGround);

                for (int i = 0; i < colliders.Length; i++)
                {
                    if (colliders[i].gameObject != gameObject)
                    {
                        myAnimator.ResetTrigger("jump");
                        myAnimator.SetBool("land", false);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private void HandleLayers()
    {
        if (!isGrounded)
        {
            myAnimator.SetLayerWeight(1, 1);
        }
        else 
        {
            myAnimator.SetLayerWeight(1, 0);
        }
    }
}
1
Can you open the animator windows from windows -> animator and then play the game click on your player and press space and see what is happening with your animation. It might be playing the animation so fast and going to idle again or it might starting to play and then suddenly going back to idle. Check what is causing it to turn back to idle maybe you should play with your animation's exit time property.Çağatay IŞIK

1 Answers

6
votes

I think the way you have your animations set up is making this more challenging than necessary. Let's change some things that will hopefully make animating your character a little easier.

First, it is my opinion that using an animation trigger is unreliable when it comes to scripting jumping animations. A better approach is to create a float in your animator, which I have called velocityY, that represents the player's Rigidbody2D.velocity.y. I've also created a new bool called isGrounded, as I think this is clearer and more applicable to many "jump" scenarios.

Once these variables have been created you can link your three animations - idle, jump and land - in the following way:

  1. Set the default animation to "idle".
  2. Make a transition from "idle" to "jump" in which the conditions are:

    • velocityY > 0
    • isGrounded = false
  3. Make a transition from "jump" to "land" with the conditions:

    • velocityY < 0
    • isGrounded = false
  4. Make a transition from "land" to "idle" when isGrounded = true.

  5. Finally, to animate your character when it falls (without jumping first), you can optionally make a transition from "idle" to "land" where:

    • velocityY < 0
    • isGrounded = false

Now to the code. Here's a working example that I tested in a project to achieve your desired results. Note that I did not include everything in your script, just the parts that let the character move and animate its jump correctly. Try using this script and playing around with the movement values, as well as the gravity multiplier on your player's Rigidbody2D component; the default values and a gravity multiplier of 3.5 felt fun to me!

using UnityEngine;

public class Player : MonoBehaviour
{
    //Components on Player GameObject
    private Rigidbody2D myRigidbody;
    private Animator myAnimator;

    //Movement variables
    [SerializeField]
    private float movementSpeed = 9; //Set default values so you don't always
    [SerializeField]                //have to remember to set them in the inspector
    private float jumpForce = 15;

    //Ground checking
    [SerializeField]
    private Transform groundPoint;
    [SerializeField]
    private float groundRadius = 0.1f;
    [SerializeField]
    private LayerMask whatIsGround;

    private float velocityX;
    private bool isGrounded;
    private bool facingRight;

    // Use this for initialization
    private void Start()
    {
        facingRight = true;
        myRigidbody = GetComponent<Rigidbody2D>();
        myAnimator = GetComponent<Animator>();
    }

    private void Update()
    {
        Flip();
        HandleInput();
        HandleAnimations();
    }

    private void FixedUpdate()
    {                       
        HandleMovement();  //It's generally considered good practice to 
                           //call physics-related methods in FixedUpdate
    }

    private void HandleAnimations()
    {
        if (!isGrounded)
        {
            myAnimator.SetBool("isGrounded", false);

            //Set the animator velocity equal to 1 * the vertical direction in which the player is moving 
            myAnimator.SetFloat("velocityY", 1 * Mathf.Sign(myRigidbody.velocity.y));
        }

        if (isGrounded)
        {
            myAnimator.SetBool("isGrounded", true);
            myAnimator.SetFloat("velocityY", 0);
        }
    }

    private void HandleMovement()
    {
        isGrounded = Physics2D.OverlapCircle(groundPoint.position, groundRadius, whatIsGround);

        velocityX = Input.GetAxis("Horizontal");

        myRigidbody.velocity = new Vector2(velocityX * movementSpeed , myRigidbody.velocity.y);
    }

    private void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Jump();
        }
    }

    private void Jump()
    {
        if (isGrounded)
        {   //ForceMode2D.Impulse is useful if Jump() is called using GetKeyDown
            myRigidbody.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
        }

        else 
        {
            return;
        }       
    }

    private void Flip()
    {
        if (velocityX > 0 && !facingRight || velocityX < 0 && facingRight)
        {
            facingRight = !facingRight;

            Vector3 theScale = transform.localScale;
            theScale.x *= -1;

            transform.localScale = theScale;
        }
    }
}

I also took some time to reorganize your code. You don't necessarily need to concern yourself with too much organization right now, but I thought it might interest you since you're still learning.

As you can see, each method in the script handles a concrete task. For example, there's a method solely for handling animation, and another that lets the player jump. It's a good idea to set up your code this way so that if you have to change one aspect later, such as player movement, then all of the related code is in the same place. I think this is especially true for creating methods that deal with player "actions" like jumping or attacking; if you have enough of them you could even create an entire Actions class.

One last thing to mention is this line of code:

isGrounded = Physics2D.OverlapCircle(groundPoint.position, groundRadius, whatIsGround);

I found that this was an easier way to determine when the player is grounded. I achieved this by adding a child GameObject to the player, adding a colorful icon for ease of placement (you can do this by clicking the colorful box near the name of a GameObject), and placing it in between the player's feet.