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.
(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();
}
}
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.