Skip to content

Adding HUD Elements

Ste edited this page Feb 25, 2025 · 10 revisions

Table of Contents


Introduction

In this part, we will learn how to add HUD elements and how to interact with the Data system of Dome Keeper.

Creating a New HUD Element

When planning to add a new HUD element, it's recommended to check out how the original HUD elements work.

💡 Tip: As always when creating new mod content, it's best to look into the content that is already in the game. You can find some of the HUD elements in res://content/hud.

For this tutorial, we won't go into too much detail on how Godot's UI system works or how the new HUD scene is created. If you are interested in a deep dive into nodes of the Control class, make sure to check out the official documentation.

🚨 If this seems a bit overwhelming, feel free to copy the content/hud/DrillbertSleepingIndicator.tscn scene/file from the Example Mod to your first mod.

Let's assume you created the new HUD elements scene, added a script extending HudElement (more about this here) and set up working values for the HudElement properties.

The new scene is saved at res://mods-unpacked/Raffa-DrillbertSleepIndicator/extensions/content/hud/DrillbertSleepingIndicator.tscn - now let's add it to the game!

Adding the HUD Element to the Game

To do this, we need to add some code to our mod_main.gd file:

func _ready():
	ModLoaderLog.info("Done", MYMODNAME_LOG)
	add_to_group("mod_init")
	StageManager.connect("level_ready", self, "addDrillbertHud")

func addDrillbertHud():
	var hud = Level.hud.addHudElement({"hud": "mods-unpacked/Raffa-DrillbertSleepIndicator/extensions/content/hud/DrillbertSleepingIndicator.tscn"})

The Hud.tscn scene already has a method to add new HUD elements called addHudElement and we can get access to the Hud by using Level.hud. We just need to wait until the Hud is ready.

To do this we connect our script to the level_ready signal of the StageManager. This is signal is emitted when the level is ready. We recommend checking out the official docs about signals.

With this code, our new addDrillbertHud() method will be called each time a new level is ready. When we start the game and start a new game, we can see that the HUD element is added to the game's interface.

wiki_tut_hud_element_1

Connecting the HUD Element

There are a lot of ways to communicate between the extended Drillbot.gd script and our user interface. One of the easiest ways to do this is by making use of the Data singleton / class. You can read more about Data here.

Adding a New Data Property

Let's create a new property in Data with this code in our Drillbot.gd:

res://mods-unpacked/Raffa-DrillbertSleepIndicator/extensions/content/gadgets/drillbot/Drillbot.gd

func _ready():
	Data.apply("drillbert.sleeping", false)

This will add a new property to Data and set it's value to false.

To change this value, we also add new code to the other two functions and replace our print lines:

res://mods-unpacked/Raffa-DrillbertSleepIndicator/extensions/content/gadgets/drillbot/Drillbot.gd

func goToSleep():
	super() # this calls the base class function
	Data.apply("drillbert.sleeping", true)

func wakeUp():
	super()
	Data.apply("drillbert.sleeping", false)

Listening to Data Changes

The next step is to listen to changes of this property. As the HUD element is the place where we want to react to a change to this property, this is also where we need to add the following code:

res://mods-unpacked/Raffa-DrillbertSleepIndicator/extensions/content/hud/DrillbertSleepingIndicator.gd

func _ready():
	Data.listen(self, "drillbert.sleeping", true)

💡 Tip: By holding CTRL and left clicking on highlighted function names, you will jump to the corresponding function. Try it with listen in Data.listen

Above the listen function in Data.gd you will find boilerplate code on how to implement the function. We will use this template to build the code to react to the change of the property in our HUD Element script:

func propertyChanged(property:String, oldValue, newValue):
	match property:
		# ONLY LOWERCASE HERE
		"drillbert.sleeping":
			if newValue == true:
				$Sleeping.show()
				$Awake.hide()
			elif newValue == false:
				$Sleeping.hide()
				$Awake.show()

This code will hide and show certain nodes, based on the value of drillbert.sleeping.

The mod is now working and pretty much "feature complete".

But there are a couple of things we can do to improve it and make it work nicer.

Supporting Game Loading

A lot of nodes have serialize and deserialize functions that are used to save and load the element. We can also find those in the vanilla Drillbot.gd. To support loading games and update our mod correctly, we can extend this function:

func deserialize(data: Dictionary):
	super(data) # this calls the base class function
	if state == State.SLEEPING:
		Data.apply("drillbert.sleeping", true)

This will apply the correct value to our new Data property and like this, also update the HUD element.

Showing HUD Element Only When Drillbert Is Available

We only want to show the new HUD element if the player unlocked Drillbert as a gadget!

Knowing how Data works, we can listen to the property drillbot.headcount as this will change when the player gets the Drillbert gadget.

Let's change some code in our mod_main.gd:

func _ready():
	ModLoaderLog.info("Done", MYMODNAME_LOG)
	add_to_group("mod_init")
	StageManager.connect("level_ready", self, "listenToDrillbot")

func listenToDrillbot():
	Data.listen(self, "drillbot.headcount")

func propertyChanged(property:String, oldValue, newValue):
	match property:
		# ONLY LOWERCASE HERE
		"drillbot.headcount":
			if newValue == 1:
				addDrillbertHud()

func addDrillbertHud():
	var _hud = Level.hud.addHudElement({"hud": "mods-unpacked/Raffa-DrillbertSleepIndicator/extensions/content/hud/DrillbertSleepingIndicator.tscn"})

Making It Work with Drilliam

Drillbert comes with an upgrade to add Drilliam, his sibling. Now we have a new scenario: two sleeping little "Drillberts", one wakes up, the other one is still sleeping. The HUD will update for waking up one of them, but the other is still asleep. This is a bit problematic. So let's update our code to take this into account.

We should change the code in the Drillbots.gd:

func _ready():
	for drillbert in get_tree().get_nodes_in_group("drillbots"):
		if drillbert.state == State.SLEEPING:
			return
	Data.apply("drillbert.sleeping", false)

func deserialize(data: Dictionary):
	super(data) # this calls the base class function
	if state == State.SLEEPING:
		Data.apply("drillbert.sleeping", true)

func setState(to):
	super(to)
	if to == State.SLEEPING:
		Data.apply("drillbert.sleeping", true)
	else:
		for drillbert in get_tree().get_nodes_in_group("drillbots"):
			if drillbert.state == State.SLEEPING:
				return
		Data.apply("drillbert.sleeping", false)

This way, the icon will only display Drillbert being awake when both of the siblings are awake.

With this change, the mod is complete and we can upload it to the Steam Workshop! We always recommend testing a few different scenarios with a new mod, like saving & loading. Different maps, modes etc. The tips over here can really help you to speed up the process!

domekeeper_mod_drillbert_sleep_indicator

Next Step

✨ Next Step: Distributing your Mod