Godot 3.0: Simple Mobile UI (with transitions)

Tags: godot gamedev tutorial

Mobile 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:

Here’s an example of what we want to accomplish, moving between screens with a nice animated transition:

< preview gif >

You can download the example project here:

Mobile UI Example

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:

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.

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.

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.

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:

mobile_ui_main_screen

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:

Comments