How to use Animation player for Top-Down, Right-Up Movement? GAME JAM URGENT HELP!

Godot Version

v4.4.1.stable.mono.official [49a5bc7b6]

Question

how-to-use-animation-player-for-top-down-right-up-movement-v0-psug4g3rjvse1

This is the asset i am using, I am in a game Jam so i have to understand this, learn about how to fix this. I have set up the animation tree and made it work, but i’m not sure about how to add it in the grid shown exactly bellow, I know how to set it up for Right-Left-Down, but the asset is like Right_Up-Right_left-Down_left if that does make sense.

I have tried to fix it, but to no avail. One other issue is i’m not sure how to make good changes between the states, but this is what i have done. Idle goes to walking ect, Idle works and the cycle works Attacking works as well. Damage isn’t added yet and will be once i fix the and make the enemy tomorrow. I will also attach the player movement as it uses velocity to see if the player is idle. I am realy confused and will attach anything that you need.

On a unrealed topic which isn’t as important, do you know any good tutorials on how pathfinding works and set enemy spawning works(My first time using Godot for a project and its a GAME JAM)

extends Node

@onready var anim_tree: AnimationTree = $/root/World/Player/Player_AnimationTree
@onready var playback = anim_tree.get("parameters/playback")
@onready var body = get_parent() as CharacterBody2D

var was_attacking = false
var attack_timer := 0.0
const ATTACK_DURATION = 0.5

# A small threshold to filter out tiny movements.
const MOVE_THRESHOLD = 5.0

func _ready():
	anim_tree.active = true

func _process(delta):
	# Attack logic: if attack is triggered, lock in the Attacking state.
	if Input.is_action_just_pressed("attack") and !was_attacking:
		was_attacking = true
		attack_timer = ATTACK_DURATION
		playback.travel("Attacking")
	
	# Reduce the attack timer and reset the flag when done.
	if was_attacking:
		attack_timer -= delta
		if attack_timer <= 0:
			was_attacking = false
	
	# If attacking, we don’t change the movement animations.
	if was_attacking:
		return

	# Determine movement-based state transitions based on velocity.
	if body.velocity.length() > MOVE_THRESHOLD:
		if playback.get_current_node() != "Walking":
			playback.travel("Walking")
	else:
		if playback.get_current_node() != "Idle":
			playback.travel("Idle")

And the Player movement script

extends CharacterBody2D

const speed = 50
const accel = 2
var input:Vector2
func get_input():
	input.x = Input.get_action_strength("right") - Input.get_action_strength("left")
	input.y = Input.get_action_strength("down") - Input.get_action_strength("up")
	return input.normalized()
func _physics_process(delta):
	var player_input = get_input()
	if player_input != Vector2.ZERO:
		velocity = velocity.lerp(player_input * speed, delta * accel)
	else:
		# Snap to 0 quickly when no input
		velocity = velocity.lerp(Vector2.ZERO, delta * accel * 2)
	move_and_slide()

I thank all of you in advance for trying to help.

Your animation tree and blendspace 2D seem to be set up correctly, maybe pull some of the corners in to match normalized inputs. I don’t see where you set the blend position in code though.

What do you expect to happen versus what really happens?


instead of get_input() you can use Input.get_vector("left", "right", "up", "down"), I see that obtuse get_input() often, where does it come from?

you need to pass direction to the all the blendspaces2D at the same time when direction is not 0.

var player_input : Vector2 = Input.get_vector("left", "right", "up", "down")#don't use that function, you need the vector without normalization.
if player_input:
	anim_tree.set("params/idle/blend_position", player_input)
	anim_tree.set("params/Walking/blend_position", player_input)
	anim_tree.set("params/Attacking/blend_position", player_input)
	velocity = velocity.lerp(player_input.normalized() * speed, delta * accel)
else:
	velocity = velocity.lerp(Vector2.ZERO, delta * accel)

I’m confused as how to call anim_tree since there are no public vars how do i call it from a diffrent script I just remade the var but that should be fine as it is @onready. I was trying to use composition and keep the animation script diffrent because i could then use the same logic, also how would i incoporate death animations and attacking, if its checking for the player input and i attack, it would bring in another value and then you can’t rly check?

On top of this, it does work but only one walking animation is being played
I did some testing and its the same when i have is_walking set to always true

It comes from not knowing Input.get_vector is a thing

this is a problem when using blendspace2D. you can detach that part and use a method in your animation script, but you still need to call it from the player script. but don’t worry, composition can still be done.

don’t put the code for controlling animation tree in a node, put it in the animation tree.

here’s what I mean by using a method:

anim_tree.gd

func update_direction(dir : Vector2) -> void:
	anim_tree.set("params/idle/blend_position", dir)
	anim_tree.set("params/Walking/blend_position", dir)
	anim_tree.set("params/Attacking/blend_position", dir)

player.gd

var player_input : Vector2 = Input.get_vector("left", "right", "up", "down")#don't use that function, you need the vector without normalization.
if player_input:
	anim_tree.update_direction(player_input)
	velocity = velocity.lerp(player_input.normalized() * speed, delta * accel)
else:
	velocity = velocity.lerp(Vector2.ZERO, delta * accel)

use ready to get the animation tree. you can swap it with a different one so long as the name and path remain the same:

@onready var anim_tree : AnimationTree = $AnimationTree

don’t do this. you need scenes. in the scene you get the node locally.
for example, player has an AnimationTree node, you just get AnimationTree. you don’t use the whole path from root.

you can just go into your scene and drag the node to your script while pressing Ctrl, that will create a line of code getting the node.

instead of using travel, use Expressions. here’s a tutorial while they add this feature to the docs:
https://godotforums.org/d/39521-this-is-how-to-use-animationtree-state-machine-transitions-with-expressions

since this is 2D, you are probably using your own animations in AnimationPlayer.
you can animate properties or methods of a script, meaning you can set an attack variable to false.

so, create a isAttacking variable to check, use expressions, then in the animation, set it to false. then the animationTree would automatically transition back with not isAttacking.

you can alternatively use timers, but you need a timer node and to check when the timer finishes to set isAttacking to false.

Thank you very much, I was using conditions to check, should i use expressions in the future?

it is the brand “new” feature of godot 4 and very powerful, just not very well known.
I find it better because it makes code cleaner, but you need to type into each expression field of each transition, so it has advantages and disadvantages.

conditions can make the code very messy and require a path, so if you make changes to the state machine it can break.
blendspaces and oneshot still have to be triggered or changed from code though.

I thank you very much for all of your help and for clarifying this :grinning:

I see that you’ve garnered some help here but I figured I’d chime in because unless I’m missing something this is reaching around your elbow to lick your nose.

Treat your sprites like facing_up and facing_down sprites.
When player moves horizontally, make sure sprite is flipped correctly.
Done.

Well yes i could do that but its 4 diffrent animations, that was the solution that i did do because it was simpler but the other method still work better and i more fool proof

1 Like