Animation debugging help

Godot Version

4.6.3

Question

I got a bug that triggers when I do diagonal inputs where I let go of both the state still changes but the previous states animation keeps playing

and here is the code for changing the animation:

class_name PlayerState extends State

var player : PlayerController
var is_attacking : bool = false
var can_attack : bool = true

func _init(player_controller:PlayerController, state_m : StateMachine) -> void:
	state_machine = state_m
	player = player_controller

func handle_animations(prefix: String) -> void:
	prefix = prefix.to_lower()
	
	match player.movement.last_direction:
		Vector2.LEFT:
			player.animations.play(prefix + "_left")
		Vector2.RIGHT:
			player.animations.play(prefix + "_right")
		Vector2.UP:
			player.animations.play(prefix + "_up")
		Vector2.DOWN:
			player.animations.play(prefix + "_down")

do I need to specify what to do for diagonals too?

You are using match to compare with Vector2, which is prone to fail, because you are then comparing floating point numbers, which are often not exact:

For instance Vector2.UP is just another name for Vector2(0.0, 1.0)

Because you are testing with a keyboard it is working right now, but the moment you try to do this with a game controller stick this will fail.

A diagonal is expressed as a normalized direction -Vector2 of approximately 1.0 length, ergo, a Vector2 with an x and a y somewhere between 0.0 and 1.0

Have you followed a good 2D game tutorial yet?

I have but just altering it to make it fit the structure I want

tutorial in question:

Hmm. Well, if Coding with Russ says using exact matching with Vector2 is a good idea, I don’t really know what to make of that.

@dragonforge-dev , do you know of some good material? I don’t remember if dodge the creeps covers this

he used if statements

I used match because to me that made sense didn’t know that would cause a problem

That explains it. Well, you could do two things: either use an angle based on the variable you have now (which contains a direction Vector) or check which buttons are pressed using Input, which is probably easier.

how would I get the angle?

plus the movement func if that helps any thing:

func directional_movement() -> void:
	var normalized_direction = parent.input.direction.normalized()
	
	if normalized_direction != Vector2.ZERO:
		parent.velocity = normalized_direction * speed
		last_direction = normalized_direction
	else:
		parent.velocity = Vector2.ZERO

Look in the online documentation of Vector2 to find the ways to read out an angle.

Then figure out a way to determine what angle maps to what exact direction using if statements and the <=, >= operators or similar.

Then do your animation stuff.

After reading I’m more confused, more specifically in the sense of how to implement it. I might just use the input way

I think that is the best choice too. Unless you want all directions in stead of 8. If you map a stick to Input actions that will work like a keyboard as well (kind of)

Direction vectors are for more fine grained control over speed and direction by a user

yeah, also after I sent that message I started experimenting with it and am I getting closer to what you’re telling me?

var angle_direction = rad_to_deg(player.movement.last_direction.angle())

You are! Good going. But I think you do not have to ask for reassurance this much. Use the docs a lot. They are also in the editor (faster and better search than online)

You can use F1 to open them, or Ctrl+click on methods in the gdscript editor to read the docs about them. This is also a great reason to always assign the correct (data)type to a var using the colon : operator. This way the interpreter knows what methods and properties to autocomplete.

sry, I just want to know if I’m correct because I can look a docs all day and have none of it click without clarification from others or a code example of the setup

edit : IT WORKED, YES!!!

also why does it floor to -91 instead of -90?

func handle_animations(prefix: String) -> void:
	prefix = prefix.to_lower()

	var angle_direction : float = floor(rad_to_deg(player.movement.last_direction.angle()))

	var is_left : bool = angle_direction > -91.0 and angle_direction > 90
	var is_right : bool = angle_direction > -90 and angle_direction < 90
	
	if is_left:
		player.animations.play(prefix + "_left")
	elif is_right:
		player.animations.play(prefix + "_right")
	elif angle_direction == -91:
		player.animations.play(prefix + "_up")
	elif angle_direction == 90:
		player.animations.play(prefix + "_down")

This is of course the best reassurance.

So there are of course still quite some bits that need improvement.

Even though rad_to_deg(..) returns seemingly whole numbers, it in fact does not. Same goes for floor(..).

They return float, which still makes exact matching with == unreliable. Reliable approximate comparisons between floats can be done with the is_equal_approx(..)

The other thing I noticed is that by using floor the angle representing the up direction becomes =~ -91.0 which is a little counter intuitive.

Then comes your wish to do the diagonal directions as well, which will come in multiples of - roundabout - 45.0

One more issue is that your angle may end up outside the ranges you test for entirely, a regular circle is often expressed between 0.0 and 360.0 decimal degrees. This means you need to somehow normalize your number to be in a predictable range:

So I’m sure there is a game dev best practice for what you’re doing that you can find in a tutorial somewhere, but at the moment it seems you’re stuck with me :grin:, so here’s what I would do.

Make a set of boolean tests (like you did) to see in which range your normalized angle falls:

Up: between 360.0−(45.0*0.5) and 45.0*0.5
Up/Right: between 45.0*0.5 and 45.0+(45.0*0.5)

.. etcetera ..

Now I would also assign these values to const’s to give them meaningful names and to prevent them from having to be calculated over and over (although that’s probably optimized low level so doesn’t really cost anything performance wise, but we shouldn’t assume that).

Then.. at some point I would look at all this code I made and conclude: this code would have looked so much simpler if I had just opted for the Input option. Then I would:

  • stash this code in a stale git branch for another day (never throw away code)
  • delete all the lines and rewrite it using a good old Input map :slight_smile:

I actually did do all this while learning Godot myself. I have been starting over all my life.

I give answers on this forum, but I’m still corrected all the time by more senior developers.

So in short: keep doing what you’re doing, but I won’t be here forever, so hopefully someone else will take over or, better yet, point you to the best learning material.

I’m just here a lot now because I only have my phone, get bored quickly and have a addictive personality (currently temporarily to this forum).

Sorry, missed this. Floor means round down to the lower whole number. With negative numbers only ceil will round up to -90.0

Your better choice would have been round.

Here’s the problem @GD_Unknown25 - you are trying to optimize a tutorial as you follow it. That inevitably leads you down the path you have gone down - which is you have bugs that you’re not sure where they came from. Personally, I always follow the tutorial as it is written - even if I think it is dumb. My refactoring comes when I copy the lessons over to my games.

Even if you follow a tutorial exactly, you will often run into bugs. Especially video tutorials. Then you have to go back and figure out what step you missed (or occasionally) watch to see what thing they did that they didn’t say they did.

When you alter variable names, logic, etc. you will inevitably run into problems like this.


As for the Vector2 matching, you are likely running into floating point rounding errors. match only does equals comparisons. Which means if your Vector2 does not match exactly with 14 decimal points of precision, that option will never match. Looking at your original code, my guess is you are not normalizing your player.movement.last_direction, or you are using a controller and getting numbers that do not exactly match even if normalized.

I’ve learned over the years of watching any tutorial is that, I don’t really understand the tutorial unless I put my own spin to it with the other stuff I understand. Idk if that’s a a bad thing but that’s how my brain works, it maybe but it’s been working so far. Plus I love trying to figure it out no matter how mind numbly frustrating it can get because it gets me to think and I like to be in deep thought.

but again that’s just me

It’s not a bad thing, imho, my brain works that way too.

But you may want to sit on your hands a little longer before asking for help because before long not only you, but both of us are following that :rabbit: into the wrong hole. :wink:

Except that’s not what you’re doing. Because you don’t understand how Vector2 data types or match work in GDScript.

Personally, I think this is a cop out. Making things harder is not a learning style. Applying what you learn is. This is like telling your algebra teacher that if you don’t use calculus you can’t learn algebra.

It might be worthwhile to take some time to reflect on why this is true for you. I too, like to be deep in thought. But the point of learning is not to solve a problem - but to learn something new.

The other reason I follow tutorials as closely as possible is it’s faster. Then I get to take the new knowledge and apply it to the problem I want to solve, instead of fixing someone else’s code that I’m not going to use.

Right. And only you can change you. I’d challenge you to examine these assumptions you have about learning.

I’ve seen people who think this way for 30 years in business settings. And frankly, they get in their own way. They overcomplicate things and work longer hours to accomplish the same amount of (or less) work than others. Then their identity is tied up in the martyrdom of how long they work. It has the side effect of destroying their personal relationships.

But, you do you.

well if my explanation was bad that’s because it is never been good at explaining my thoughts well constantly starting and stopping my sentence to attempt to convey what I mean, mainly adding more confusion.