Godot Version
4.4.1
Question
I’ve been working on a top down 2D game (I’m fairly new to the engine). I’m having difficulties with some of the 2D animation. I’ve set up the animation tree which I think is working as intended so I believe the issue must be with my script. See when the character goes from walking → idle when walking in a diagonal direction the character refuses to play the idle animation on the diagonal. I believe this is due to key press release and how it’s extremely difficult to release both the keys at the exact same time. I’ve tried many solutions at this point but nothing I was satisfied with. My current best solution is keeping track of the previous frames direction and making a guess which direction the player intended however there is still 1-2 frames where the wrong animation plays. Anyone have any solutions that I’m too dumb to see myself?
extends Node2D
@onready var animation_tree: AnimationTree = $"../AnimationTree"
@onready var animated_sprite: AnimatedSprite2D = $"../AnimatedSprite2D"
@onready var commit_timer: Timer = $CommitTimer
@onready var player: CharacterBody2D = $".."
var last_facing_direction := Vector2(0, -1)
var direction_history: Array[Vector2] = []
const HISTORY_LENGTH := 10
func _physics_process(delta: float) -> void:
var idle = !player.velocity
if !idle:
last_facing_direction = player.velocity.normalized().round()
_update_direction_history(last_facing_direction)
else:
last_facing_direction = _get_most_frequent_direction()
animated_sprite.flip_h = last_facing_direction.x == -1
animation_tree["parameters/Idle/blend_position"] = last_facing_direction
animation_tree["parameters/Walk/blend_position"] = last_facing_direction
animation_tree["parameters/conditions/walk"] = !idle
animation_tree["parameters/conditions/idle"] = idle
func _update_direction_history(dir: Vector2) -> void:
direction_history.push_front(dir)
if direction_history.size() > HISTORY_LENGTH:
direction_history.pop_back()
func _get_most_frequent_direction() -> Vector2:
var counts := {}
for dir in direction_history:
if dir == Vector2.ZERO:
continue
counts[dir] = counts.get(dir, 0) + 1
var most_frequent := Vector2.ZERO
var max_count := 0
for dir in counts.keys():
if counts[dir] > max_count:
max_count = counts[dir]
most_frequent = dir
return most_frequent
func is_diagonal(vector: Vector2) -> bool:
return abs(vector) == Vector2(1, 1)
For anyone wondering I did come to a solution. I’m pretty happy with. I switched the direction logic to be based on the player’s input_direction instead of their velocity, and that definetly made things a lot easier to manage.
What I ended up doing was this: whenever the player changes direction, I store the direction that they were previously facing and start a short timer. If the timer expires and the player has stopped moving, the animation idles in that last stored direction. If the player keeps moving, then the animation updates to match the new input_direction.
There’s still a slight downside, this approach makes the animation feel a tiny bit less responsive but overall, it’s working well enough for my needs right now.
I’m always open to ideas so if you have any improvements let me know!
extends Node2D
@onready var animation_tree: AnimationTree = $"../AnimationTree"
@onready var animated_sprite: AnimatedSprite2D = $"../AnimatedSprite2D"
@onready var commit_timer: Timer = $CommitTimer
@onready var movement_controller: Node2D = $"../MovementController"
@onready var player: CharacterBody2D = $".."
@export var inital_facing_direction := Vector2.DOWN
var facing_direction := Vector2.ZERO
var last_facing_direction := Vector2.ZERO
func _ready() -> void:
movement_controller.input_direction = inital_facing_direction #Set input_direction to inital direction to avoid animation flicker
facing_direction = inital_facing_direction #Set current direction to
func _process(_delta: float) -> void:
var input_direction = movement_controller.input_direction
var idle = !player.velocity
#Only change player direction if the player is moving and the commit timer is stopped
if !idle && commit_timer.is_stopped() && input_direction != facing_direction:
last_facing_direction = facing_direction #The angle we wish to face is the angle of the value right before we swapped
commit_timer.start()
animated_sprite.flip_h = facing_direction.x == -1
### NOTE: blend position should never be (0, 0) as it causes incorrect animation
animation_tree["parameters/Idle/blend_position"] = facing_direction
animation_tree["parameters/Walk/blend_position"] = facing_direction
animation_tree["parameters/conditions/walk"] = !idle
animation_tree["parameters/conditions/idle"] = idle
func _on_commit_timer_timeout() -> void:
if !player.velocity:
facing_direction = last_facing_direction
last_facing_direction = Vector2.ZERO
else:
facing_direction = movement_controller.input_direction
It feels like this script is too complicated for what youre trying to achieve. I think all you need is the animated sprite and check the input directions components
#Set player direction
if !input:
direction = last_facing
else:
direction = input
#Flips sprite
if player.direction.x < 0:
animated_sprite.flip_h = true
elif player.direction.x > 0:
animated_sprite.flip_h = false
#Here use match to play idle/walk anims based on input i.e Vector2(0, 1) would play walk right or idle right depending on velocity
if !player.velocity:
match direction:
#vectors to match go here
else:
match direction:
#and here
I like the animation tree but it might be a bit much for what youre trying to accomplish but if youre just trying to learn it by all means use it
Hey!
Thanks for the response. I started with a script that was very similar to this, and you are right that my script is a little too complex for what is going on. However, the problem I had was when I pressed multiple keys, upon key release for a slight second, there would be an added input from the key that was released later than the key before, leading to an extra input and having the character face the wrong way when idling, this drove me mad so the slight delay before animation change fixed my issue. If you have any other suggestions its always much appreciated!