Is there a way to check if a certain function is running?

Godot Version

v4.7.2.stable.official [ed1daf0bf]

Question

My game has palette animations that are done via shaders and can be played independently of AnimationPlayer animations. However, since they’re all done in code, it means several copies of the same animation can be running in parallel. To give you an example, here’s the getting hurt animation:

func _blink_hurt() -> void:
	palette_anim_playing = true
	palette.set_color(0, Color("c74083")) # pinkish red outline
	for i in range(1, 7):
		palette.set_color(i, Color("f9f9f9")) # off-white
	
	palette_anim_playing = true
	await frame_changed
	
	# gradually changing it back to player's original colors
	# using the colors from the game's limited palette
	palette.set_color(0, Color("820041"))
	palette.set_color(1, Color("807569"))
	palette.set_color(2, Color("b8b7b4"))
	palette.set_color(3, Color("f1dbb3"))
	palette.set_color(4, Color("f1dbb3"))
	palette.set_color(5, Color("3678b3"))
	palette.set_color(6, Color("3ebce6"))
	
	palette_anim_playing = true
	await frame_changed
	
	palette.set_color(0, Color("541554"))
	palette.set_color(1, Color("454545"))
	palette.set_color(2, Color("807569"))
	palette.set_color(3, Color("c9a865"))
	palette.set_color(5, Color("202863"))
	palette.set_color(6, Color("274b8c"))
	
	palette_anim_playing = true
	await frame_changed
	
	palette.set_color(0, Color("202020"))
	palette_anim_playing = false

Right now, I have to reset the palette_anim_playing flag before every new frame in case another copy of this is running and happens to end and set the flag to false right in the middle of the animation. But this definitely isn’t the best way to do it, so like… what is the best way to do it?

Personally, I just have some sort of flag that gets set when the function first runs, and then make sure to check if the flag is set or not while the function is running

Something along the lines of

var bink_animation_playing: bool = false

func _blink_hurt() -> void:
    if not blink_animation_playing:
        blink_animation_playing = true
        # {...}
        # Somewhere in this code, you need to make sure that
        # blink_animation_playing gets set to false when everything is done

There is probably an infinite amount of better ways to do this that I hope someone provides, but hopefully this should be a decent starting place

What’s “another copy”?

You could just add

if palette_anim_playing:       
     return

to the absolute top of the function.

I would also get rid of all the "palette_anim_playing = true"s that are spread around the script, except for the first one, right after the if palette_anim_playing-check. You don’t need to set it to true multiple times as long as you only set it to false in one place, right at the end of the function.

I meant that this function gets called again after it has been called a bit earlier but hasn’t finished waiting for all the frames to be done yet

I did try this but it messed up the way I want my animations to work, like if the player is hurt, I want them to blink every time they’re hurt, as an additional indicator how often they’re being hit

That’s how it originally was, and that resulted in the animation ending on the wrong frame if the player received multiple i-frame-ignoring hits in quick succession

Why would it be called again? If you run the same script on multiple instances then each will operate in its own separate object context and they wouldn’t interfere with each other in any way.

In any case, doing it like this with awaits looks bad. There certainly are better alternatives but we first need to determine what exactly are you trying to do. Who calls _blink_hurt() and when? Be more specific and provide more context.

It’s called in the player’s script in the take_damage() function, which is called every time the player gets hit by anything marked as a hazard (or currently, for testing purposes, take_damage() can be called by pressing a key, where it has the ignore_iframes argument set to true). I’m trying to imitate the way regular sprite animations work, by using delta in _physics_process() to count down and emit the frame_changed signal every time a frame is, well, supposed to change.

if palette_anim_playing:
	if palette_anim_timer > 0:
		palette_anim_timer -= delta
	else:
		palette_anim_timer = palette_anim_time
		frame_changed.emit()

I came up with that as a solution to being unable to blend multiple animation players, and so far it mostly works for me aside from this one issue. What exactly is the problem with doing it with awaits?

Well it’s the problems that prompted you to ask for help :wink:
Most notably, you can’t cancel an awaiting coroutine “from the outside”.
Awaits tend to result in a code execution flow that’s hard to follow and control. They can cause cryptic bugs especially as the project grows larger. You should generally not use them unless you 100% know what you’re doing.

So you want to avoid a mess when there are multiple hits in a short succession?
And the only animation that runs on hits is palette change?
How are consecutive hits supposed to look in respect to this palette animation, the new one just override the old one?

Yes

Well, it’s not the only animation, there’s also the AnimationPlayer that sets the sprite to the hurt one but that seems to work fine so it’s not an issue

Kinda yeah I guess? Every time the player takes damage, the blinking animation is supposed to begin anew

Do the palette animation using the animation player (or a tween) as well. That way you can easily cancel/override the currently running animation.

What happens in palette.set_color()?

Ok so instead of return if already running, just do a reset of the entire animation? I would separate the entire animation part into a separate function so you can call it from different places. And make it a tween or series of tweens so you can kill the ongoing animation

I think I tried and animation players don’t interact correctly if one of them is in an animation tree? Or did I get that wrong? Also wasn’t there a warning that using timers/tweens with a wait time of 0.1 or less could be unreliable?

class_name PaletteMaterial
extends ShaderMaterial

const MAX_PALETTE_COLORS := 8

func set_color(index: int, new_color: Color) -> void:
	if index < 0 or index > MAX_PALETTE_COLORS - 1:
		printerr("Color index exceeds palette boundaries.")
		return
	
	set_shader_parameter("color" + str(index), new_color)

Not sure I know what you mean here.

No. You likely misunderstood something.

Try this:

@export var flash_colors: Array[Color]

In the inspector, add the required number of colors to this array.

Then create an animation and add a property track that animates this array. Add keyframes and manually adjust colors in the inspector for each keyframe.

In _process() check if the animation is running and if yes, set your actual palette colors to values from the flash_colors array.