Section 7: Game Interface
In this section, we took the time to add a UI to our game to start the game, pause the game, view stats, and play game over/victory screens.
Section Intro - Game Interface
There are no notes for this lecture.
Setting Up the Start Menu
In this lecture, we designed an interface with Godot's nodes. There are no notes for this lecture.
Grabbing UI Containers
In this lecture, we applied a class to our containers. Containers are nodes used for storing and arranging additional UI nodes. We'll be storing most of interfaces in containers. Since that's the case, it'll be easier to select them with a class attched to them. So, we added a class called UIContainer
.
In this class, we exported a property with an enum.
Here's the enum definition.
It contains a list of the type of UI elements we'll be creating in our game.
To select a container, we created a dictionary called containers
. Dictionaries are a feature in C# for storing a collection of data. The main difference between a dictionary and array is that a dictionary allows developers to configure the index for each item in an array. You don't have to use numeric indexes.
We can define a dictionary by importing the following namespace:
Next, we can use the Dictionary
type with the data type for the key and value, respectively.
For the value, we're initializing the field from the _Ready()
method.
We're using Linq to help us create the dictionary. Firstly, we're using the GetChildren()
method, which is available on nodes for grabbing a list of child nodes in the current node. Since this script is applied to the root node of the UI scene, we'll grab all the nodes directly under it.
Next, we're filtering the nodes to check if they have the UIContainer
class attached to it. The result is an array of nodes, but we want to cast the results into the UIContainer
class, which is what we're doing with the Cast
method. Lastly, we're converting the array into a dictionary with the ToDictionary()
method, which accepts a lambda function for specifying the key for each item in the array.
Lastly, we toggled the visibility of a node by setting the Visible
property.
To access an item from a dictionary, we can use the enum inside the square brackets instead of a numeric index.
Resources
Commonly Used Collection Types - https://learn.microsoft.com/en-us/dotnet/standard/collections/commonly-used-collection-types
Starting the Game
In this lecture, we learned how to pause and unpause the game. Games in Godot can be paused by grabbing the scene tree and setting the Paused
property to true
.
Not everything in Godot will be paused, but you can expect most of Godot's behavior to be paused from physics process to input methods.
To unpause the game, we listened for a button press on the button node. Every button node has a signal called Paused
, which we can listen to like so:
Reparenting Nodes
In this lecture, we learned how to reparent nodes. First, we created a custom event by defining a class for storing our events called GameEvents
.
We're using the static
keyword so that we can access the event outside the class without needing an instance. Static members are similar to constants except that their values can change.
As a naming convention we'll be following in this course, event names always start with the word On<Name>
. Event handlers will be called Handle<Name>
and raisers will be called Raise<Name>
.
After creating this event, we can subscribe to it like so:
This event will be raised when the game starts. Once it does, we'll reparent the camera node. Reparenting is the process of moving a child node to a completely different parent node. Every node has access to a method called Reparent
. This method accepts an instance of a Node
class to move a node. We exported a field for storing the target.
Next, we called the Reparent
method with the target like so:
The event Keyword
In this lecture, we learned about the event
keyword to prevent us from causing errors in our game. When we define custom events, we have the option of adding the event
keyword like so:
If we add this keyword, we'll only be able to register and unregister methods. We're not allowed to completely override the field. For example, we can't do the following:
We can only use the +=
operator when assigning a method.
Handling the End Game Event
In this lecture, we updated our games events to include an event for when the game ends. First, we added the event and method for raising the event in the GameEvents
class.
Afterward, we raised this event from the PlayerDeathState
class.
Lastly, we subscribed to this event. During this event, we reparented the Camera
node in our game.
Once again, we're using the Reparent
method. In this method, we're passing on the GetTree().CurrentScene
property, which contains the root node of the current scene in our game.
Stats UI
In this lecture, we created a UI for displaying the player's stats. There are no notes for this lecture.
Dynamically Updating Labels
In this lecture, we updated the labels for displaying the player's stats. We used the resource to help us perform this task. The great thing about resources is that they're independent from a node. They can be applied to multiple nodes and share data. So, in the StatResource
class, we added an event called OnUpdate
and then raised it from the set
accessor.
Next, we subscribed to this event from a custom class attached to a node called StatLabel
. In this class, we're setting the Text
property to the new value.
It's important to note that we are using the ToString()
method to convert the value into a string since the Text
property only accepts strings.
Counting the Enemies
In this lecture, we counted the enemies in our game, kept track of this information, and then updated the label in the stats UI to display this information. Firstly, to grab the number of enemies, we stored the enemies in a node and attached a script to it. From this script, we used the GetChildCount
method to count the number of child nodes.
Next, we raised an event to allow other nodes to gather this data. Godot has a signal that we can subscribe to when a child node is deleted. Since we're deleting our enemies from the game, we decided to use it to update the count. The signal is called ChildExitingTree
.
Here's the method handler.
In this method, we're grabbing the count again and subtracting one. It's important to subtract 1 since this signal gets called before the enemy is deleted.
For the OnNewEnemyCount
event, we defined the event with a generic.
If you plan on sending data with an event, you must add a generic to describe the type of data you plan on sending. Whenever you subscribe to this event, the method must accept the argument. Otherwise, C# will complain.
Defeat UI
In this lecture, we designed and coded a UI for the defeat screen. When the player is defeated, we raised an event from their PlayerDeathState
class. Specifically, we decided to raise this event when the animation is finished.
When the event is raised, we're subscribing to this event and then reparenting the node so that it doesn't get deleted.
Victory UI
In this lecture, we worked on the victory UI. When the player wins the game, the game should pause to prevent them from performing any other actions. Instead, we decided to display a victory screen to let the player know they've defeatd all enemies. The most important step in this process pausing the game when the event was raised.
In this method, we hid the stats UI and displayed the victory UI before pausing the game.
Pause UI
In this lecture, we worked on creating a pause UI. We didn't really learn anything new in this lecture aside from learning how to toggle a boolean value. In the _Input
method, we performed a few steps.
Firstly, we checked if the player can pause. If another UI element is visible, such as the defeat, start, or victory screen are displaying, the player shouldn't be able to pause. Next, we're checking the Pause
action for a key press. If it wasn't pressed, we didn't bother running the rest of the method.
Lastly, we toggled the stats, Paused
property, and pause UI. The order does matter since we don't want to use the Paused
property twice for setting a property on a container.
Last updated