Invalid set index 'frame' (on base: 'Nill') with value of type 'int'

Godot Version

<stable4.2>

Question

<Im trying to make health UI using Panel and sprite animation. If player health is 0-2 make health_bar01 sprite frame 0-2, and if health is 3-4 make health_bar02 sprite frame 3-4, and so on. I would appreciate if you could help me solve an error.

extends CanvasLayer

@onready var player_health_bar01 := $HBoxContainer/crystal_heart1/Sprite2D as Panel
@onready var player_health_bar02 := $HBoxContainer/crystal_heart2/Sprite2D as Panel
@onready var player_health_bar03 := $HBoxContainer/crystal_heart3/Sprite2D as Panel
@onready var player_health_bar04 := $HBoxContainer/crystal_heart4/Sprite2D as Panel
@onready var player_health_bar05 := $HBoxContainer/crystal_heart5/Sprite2D as Panel

func update_player_health_bar(points: int):
	if points >= 0 and points <= 2: #0-2
		player_health_bar01.frame = int(clampf(points, 0, 2))
	if points >= 3 and points <= 4: #3-4
		player_health_bar02.frame = int(clampf(points, 0, 2))
	if points >= 5 and points <= 6: #5-6
		player_health_bar03.frame = int(clampf(points, 0, 2))
	if points >= 7 and points <= 8: #7-8
		player_health_bar04.frame = int(clampf(points, 0, 2))
	if points >= 9 and points <= 10: #9-10
		player_health_bar05.frame = int(clampf(points, 0, 2))

i think the problem is that you set your player_health_bar0X “as Panel” eventhough they are sprites (im assuming from their name). Remove the as Panel and try again. Another possible problem is that since the variables are onready, they might not have been initialized when you first call the function

3 Likes

Thank you I changed it to as Sprite2D and an error solved. but sprite frame is not working as i expected. I have 5 sprites representing heart and each has 3 frames (0-2, 0 is empty, 1 is half, and 2 is full) I want to make each sprite’s frame to 2 if the points is 10. if the points is 9, make frame of sprite of 01 to 04 to 2 and 05 frame to 1. if the point is 8, 01-04 frame should be 2 and 05 frame to 0. and so on.

To achieve this you would want to set every sprite.frame (1-5) for all if statements, so everything is updated accordingly.
Extra tip:
Since you are trying to do a healthbar, you can use a texturerect and set the texture to a full heart. Then you set the stretchmode to “Tile” and now you can adjust the length of the texture rect to represent the current health you have availabe. In code it would look something like this:

const HEART_SIZE: int = 10 # Size of your heart in pixels
func changeHealth(health: int):
    $TextureRect.size.x = health * (HEART_SIZE / 2)

And if you have a background for your hearts you can instead use the TextureProgressBar. I can tell how to set it up if you want

I think I figured out.

var points_per_bar = 2

func update_player_health_bar(points: int):
	player_health_bar01.frame = int(clampf(points, 0, points_per_bar))
	player_health_bar02.frame = int(clampf(points - points_per_bar, 0, points_per_bar))
	player_health_bar03.frame = int(clampf(points - 2 * points_per_bar, 0, points_per_bar))
	player_health_bar04.frame = int(clampf(points - 3 * points_per_bar, 0, points_per_bar))
	player_health_bar05.frame = int(clampf(points - 4 * points_per_bar, 0, points_per_bar))

I would like to stick with a single sprite for single heart, since I want to animate them to move like sine wave (like vital sign). But I just realized that I cannot move position of panel cuz its a child of HBoxContainer.

you can still achieve this animation by changing the offset-property of the sprite. You can use a tween for example to animate in code

Oh I didnt know that. Would you mind teaching me how to do this by tween?

for every sprite you would have to do the following (with delay):

func animateSprite(sprite: Sprite2D):
    const offsetMax: Vector2 = Vector2(0, -10) # Insert the offset you want
    const offsetOriginal: Vector2 = Vector2(0, 0) # Insert Original offset
    const time: float = 0.5
    var tween: Tween = get_tree().create_tween()
    tween.tween_property(sprite, "offset", offsetMax, time)
    tween.chain().tween_property(sprite, "offset", offsetOriginal, time)

If you call this function it should animate your sprite like you want it.
You can even change the animation type with:

tween.set_trans(Tween.TRANS.QUINT)
tween.set_ease(Tween.EASE_OUT)

Here are some examples:

wow that worked magically! I added TRANS_EXPO and EASE_IN_OUT to make it look nicer. but how would I add delay for each sprite?

you could do a function:

func animateHealthBar() -> void:
    for sprite in sprites: # sprites should be a list of your sprites
        animateSprite(sprite)
        await get_tree().create_timer(0.1).timeout

If I set the timer to 0.1, first three sprites move same time. Do you have any idea what is the cause of this. and is it possible to make the delay longer after the tween for last sprite is executed?

When do you want to call this function? you could consider using an animationPlayer with a function-track, that calls animateSprite() on the different frames. This might be easier if you want it to play continously and you can also change the play-speed of the animationplayer

1 Like

ohh that would be indeed absolutely easier. but how would I change the value inside () from animationplayer?

You can set the argument-count in the function-key and then set arguments

I cannot find argument Object/sprite2D, or should it be string and name the sprite node name?
I tried with string with value name but didnt work. what am i doing wrong?

You can change the function to take an integer as an argument and in your script you can then reference a list with the sprites with this integer OR you set the argument type to NodePath then you should be able to put sprite-paths into it

thank you for the help herrspaten!

1 Like

Glad i could help. Feel free to play around with animation length and the tween properties, its a lot of fun

1 Like