You've made a simple game in Defold already? Now it's time to add a level selection screen and a basic Graphical User Interface!
Welcome to the Colorslide tutorial. In this guide, you'll add a complete GUI flow to an existing multi-level puzzle game.
This tutorial requires basic familiarity with the Defold Editor (check out the Editor overview) and knowledge about Defold building blocks. If you're new to Defold, check our learning resources on the Learn website, other tutorials and manuals.
Colorslide is a tile puzzle where you move colored faces until each one reaches a matching tile.
By the end of the tutorial, you will learn how to:
- Load different levels using collection proxies.
- Build a level selection GUI with buttons, labels, and input handling.
- Add an in-level GUI with a back button, level label, and a completion popup.
- Create a start screen and connect it to the rest of the game flow.
These steps should take about 30 minutes. Most of the gameplay code is already prepared, so you can focus on GUI flow and messaging.
Before beginning the tutorial, build and run the game to try it. You can also select Project ▸ Build in the menu or shortcut Ctrl+B (Cmd+B on Mac).
Tip: When you have this tutorial README.md file opened in the Defold Editor, you can use the links in here to perform certain actions, like running the game via the link above, or opening a file.
Try clicking one of these colored faces with the left mouse button to move them to the free space. To complete the level, move all the colored faces to the cells wherein their color matches with them.
Right now, after moving all the colored faces to the matching positions, nothing happens. We'll add a message about the complete level later.
For now, we're going to change the level to load as the first step of our changes!
First, we'll learn the project structure and change the level we are loading.
Main collection (or "Bootstrap collection") is a collection that is loaded on the start of the game, in our case it's named "main.collection".
Find the file called main.collection and open it to see how the game is set up.
You can always open any file with the menu item File ▸ Open... or shortcut Ctrl+P (Cmd+P on Mac) and start typing the word "main" to search among all the available assets.
You can also go to the Assets menu on the left side and find it on your own: Select the folder with the project name main (by clicking the ► icon once or double-clicking the name with left mouse button), and open main.collection (double-click on its name or right click and select Open).
It might be convenient for this tutorial to split the Code Editor into two panes, so that you can see README.md and the currenly edited file simultaneously.
- Right click on the file tab.
- Select Move to Other Tab Pane.
The main.collection is a file containing the whole game inside. Check out the manual page about collections for the detailed information.
Currently, the level is launched in a subcollection called level (which is inside the main.collection) and references the file /main/level_2/level_2.collection. If you go to the Outline pane on the right side and click the arrow ▸ near the level collection (or double click the collection icon), it'll expand and reveal two game objects:
boardlevel
The board game object contains a tilemap component (named here tiles), which is a grid of square tiles. Open the tilemap and notice that there are two layers on the tilemap, one is the actual playfield (with the board layer ID) and one contains the initial setup for the bricks (with setup layer ID). You can switch their visibility by clicking the eye icon next to them in the Outline.
Leave the layers visible at the end.
Check out our manual about tilemaps for further details.
When the game starts, the code that is already written in this projects looks at the "setup" layer and replaces the brick tiles in-place with separate game objects that can be animated freely. It then clears the layer.
In Defold game objects can have components. The level game object contains the game logic script component (level.script) and a factory component called brickfactory used to spawn bricks on game start. This level game object is stored in a separate file called /main/level.go so game objects of this "blueprint/prototype" file can be instantiated in each separate level collection.
Check out our manual about factories, if you are interested in more information.
Now try to change the current level to the other one!
- Open the
main.collection. - Make sure you have selected the level collection. It'll show its properties in the
Propertiespane on the right side. Properties are a series of editable values about the selected game object. Don't forget to check out the manual about properties if you are looking for more information. - Find the Path property and change its value to
/main/level_3/level_3.collection. You can manually type it in the text field or locate the file by clicking the ... button to the right of it and in the Select Resource window, select the file with the same name and clickOKbutton (or just double click on it). - Save all (Ctrl+S / Cmd+S) by selecting File ▸ Save All in the menu or shortcut Ctrl+S (Cmd+S on Mac)
- Build and run the game again by selecting Project ▸ Build in the menu or shortcut Ctrl+B (Cmd+B on Mac).
Now you should see a very different look of the level! Try to experiment with the other levels (from 1 to 4).
What is needed for this game is a way to make the level loading automatically and depending on player choice. In this case, we're going to use collection proxies.
Collection proxies allows you to create a new "world" based on the collection. Any object that is spawned from the collection content will be part of the created world and be automatically destroyed when the collection is unloaded from the proxy.
The new world that is created has an overhead cost attached to it so proxies are not a good fit for spawning large quantities of small collections simultaneously.
You should check out our manual about collection proxies to read more information about them.
-
Open the "main.collection" file and delete the level collection reference. You can do that by selecting it with the right mouse button and clicking Delete or, when it is selected, by pressing Delete key.
-
Instead of it, add a new game object to the main collection. In the Outline menu, right click the Collection and select Add Game Object. You can also select it once with the left mouse button and press A key to create the game object.
-
After that, go to the Properties pane, find the field with Id name and type in the text field loader. Make sure you didn't leave a typo - the name must be the same, because we'll reference it in code later on.
- Now add a Collection Proxy component to the
loadergame object (right click ▸ Add Component ▸ Collection Proxy). Give it a name proxy_level_1 and in the Collection property, select (or type):/main/level_1/level_1.collection
- Repeat the step 1 for 4 levels - creating collection proxies for each.
Here we need to add a new script to the project to complete this step. Do the following:
- From Assets menu, right click the main folder and select New ▸ Script.
- Name it loader and click Create Script or press Enter. You can skip extension (
.script), it will be automatically added by Defold.
-
The script will be opened in a built-in Code Editor. Remove all its content and paste this code:
function init(self) msg.post("#proxy_level_1", "load") -- [1] end function on_message(self, message_id, message, sender) if message_id == hash("proxy_loaded") then -- [2] msg.post(sender, "init") msg.post(sender, "enable") end end
- Send a message to the proxy component telling it to start loading its collection.
- When the proxy component is done loading, it sends a
proxy_loadedmessage back. You can then sendinitandenablemessages to the collection to initialize and enable the collection content. We can send these messages back tosenderwhich is the proxy component (in this case).
If you need additional information about how the messaging system works in Defold, be sure to check out our manual.
-
Save the script. Last step is to attach this script to our loader game object, so get back to the
main.collection, right click on the loader game object, select Add Component File and select our newly createdloader.script -
Add this script to the made collection proxy (right click ▸ Add Component File and select the
loader.script).
Now save all (Ctrl+B / Cmd+B) and try to run the game (Ctrl+B / Cmd+B).
Unfortunately there is an instant error. The console says:
ERROR:GAMEOBJECT: The collection 'default' could not be created since there is already a socket with the same name.
ERROR:GAMEOBJECT: AcquireResources NewCollection RESULT_OUT_OF_RESOURCES
WARNING:RESOURCE: Unable to create resource: /main/level_1/level_1.collectionc: OUT_OF_RESOURCES
ERROR:GAMESYS: The collection /main/level_1/level_1.collectionc could not be loaded.
This error occurs because the proxy tries to create a new world (socket) with the name default, but a world with that name already exists - the one created from main.collection at engine boot. This is a common pitfall, sometimes missed when working with many collections and collection proxies, so it was worth showing it in this tutorial. The socket name is set in the properties of the collection root, so it's very easy to fix.
- Open the
level_1.collection. (You can also double click the collection proxy to open it). - Click on the root of the collection Collection in the Outline.
- In the Properties pane. Name property and set it to
level_1. - Click outside the text field to stop editing it.
- Repeat the steps 1-4 analogically for the rest: rename the collections:
level_2.collection,level_3.collectionandlevel_4.collection.
TODO: Verify if level_4 can have a name default in the beginning of the tutorial!
Save all (Ctrl+S / Cmd+S) and try running the game again.
The level now shows up, but if you try to click on the board to move a tile, nothing happens. Why is that?
The problem is that the script that deals with input is now inside the proxied world. The input system works like this:
- It sends input to all game objects in the bootstrap collection that has acquired input focus.
- If one of these objects listening to input contains a proxy, input is directed to any object in the game world behind the proxy that has acquired input focus.
So in order to get input into the proxied collection, the game object that contains the proxy component must listen to input.
You should check out our manual about how input works in Defold to find even more information.
-
Open the
loader.scriptfile. -
Add the following line to the
initfunction:msg.post(".", "acquire_input_focus") -- [1]
End result:
function init(self) msg.post("#proxy_level_1", "load") msg.post(".", "acquire_input_focus") -- [1] end
- Since this game object holds the proxy for the collection that needs input, this game object needs to acquire input focus too.
Run the game again. Now everything should work as expected.
Because the game contains four levels you need to add proxy components for the remaining three levels.
Before, make sure you changed the Name property of all collections to a unique name for each level collection so the socket names don't collide, as described above.
- In the
main.collectioncopy theproxy_level_1collection proxy component (right click on it and select Copy or when it's selected use shortcut Ctrl+C or Cmd+C on Mac). - Then, paste it 3 times, so that we have 4 proxies (right click on
loaderand select Paste or when it's selected use shortcut Ctrl+V or Cmd+V on Mac). - Change the proxy_level_ to proxy_level_4, as all other copies increase the last number automatically when pasted, so the rest should be respectively
proxy_level_2andproxy_level_3. - For each collection proxy number
Xchange theCollectionproperty to the corespondinglevel_X.collection, where X is a number (2-4), so that proxy_level_2 has Collection level_2, and so on.
- Test that each level loads by altering the proxy component you send the "load" message by altering
msg.post("#proxy_level_X", "load")line whereXis the number from 1 to 4 in theloader.script. Don't forget to save each time before running the game.
Common pitfall: If you'd forget to create a proxy for some of the levels in the main.collection, and when you try to load that level you get an error:
ERROR:GAMEOBJECT: Component '/loader#proxy_level_X' could not be found when dispatching message 'load' sent from default:/loader#loader
To fix it, you need to simply add collection proxy component for this level in the main.collection.
Now you have built the setup required to load any level at any moment, so it is time to construct an interface to the level loading.
First, let's create a new GUI - a Graphical User Interface, which is a component in Defold:
- In Assets pane - inside of the main folder - create a new GUI file (right click on the folder ▸ New ▸ GUI) and name it level_select.
- It will be opened in the GUI editor, look at the Outline pane.
- Add the
bricksatlas to the Textures section (right click on Textures ▸ Add ▸ Textures ▸ select/assets/bricks.atlas).
- Similarly, add the headings font to the Fonts section (right click on Fonts ▸ Add ▸ Fonts ▸ select
/assets/headings.font).
If you are interested in more detailed information about atlases and fonts in Defold, check out the manuals.
Now we can start creating buttons for our level selection screen!
Construct an interface with 4 buttons, one for each level. Notice that you can copy and paste the created button so you don't have to do each step once again. If you do this, make sure you have correct names in the parent and its children nodes.
While being in the created GUI file, do the following steps:
- Create
Boxnode (right click on Nodes ▸ Add ▸ Box)
- Go to Properties menu.
- Set Id property of the created box nodes to
button_level_1. - Copy and paste this box node in the Outline 3 more times (so that we have 4 buttons).
- You can change the order of the selected node in the Outline by clicking arrows up and down while holding the Alt (Option on Mac).
- Change the name of the button_level_ to button_level_4.
- For each button now set Position property to one of the following depending on the level number:
- (X: 212, Y: 700, Z: 0).
- (X: 428, Y: 700, Z: 0).
- (X: 212, Y: 500, Z: 0).
- (X: 428, Y: 500, Z: 0).
- For convenience, while holding Shift select all the nodes in the Outline (click first and last), or for each of the nodes sepearately, and set Size Mode property to Manual. This will make editing Size property available.
- Set Size property to: (X: 150, Y: 100, Z: 0).
- Set Alpha property to 0 (you can type manually or drag the slider to the left).
Check out our manual about GUI scenes if you need some more information about how to use it.
If you'd change the size of your graphics make sure that each root node is big enough to cover the whole button graphics because the root node will be used to test input against. It is a common technique to make input area a bit bigger than the visuals of the button itself, as it is helpful especially for touch input devices.
This is the configuration of the root node. Now you need to setup the sprite and the text and these will be the children (nested) nodes.
For each of the created buttons do the following steps (or do it once for the first button and the copy/paste for the rest, but then don't forget to change the Ids):
- Add a child
Boxnode (right click on the rootbutton_level_Xnode (whereXis the number from 1 to 4) and select Add ▸ Box). - Go to Properties menu.
- Set Id property to
button_level_X_bg(whereXis the number from 1 to 4). You can also click F2 in the Outline to start editing the name in place. - Set Size Mode property to Manual. This will make editing Size and Slice-9 properties available.
- Set Size property to: (X: 150, Y: 100, Z: 0).
- Set Texture property to
bricks/button. - Set Slice-9 property to: (L: 40, T: 40, R: 40, B: 40). This is important to type because it will cause adjusting the sprite's edges so it won't be stretched.
- Uncheck Inherit Alpha property so it renders even if its parent is transparent.
If you need more detailed information about how the slice-9 sprites work in Defold, check out the manual page.
We have done the first of two children nodes. Now we have the last element to create!
For each of the created now buttons do the following steps (or do it once for the first button and the copy/paste for the rest, but then don't forget to change the Ids):
- Add a child
Textnode (right click on the rootbutton_level_X_bgnode (whereXis the number from 1 to 4) and select Add ▸ Text). - Go to Properties menu.
- Set Id property to
button_level_X_text(whereXis the number from 1 to 4). You can also click F2 in the Outline to start editing the name in place. - Set Size property to: (X: 150, Y: 100, Z: 0).
- Set Text property to
X(whereXis the number from 1 to 4). - Set Font property to
headings.
We have prepared everything we need to display the buttons!
The header text is the last thing we need to complete creating the GUI!
- Create
Textnode but this time outside of any root nodes for the buttons (right click on Nodes ▸ Add ▸ Text). - Go to Properties menu.
- Set Id property to
select_level_header. - Set Position property to: (X: 320, Y: 900, Z: 0).
- Set Size property to: (X: 320, Y: 64, Z: 0).
- Set Text property to
SELECT LEVEL. - Set Font property to
headings.
That's it! You should have everything setup as on this image:
Now we're going to add extra code to make it work!
For programming GUI, we use a special kind of script in Defold: GUI Script. You can check out the manual we have about the GUI scripts.
Here's what you need to do:
-
In Assets pane, right click the main folder and select New ▸ GUI Script.
-
Name it level_select and click Create GUI Script or press Enter. You can skip the extension (
.gui_script), it will be automatically added by Defold. -
The GUI script will be opened in the code editor, remove all its content and paste this code:
function init(self) msg.post(".", "acquire_input_focus") msg.post("#", "show_level_select") -- [1] self.active = false end function on_message(self, message_id, message, sender) if message_id == hash("show_level_select") then -- [2] msg.post("#", "enable") self.active = true elseif message_id == hash("hide_level_select") then -- [3] msg.post("#", "disable") self.active = false end end function on_input(self, action_id, action) if action_id == hash("touch") and action.pressed and self.active then for n = 1, 4 do -- [4] local node = gui.get_node("button_level_" .. n) if gui.pick_node(node, action.x, action.y) then -- [5] msg.post("/loader#loader", "load_level", { level = n }) -- [6] msg.post("#", "hide_level_select") -- [7] end end end end
- Set up the GUI.
- Showing and hiding the GUI is triggered via messaging so it can be done from other scripts.
- React to the pressing of touch input (as already set up in the input bindings).
- The button nodes are named
button_level_1tobutton_level_4so they can be looped over. - Check if the touch action happens within the boundaries of node
level_n. This means that the click happened on the button. - Send a message to the loader script to load level
n. Notice that aloadmessage is not sent directly to the proxy from here since this script does not deal with the rest of the proxy loading logic, as a reaction toproxy_loaded. - Hide this GUI.
Now we need to attach the GUI script to the GUI:
- Go to the created GUI and select its root "GUI" to display its properties.
- In the Properties pane find the Script property and set it to
main/level_select.gui_script.
To finish off this step, the loader script needs a bit of new code to react to the load_level message, and the proxy loading on init should be removed.
-
Open
loader.scriptand changeinitandon_messagefunctions as follows:function init(self) -- Remove this line: -- msg.post("#proxy_level_1", "load") -- [1] msg.post(".", "acquire_input_focus") end function on_message(self, message_id, message, sender) -- Add this "load_level" message handling branch: if message_id == hash("load_level") then local proxy = "#proxy_level_" .. message.level -- [2] msg.post(proxy, "load") -- elseif message_id == hash("proxy_loaded") then msg.post(sender, "init") msg.post(sender, "enable") end end
- First, remove the line where we requested to load a hardcoded level_1.
- Then, handle the "load_level" message" - construct which proxy to load based on message data.
That's not the end. We need to add our level select to the game!
Do the following steps:
- Go to "main.collection".
- Add a new game object to it and name it guis.
- Right click on this game object, click Add Component File, and select
main/level_select.gui).
Save all, run the game and test the level selector screen. You should be able to click any of the level buttons and the corresponding level will load and be playable. Great!
You can now start and play a level, but there is no way to go back.
The next step is to add an in game GUI that allows you to navigate back to the level selection screen. It should also congratulate the player when the level is completed and allow moving directly to the next level.
The first step is to create another GUI for the level. You should be already familiar with the process - to simplify now, we will reuse our previously created GUI, so duplicate it and name "level" - follow these steps:
- Go to Assets menu.
- Locate the
level_select.guifile inside ofmainfolder, copy it (right click on it ▸ Copy or shortcut Ctrl + C / Cmd + C on Mac) and paste it insidemain(right click on the main folder ▸ Paste or shortcut Ctrl + V / Cmd + V on Mac). - Rename the duplicated GUI to
level.gui. - Open it and go to Outline pane.
We only need one button - to get back to the level_select GUI. So remove the remaining 3 of them and leave only one:
- In the Outline, while holding Shift, select buttons from 2-4 and click Delete key.
- Rename the
button_level_1tobutton_back(right click on it ▸ Rename or shortcut F2 or rename in the Properties pane). - Change the "Position" property of this node
button_backto (X: 125, Y: 1086, Z: 0). - Select and rename the child box node
button_level_1_bgtobutton_back_bg. - Select and rename the child text node
button_level_1_texttobutton_back_text. - Change the "Text" property of the
button_back_texttext node toBack.
Now, we'll reuse the "SELECT LEVEL" text node:
- Rename the
select_level_headertext node tolevel_number. - Change the "Position" property to: (X: 500, Y: 1085, Z: 0).
- Change the "Size" property to: (X: 180, Y: 64, Z: 0).
- Change the "Text" property of this node to
LEVEL 1.
Now we need to add a message for level complete with a "well done" message and a button to advance to the next level.
While being in the created root node, do the following:
- Create
Boxnode (right click on the root node "Nodes", and click Add ▸ Box). - Go to Properties menu.
- Set Id property to
well_done_message. - Set Position property to: (X: 990, Y: 568, Z: 0). You might wonder, why we put it outside the white rectangle showing our game resolution, but we do it on purpose here - we need to place it outside of the view so it can slide into view when the level is completed.
- Set Size property to: (X: 650, Y: 350, Z: 0).
- Set Color property to
#cccccc(you can type it manually or select the color picker to the right and click on the cell in the second row and fourth column).
We have a background. Now the rest of the elements!
To create a text as a child of our well_done_message node, follow these steps:
- Create
Textnode (right click on thewell_done_messagenode ▸ Add ▸ Text). - Go to Properties menu.
- Set Id property to
well_done_message_text. - Set Position property to: (X: 0, Y: 70, Z: 0).
- Set Text property to
Well done!. - Set Font property to
headings. - Set Color property to
#000000(you can type it manually or select the color picker to the right and click on the cell in the second row and the last column to the right).
The last thing to do is the button to proceed to the next level.
We will again reuse the existing button, that we created:
- Copy the
button_backnode. It will be copied with all its children. - Paste it inside the
well_done_messagebox node. - Rename the Id property of the
button_back1tobutton_next. - Change the Position property of the
button_nextto (X: 0, Y: -70, Z: 0). - Rename the Id property of the child
button_back_bg1tobutton_next_bg. - Change the Texture property of the
button_next_bgtobricks/greento make the button green. - Rename the Id property of the child
button_back_text1tobutton_next_text. - Change the Text property of the
button_next_texttext node toNext.
Usually, GUI might have a lot of elements that could be reused, like the buttons we are copying. In Defold, you can maintain such similar elements in the GUI using the templates. We are not using them in this tutorial, but we recommend to check out the manual later on.
The GUI is complete. Now we need to put additional code to make it work!
- From Assets menu, right click the main folder and select New ▸ GUI Script.
- Name it
leveland click Create GUI Script or press Enter. - File
level.gui_scriptwill be created and opened in the Code Editor. - Remove all its content and paste this code:
function on_message(self, message_id, message, sender) if message_id == hash("level_completed") then -- [1] local well_done_message = gui.get_node("well_done_message") gui.animate(well_done_message, "position.x", 320, gui.EASING_OUTSINE, 1, 1.5) end end function on_input(self, action_id, action) -- [2] if action_id == hash("touch") and action.pressed then local button_back = gui.get_node("button_back") if gui.pick_node(button_back, action.x, action.y) then msg.post("default:/guis#level_select", "show_level_select") -- [3] msg.post("default:/loader#loader", "unload_level") end local button_next = gui.get_node("button_next") if gui.pick_node(button_next, action.x, action.y) then msg.post("default:/loader#loader", "next_level") -- [4] end end end
- If message
level_completeis received, slide thewell_done_messagepanel with thebutton_nextbutton into view. - This GUI will be put on the
levelgame object which already acquires input focus (throughlevel.script) so this script should not do that. - If the player presses
back, tell the level selector to show itself and the loader to unload the level. Note that the socket name of the bootstrap collection is used in the address. - If the player presses
button_next, tell the loader to load the next level.
- If message
Now we need to attach the GUI Script to the GUI:
- Go to the created
level.guiand select "GUI" to display its properties. - In the Properties pane change the Script property to
main/level.gui_script.
If you need more information about how addressing works in Defold, be sure to check out the manual.
We also need to make additional changes to the already existing game objects. Do these steps:
- Open the
loader.scriptfile. - Change its content to the following:
function init(self) msg.post(".", "acquire_input_focus") -- Add this line: self.current_level = 0 -- [1] end function on_message(self, message_id, message, sender) if message_id == hash("load_level") then -- Add this line: self.current_level = message.level -- [2] local proxy = "#proxy_level_" .. message.level msg.post(proxy, "load") -- Add these lines: elseif message_id == hash("next_level") then -- [3] msg.post("#", "unload_level") msg.post("#", "load_level", { level = self.current_level + 1 }) elseif message_id == hash("unload_level") then -- [4] local proxy = "#proxy_level_" .. self.current_level msg.post(proxy, "disable") msg.post(proxy, "final") msg.post(proxy, "unload") -- elseif message_id == hash("proxy_loaded") then msg.post(sender, "init") msg.post(sender, "enable") end end
- Keep track of the currently loaded level so it can be unloaded and it is possible to advance to the next one.
- Update the current level whenever we load a new level.
- Load next level. Note that there is no check if there actually exists a next level.
- Unload the currently loaded level.
- Open the
level.scriptfile. - Add a message to the level GUI when the game is finished:
Put this line:
there at the end of the
msg.post("#gui", "level_completed") -- [1]
on_inputfunction inside the last if conditionall_correct(self.bricks)(below line 117):-- check if the board is solved if all_correct(self.bricks) then -- Add this line: msg.post("#gui", "level_completed") -- [1] self.completed = true end
- Tell the GUI to show the level completed panel.
- Open the
level.gofile. - Right click on this game object and Add Component File ▸
level.gui. - Set its Id property to
gui.
Run the game. You should be able to select a game, go back to the level selection screen (with the Back button) and also start the next level when one is finished.
The final piece of the puzzle is the start screen!
We will once again duplicate the GUI, this time, copy and paste the level.gui.
-
In the "Assets" pane in the "main" folder, copy and paste the
level.gui. -
Rename it to
start.gui. -
Open it and delete the "Back" button (
button_backgui node) and the "Level number" (level_numbertext node). -
Change the parent
well_done_messagegui node:- Rename the
well_done_messagetowelcome. - Change the Position property of the
welcometo (X: 320, Y: 568.0, Z: 0). - Change the Size property of the
welcometo (X: 650, Y: 1150.0, Z: 0).
- Rename the
-
Delete the child node
well_done_message_text. -
Change the button
button_nextnode:- Rename the
button_nexttobutton_start. - Rename the
button_next_bgtobutton_start_bg. - Rename the
button_next_texttobutton_start_text. - Change the Text property of the
button_start_texttoStart.
- Rename the
-
Add a logo:
- Add a new
Boxnode (right click on thewelcomenode and select Add ▸ Box) - Name it
logo. - Move it a bit up, so change its Position property of the
logoto (X: 0.0, Y: 200.0, Z: 0.0). - Change the Scale property of the
logoto (X: 0.5, Y: 0.5, Z: 1.0). - Change the Texture property of the
logotobricks/logo.
- Add a new
This is the end of creating the start GUI!
Now we need to add some code to make it work!
-
In the Assets pane, right click the
mainfolder and select New ▸ GUI Script. -
Name it
startand click Create GUI Script or press Enter. -
Open this
start.gui_scriptfile, clear it and paste inside the following content:function init(self) msg.post("#", "show_start") -- [1] self.active = false end function on_message(self, message_id, message, sender) if message_id == hash("show_start") then -- [2] msg.post("#", "enable") self.active = true elseif message_id == hash("hide_start") then msg.post("#", "disable") self.active = false end end function on_input(self, action_id, action) if action_id == hash("touch") and action.pressed and self.active then local start_button = gui.get_node("button_start") if gui.pick_node(start_button, action.x, action.y) then -- [3] msg.post("#", "hide_start") msg.post("#level_select", "show_level_select") end end end
- Start by showing this screen.
- Messages to show and hide this screen.
- If the player presses the
startbutton, hide this screen and tell the level selection GUI to show itself.
-
Go to the created
start.guiand select "GUI" to display its properties. -
In the Properties set the Script property to
main/start.gui_script.
The last thing is to connect this screen with the level select! Do the following steps:
-
Go to the "main.collection".
-
Add
start.guito theguisgame object (right click onguisgame object ▸ Add Component File ▸ selectmain/start.gui.gui).
Save all and try your game - it should now welcome you with a start screen and you can click "Start" to proceed to the level selection!
We only can't go back to start menu ever. Sometimes you might want it, e.g. if you have some other things to do in the start menu, so let's add this possibility!
We don't have a back button in the level selection GUI, because we didn't need it, but we added one in the level.gui, so we'll reuse it.
-
Open "level.gui".
-
Copy the button
button_backnode. -
Open "level_select.gui".
-
Paste the button (with its children) to the root "Nodes".
Now we only need to add code to handle the back button in the level selection:
-
Open "level_select.gui_script".
-
Add the code for returning to the start screen in
on_input:function on_input(self, action_id, action) if action_id == hash("touch") and action.pressed and self.active then for n = 1, 4 do local button_level = gui.get_node("button_level_" .. n) if gui.pick_node(button_level, action.x, action.y) then msg.post("/loader#loader", "load_level", { level = n }) msg.post("#level_select", "hide_level_select") end end --- Add these lines: local button_back = gui.get_node("button_back") -- [1] if gui.pick_node(button_back, action.x, action.y) then msg.post("#level_select", "hide_level_select") msg.post("#start", "show_start") end --- end end
- Check if the player clicks "back". If so, hide this GUI and show the start screen.
-
In the
initfunction change line 3:msg.post("#", "show_level_select")to:msg.post("#", "hide_level_select"),function init(self) msg.post(".", "acquire_input_focus") msg.post("#", "hide_level_select") -- [1] self.active = false end
- Change to "hide_level_select" so that instead of showing, we hide the level selection GUI on startup.
And that's it! Run the game and verify that everything works as expected and you can switch freely between the scenes.
The full source code for the project at this stage can be found in the tutorial-done branch.
We have reached the end of this tutorial. You learned how to create GUI screens, buttons, texts, and how to connect them all into one game flow.
This GUI implementation is quite simple. Each screen manages its own state and contains the code needed to hand over control to the next screen by sending messages to the other GUI component.
If your game does not feature complex GUI flows this method is sufficient and clear enough. However, for more advanced GUIs things can get complicated. In that case, you might want to use some kind of screen manager that controls the flow from a central location.
You can either roll your own or include an existing one as a library. Check out asset portal for GUI libraries.
We recommend to go through the source code once again to see how things are implemented in this game.
If you want to continue experimenting with this tutorial project, here are some suggested exercises:
- You may have noticed that the "Level 1" header while playing a level is static. Add functionality so the header text shows the correct level number.
- Implement level unlocking. Start the game with all except the first level locked and unlock them one by one as the game progresses.
- Implement saving of the level unlock progression state. Here, you will need a system for saving and loading persistent data which is called "serialization". You can try and experiment with these ready-to-use libraries:
- Fix the case where the player completes the last level and there is no "next" one.
- Use GUI templates to create the button once and make it reusable in many scenes of the game. You can read more information about GUI templates in our manual.
- Make the buttons response visually (react to press) and with sound.
- Add sound to the game.
- Create a solution to when there are more levels than what fits the screen.
Check out the documentation pages for more examples, tutorials, manuals and API docs.
If you run into trouble, help is available in our forum.
Happy Defolding!


























