Godot 3.0: Simple Mobile UI (with transitions)
Sat, Jun 16, 2018Mobile UI
Building the UI is usually the least fun part of your game. When I’m working on a project, I tend to ignore this part for as long as I can. On the plus side, once you’ve built a few game screens and UI systems, you can often use them across mulitiple projects.
In this tutorial, we’ll make a small UI system for a mobile game. We’ll focus on the UI screens only, with the goal that you can plug this into your own game to jump-start construction of your various game screens.
A simple setup might be to have four screens:
- Title Screen
- Settings
- Gameplay (HUD)
- End Screen
Here’s an example of what we want to accomplish, moving between screens with a nice animated transition:
You can download the example project here:
Display setup
For this demo, we want to simulate a mobile device screen in Portrait mode. In
Project Settings > Display > Window set the Width and Height to 480
and
854
and the Orientation to “portrait”.
Master screen
Because all of the screens are going to share some common layout, we can use Godot’s scene inheritance to make things a bit easier. We’ll create a master scene containing the common nodes and configuration and use that as the base for the individual screens.
Here’s the node setup (node types are in parentheses):
╴Screen (CanvasLayer)
┠╴Margins (MarginContainer)
┃ ┖╴Main (VBoxContainer)
┃ ┖╴Title (Label)
┖╴AnimationPlayer
Save this as “Screen.tscn” and set up the nodes as follows:
- Screen (CanvasLayer)
We’re using a CanvasLayer
node as the scene’s root, which will allow the UI
screens to be independent of any Camera movement or other transformations you
apply to the default layer. The menu screens will “float” on top of any game
objects.
- Margins
This node will be the root container, and allows us to arrange everything with a nice margin around the edges of the screen. Remember that in Godot’s UI hierarchy, containers will automatically control the size of their children, so you won’t be individually setting any other nodes’ layout. This automatic formatting will be especially useful if you change resolutions or rotate from portrait to landscape.
From the “Layout” menu select “Full Rect” and then in the “Custom Constants”
section of the Inspector, set the four Margin properties to 20
.
Note: Most control nodes have a “Custom Constants” section where their unique properties can be set.
- Main
The VBoxContainer
automatically “stacks” its children, spacing them out
vertically from the top, midddle, or bottom (set by the Alignment property).
The only setting we need to change is the Separation, which we’ll set to 100
to space out the container’s items.
- Title
Most, if not all, of the screens will have some sort of title at the top, so we’ll give it a common configuration here. Set Align to “Center”. Put “Title” or any other word in the Text property so you can see the result.
Next, configure the “Custom Fonts” section with your preferred font. For this demo I’m using the free “Xolonium” font, which you can download here.
TIP: Once you’ve configured the font, click the “Save” icon to save the resource. Then you can add it to another node later and avoid having to set all the properties again.
Transitions
The next step is to set up the transition animation that will take the user from screen to screen. For this example, we’ll make the screens “slide” in and out from the right side. You could also add any other kinds of transitions, such as zooms, fades, etc.
First, we’ll make sure the screen starts in the right state. Add a new animation to the AnimationPlayer called “init” and set it to “Autoplay on Load”.
We want the screen’s starting position to be “offscreen”, to the right of the
display, so set the CanvasLayer’s Offset to (500, 0)
and click the key icon
to add a keyframe.
Now add another animation called “transition”. Set the Length to 0.3
(or however
long you want the transition to take - faster is probably better). Add a keyframe
for Offset at the start of (500, 0)
and at the end of (0, 0)
. Play the animation
to test it out.
You can add any other transition effects here as well.
Common screen code
Add a script to the “Screen” scene:
extends CanvasLayer
func appear():
$AnimationPlayer.play('appear')
func disappear():
$AnimationPlayer.play_backwards('appear')
We’ll extend this script on other screens to process buttons, etc.
Individual screens
Let’s start with the title screen. Click Scene > New Inherited Scene and select
res://Screen.tscn
. Name the new scene “TitleScreen” and save it.
Change the Title’s Text to your game title and add the following new nodes:
╴TitleScreen
┠╴Margins
┃ ┖╴Main
┃ ┠╴Title
┃ ┖╴Buttons (HBoxContainer)
┃ ┠╴PlayButton (TextureButton)
┃ ┖╴SettingsButton (TextureButton)
┖╴AnimationPlayer
In the Buttons
container, set the Alignment to “Center” and Custom Constants/Separation
to 75
. Add the appropriate texture to each button’s Textures/Normal property.
Disconnect the script and add a new one extending res://Screen.gd
. Connect
both buttons’ pressed
signal:
extends "res://Screen.gd"
signal play
signal settings
func _on_PlayButton_pressed():
emit_signal('play')
func _on_SettingsButton_pressed():
emit_signal('settings')
Each screen you want will be set up like this. Add any buttons or other UI controls and if they need to trigger a transition, emit an appropriate signal. The main display script can then handle the signals and initiate the transition. In this way, the individual screens need no knowledge of each other or how they are connected.
Switching scenes
To switch between the screens, we’ll make a “main” script. In your game, this might
be your main scene or a separate UI scene that you load when the game starts. For
this demo, just make a new scene with a Node
root and instance one of each of
your screen scenes in it. For example, here’s how mine looks once I’ve made the
four screens I described in the introduction:
Now add a script and connect each screen’s signals (which are emitted when the player pushes its buttons). Your script will look like this:
extends Node
# this signal can let your main scene know that the player
# started the game
signal start_game
var current_screen = null
func _ready():
change_screen($TitleScreen)
# When changing screens, make sure to wait until the transition
# animation has finished before calling another
func change_screen(new_screen):
printt('changing to:', new_screen.name)
if current_screen:
current_screen.disappear()
yield(current_screen.get_node('AnimationPlayer'), 'animation_finished')
current_screen = new_screen
current_screen.appear()
yield(current_screen.get_node('AnimationPlayer'), 'animation_finished')
# Below here are the functions connected to each screen's
# signals, i.e. your "map" of what screen connects to
# another.
func _on_TitleScreen_play():
change_screen($PlayScreen)
emit_signal('start_game')
func _on_TitleScreen_settings():
change_screen($SettingsScreen)
func _on_SettingsScreen_return_to_title():
change_screen($TitleScreen)
func _on_PlayScreen_game_over():
change_screen($EndScreen)
func _on_EndScreen_home():
change_screen($TitleScreen)
func _on_EndScreen_play():
change_screen($PlayScreen)
emit_signal('start_game')
That’s the basic idea.Download the project to see it in action and experiment with how everything is connected up.
Styling
I’ve purposely left this demo with very little styling beyond the container layout. Feel free to add your own button images and spice up the look-and-feel.
Some other things you can add to spice it up:
- Additional transition effects
- Color and background animation
- Audio: play sound effects on transition and button press