Adding Juice to a Zelda-Style Heart System with Python/Godot

Previously, I created a tutorial walking you through how to create a basic Zelda-Style Heart System in Godot. This project allowed you to create a grid of hearts that increase and decrease in value to show different sprites just like Zelda. While this works perfectly fine, it’s still stagnant and doesn’t quite feel right. It’s time to take things to the next level by adding what game developers love to call juice . In this tutorial, we’ll be adding two animations to our hearts: pulsing and flashing. By the end of this tutorial, the active heart—the furthest heart along in the array—will play a heartbeat animation and all non-empty hearts will flash while the player is considered to be at low health.

Before You Begin

This tutorial is a follow-up to a previous tutorial I created, which you can find here . In that tutorial, we created the Heart HUD which displays 4-part hearts in a grid, mirroring the Zelda-like style. Prior to completing this tutorial, you should ensure you have the basic components of that tutorial set up.

Complete Project Download

If you just want to comb through the finished project, you can grab it here .

Adding Animations

For our juice in the system, we need two animations and two animation players, each with a unique animation capable of playing independently from or conjunction with the other.

Adding the Animation Nodes

  1. In the lower left-hand sidebar, in the FileSystem tab, double-click the player_ux_heart.tscn scene. The player_ux_heart scene will open.
  2. In the upper left-hand sidebar, in the Scene tab, right-click the Heart node, then click Add Child Node… The Create New Node window will appear.
  3. In the Create New Node window, in the Search field, type “AnimationPlayer”. The AnimationPlayer node will be highlighted in the list of matches.
  4. Double-click the AnimationPlayer node in the list of matches. The AnimationPlayer node will appear as a child of the Heart node.
  5. Right-click the new AnimationPlayer node, then click Duplicate . A copy of the node will appear with the name AnimationPlayer2 .
  6. Right-click AnimationPlayer , then select Rename . You will be prompted to rename the node.
  7. In the node’s name field, rename it “FlashAnim”.
  8. Repeat steps 6-7 to rename AnimationPlayer2 to “PulseAnim”.

Setting Up the Flash Animation

The Flash animation is an animation that will play on any hearts that are not currently empty while the player’s health is considered to be low. For this tutorial, we’ll set the heart up to flash red, but you can set it up to flash any color you wish.

  1. In the upper left-hand sidebar, in the Scene tab, double-click the FlashAnim . The Animation window will open at the bottom of the Godot project window.
  2. In the Animation window, click Animation , then New.. . The Create New Animation window will appear.
  3. In the Create New Animation window, name your animation “heart_flash”, then click OK .
  4. In the Scene tab, select HeartTexture .
  5. In the right-hand sidebar, in the Inspector tab, click the Visibility dropdown, then click the key icon next to Self Modulate .
  6. In the window that appears, ensure Create RESET Track(s) is checked, then click Create . self_modulate will be added as a track to the heart_flash animation in the Animation window for HeartTexture .
  7. In the Animation window, in the self_modulate track, right-click and select Insert Key… . A white box will appear.
  8. Click the new white box that appeared and, in the right-hand sidebar, in the Inspector tab, change the Time value to “1.0”.
  9. In the Animation window, in the self_modulate track, right-click between the two white boxes and select Insert Key… . A third white box will appear.
  10. Click the center white box and change its Time value to “0.5”, then change its Value in the AnimationTrackKeyEdit section to the color you want the heart to flash. The color red is used in the example for this tutorial.
  11. In the Animation window, next to the Animation Length field in the top right corner, click the loop icon to allow the animation to play on a loop.
  12. (Optional) In upper left corner of the Animation window, click Play to test your animation.

Setting Up the Pulse Animation

The Pulse animation will play perpetually, but only on the currently-active heart (the highest value heart that is not empty). This is an animation meant to resemble a heartbeat. This animation also serves to make the active heart slightly larger (0.05%) than the other hearts.

  1. At the top of the Godot project editor, click Project , then Project Settings… . The Project Settings window will appear.
  2. In the Project Settings window, change the following settings:
  • GUI/Common/Snap Controls to Pixels : Uncheck this box.
  • Rendering/2D/Snap 2D Transforms to Pixel : Uncheck this box.
  • Rendering/2D/Snap 2D Vertices to Pixel : Uncheck this box.
  1. In the upper left-hand sidebar, in the Scene tab, double-click the PulseAnim . The Animation window will open at the bottom of the Godot project window.
  2. In the Animation window, click Animation , then New… . The Create New Animation window will appear.
  3. In the Create New Animation window, name your animation “heart_pulse”, then click OK .
  4. In the Scene tab, select HeartTexture .
  5. In the right-hand sidebar, in the Inspector tab, click the Transform dropdown, then click the key icon next to Scale .
  6. In the window that appears, ensure Create RESET Track(s) is checked, then click Create . scale will be added as a track to the heart_pulse animation in the Animation window for HeartTexture .
  7. In the Animation window, in the self_modulate track, right-click and select Insert Key… . A small white diamond will appear.
  8. Click this second white diamond and, in the right-hand sidebar, in the Inspector tab, change the Time value to “1.0”.
  9. Repeat steps 6-7 to create new keys with the following time values in the scale track:
  • 0.4
  • 0.55
  • 0.8
  1. For each key, change the Value field in its animationTrackKeyEdit section of the Inspector tab to correspond with the following:
  • 0.0: 1.05
  • 0.4: 1.15
  • 0.55: 1.05
  • 0.8: 1.15
  • 1.0: 1.05
  1. In the Animation window, next to the Animation Length field in the top right corner, click the loop icon to allow the animation to play on a loop.
  2. (Optional) In upper left corner of the Animation window, click Play to test your animation. You should see the heart beating.

Writing the Code

Now we need to actually tell our heart texture to play these animations based on predetermined variables. We need to figure out which heart is active, whether that heart is empty, and whether our player is at full health. After that, we’ll call a function for pulsing and flashing the relevant hearts.

Before writing the code that determines when these functions will be called, we’re going to create two blank functions for the heart_gui.gd . Blank functions provide a reference to functions we can call throughout our code. Later on, we’ll set up these functions to actually play their corresponding animations.

Adding the Placeholder Functions

  1. In the left-hand sidebar, in the FileSystem tab, click Scripts , then double-click heart_gui.gd . The heart_gui.gd script will open.
  2. In heart_gui.gd , add the following blank functions at the bottom of the #region FUNCTIONS section:
  3. func flash(flash_state : bool):
    pass
    
    func pulse(pulse_state : bool):
    pass
    

Note : You may get a yellow error for the time being, since neither _flash_state nor _pulse_state are used in the function, right now. You can ignore this, as it will be resolved once we write the functions.

Making the Heart Pulse

First, let’s focus on making the active heart pulse. This will require determining which heart in the index is active . This is a straightforward line of code: all we need to do is divide our current health by 4 (ensuring we are calculating actual hearts instead of hit points), then subtract one to ensure we aren’t grabbing an empty heart.

Setting Up the Heart Pulse

  1. In the left-hand sidebar, in the FileSystem tab, click Scripts , then double-click heart_grid.gd . The heart_grid.gd script will open.
  2. At the top of the update_hearts function, where the variables are written, add the following variable:
  3. var active_heart_index = int(ceil(cur_health / 4.0)) -1
    
  4. At the bottom of the update_hearts function, after calling heart.update(heart_fill_value) , add the following code within the for i in range(hearts.size()): statement:
  5. var pulse : bool = (i ==active_heart_index and heart_fill_value > 0)
    heart.pulse(pulse)
    

These few lines of code communicate to each heart when the hearts are updated, checking to see if the heart is active and ensuring the heart is not empty, then passing that value as a boolean to the pulse function. Now, all that’s remaining for the pulse functionality is setting up the animation via code.

Making the Heart Pulse Animation Play

  1. Open the heart_gui.gd .
  2. At the top of the script, in the #region VARIABLES section, under the @export_category(“Heart Components”) category, add the following export variable:
  3. @export var pulse_anim : AnimationPlayer
    
  4. At the bottom of the #region FUNCTIONS section, replace the pass section of the pulse function with the following code:
  5. if not pulse_anim:
    	return
    if pulse_state:
    	if pulse_anim and pulse_anim.current_animation != “heart_pulse”:
    		pulse_anim.play(“heart_pulse”)
    elif pulse_anim.is_playing or sprite.scale != Vector2.ONE:
    	sprite.scale = Vector2.ONE
    	pulse_anim.play(“RESET”)
    

This code both plays the pulse animation and ensures hearts reset to their default scale of 1x1 if they are somehow stuck at a larger scale due to their animation being interrupted.

Connecting the Pulse Animation Player

  1. In the player_ux_heart scene, in the left-hand sidebar, click Heart in the Scene tab.
  2. In the right-hand sidebar, in the Inspector tab, you will see the Pulse Anim field appear in the Heart Components category.
  3. Click the Pulse Anim field, then select PulseAnim in the dropdown that appears. PulseAnim will populate the field.

Testing the Heart Pulse

  1. In the left-hand sidebar, in the FileSystem tab, click Scenes , then double-click the test.tscn to open it.
  2. In the Godot editor, in the top menu bar, click the Run Current Scene icon to run the test scene. The (DEBUG) window will appear.
  3. You should see the furthest heart in the grid array playing the pulse animation.
  4. Using your debug keys, decrease the health value until the active heart changes to verify the animation change functions as expected.

Making the Hearts Flash

With the active heart pulsing, it’s time to add heart flashing while health is low. This functionality will cause the heart_flash animation to play on all hearts that are not empty while the player’s health is at a predetermined low-health threshold. Determining this threshold is entirely up to you—it could be based on a flat number of hearts, such as having only 1 heart remaining (in which case, the value would be 4 health), or it could be more complex like the one in this tutorial. How you determine what constitutes ‘low health’ depends entirely on your game. This tutorial features a scaling low HP threshold based on a system where the player begins with 3 hearts and can end the game with up to 20 hearts.

Setting Up the Heart Flash

  1. In the left-hand sidebar, in the FileSystem tab, click Scripts , then double-click heart_grid.gd . The heart_grid.gd script will open.
  2. In the update_hearts function, after the hearts variable is created, add the following variables:
  3. var max_hp : int = int(hearts.size() * 4)
    var low_hp_threshold : int = clampi(int(floor(max_hp * 0.25)), 4, 15)
    var hp_low : bool = (cur_health <= low_hp_threshold and cur_health > 0)
    
  4. At the bottom of the update_hearts function, after calling heart.update(heart_fill_value) , add the following code within the for i in range(hearts.size()): statement:
  5. var flash : bool = (hp_low and heart_fill_value > 0)
    heart.flash(flash)
    

The formula used for the low_hp_threshold variable in this tutorial is designed for a 20-heart maximum system. However, when determining the value of the low_health_threshold , it sets it to a value equal to 1/4 of the player’s maximum hit points as long as it is within the range of 4 hit points (1 heart) and 15 hit points (3 and 3/4 hearts). This keeps the player from being warned too early or too late that their health is low.

With that calculated, we then determine whether we are at low health every time our hearts are updated. That information is passed to the hearts as a boolean value, telling the hearts whether they should or should not be flashing after their fill values are updated.

Making the Heart Flash Animation Play

  1. Open the heart_gui.gd .
  2. At the top of the script, in the #region VARIABLES section, under the @export_category(“Heart Components”) category, add the following export variable:
  3. @export var flash_anim: AnimationPlayer
    
  4. At the bottom of the #region FUNCTIONS section, replace the pass section of the flash function with the following code:
  5. if not flash_anim:
    	return
    if flash_state:
    	if flash_anim and flash_anim.current_animation != “heart_flash”:
    		flash_anim.play(“heart_flash”)
    elif flash_anim.is_playing() or sprite.modulate != Color.WHITE:
    	sprite.modulate = Color.WHITE
    	flash_anim.stop()
    

This code both plays the flash animation and ensures hearts reset to their default coloring if they are somehow stuck at mid-flash due to their animation being interrupted.

Connecting the Flash Animation Player

  1. In the player_ux_heart scene, in the left-hand sidebar, click Heart in the Scene tab.
  2. In the right-hand sidebar, in the Inspector tab, you will see the Flash Anim field appear in the Heart Components category.
  3. Click the Flash Anim field, then select FlashAnim in the dropdown that appears. FlashAnim will populate the field.

Testing the Heart Flash

  1. In the left-hand sidebar, in the FileSystem tab, click Scenes , then double-click the test.tscn to open it.
  2. In the Godot editor, in the top menu bar, click the Run Current Scene icon to run the test scene. The (DEBUG) window will appear.
  3. Using your debug keys, decrease the health value until it reaches your low_health_threshold value. All of the hearts that are not currently empty should begin playing the flash animation, including the active heart.

Conclusion

And that’s it for adding juice to the Zelda-Style heart system! Or… is it? There’s still so much more you could do to improve the field of this system. Animations, sound effects, and more can be added to enhance the feel of our hearts and make things even… juicier. Have a great idea for how to improve this system? Already executed your plan? Feel free to share!

Next
Next

Create a Zelda-Style Heart System in Godot with Python/GDScript