Universal Features
This guide assumes you’ve gone through the basics guide.
Games have some nearly universal features.
These features will be flexible and done in different ways for the game. Some artful games totally forgo some, or any, of these staples of the medium.
But this isn’t about how there always has to be a character controller of some variety. Character controllers come in all shapes and sizes.
This is about things like the main menu, settings, and things of the sort. These are very rarely done differently, aside from the layout and aesthetics.
Main Menu
Main menus are nigh universal traits. They are usually the same formula copied and slightly twisted to fit the aesthetic.
Setting up
Make a new scene and make the root node a Control node, listed as User Interface.
For this, we’ll need the node tree:
- Control - Root node
- ColorRect - The background
- Control - Houses the main menu buttons, to hide when settings open
- RichTextLabel - The title
- VBoxContainer - Holds the buttons
- Button - Start button
- Button - Settings button
- Button - Quit button
Anchors
Tons of people have differently sized screens; not everyone has a 16:9 aspect ratio monitor. For this reason we need anchors, it’s similar to designing a responsive website.
Select the root Control node first. On the right of your toolbar. which is at the top of the viewport, there should be the option to set the anchor preset.
The root Control node, the menu container Control node, and the background ColorRect should be set to Full Rect so it covers the entire background and resizes to cover the entire background no matter the aspect ratio of the player’s monitor. Feel free to change the ColorRect’s color.
The RichTextLabel should be connected to the part of the screen they’ll move with when the screen is resized. In the anchor preset menu, select the closest one to where you want it to be, then move the nodes to be roughly where you want them to be.
Set the text of the RichTextLabel to the name of your wonderful game. Change the text color to black in the inspector under Control > Theme Overrides > Colors. Make the node larger until you can see the text. You can resize the text in Control > Theme Overrides > Font Sizes. If you want to center your text enable BBCode at the top of the inspector and wrap all text with [center]Your title here[/center]
.
Give your buttons text and make the font size larger. You can change the font size on all three of them at once by selecting all three nodes with shift/ctrl/cmd + click. They won’t be visible until you resize the VBoxContainer.
The VBoxContainer node stacks all of its children nodes in a Vertical Box.
Make it wide and tall enough to contain the buttons, anchor and size it properly. Your buttons should be there.
Test out moving them around by selecting the root Control node and resizing it with the orange dots. Everything should scale properly. If your game is being designed for desktop platforms you only really need to test out aspect ratios between 1:1 and 2:1 to cover most bases. You should test the opposite 1:1 to 1:2 for mobile.
Also, you don’t need to test it at any smaller size of node because Godot will just lower the resolution instead of changing the actual node size. In (slightly) simpler terms, there will be fewer pixels to display your gorgeous game, but your nodes (buttons, title, etc.) will not escape the screen as they do when you make the Control node smaller.
User interfaces in games are a bit finicky due to the fact that not all screens are built equal. Some people have ultra-wide monitors, some people play games on old CRT screens. Who knows, maybe someone will play your game by projecting it into their brain one day. Making your game accessible is paramount.
Interaction
It’s good practice to separate different uses into as many files as you can. This is helpful for keeping tidy, working with people using Git, help remind future-you what now-you were thinking, and just generally be easier. It’s for these reasons, we’ll use a short and separate script for each of the buttons.
Also, just so we don’t clutter up your scripts folder and we’ll never touch these again, we’ll use built-in scripts which are stored in the .tscn
file for your scene instead of a separate .gd
file.
Give your start button a script, toggle built-in, and make it. Delete everything other than extends Button
. Connect the start button’s pressed()
signal to its own script and set the function’s body to get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
. Replace res://scenes/main_menu.tscn
with the path to the first level, the main scene, or whatever scene you would normally open to play your game. If you’re starting with just a main menu you can set this later.
If you’re starting with a main menu and no game, start the game with F5 and set the current scene as the main scene to run. Otherwise, if you are adding this to a project, go to Project > Project Settings… > General tab > Application category > Run and set Main Scene to your main menu.
Your quit button is just as easy. Make a built-in script, connect the signal. This time in the function body give it get_tree().quit()
.
The settings button requires a settings menu.
Settings
This settings guide is going to build on top of the main menu done above. If you already have a menu, be it a main, pause, or some other kind, and want to add a settings menu after you press a button on your menu, then do everything the same but replace the base Control node with whatever root node your menu has.
Setting up
As a child of your Control node, add TabContainer. We will provide examples for changing video, audio, and controls. But you will have to think of, and provide settings for, the gameplay, since it’s all about your unique game.
Make sure to anchor your TabContainer to Full Rect so it takes up the whole screen.
Add this as a child of the root node:
- Control - holds the settings
- Button - closes settings
- TabContainer - Contains the tabs
- TabBar - Video tab
- VBoxContainer - Lists the options vertically
- HBoxContainer - Holds the label and the option for the setting
- Label - Tells the name of the setting
- VSeparator - Vertical separator between the label and the option
- OptionButton - Lets the user decide between 2D MSAA
- OptionButton - Lets the user decide between 3D MSAA
- CheckBox - Lets the user decide if to use screen space FXAA
- Checkbox - Lets the user decide if to use TAA
- HBoxContainer - Holds the label and the option for the setting
- VBoxContainer - Lists the options vertically
- TabBar - Audio tab
- VBoxContainer - Lists the options vertically
- HBoxContainer - Holds the label and the option for the setting
- Label - Tells the name of the setting
- VSeparator - Vertical separator between the label and the option
- HSlider - Lets the user change the volume
- HBoxContainer - Holds the label and the option for the setting
- VBoxContainer - Lists the options vertically
- TabBar - Gameplay tab
- TabBar - Controls tab
- VBoxContainer - Lists the options vertically
- HBoxContainer - Holds the label and the option for the setting
- Label - Tells the name of the setting
- VSeparator - Vertical separator between the label and the option
- Button - Lets the user change the related button
- HBoxContainer - Holds the label and the option for the setting
- VBoxContainer - Lists the options vertically
- TabBar - Video tab
A lot of this is very similar. You can just make one TabBar and its children, then copy-paste it onto the TabContainer three times. If you do this, make sure to change the related TabBar names and the option nodes.
You must rename the TabBar nodes to change the title of the tabs.
Close button
Set the text of the Button that’s the child of your new Control node to something like “X” or “Close Settings”.
If you’re implementing the settings in a pause menu look below for a more flexible guide that allows you to make the Control node into a scene and copy it anywhere.
Otherwise, skip making it modular.
Making it modular
This way is going to assume you have the same settings setup shown in the previous section. But, this time we’re also going to assume that the Control node container for settings is split off into its own scene so that you can instantiate the settings menu in both a pause menu and main menu way, or any other place you may need it.
If you didn’t have it in a scene but wanted it in both places, you’d have to make all changes to your settings twice. Which you will inevitably forget to do, leading to two different settings menus. Plus it’d just be a hassle.
This means we can’t just connect the button straight to a node that can turn each menu visible/invisible since it’s inside a scene. We’ll have to daisy-chain signals.
Create a script on your settings Control node container, it’s probably a good idea to not make this built-in. You can if you wanted to but it has the possibility of creating a mess if you accidentally disconnect it from another scene when this one is instanced in it. If a built-in script gets disconnected your only recourse is hoping you committed recently with git or rebuildig the script. Part of why we only use built-in scripts for very, very small scripts that do basic tasks and won’t be reused.
Connect the Button’s pressed()
signal to the script.
At the top of the script make a new signal. This can be done simply with:
Now, if you click on your settings Control node container and go to its signals, you’ll see your new close_settings()
signal.
In the function made by connecting the Button to this script, use close_settings.emit()
.
At anytime you can split off a set of nodes into its own scene by right clicking the new scene’s root node and pressing Save Branch as Scene.
If you’re adding this to a pause menu, structure the pause menu similar to the main menu setup shown near the top of this page. Specifically, make sure there’s a root Control node, then a Control node that houses your pause menu. When you instance your settings scene, instance it to the root Control node. Now follow the next section.
Part of the main menu
Give the root node for your main menu a script. Since this script won’t be able to be used for any other scene or node since it’s connecting just its children and it’s a very niche use-case that you probably won’t copy, make the script a built-in script.
Connect the pressed()
signal from the Button that’ll close your settings (or the close_settings
signal from the Control node if you made it modular), and the pressed()
signal from the Button that opens your settings from the main menu to this new script.
Use ctrl/cmd to select both of the Control container nodes for your main menu and your settings menu. Then click and drag them into the script, but before you release the mouse hold down ctrl/cmd. This is a quick and easy way to make variables for nodes in a script. It also works for preloading files in a script!
When the settings-open button is pressed, make menu invisible and settings visible by changing their visible
property. You’ll need to get their nodes as a global variable in the script by dragging them into the script and before you let go of the left mouse button hold ctrl/cmd.
Do the opposite when close settings is pressed.
Video: Anti-Aliasing
Godot has some good documentation for both 2D and 3D anti-aliasing. They have some good diagrams showing what it’s like with and without anti-aliasing.
There are many ways of creating this effect, Godot has a few built-in. Sometimes you may not want to fight aliasing at all and give the game a retro-style.
We’ll let the player choose between the built-in Godot ones.
Making the option
Go to the inspector for each MSAA nodes and add elements to the Items array property. Give it the options Disabled, 2x, 4x, and 8x. Of course, you can remove the irrelevant 2D or 3D node if you know what your game is going to be.
If you want the default settings to be something other than disabled, you can change the Selected property for the OptionButtons, and the Button Pressed property for the CheckBoxes to match your default.
Add a script to the HBoxContainer on the video tab and call it something like setting_anti_aliasing.gd
in a scripts folder.
Navigate to Project > Project Settings… > Rendering > Anti Aliasing. This is where you can see all the options. You might recall you made two CheckBoxes, but this screen has only one and three OptionButtons. Well, if you select Screen Space AA you can see there are only Disabled and FXAA.
From here you can right-click and copy the path to the setting, this is what we’ll use to set the setting.
Connect the item_selected(index: int)
and the toggled(toggled_on: bool
signals from all four option nodes.
Finally, use the RenderingServer to change the display. RenderingServer
has the four functions we need, which you can look up with the Search Help button or see below. It requires the viewport RID which you can get with get_viewport().get_viewport_rid()
.
The final hiccup will be with screen space anti-aliasing since it’s technically an array instead of a boolean, even though it just has Disabled and FXAA as options. For this you can just type-cast the bool
to int
using int(your_bool_here)
to make true
into 1 and false
into 0.
All in all, your script should look something like:
Audio: Master volume
Audio in Godot goes through buses. When adding an AudioStreamPlayer or the 2D/3D equivalent, it’s going through the custom bus set for it, then to the master bus. If no custom bus is set, it just goes through the master bus.
At the bottom of Godot you can switch to the Audio dock, this is what it looks like by default:
When adding audio you can make custom buses and route the audio through them, then change their volume with new sliders like the one we’re about to make for the master bus.
This one is simple, after changing your label to say something like “Master”, start with a single script on the HSlider.
Connect its own value_changed(value: float)
signal to itself.
In the Inspector set the Min Value to 0, Max Value to 1, and step to 0.1. Also, set Layout > Custom Minimum Size’s x value to something like 300px, so the slider is wide.
Then, in your function connected to the value_changed
signal, add the line AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear_to_db(value))
.
That’s it.
Controls: Changing Input Button
First, make sure you have custom input in Project > Project Settings… > Input Map to change.
Change your Label to the name of the action you want to change, and the Button’s text to something like “Change input”.
Give the Button a script:
Connect the Button’s pressed
signal to the _on_pressed
function.
It’s a relatively simple script, just using Input
, InputMap
, InputEvent
and their related functions to change things.
This will remove all the events related to the action and make the next pressed button the event for the action. It’s cutting out mouse events because moving your mouse or left-clicking isn’t very likely to be something you want to change, but feel free to modify the not event is InputEventMouse
to something like not event is InputEventMouseMotion
to allow mouse buttons to be bound.
Singleton
Almost every game needs a singleton of some variety. The Godot docs have a good guide on how to make a singleton and what one is.
What is a Singleton?
To put it in simple terms, singletons contain a piece of data that any part of your game can access at any time.
Storing your player’s health in the player script isn’t very good if other scenes, like an enemy, need to get the player’s health and modify it regularly. Often it’s slower to constantly be getting the node tree, and then finding the player node, and then getting the variable and changing it. It can cause unforseen bugs and just be a mess. So we use singletons because it’s quick, easy, efficient, and just better.
Another benefit is that they aren’t part of the main game, they are seperate. So even if you switch between levels, characters, menus, or whatever else you have, it’ll always be the same. Never changed unless a part of the game changes it.
However, this means if you store your player’s health in a singleton, it’ll need to be manually reset. For example, when restarting the level because a player died you’ll also need to reset the health, rather than just reinstantiating the player with their original health data.
Making a Singleton
To make a singleton, make a new script in the FileSystem dock, in your scripts folder. The script needs to extend from Node.
Go to Project > Project Settings… > Autoload and click the folder icon. Add your new script to the autoload.
Name it something like Singleton or Global, so that you always know you can get the globally accessed variables by using Singleton.player_health
or Global.player_health
. To make any variable accessible in that way, all you have to do is add the variable to your singleton.gd
file, or whatever file is your singleton.