derkork / godot-statecharts

A state charts extension for Godot 4
MIT License
734 stars 35 forks source link

fix: keep track of Ninja Frog's direction to avoid unwanted flipping #35

Closed Brawmario closed 11 months ago

Brawmario commented 11 months ago

Whenever the Ninja Frog returns to Idle, he faces left regardless of what direction he was previously facing (essentially meaning that the "idle_right" animation is never played). He also flipped in unwanted ways in the air too.

This PR introduces a way to keep track of the frog's facing direction and fixes this issue, stopping him from flipping when the velocity becomes zero. Normally I wouldn't even have left and right animations in pixel art games, it would have made more sense to just flip the Sprite node on demand and only keep one of the animations, but I decided for a less intrusive solution. Maybe it would be interesting to refactor this further in order to avoid this somewhat "hacky" solution.

derkork commented 11 months ago

Normally I wouldn't even have left and right animations in pixel art games, it would have made more sense to just flip the Sprite node on demand and only keep one of the animations

Interesting. This is how I started out as well. I then used a Blendspace2D as it was an easy enough way of mapping velocity to animations without having a ton of if/else branches in your code. So I just needed to give a combined vector of horizontal/vertical velocity into the animation tree. Then again i know very little about animation, so I'd be curious as how a "nice" solution would look like. We could do a

if abs(velocity.x) > 0:
   sprite.flip_h = velocity.x < 0  # flip if we run left, otherwise don't flip, keep as is if horizontal velocity is 0

Then we could use a Blendspace1D to blend between vertically stationary, jumping and falling in the moving state and just use the idle animation in the idle state and cut out half the animations. Would this be closer to "nice" ?

Another thing that would be interesting to know is how to handle different collision shapes if the sprite is not symmetrical. Would you create two different collision shapes and en/disable them depending on whether we are looking left or right or do you have another approach for this?

Brawmario commented 11 months ago

Normally I wouldn't even have left and right animations in pixel art games, it would have made more sense to just flip the Sprite node on demand and only keep one of the animations

Interesting. This is how I started out as well. I then used a Blendspace2D as it was an easy enough way of mapping velocity to animations without having a ton of if/else branches in your code. So I just needed to give a combined vector of horizontal/vertical velocity into the animation tree. Then again i know very little about animation, so I'd be curious as how a "nice" solution would look like. We could do a

if abs(velocity.x) > 0:
   sprite.flip_h = velocity.x < 0  # flip if we run left, otherwise don't flip, keep as is if horizontal velocity is 0

Then we could use a Blendspace1D to blend between vertically stationary, jumping and falling in the moving state and just use the idle animation in the idle state and cut out half the animations. Would this be closer to "nice" ?

I do think that would be better, since it cuts down on the amount of animations that were very similar and only adds an one line if branch.

Another thing that would be interesting to know is how to handle different collision shapes if the sprite is not symmetrical. Would you create two different collision shapes and en/disable them depending on whether we are looking left or right or do you have another approach for this?

I'm not really sure how to handle asymmetrical collision for the character body 2d itself (although I don't see anything wrong with your approach), but typically if you want to put a "hitbox" (with an Area2D) like a punch in front of the character, the normal approach I would have to this is to make an Node2D called "Body" (positioned at local (0, 0)) and than I would make the hitbox Area2D a child of it. That way I can use the "Body" node as a pivot, inverting the scale on the x axis in order to flip all of it's children from right to left. Then I would also make the Sprite node a child of Body, that way I would also not need to flip the Sprite node as well, simply flipping the "Body" node would suffice.

It would be just like you proposed:

@onready var body: Node2D = $Body # Sprite2D and other position dependent nodes would be children of this

# somewhere in process or physics_process
var direction := signf(velocity.x)
 if direction != 0.0:
    body.scale.x = direction  # flip if we run left, otherwise don't flip, keep as is if horizontal velocity is 0
derkork commented 11 months ago

So I rebuilt it to not have separate animations anymore but something is still fishy with the double-jump animation. I will need to investigate this a bit further.

derkork commented 11 months ago

I fixed the problem in the way you indicated. Also all animations are now in a single sprite sheet which further simplifies things. Thanks a lot for bringing it up and sending this PR!