I am making a 2D side-scroller, but I am having problems getting certain animations to work

I’ve been wrestling with this problem for days by now, and despite all my efforts
to isolate the problem, it’s still so mysterious to me that it’s like trying to
reverse engineer some kind of alien spaceship. I am not equipped to solve this
on my own, so now I am asking for help here.

Here’s the rundown:

I’m just starting to make games in unity, and I’m currently working on my first.
it’s a simple 2D pixel sidescroller.

The first thing I wanted to program was player functionality, such as being able
to move, attack, jump, get hurt, die, etc, as well as programming animations.
I achieved this by copying, yet also learning from video tutorials on youtube.

At first it was a success, I managed to make the character (named ernie) do all
of those things without a single major problem. And every problem that I did
encounter could be reasoned with using enough thought and effort.

https://imgur.com/NbyyDHh

(NOTE: I don’t use the Animator interface to control the animations. Instead,
it’s entirely handled using code, specifically, the ChangeAnimation() and
CheckAnimation() functions demonstrated by Small Hedge Games on youtube.)

Eventually, however, I realized that it would be better long-term if I
re-organized my code to be more readable, modular, and versatile.
At this point, ernie’s code was a little messy, and all of the actions could
only be controlled using physical input from the player. However, this wasn’t
feasible if I wanted to, for example, make a cutscene later on where ernie
performs those actions automatically using a script instead of player input.

So I reorganized the actions into their own functions using a script called “PlayerActions”. Hmove() for horizontal movement, Jump(), Attack(), you get the idea.
That way I could use not just physical controls, but even just input from
code and even in other scripts!

PlayerActions looks like this:

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

public class PlayerActions : MonoBehaviour
{
    //References
    public PlayerProperties playerProperties;
    public Rigidbody2D playerRb;
    public Transform feetPosition;
    public SpriteRenderer playerSr;

    //Mechanism Counters
    public float hVelTime;
    public float hVelCounter;

    public float extendedJumpTime;
    public float extendedJumpCounter;

    public float attackTime;
    public float attackCounter;

    //Animation Counters

    //Booleans
    public bool groundContact;

    public bool notHmoveFriendly = false;
    public bool notGroundIdleFriendly = false;
    public bool notJumpFriendly = false;
    private bool notAttackFriendly = false;

    public bool jumpingFirstIfRuns = true;
    public bool extJumping;

    public bool attackingFirstIfRuns = true;



    //layers
    public LayerMask groundLayer;

    //Floats
    public float groundCheckCircle;
    public float hInput;

    //Strings



    //Function definitions for controllable actions

        //Walking or horizontal air movement
    public void Hmove(float direction)
    {
        if (direction != 0f && notHmoveFriendly == false)
        {
            notGroundIdleFriendly = true;
            if (groundContact == true && playerProperties.lockX == false)
            {

                if (direction > 0f)
                {
                    playerSr.flipX = false;
                }
                else
                {
                    playerSr.flipX = true;
                }



                if (hVelCounter > 0f)
                {
                    hVelCounter -= Time.deltaTime;
                }
                else if (hVelCounter <= 0f)
                {
                    hVelCounter = hVelTime;
                    playerRb.velocity = new Vector2(playerProperties.hVel * direction, playerRb.velocity.y);
                }

            }
            else if (groundContact == false && playerProperties.lockX == false)
            {

                if (direction > 0f)
                {
                    playerSr.flipX = false;
                }
                else
                {
                    playerSr.flipX = true;
                }



                if (hVelCounter > 0)
                {
                    hVelCounter -= Time.deltaTime;
                }
                else if (hVelCounter <= 0f)
                {
                    hVelCounter = hVelTime;
                    playerRb.velocity = new Vector2(playerProperties.hVel * direction, playerRb.velocity.y);
                }

            }
        }
        else
        {
            notGroundIdleFriendly = false;
            if (playerRb.velocity.x != 0f)
            {
                playerRb.velocity = new Vector2(0f, playerRb.velocity.y);
            }
        }

    }

        //Jumping
    public void Jump(bool controlInput)
    {
        if (controlInput == true)
        {

            if (groundContact == true && notJumpFriendly == false && jumpingFirstIfRuns == true)
            {
                notAttackFriendly = true;

                extJumping = true;
                extendedJumpCounter = extendedJumpTime;

                playerRb.velocity = Vector2.up * playerProperties.jumpVel;

                notAttackFriendly = false;
                jumpingFirstIfRuns = false;
            }

            if (extJumping == true)
            {
                playerRb.velocity = Vector2.up * playerProperties.jumpVel;
            }

            if (extendedJumpCounter > 0f)
            {
                extendedJumpCounter -= Time.deltaTime;

                if (extendedJumpCounter <= 0f)
                {
                    extJumping = false;
                    jumpingFirstIfRuns = true;
                }
            }

        }

        if (controlInput == false && extJumping == true)
        {
            extJumping = false;
            jumpingFirstIfRuns = true;
        }

    }

        //Attacking
    public void Attack(bool controlInput)
    {

        if (controlInput == true && notAttackFriendly == false && attackingFirstIfRuns == true)
        {
            if (groundContact == true)
            {
                notHmoveFriendly = true;
            }
            notJumpFriendly = true;
            notGroundIdleFriendly = true;

            attackCounter = attackTime;
            attackingFirstIfRuns = false;
        }

        if (attackCounter > 0f)
        {
            attackCounter -= Time.deltaTime;

            if (attackCounter == attackTime / 2)
            {
               //damage enemies within range.
            }

            if (attackCounter <= 0f)
            {
                notHmoveFriendly = false;
                notJumpFriendly = false;
                notGroundIdleFriendly = false;
            }

        }

        if (controlInput == false && attackingFirstIfRuns == false)
        {
            attackingFirstIfRuns = true;
        }

    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // All actions are called in this update function during runtime.
    void Update()
    {
        //Enviroment Sensors
        groundContact = Physics2D.OverlapCircle(feetPosition.position, groundCheckCircle, groundLayer);

        //Player Input
        hInput = Input.GetAxisRaw("Horizontal");

        //Controllable actions
           //Walking or horizontal air movement
        Hmove(hInput);
           //Jumping
        Jump(Input.GetButton("Jump"));
        //Attacking
        if (Input.GetKeyDown(KeyCode.F) && groundContact == true && notAttackFriendly == false && attackingFirstIfRuns == true)
        {
            notHmoveFriendly = true;
            notJumpFriendly = true;
            notGroundIdleFriendly = true;

            attackCounter = attackTime;
        }

        if (attackCounter > 0f)
        {
            attackCounter -= Time.deltaTime;

            if (attackCounter == attackTime / 2)
            {
                //damage enemies within range.
            }

            if (attackCounter <= 0f)
            {
                notHmoveFriendly = false;
                notJumpFriendly = false;
                notGroundIdleFriendly = false;
            }

        }
    }

}

I also created a secondary script labled “PlayerProperties” which, just as the name says, contains some basic data about the player (health, attack damage, speed, jump force, etc).

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

public class PlayerProperties : MonoBehaviour
{
    //Movement Properties
    public float hVel;
    public float jumpVel;

    //Attack Properties
    public int sDamage;

    //Boolean Properties
    public bool inputToggle;
    public bool lockX;
    public bool lockY;
}

Now, this is where the problem begins.
Not very long into re-organizing my code, I ran into a problem where some
animations (like walking) work, but others (like attacking) just flat out
refuse to play properly. I’m currently trying to program an attack and this
is what I’m facing. To clarify, the actual attack() as an action seems to work,
but not its animation.

What happens is I press the attack button, and then the animation either A:
doesn’t show, ernie keeps idling, B: plays a little blip before stopping, or C:
completely bugs out and rapidly switches between the attacking and idling
animation.

I contain the animation scripts in their own separate script from “PlayerActions.”
I will post this script as well as a video of this glitch happening during
runtime.

The script goes as follows:

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

public class PlayerAnimations : MonoBehaviour
{
    //PlayerActions is a script that contains functions for all the player's controllable actions, like Hmove(), Jump(), and Attack(). It also
    //contains booleans formatted as "not[Blank]Friendly" to disable other actions while a specific one is happening (for example, when a ground
    //attack occurs, notJumpFriendly is set to true to disable jumping).
    //These booleans, among other things, can be used to control animations as well.
    public PlayerActions playerActions;
    public Animator playerAn;
    public SpriteRenderer playerSr;
    public string currentAnimation;

    public void ChangeAnimation(string animationName)
    {
        if (currentAnimation != animationName)
        {
            playerAn.Play(animationName);
            currentAnimation = animationName;
            Debug.Log(currentAnimation);
        }
    }

    public void CheckAnimation()
    {
        //walking
        if (playerActions.notHmoveFriendly == false && playerActions.groundContact == true && Input.GetAxisRaw("Horizontal") != 0)
        {
            ChangeAnimation("ernie_walk");
        }

        //attacking
        //The "attackCounter" is a float that counts down how long the attack happens. 
        if (playerActions.attackCounter > 0)
        {
            if (playerActions.groundContact == true)
            {
                ChangeAnimation("ernie_ground_attack");
            }
            else
            {
                ChangeAnimation("ernie_air_attack");
            }
        }

        //Idling on ground
        if (playerActions.notGroundIdleFriendly == false)
        {
            if (playerActions.groundContact == true)
            {
                ChangeAnimation("ernie_idle");
            }
        }

        //Being in the air
        if (playerActions.groundContact == false)
        {
            ChangeAnimation("ernie_in_the_air");
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        CheckAnimation();
    }
}

https://imgur.com/a/onsDcAS

in the video, I demonstrated the walking and the jumping,
which appear totally fine. Now pay close attention to the parts afterwards
where he keeps walking and abruptly freezing. This is me pressing the attack
button, the freezing is actually intentional because he isn’t supposed to move
during his ground attack (which is the swipe of his hand). But the problem here
is that no animation plays while he’s attacking!

Also pay attention to the debug console in the video. I have it so that
it shows his current animation in the debug console every time his animation
changes. During the attacks you can see in the console that his animation rapidly
alternates between idling and attacking, like I mentioned earlier. So this tells
me that the attack animation does happen, but it flips to the idle one before
it even gets a chance to start.

But I have absolutely no idea why this could be happening. I checked numerous
times, so I know I don’t have any faults that would allow two incompatible actions to happen
at the same time. And I really don’t think it has to do with the order of the
actions’ code either. If I had to make a conclusion, I’d say it was a problem
with the animations themselves because all the code apparently makes perfect
sense, I cannot put my finger on a single thing and say “that’s why this is
happening”.

But maybe you folks can. If you know anything about this or have any suggestions
whatsoever, please help me out here. If you have any questions about my code don’t be afraid to ask.

Sounds like you wrote a bug… and that means… time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

ALSO, anything with Animations / Animators / Mechanim:

Always start with the Animator state machine and prove it works in isolation, no code at all:

Thank you, I will try what you said and report back the results.

My solution to stuff like this is to have a “HighPrioritySprite” and a “LowPrioritySprite”.

This might not entirely fix your issue, but it will certainly help diagnose it. I have my movement code outputting to my low priority, and my attacks to my high priority. High priority will always take prescedent.

If you want cutscenes, I would just extrapolate this idea and add a third “ForcedPrioritySprite” and play your cutscene animations in there.

interesting idea, never thought of that. By the way, I figured out what was wrong with my code by now. It turns out that there was this one foolish line I wrote in the Hmove() function that constantly enabled idling no matter what.

Its something I learned to do in Scratch, because scratch has no built in animation handler. Its easy to copy/paste the code between multiple games in scratch and have it easily handle certain things.