Adding elemets to an array that is being scrolled through asynchronously

Godot Version

v4.3.stable.mono.official [77dcf97d8]

Question

I’m trying to create a health display for a turn based game. Basically when a character’s health changes I want to spawn a little number that floats upwards from the character icon. I did this easily and it works, but the problem is when a character takes damage in quick succession, the numbes spawn on top of each other, which I don’t want. So I wanted to implement a delay between the spawns.

This is how I’m trying to do it:

I have an array in which I hold all of the health changes. When the health changes, I call an asynchronous function that scrolls through the array and spawns a display, then waits for 300 ms, and goes on to the next element. The function also has a bool which prevents it from being called while it’s already active, so I don’t have multiple instances of it. The idea is that, while the function is waiting, new elements would be added to the array, so once it has finished waiting, a new element would be inside to spawn a display for, and if there aren’t any, then I just clear the array and unlock the function.

Problem: it does not work. At all.

It only ever spaws the first display.

Here is the script:

    Godot.Collections.Array<float> hpchange = new Godot.Collections.Array<float>();
    bool displayingHealthChange = false;
    void OnHealthChanged(float oldVal, float newVal)
    {
        healthBar.SetValue(sheet.statBlock.CurrHealth.ModValue);
        hpchange.Add(newVal - oldVal);

        GD.Print("Health Changed");
        if (!displayingHealthChange) DisplayHealthChange();
    }

    async void DisplayHealthChange()
    {   
        displayingHealthChange = true;
        foreach(float change in hpchange)
        {
            GD.Print(hpchange.Count);
            var display = hpChangedDisplay.Instantiate<HealthChangeDisplay>();
            AddChild(display);
            display.Position = hpDisplayPos.Position;
            display.Start(change, ai);
            await Task.Delay(500);
        }

        hpchange.Clear();
        displayingHealthChange = false;
    }

I had a similar use case, but for printing documents on a printer. You can add documents to the queue and the printer will try to print them all one by one in the _process() method. If there are no documents in the queue, it will do nothing and wait for the documents to be added to the queue. Below is my sample code that you can try to reuse for your purpose.

class_name Printer
extends ComputerInterface


@export var document_spawn_position: Node3D
@export var document_target_position: Node3D
@export var document_scene: PackedScene


var printing_queue: Array[Document]
var is_printing: bool

var printed_documents: Array[PhysicalDocument]


@warning_ignore("unused_parameter")
func _process(delta: float) -> void:
	try_printing_next_document()


func add_document_to_queue(document: Document) -> void:
	printing_queue.append(document)


func try_printing_next_document() -> void:
	if is_printing:
		return
	var document: Document = printing_queue.pop_front()
	if document == null:
		return
	
	print_document(document)


func print_document(document: Document) -> void:
	is_printing = true
	var physical_document: PhysicalDocument = (document_scene.instantiate() as PhysicalDocument).with_data(document)
	add_child(physical_document)
	physical_document.position = document_spawn_position.position
	physical_document.rotation.x = deg_to_rad(-90)
	
	var tween: Tween = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_SINE)
	tween.tween_method(func(new_position: Vector3) -> void: physical_document.position = new_position, document_spawn_position.position, document_target_position.position, 1.0)
	await tween.finished
	printed_documents.append(physical_document)
	is_printing = false