The diagram you drew is not what is happening so you are probably misunderstanding await
when you do await get_tree().create_timer(...).timeout
the current function is suspended and will resume once it receives the timeout
signal from the scene tree timer. This will probably not happen during the same frame so at some later point, the rest of this function will execute, but it’s just waiting until then.
For the process frame 1, when you start out with a state of END_QUOTE
you will call handle_end_quote_state()
which will stay suspended but do nothing else yet.
On process frame 2, your state hasn’t changed yet so you again call handle_end_quote_state()
which also does nothing yet but stays suspended.
Somewhere in between process frame 2 and 3, you receive the timeout
signal from the timer you created during process frame 1 so the first call to that function resumes executing. This is why it looks like the second frame continued with the function, but that wasn’t the case at all. your currentState is now HIDE_FACE
On process frame 3 your state is HIDE_FACE
so you instead call handle_hide_face_state()
which plays an animation and then suspends itself until the animation player has finished that animation. Your state is not set to IDLE
because the animation hasn’t completed yet.
And finally sometime after process frame 3 you receive the second timeout
signal from the second timer that was created during process frame 2 so that function resumes execution and your state is set to HIDE_FACE
although it was probably still set to that anyway.
Hope this helps clear up what’s going on.
Fixing it
You can make things less confusing by connecting to a signal rather than awaiting it.
For the quotePlayer you could connect to the animation_finished signal with a method like:
func _on_quotePlayer_animation_finished():
if currentState == HIDE_FACE:
currentState == IDLE
The logic for changing state needs to be able to account for the fact that something unexpected may have happened in between when you started the animation and when it finished, but that’s a separate topic
and for the case of waiting for a delay in the end quote state, you could use a Timer
node to do the delay instead of the temporary timer, but to use the temporary timer you can connect to it’s signal like this
var delay: = get_tree().create_timer(delayBetweenQuotes)
delay.timeout.connect(_on_quote_delay_finished, CONNECT_ONESHOT)
func _on_quote_delay_finished():
if currentState != END_QUOTE:
# Something else happened in the meantime
return
else:
currentState = HIDE_FACE
quoteLabel.text = ""
quoteTimer.stop()
Finally, you probably only want to call handle_end_quote
or handle_hide_face
once not every frame during those states until you change to another state. This is actually the main reason why you’re having problems here. Instead you should probably handle code that needs to run once during a state transition at the same time you switch to another state. like having a change_state(new_state)
method that handles all of the one-time tasks that need to be performed.
If that isn’t a good option for some reason and you want the one time action to be performed on the first frame of the new state instead you can just make sure the logic only runs once. There are a lot of ways to handle this but I would suggest doing something like this:
var processStateEnabled: = true
# I'm using a setter to ensure that every time I change state, processing is enabled
var currentState: = State.END_QUOTE:
set(new_state):
currentState = new_state
processStateEnabled = true
func _process(_delta):
# Check if we should call the process method for the current state
if not processStateEnabled:
return
match currentState:
...
func handle_end_quote_state() -> void:
# This should only run for first frame of being in this state, so disable processing
processStateEnabled = false
...
# Same for any other state method that happens only once