Delayed health reduction with indicator in TextureProgressBar

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diet Estus

I use a TextureProgressBarwith nine_patch_stretch enabled for for my player’s health. (The nine_patch_stretch allows me to increase the health bar to arbitrary sizes when the player upgrades his health.)

Currently, when the player takes damage, his health and the progress bar update immediately.

What I want is for his health to incrementally decrease after he takes damage, but for there to be a color change in the TextureProgressBar indicating how much damage he is losing.

Here’s an illustration:

enter image description here
It’s easy to get the health to incrementally decrease. I use a variable called damage_queue. Whenever the player takes damage, I add the damage to damage_queue, rather than subtracting it immediately from the player’s health. If damage_queue is ever non-zero, I start a timer which incrementally decreases the health.

The problem is that I don’t know how to implement the color change on the damaged portion of the health in my TextureProgressBar (the yellow part in my illustration).

Any ideas?

:bust_in_silhouette: Reply From: kidscancode

You’ll probably need to change the texture you’re using, which is accessed via the texture_progress property. When you’re updating your progressbar’s value, change the texture at the same time.

For example, in one of my games I have the following to change the color of the bar as it grows:

extends CanvasLayer

var bar_red = preload("res://assets/bar_red.png")
var bar_green = preload("res://assets/bar_green.png")
var bar_yellow = preload("res://assets/bar_yellow.png")

func update_powerbar(value):
    $PowerBar.texture_progress = bar_green
    if value > 40:
            $PowerBar.texture_progress = bar_yellow
    if value > 70:
            $PowerBar.texture_progress = bar_red
    $PowerBar.value = value

The problem you’ll have is that TextureProgress only supports one texture for displaying value, so if you want a multicolored bar you have to have a texture with multiple colors. Depending on how you construct your textures, you may be able to use the “Nine Patch Stretch” mode and/or and AtlasTexture to programmatically select the texture portions you need.

Another idea would be to use the “Over” texture to display the yellow portion.

Good ideas, thanks! I was thinking I could also just render either a yellow ColorRect or another TextureProgressBar over the damaged portion of health, and decrease their size as the health is removed below them. But I’m not certain that I’d be able to line things up properly. Is there a way of determining the top right point of the progress texture? Maybe I could use that point to determine where to render a yellow ColorRect.

Diet Estus | 2018-04-29 19:30

Control nodes have a rect_position property (the top-left) and a rect_size you can use to figure out the top-right corner.

kidscancode | 2018-04-29 21:15

But the progress texture is not a control node, (I don’t think), just a texture attribute of the larger TextureProgressBar. In any event, I’ll experiment tonight and figure something out!

Diet Estus | 2018-04-29 22:43

:bust_in_silhouette: Reply From: Diet Estus

I managed to implement this, but it’s rather hacky and I would really love a better way. In any event, I’ll post what I’ve done. Maybe others can suggest how to improve it.

I created a ColorRect attribute for my player called damage_block.

var damage_block = ColorRect.new()

func _ready():
    health_bar.add_child(damage_block) # add to health_bar
    damage_block.color = Color('#CFB53B') # set color
    damage_block.rect_size = Vector2(0, 6) # set initial size

When my player takes damage, I add the damage value to a variable called damage_queue. In _process(), whenever damage_queue is greater than 0, I resize damage_block and position it within the health bar at the appropriate position. Then, I use a Timer to decrement the Player’s health and damage_queue simultaneously. Because resizing and positioning is done every frame, as damage_queue decrements, so does the size of the damage_block and the player’s health.

To position the damage block correctly, I first calculate how many pixels it takes to represent one HP. I call this pixels_per_hp. This is just the size of the health bar divided by max health.

The width of the damage block is just damage_queue * pixels_per_hp.

To position the damage_block, I first find the width of the player’s actual health within the health bar. This is just health * pixels_per_hp. Then I subtract the width of the damage_block. (I also add 1 pixel since there is a 1 pixel border around my health bar.)

When the damage_queue is 0, I set the width of damage_block back to 0 so that it is no longer visible.

func _process(delta):
    ...
    if damage_queue > 0:
        # get number of pixels that represent 1 HP
        var pixels_per_hp = health_bar.rect_size.x / float(max_health) 
        
        # create damage block of appropriate size
        damage_block.rect_size = Vector2(damage_queue * pixels_per_hp), 6)

        # position damage block inside health bar
        damage_block.rect_global_position = health_bar.rect_global_position + Vector2(0,1) + Vector2(health * pixels_per_hp, 0) - Vector2(damage_block.rect_size.x, 0)

    if damage_queue == 0 or health <= 0:
        damage_block.rect_size.x = 0

I find this implementation rather clumsy, but it works as a first pass. One of the problems is that you can see a slight jitter on the left of the damage_block, probably relating to the fractions and floating point values in the calculations.

If anyone can suggest improvements or another implementation altogether, that would be swell!