Tutorial
...learn to build a GUI for score and lives!
What Will I Learn?
Hi,
I've been distracted recently by writing some Physics based tutorials, please visit the more advanced subjects (as found in the list at the bottom of this post), but I return tonight to the beginners game!
My last post led us to incorporate the colour differentiator into the game, i.e. the Player, Invaders and the bullets which are all now part of the game mechanic.
Before I begin with the Wave patterns in the next article, I felt we should get the boring subject of GUI elements out of the way. After all, I want to be able to see how well I'm doing, therefore, a score is always helpful!
This is what we shall create:
Assumptions
- You have installed Godot Engine v3.0
- You are familiar with GDScipt
You will
- Be given an overview
- Create a Label with a new Font
- Creating a new Scene from Nodes!
- Placement of the labels
- Script the labels
- Connect labels to game
Requirements
You must have installed Godot Engine v3.0.
All the code from this tutorial is provided in a GitHub repository. I'll explain more about this, towards the end of the tutorial. You may want to download this
Overview
I'm no graphics artist, therefore I have to bow down to those that are capable of putting mouse to pixels! My other bad trait is being Colour-blind, so this causes me no end of problems. I, therefore, prefer contrasting and bold colour combinations as I prefer to see shapes and definition rather than beautiful blends.
With this in mind, I'm going to keep the GUI in the play scene simple. I require the three elements:
- Score
- Invasion "Wave" number
- Lives
Godot provides a rich set of GUI Nodes, extended from Control, which as the name suggests, contains the inherited functions to receive user input.
For the GUI of the game, I'm going to use the Label Node, as it provides a simple way to place String text on the screen. Given there are values that will change, we'll have to write some scripts to go with the labels.
To display text on the screen, we'll also need to embed a font! Godot does default to a font of sorts, but it ain't pretty!
I did a search of the internet and stumbled upon this:
It's called Stardate_81314 and is accredited to Emwedo.
As stated in my Invaders Graphics post, it is very IMPORTANT to check the licensing stipulations when reusing media from the internet.
This font has a Public Domain license and is approved for personal and commercial use:
However, I will contact the owner to let him know of its use in this Tutorial and will also ensure the original reference link is placed into a readme file within the Project structure; in that way, I maintain his rights if someone else looks into my project and I also cover my rear if a dispute arises in the future.
Let's get building!
Creation of a GUI layer
I'm going to show you a neat capability of Godot Engine, as part of the GUI build process, therefore it will appear to take a little longer to construct, but you will learn something both interesting and useful for the future.
We need a layer that is going to fit the entire screen. Godot Engine does provide a Node known as CanvasLayer and it is extended by many other classes to draw on. It has the capability to prioritise the order of display, i.e. what is in front and behind it. I really like the Node, but find it has one extremely annoying trait that prevents me from using it (after all, it's purpose is for GUIs)! It lacks a 'visible' property, therefore I can't hide it in the Editor when I'm testing; so annoying!
So, alternatively, I use my favourite, trusted and reliable Node2D! This works because it has a position, visibility and z indexing!
In the Game Scene, add a new Node2D and name it GUI.
Creating a Label with a new Font
As a child, please also add a Label Node and name it Score (I'm going to term this Label as Title from now on):
In the text property, type Score:
.. however, if you look at in the 2D View, nothing appears within the frame!
This is because we need to set the font colour, it defaults to black! Scroll down to Custom Colors > Font Color; tick the box and select a colour of your choice:
If I look at the 2D view again:
The score is shown but is barely visible, I have to zoom right in:
What we need to do is change the font size. Whilst we are doing that, we might as well introduce the font I mentioned above. Make sure you've downloaded it, extracted the "ttf" and placed the file somewhere in the project structure.
Now click on the pull-down option to the right of in the font option and select New DynamicFont:
We then have to edit that DynamicFont by right-clicking on the element and selecting edit:
In the Dynamic Font view, select font and New DynamicFontData:
You'll then need to reopen the new element and select load:
Go find your font "ttf" file, select it and click open (bottom left button):
Nothing shows yet because we have one more thing to do!
In the Settings > Size, enter 72:
If you check your 2D View, all will be well with the world!
To create the Wave and Lives Titles, click duplicate on the Score Label:
Then change the name and the text of the label!
You should have this:
.. but it will look a mess on screen:
.. because we need to position the three labels, I set mine to:
- Score : (20, 16)
- Wave : (960, 16)
- Lives : (1700, 16)
However, these will be tweaked further down, this is just a good gambit opening.
If we now run the game:
... this is not quite as I expected. The aliens should move under, not over the text in my opinion!
This is remedied by changing the GUI Node2D Z-Index (which is a value that represents how close to you that the object is; i.e. as in the 3rd dimension, x, y & Z!). Please set the GUI Z-Index to 1000:
Try rerunning:
Perfect, the aliens are now under our GUI!
Creating a new Scene from Tree Nodes!
At this stage, I'm hoping you might have wondered why I've been creating the GUI within the Game Scene, rather than in its own Scene and then instancing in; if not, you should have!
Grouping elements into Scenes the BEST way to keep the game code simple, reusable and tidy!
Fortunately, there is a nice feature in Godot which make your life easy. We can easily change what we now have by using it. The product developers clearly faced this same problem many times and created a Menu option to do it.
Right-click on the GUI Node and select Save Branch as Scene:
Next, you'll need to provide a location, I created a Folder named GUI and then saved the Scene as GUI.tscn:
When saved, you'll find Godot has moved the Nodes, saved it to the file you entered and instanced it into the Game Scene for you!
When prototyping, I find I create Node hierarchies in the Game Scene and then save them just like I've demonstrated. Spotting reusable Nodes for Scenes is rewarding and it negates the need for me to figure out the 'save place' and structure beforehand!
I can build my scene and when it works, I then save the specific elements into individual Scenes, ready for instancing!
Out of habit and a little OCD, I moved the GUI up in the hierarchy, above Stars and Player:
There is ABSOLUTELY no real benefit here, barring aesthetics, because the GUI has a higher Z-Index to the other Nodes. If all three had the same Z-Index, then the priority is taken from the order found in the tree!
Placement of the Labels
The next task is to add the values to the Titles and script them, so that they will reflect the state of the game, i.e. the players' score, etc!
Open the new GUI Scene:
The Values need to be added as children Labels to the Titles:
I did this by duplicating the Score Title label, renamed it to Value and set the text to 0000. I then duplicated it twice more and moved them as appropriate children.
Check to make sure their positions are set to (0,0), because Godot has a habit of remembering the original position of the Node duplicated, which in the scenario will be the Score!
You should end up with some overlapping corruption:
There is a solution to this, we need to make the Title to be justified from the right:
The Value should be justified to the Left:
The Value's position should then be set to the (width of the Label + a Positional offset):
i.e. the score has a 123 x 72 size and is at 20 x 16, therefore its value should be placed at least at 143 x 16. This looks better:
Positioning is now based on the Title label! i.e. if you drag the Score Title around, you'll notice the Value will move and stay relative to it!
The Title label will never change size because it is a fixed string, only the Value will; therefore padding the score with leading zero's ensures the maximum length of the display is known.
This is important when designing layouts; i.e. to ensure a score doesn't fall off the screen, for example!
Shift things around until you get it how you like it; for example, at the end of each Title, I've added a cosmetic colon.
This is all starting to look very neat and tidy, but next is up are some scripts to add dynamic capabilities.
Adding scripts to the Title and Values
The next step is to include functionality into our Score and Value Node. Attach a script to the Score Title:
Add the following script:
Let's walk through the code:
extends Label
export (int) var length = 8
var value = 0
Extend the Label class, expose a padding length that can be set in the tool (defaulted to 8 digits) and declare a value variable to hold the integer score
Note: Given the Value Label will display the score value, it is better (in my opinion) to keep an absolute copy, which will be used to calculate, isolating the Value Label to be used in the presentation of it
func _ready():
update()
When the Node is ready call the update function, which will update the Label text (see below)
func reset():
value = 0
update()
A function to reset the value to zero and update the Value Label text
func adjust(adjustment):
value += adjustment
update()
A function to adjust the value and then update the Value Label text. The adjustment may be positive or negative, hence why this function is added in this way. We could add a 'setget' function to value instead, but I personally prefer this implementation style
func update():
$Value.text = ("%0*d" % [paddingLength, value])
This is the last function, which updates the Label Value text. It does so by using what is known as Format Strings. These are worth understanding because it will be usable in MOST games!
- The % at the start of the string states this is a Format String
- The 0 following, states to pad with zero
- The * states pick up the padding length
- The d states a decimal
- The value is then placed inside the *d, i.e. with a default padding of 8, "%08d"
If we run the game as-is:
The score is shown as 8 zeros, but if I change the Tool value to 4:
We end up with the 4 digits instead:
Hopefully, a red-flag is waving in your head, as you look at the following:
...No? ....Yes?
What I've created for Score is obviously applicable to the Wave and Lives too! So we should remove those, save the Score as a generic TitleValue Class and instance it three times!
I've shown you how to do this, so make it so! .... what are you waiting for? :-)
I've changed mine, having set Score to 8 digits, Wave to 4 and Lives to 1; however, it doesn't reflect in the Tool Editor:
Godot DOES provide this capability, IF and ONLY IF the instance is self-contained, i.e. it does not use any other Nodes.
This solution doesn't, therefore, we can implement the 'Tool' option in our script!
Open it up and add Tool in front of the Extends Label clause:
Once you save the script, you will have to force the editor to refresh. Simply close the GUI Scene and reopen it, or switch to the Game and return. It is a little temperamental, but will work:
Perfect! We now have a usable Tool Node as part of our toolset!
Connecting the TitleValues to the Game
Our final task in this Tutorial is to link the TitleValue's to our game, but how?
There are many many different ways to solve this, but I like to look at what I have and then 'think' carefully about it.
This is our Game Scene:
Let's consider the Node responsibilities:
- The Game Node creates the alien and processes the players' input
- The GUI is a child of the Game Node
My solution is to delegate the task of updating the GUI to the Game Node (an obvious decision).
How and when will the Score change? ...when an invader is killed, therefore we need the Invader to report to the Game when it dies. In turn, the game will send an update to the Score in the GUI Node.
There are many ways to achieve this, i.e. we could set-up a group message, a signal or a direct reference, however, I decided to utilise a look-up of the Game Node in the Tree from the Invader!
This is perfectly acceptable for our needs (for now).
Open the Game Script and add the following function:
func invaderDead():
$GUI/Score.adjust(1)
This function locates the Score Node as a Child in the GUI and calls the adjust function to increase it by 1.
For now, I'm ignoring the Reset function, but we'll use it in a future tutorial
Open the Invader Script and amend the hit function:
func hit(object):
if object.name == 'LaserArea':
if object.get_parent().colourType == colourType:
__get_node("/root/Game").invaderDead()__
queue_free()
else:
$Shield.modulate.a = 0.9
The get_node function is used to locate the root of the tree and then the Game under it. The new Game function to inform it that the invader is dead is then called.
Let's run the game and see our beautiful counter working!
..yes, it worked!! That completes this article!
Finally
I hope this has been useful to you! I've enjoyed putting it together and look forward to working on the Waves of Invaders next!
Please do comment and ask questions! I'm more than happy to interact with you.
Sample Project
I hope you've read through this Tutorial, as it will provide you with the hands-on skills that you simply can not learn from downloading the sample set of code.
However, for those wanting the code, please download from GitHub.
You should then Import the "Space Invaders (part 9)" folder into Godot Engine.
Other Tutorials
Beginners
Competent
Posted on Utopian.io - Rewarding Open Source Contributors
May have been worth mentioning that it is good practice to child HUD type canvas items to a canvas layer. I know its not necessary for this type of game, but still worth letting new people know.
At least I had no clue when I first started :P
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Hi wolf. As I mentioned in the article, I dislike the Canvas Layer right now, as it can't be hidden from the editor. OR is there a way that I'm not aware of? Hence why I resort to Node2D, which is just an extension of Canvas Layer anyway.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
I guess Im just confused why you would use node2d over control.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Ah I see, there isn't much in it. I personally assumed, but need to check this out now, that the control Node, which inherits from Canvas, is going to be heavier than Node2D (which also inherits direct from it), because of all the extra input handling. I ideally want to use the Canvas, but as stated, that doesn't have the visibility option.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Thank you for the contribution. It has been approved.
Suggestions:
You can contact us on Discord.
[utopian-moderator]
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Hey @sp33dy I am @utopian-io. I have just upvoted you!
Achievements
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
rj1, you are always there first! Respect dude... Thanks for supporting me.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit