State Machine not playing as intended?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By newgodot_user
func _physics_process(delta):
handle_movement(delta)

func handle_movement(delta):
var direction = Vector3()

if Input.is_action_pressed("forward"):
	direction += transform.basis.z
	is_moving = true
	
elif Input.is_action_pressed("backward"):
	direction -= transform.basis.z
	is_moving = true
	
elif Input.is_action_pressed("left"):
	direction += transform.basis.x
	is_moving = true
	
elif Input.is_action_pressed("right"):
	direction -= transform.basis.x
	is_moving = true

else:
	is_moving = false
	
if is_moving == true:
	animation_mode.travel("WALK")
else:
	animation_mode.travel("IDLE")

I have this code here and all the animations are looped in the Animation Player. But for some reason, when I test it out in-game, it doesn’t loop as intended.

Am I missing something? If so, what is it? If I am doing something wrong, can someone help me fix it? I’m pretty new to the whole state machine thing. I mainly used animations instead of the Blendspace 1Ds because I didn’t understand how to work those.

:bust_in_silhouette: Reply From: Pomelo

I havent worked with AnimationPlayer for a while, so I dont remember what exactly travel() does , I think it esentially starts to plays the desired animation? If I am right, then your problem is that you are triggering travel() every frame, since it is inside a procces() function (I think the last 3/4 questions I answered, were of people not realizing of procces() triggering stuff, haha). I may be wrong though if travel() works differently.

Without knowing the rest of your code and how you setted up your state machine, I dont have a “best” way to solve your issue, but what you have to do is make sure to not trigger travel() every frame. I personally use setters in my state machines, to trigger stuff only when entering or exiting states.

Not related to your question, but maybe it will help. I think it is a good aproach to manage inputs in _unhandled_input() and just let the input determine a normalized direction Vector3() and maybe a “strength” var (if you care to get the strength of the inputs). Then in procces() you just move using this direction

Pomelo | 2022-06-24 14:27

To prevent the animation from being played each frame, instead of going into idle animation if the user didn’t press a direction, you should have is_moving = false when the player releases forward or back. That way, pressing forward/back moves, the character only enter the moving animation when you initially pressed the button (not when you hold it down), just like you have, and when they release the button the player goes idle.

Your current logic right now is every frame a button hasn’t just been pressed, go idle. So your character will immediately stop after 1 tick

godot_dev_ | 2022-06-24 15:00

func _physics_process(delta):
handle_movement(delta)

func handle_movement(delta):
var direction = Vector3()

if Input.is_action_pressed("forward"):
	direction += transform.basis.z
	
elif Input.is_action_pressed("backward"):
	direction -= transform.basis.z

if Input.is_action_just_pressed("forward"):
	is_moving = true
	
elif Input.is_action_just_pressed("backward"):
	is_moving = true
	
if Input.is_action_pressed("left"):
	null #You will dash to the left/right and I haven't gotten to that so I put null for now
	
elif Input.is_action_pressed("right"):
	null
	
if Input.is_action_just_released("forward") or Input.is_action_just_released("backward"):
	is_moving = false

if is_moving == true:
	animation_mode.travel("WALK")
else:
	animation_mode.travel("IDLE")
	
direction = direction.normalized()

this is what I got. The animations now play when they are supposed to, but it still doesn’t loop properly. @Pomelo when you said that don’t trigger travel every frame, I assumed @godot_dev_ provided a fix to that. If I weren’t to do it in physics process, where would I put my animations?

newgodot_user | 2022-06-25 00:04

I dont agree with what @godotdev wrote. as I said in my awnser, everything that you write in procces() is “read” by the engine in every frame. I recomend you getting used to read the Documentation! For example I recomended you to read unhandled_input() and also understand setters (both come in handy so you dont end up using procces() for everything).

now back to your problem, what you want to solve is not a matter of changing the state of your state machine, but when to trigger the animations. You could make a state machine that handles this, but since it seems you are quite new to coding I will recomend an easier but less powerfull aproach: (I wrote only what is relevant to the animation)

var is_walk_playing := false # write this outside of procees

if Input.is_action_pressed("forward") or Input.is_action_pressed("backward"): 
    if not is_walk_playing:
        animation_mode.travel("WALK")
        is_walk_playing = true

This way when you press forward, walk starts to play, but it wont trigger in the next frame even if you keep pressing, or even if the state is is_moving. then when you to stop walking, make sure to do is_walk_playing = false, so you can trigger the animation the next time.

Pomelo | 2022-06-25 01:28

I tried to implement the code you provided here (I put here the whole script. The part you provided is at the bottom):

extends KinematicBody

export var speed : float = 20
export var acceleration : float = 50
export var air_acceleration : float = 5
export var gravity : float = 0.98
export var max_terminal_velocity : float = 54
export var jump_power : float = 20

export(float, 0.1, 1) var mouse_sensitivity : float = 0.3
export(float, -90, 0) var min_pitch : float = -90
export(float, 0, 90) var max_pitch : float = 90

var velocity : Vector3
var y_velocity : float

onready var camera_pivot = $camerapivot
onready var camera = $camerapivot/cameraboom/Camera
onready var animation_tree = $AnimationTree
onready var animation_mode = animation_tree.get("parameters/playback")

func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
animation_tree.active = true

func _process(delta):
if Input.is_action_just_pressed(“ui_cancel”):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

func _input(event):
if event is InputEventMouseMotion:
rotation_degrees.y -= event.relative.x * mouse_sensitivity
camera_pivot.rotation_degrees.x -= event.relative.y * mouse_sensitivity
camera_pivot.rotation_degrees.x = clamp(camera_pivot.rotation_degrees.x, min_pitch, max_pitch)

func _physics_process(delta):
handle_movement(delta)

func handle_movement(delta):
var direction = Vector3()

if Input.is_action_pressed("forward"):
	direction += transform.basis.z
	
elif Input.is_action_pressed("backward"):
	direction -= transform.basis.z
	
if Input.is_action_pressed("left"):
	null
	
elif Input.is_action_pressed("right"):
	null
	
direction = direction.normalized()

var accel = acceleration if is_on_floor() else air_acceleration
velocity = velocity.linear_interpolate(direction * speed, accel * delta)

if is_on_floor():
	y_velocity = -0.01
else:
	y_velocity = clamp(y_velocity - gravity, -max_terminal_velocity, max_terminal_velocity)
	
if Input.is_action_just_pressed("jump") and is_on_floor():
	y_velocity = jump_power
	animation_mode.travel("AIR")
	
velocity.y = y_velocity
velocity = move_and_slide(velocity, Vector3.UP)
func _unhandled_input(event):
var is_walk_playing := false # write this outside of procees

if Input.is_action_pressed("forward") or Input.is_action_pressed("backward"): 
    if not is_walk_playing:
        animation_mode.travel("WALK")
        is_walk_playing = true

if Input.is_action_just_released("forward") or Input.is_action_just_released("backward"):
	if is_walk_playing:
		animation_mode.travel("IDLE")
		is_walk_playing = false

Honestly, I’m very clueless on this topic. I’ve only worked with the state machine once and I only really understand how to use it for 2D Top-Down games (e.g. Zelda-style games) through Heartbeast’s RPG tutorial.

It also might be a problem with my State Machine. Say, do you know how to use Blendspace 1Ds and animations in the state machine node? I’m pretty sure the Blendspace 1Ds can help with the transition from walk to sprint but when I tried it, it didn’t work.

This is how my state machine is currently put together:
Screenshot 2022-06-25 at 7.53.50 PM.png - Google Drive

newgodot_user | 2022-06-25 11:05

After watching https://www.youtube.com/watch?v=WY2cN9uG6W8 I’m currently making a transition from the State Machine to the Blend Tree. It seems very straightforward. Hopefully it works out.

newgodot_user | 2022-06-25 12:31