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
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 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
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?
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
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)
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.