In the next two lessons, we will design and code the menu to choose a combat action during your turn. It will list available actions on the corresponding battler and emit a signal along with the selected one.
We will put the previous lesson’s tips into practice by splitting it into three nested components:
You will also see two patterns in all our UI classes: I decided to name them all UI*, for example, UIActionButton, and they tend to have a setup() function to pass data to each component and initialize it.
In this part, we will design the action button.
Let’s start with the action button. Please create a new scene with a TextureButton named UIActionButton as its root. The node will detect when the user clicks, touches, or presses it with a key.
In the FileSystem dock, search for menu_action_. We prepared four textures to assign to the texture button. In the Inspector, expand the Textures section and drag and drop the textures corresponding to each button state. For example, menu_action_bg.png corresponds to the Textures -> Normal state.
There are four textures to assign to the button.
If you haven’t already downloaded the project, you can find it in the course downloads at the bottom of every page.
Our TextureButton allows us to have a custom-looking button, but it only gives us a background texture to work with. Let’s add support for an icon and a label.
Add an HBoxContainer to align the image and text horizontally. As its children, add a TextureRect named Icon and a Label. Your scene tree should look like the following.
Select the HBoxContainer and, in the toolbar, apply Layout -> Full Rect to resize it and anchor it to the button’s corners.
Then, drag the node’s scale handles to give it some margins.
In the Inspector, toggle on Rect -> Clip Content so when you experiment with icons and text, they won’t change the node’s size even if they are big.
You can add some default text to the Label to see it’s too small by default. We also included a test icon, icon_punch.svg, that you can use for the Icon node’s texture for testing purposes.
To replace the default font, we will use a theme resource. Doing so allows us to reuse the font across the game’s interfaces.
In the FileSystem dock, right-click on some directory and select New Resource….
Create a new Theme and save it as combat_ui_theme.tres. In the Inspector, click the Default Font property and create a new DynamicFont. Expand the resource to see its settings.
Look for the file fira_mono_medium.ttf in the FileSystem dock and drag it onto Font -> Font Data. You can then set the Settings -> Size to the value that works best for you. I went with 32 pixels.
Select the UIActionButton and drag the theme resource onto the Theme -> Theme property. You should see the Label’s text update.
With this, we have our scene ready to code.
Attach a new script to the UIActionButton node with the following code. Below, notice how the button has no access to the battler to which it’s associated. It only has access to the ActionData in its setup() function. In a more complex project, with a large team, you may want a complete separation between the UI and the rest of the game. In that case, you would not pass the ActionData directly but instead, pass a dictionary or a new object containing only the data the button needs.
# Individual button representing one combat action. extends TextureButton # We store references to the nodes we access in the class, as usual. onready var _icon_node: TextureRect = $HBoxContainer/Icon onready var _label_node: Label = $HBoxContainer/Label # To call from a parent UI widget. Initializes the button. func setup(action: ActionData, can_be_used: bool) -> void: # In a parent node you may call `setup()` before adding the button as a child. # If that is the case, we need to pause execution until the button is ready. if not is_inside_tree(): yield(self, "ready") # Only update the icon's texture if the action data inlcudes an icon. if action.icon: _icon_node.texture = action.icon _label_node.text = action.label # The button has no notion of the battler's energy and the action's cost. # We will tell it if the player can use the action or not. If not, the button should be disabled. disabled = not can_be_used # When pressing the button, we release its focus. Doing so prevents the player from # pressing two action buttons or from pressing one twice. func _on_pressed() -> void: release_focus()
We finally need to connect the button’s pressed signal to the _on_pressed() function. You can do so using the editor’s Node dock.
In the next part, we will design and code the action list and the action menu.