Transcripts
1. Promo: Are you ready to start building your very first game in
the Gudo game engine? Or do you want to learn
more about how to use Gdo'sGraphical user
interface control nodes? Or do you want to challenge
yourself to build an entire game in a single scene using only control nodes? In this course, we will
explore everything that Godo Gui Control nodes have to offer by building a
complete game from scratch. It is recommended that you design your own game
for this project. Your game must have
only one scene and only use Gooey
control notes. We'll start with
building a title screen, containing a title,
a background image, a start button, and
social media links, exploring the wide variety of options that are available
for each node type. Then transitioning
to the main game, adding a menu bar with plenty of different styles of menu
options, including icons, shortcut keys, and sub menus, writing scripts that can
create and manage player data and settings in Godo's native scripting language, GD Script. Moving on with developing
game mechanics, specific to my design, we will explore the remainder of Gudo'Guy control nodes in detail as we go so you can figure out how to apply
them to your project. If you get stuck,
our discord server is full of other students, hobbyists, and professional game developers who can help you out. Click on the website link in
my teacher profile to join. Let's get started
building our game.
2. Hello World: Hello friends. If you
haven't done so already, you'll need to download
the GdoGameEngine from gdongine.org. The engine is free, but you can donate to support the
project if you want to. There is no
installation process. You can just extract the
zipped file and open it. Be sure to relocate the
executable to a better location. The initial launch window is a list of your current projects. But if you don't have
any projects yet, you'll be prompted to
explore demo projects. We can ignore this
and just create our own new project
either by clicking on this button in the project list or the Create button at
the top of the window. Here we can give
our project a name, which would typically be
the name of your game. I'll just put intro
to programming. The project needs to be stored on our computer's hard drive, and we'll create a new folder with the same name
of the project. By default, it wants to store everything in
the documents folder, but it would be better to create a new subfolder for
Godot projects. The renderer changes
how certain two D and three D objects
are rendered, but we won't be
drawing anything, so we can ignore these options. The renderer can also
be changed later. And version control
compatibility with Github is included by default if you want to use it to backup your project or
share it with others. When you're ready to start,
click Create and Edit. The GDOEditor window will open our newly created
empty project. The editor is divided
into five main docks. A preview of our game
is in the center. The lower left dock
contains a file browser showing the contents of the project folder we just created. It currently only
contains the Gado icon. The upper left dock
contains the scene tree, a list of everything that
is in our current scene, which is currently empty, so it wants us to create a
root for the scene tree. We won't be drawing anything, so we don't need to
specify if this scene is two D or three D or
a user interface. So we can just
select other node. There are a lot of different
nodes we can create, but we are only interested in learning how
to write scripts, so we will just use
a default node. We can rename our new node by clicking on it
after it has been selected or right
clicking to open its context menu and
selecting rename. Let's name this node Lesson one. Selecting any node
in the scene tree will reveal its properties
in the inspector, which is docked to the
right side of the window. We can see the name of the
node that is selected and its properties organized
into expandable categories, but none of these properties are applicable to
what we're doing. Before we do anything else, we should save our new scene, either by selecting
save scene from the scene menu or using the
shortcut Control S or Command S. This opens a
dialogue box where we can specify a name for our
scene with the TSCNEtension, and a destination folder. Let's create a new
folder for Lesson one, then save the scene
in this folder. We can now see in the
file system tab that our new folder has been
created for Lesson one, and it contains the
Lesson one scene, which is marked with
the Clapperboard icon. Next, we'll attach a script to the root node of our scene, either by selecting the
root node and clicking the attached script button or by right clicking on it and
selecting attached script. The language is GD Script, which is the
programming language we are going to be learning. Every script will inherit from the node type it is
attached to by default. Since the root node is
a default node type, our script inherits from node. We are not using a template
nor a built in script, and we can specify where this script will be stored
within the project. Let's put this in
the same folder. Lesson one. When naming scripts, it is important to
not be arbitrary, but instead describe the type of object or behavior we are
creating through the script. So let's name this hello. Scripts have the file
extension dotGD. Then click on Create
to create the script. This will switch our
preview to script view, displaying our newly
created hello script. We can also see our script in the file system tab
indicated with a cog icon, and the lesson one root node has the script icon to show us that it has a
script attached to it, which when we hover over, will tell us the name of the script. Clicking on this icon also opens the script if it
isn't open already. Our script only has one line, which describes its
inheritance from the basic node type using
the keyword extends. This just means that our
script can do everything a node can do plus
anything we write here. It also allows us to
override the behaviors of the node that already exist with new behaviors of our own. For example, nodes have
a function named ready, but it doesn't do anything. So if we want our script to override this behavior
of doing nothing, we can declare our
own function of the same name using
the keyword funk. Funk is then followed by
the name of the function, typically written in
lower snake case, which means all
lowercase letters separating words
with underscores. Many of the functions defined by nodes are preceded
with an underscore, and the name must
be an exact match to override the behavior. The name of the function must be followed by parentheses
and a colon. We'll go over why later. Pressing Enter after this line, the next line is automatically
indented by one level. This is how GD Script
knows which lines are contained within the
function and where it ends. The line is highlighted in red because functions are
not allowed to be empty. So it is a good idea
to write the word pass to give new functions a
body that does nothing. Now that the error is gone, we can see this blue arrow icon appear in front of the
function declaration, which is the override symbol, indicating that
this script is now overriding the ready
behavior of our node. Instead of doing nothing, we want our node to say hello, which we can do with
a print statement, replacing the line that
previously said pass. The print statement looks similar to a function
declaration with a name followed by parentheses because it is calling
a built in function. This function
requires an argument, which is what goes
inside the parentheses. And this argument is
what will be printed. Using quotation marks, we
can write anything we want, and it will be printed out by our script when
the node is ready. Since the script is named hello, the behavior we want to create is for the node to say hello. With our script ready, we can run our game using the run current scene button in the upper right or using
the shortcut F six. This will display
an empty window, since our game doesn't
draw anything, but it also opens
up the fifth dock, displaying the output panel. And in our output, we
can see our node has printed the words hello
world when it was ready. We can end this simulation
by closing the window, clicking on the stop button, or using the shortcut F eight. We can change the text that is printed to say
anything we want and run it again to see that our node can print out
anything we tell it to. We now know how to attach
scripts to nodes and run them. In the next lesson, we'll learn about constants and variables. I'll see you in the next lesson.
3. Constants & Variables: Hello, friends. We will be starting each lesson by first creating a new folder
for the lesson. Then creating a new scene in that folder of the same name. Double click on a scene in the file system tab to open it. With multiple scenes open, we can switch between them by clicking their tabs
in the preview doc. Today, we'll discuss
the differences between constants and variables using days as our frame of reference. So let's attach a new script to the root node of the
scene and name it days. Let's start with
constants and declare one at the top of the script
using the keyword const, followed by the name
of our new constant, which is conventionally
written in upper snake case. A constant is like a container where we can store information, but that information is
never allowed to change. Something that we
would consider to be a constant might be the
number of days in the week. Since it can't be changed, it must be given a
value immediately, which is why we have an error. We must follow the name
with an equal sign, the assignment operator to
give the constant a value. The number of days in
the week is seven. It is reasonable to assume
that it will always be seven and it is the
same for everyone. Therefore, it should never
need to change its value. Conversely, we have variables, which are also containers
for storing information, but ones that are
designed to be changed. We can declare a variable in the same way using
the keyword VR, then giving it a name conventionally written
in lower snake case. This makes it easy to
see the difference between constants and
variables in our scripts. Something that
changes frequently might be the value of today. Unlike constants, variables
are allowed to be empty. Let's override the
ready function of our base node and print out the values of both
the number of days in the week and today. We can see that the number of
days in the week is seven, but the value of today
is printed as null, which means empty or no value. Let's fix that by initializing
today to be Thursday. And we can make our output more readable by adding some context, starting with something
like today is then leaving a space after is a plus
sign, then our variable. In this case, the
plus sign is being used to append two pieces
of sentence together. If we try to do the same thing with our constant, however, it will cause an error because our constant is a
number, not a word. When we declared our constant, we assigned it a value of seven, which is a number without
any decimal points, which in math is
called an integer. So our constant has
a type of integer. Today, on the other hand, was assigned a value of some characters wrapped
in quotation marks. In programming, we
call this a string. So our variable has
a type of string. The plus operator
can only be used when the types of both
operands are compatible. We can get around this by
using a built in function to change the type as it is
being used here to a string, passing the constant as an
argument to the function. This will not change the type
of the original constant, only how it is being used here within the context of
the append operation. And we can continue adding more context after with another
appended string as well, remembering to add an
extra space at the front. So now we can print
out our constant and variable using them in easy
to understand sentences. After these lines, let's
pretend a day passes. We can add extra context to our scripts that will
never be seen by players but only exists to
help us as developers understand our own code or
code written by others. These are called
comments and are created using the OctathorpPound
sign or hashtag. Any text written after this
symbol will be colored in gray and won't affect how
the script runs in any way. They can be on their own lines or at the end of a line of code. So let's use a comment to explain that we are
pretending that a day has passed between the code above this line
and the code below. We'll change the
value of today to be Friday and print out
another sentence. Today is plus Today, which now holds a
different value. We can see that the value of
today was first printed as Thursday and then changed and is printed as Friday
on the next line. The comment had no effect
on the script being run. If we try to change the value
of our constant, obviously, this will cause an error because constants are not
allowed to be changed. If we no longer want to use some lines of
code in our script, we can easily change
them to comments by highlighting them and pressing
Control K or Command K, adding the comment symbol at the front of every
highlighted line. Pressing it again will
remove the comments. We can specify the
type of variable or constant by adding a
colon after the name. Followed by the type name, we want to restrict this
constant or variable too. Let's restrict our
constant to be of type int and today
of type string. Other than strings and integers, there are two other
primitive types we can use to store
basic information. If a number has decimal points, it is no longer an integer and instead a different type called
a floating point number, which uses the type name float. Let's add another variable
for today's date and give it a type of floating point
number with a value of 12.05, which is today's
date December 5. And the last type is called Boolean using the
shortened name Boole, which can either
be true or false. So we'll add another
variable for whether or not
today is a holiday. I'll restrict this
to be a boolean, but I won't give it
an initial value. Let's add two more print
statements to print out the values of these
variables with context. Today's date is plus date, and today is a holiday. Colon plus is a holiday. When we run this now, we can
see our date printed out, but is a holiday says false, despite not giving it a value. This is because all primitive
types have a default value, but the default
value can only be assigned if we specify
the variables type. Since we specified that is
a holiday is a boolean, it was assigned to
the default value for a boolean, which is false. The default value
for both an integer and a floating point
number are both zero, and the default value for a string is just
an empty string, which is not the same as null, but is a string with
no characters in it. If we don't specify the
type of a variable, its default value will be null, but it can be assigned
a value of any type, and its type can
change at any time. Let's remove the type from date. Then give it a new value of December 13 and print
it out a second time. The variable starts out being assigned a value of a
floating point number, gets used by the
print statement, then assigned a string value before being printed out again. This is allowed
because GD Script is a loosely typed language. This offers greater flexibility
when writing our scripts, but also greater
responsibility to avoid type compatibility errors like we saw with the plus operator. It is recommended that you always give your
variables a type, not only to avoid errors, but also to improve the efficiency of running
your scripts. If the engine knows the
type of your variables, then it also knows exactly how much memory it
needs to occupy. This also applies to using constants in place of variables. If the engine knows the
value can't change, then it can be used
more efficiently. In the next lesson,
we'll learn about how to control the
flow of our scripts. I'll see you in the next lesson.
4. If Statements & Conditions: Hello, friends. I've
already gone ahead and created a new scene in a
new folder for this lesson. Today we'll learn how
to control the flow of our scripts to run
different lines of code, making choices about
whether or not we can see. So let's attach a new script to the root node of our
scene and name it vision. We will start our
script by overriding the base nodes definition
of the ready function. The most basic method
of controlling the flow of our scripts
is the I statement, which starts with
the keyword I. I is followed by a
conditional statement which must either
be true or false. For now, let's put true, which is then
followed by a colon. Similar to the colon at the end of our
function declaration, this marks the end
of the statement, and continuing to the next line, it is automatically
indented another level. Any lines of code following
this if statement, which are indented
will only be run if the condition of the
if statement is true. So let's print out something
like the condition was true and then run the current scene to see our
print statement working. If we change true to false, we can see that the print statement is
skipped over this time. We can add another
line after the if statements indented body on the same indent level as the I statement simply with the keyword s
followed by a colon. This creates another conditional
body that will be run only if the condition of the original I
statement was not met. Let's duplicate the
print statement and change it to say
something different. Since the condition was false, the se block was executed
instead of the I block. And if we change the condition to true, the reverse
will happen. This isn't very useful
if we always know the outcome of the conditional statement to be true or false, but we can replace
it with a variable. Let's declare a
Boolean variable, which is whether or not the
light is on in the room. Booleans are
conventionally named in such a way that they imply
their use as a condition. In this case, light is on is implied to be true or false
based on how it is named. Now our if statement can set
its condition to be whether or not the value of our
variable is equal to true. When comparing two values
to see if they are equal, we use a double equal sign, known as the equal operator, which is different from
a single equal sign like we used before as the
assignment operator. We will then print out
either I can see or I can't see as a result of whether or not the light is on
being equal to true. Since the default value
of a boolean is false, we can see the output
as I can't see. But if we set the
variable to true, it changes to I can see. Since the variable itself is a boolean, either true or false, we can remove the comparison
operation altogether and just use the boolean as the condition and get
the exact same result. We can reverse the
value of any boolean, meaning if it is true, it becomes false
or if it is false, it becomes true by
using the keyword not. Like the STR function we
used in the previous lesson, this only changes how
it is being used in the local context and does not actually change the value
stored in the variable. So if naught, light is on, then we can't see
and else we can see. Naught can also be represented
by an exclamation point. Let's change the value of
lights on and run it again. I'll then revert these changes back to how they were before. We can make our conditions
much more complex by combining multiple conditions together using logic operators. Let's create another variable
named Has dark vision. This variable implies
that we should be able to see even if the
light is not on. So we can add to our if
statements condition to be either that the light is on
or that we have dark vision. And if either of
these conditions are true, then we can see. Only if both
conditions are false, will it result in us
not being able to see? What about if, instead
of having dark vision, we are wearing night
vision goggles, since they only allow us to see in the dark but
not in the light. Changing the variable name, the condition of our I
statement needs to change. Since if both
conditions are true, then we shouldn't
be able to see. Only if the values of these
two conditions are different, should we consider the
condition to see to be met? We can use the not
equals operator to check if the two values
are not equal to each other. If you are familiar
with logic operations, this is known in other
languages as an exclusive or. Now, only if either the light
is on or we are wearing night vision goggles and not both, will we
be able to see? Next, let's consider that our hypothetical
person might not be human and add a variable for the number of
Is that they have, which should be
represented as an integer. For now, we'll give it
a value of two eyes. Ignoring the other variables, what would be the
condition that we set to be able to see
based on this variable? If the number of eyes we have is anything
greater than zero, then we should be able to see. We can also express this
using another operator, greater than or equal to and change the right
hand side to one. So they must have eyes
greater than or equal to one. These are comparison
operators that will return a Boolean result by comparing the value of numbers on
its left and right sides. We can logically conclude that it should be impossible for any creature human or otherwise to have a
negative number of eyes. The value of our
variable should only be zero or something
larger than zero. And the condition
to be able to see really boils down to
these two options. Do we have zero eyes
or non zero eyes? Just like the boolean variable can be used implicitly
as a condition, an integer can also be used
in this way with zero being considered a false condition and anything other than zero
being considered true. The same logic is also applied
to floating point numbers. We can declare a float variable, let's name it range
of vision and give it an arbitrary
value of 7.8 meters. We can use our comparison
operators to determine whether this number is greater than less than or equal
to a certain value, or we can use it implicitly
in its own condition with zero being considered false and anything else
being considered true. What about strings? Let's change things up slightly and declare a string variable for what we heard and give it a value
of bumps in the night. Then we might logically use
this as an if statement as if what I heard was
not an empty string, then we can say, I
heard something. Otherwise, I heard nothing. And just like the
other variable types, we can drop the comparison and use the variable itself
as the condition. With all variables, their default values are
considered to be false, and anything else is always considered to be true
when used as a condition. If we declare a variable
without a type, let's call it what
I am looking at, but don't give it a value, then we already know its
default value will be null. Even without a type or a value, any variable can be used
as a bullying condition. Any variable that is empty, meaning its value is null
is considered to be false. If we were to populate this
variable with an object, then its value as a condition
would be considered true. We don't currently have any
other objects to play with, but we can always use ourselves. So let's set the
value of what I'm looking at to be
the keyword self. And this results in
being able to see. In the next lesson, we'll use loops to repeat lines
of code multiple times. I'll see you in the next lesson.
5. While Loops & Operators: Hello, friends. Today we'll learn how to repeat
lines of code multiple times with loops by
imagining a lady who has fallen in love for the first time holding a flower. So let's write a new
script and name it flower. Then override the base nodes definition of
the ready function. Our imagined lady is holding a flower that has a
number of petals, which we will store
in a variable and restrict its
type to an integer. Then give it a value of some arbitrary number,
let's say five. We'll also declare another
variable named He Loves Me, as a boolean, which we believe with its
default value of false. We can start our
script by printing out a story about the lovestruck
lady holding her flower. She removes each petal from
the flower one by one, stating either he loves
me or he loves me not. First, we need to reduce the number of petals
on the flower by one. So we will start with the
number of petals variable, then use the assignment operator to assign the
variable new value. The value we want
to assign it is the number it already
has minus one. We then want to change the
value of the boolean variable. He loves me to be its opposite. So he loves me, is assigned
to not He loves me, reversing the value of
false true or vice versa. Next, we can print out
the results. He loves me. We may want to encapsulate
this part in quotation marks, but if we try to put a quotation
mark inside our string, it will instead mark
the end of the string. Some special characters
like quotation marks can be created inside a
string using a backslash. If we write backslash
quotation mark, this now represents a
quotation mark character, not the end of the string. And we can include another one after the Ladies declaration. Let's duplicate this
line and change the declaration to
say, He loves me not. Another example of a
special character we can write using the backslash
is the backslash itself. We can also combine
the two lines above and separate
them with backslash N, which represents a new line. So they are printed out
on two output lines from a single print statement. We now have two possibilities
that we want to print out based on the value
of our Boolean variable. So it would seem that we should wrap these in an if statement. If he loves me, then print this. If he loves me not,
then print that. But in cases where if
statements are contingent on a single boolean variable
to make a minor change, there's a more efficient method. So let's return to
the original format. Our declaration will
be he loves me, if he loves me, else, he loves me not. This is not the same as an if statement despite
using the same keywords, but it is called a
ternary operation. Since it does not create a new block of code
with an indentation, it is actually executed
more efficiently and is recommended to be used in simple cases
like this one. We can actually
reduce the size of the variation even more
by only changing it to either be an
exclamation point or a space followed
by the word not, then the exclamation point. This works for the
case if he loves me, but if we change the
value to true by default, then we only see not
in the other case. Just like mathematics, there is a specific order of operations in which our scripts
will execute multiple operations
on the same line. Since both the string append and the ternary I are
both operators, one must be executed
before the other, and in this case, it will
be the string append. And just like in mathematics, brackets are always the
first to be executed. So we can wrap any
operation in brackets to ensure it will be executed first in the order
of operations. Wrapping our ternary
statement in brackets, it will now run before
the string append and give us our expected result. So now we have our block
of code which needs to repeat for as long as the
flower still has petals. Using the keyword WOW, much like an if statement, we can repeat an
indented block of code as long as the
condition remains true. The condition in
this case being that the number of petals on
the flower is not zero. Whatever the number of
petals on the flower is, it will be reduced
by one each time. Then after the print statement, the logical flow of this
script will return to the beginning of the WoW loop to check the condition again. When the number of
petals reaches zero, the condition is no longer
met and the loop breaks, proceeding with the remainder of the script, if there is any. So let's write a
short conclusion to our story based
on the results. She stands up with
tears in her eyes. But based on the final
value of He Loves Me, she's either happy or sad, and we will print out the
different final phrase. Running this scene, we can see our story printed out
in the output panel, and with five petals,
the lady is happy. If we change the number
of petals to six, then we get a
different result since the loop repeated six
times instead of five. But what happens if we change the number of petals
on the flower to zero? The loop doesn't run at all, and the love was doomed
from the beginning. I don't recommend
following this example, but just watch what
happens if we give the flower a negative
number of petals. While this is
realistically impossible, in our code, it is
very problematic. Since the condition of our
wall loop will never be met, it will never break, and the script is trapped
inside it, never finishing. This is an infinite loop and should be avoided at all costs. In this case, we can prevent the infinite loop by changing our condition to be if the number of petals
is greater than zero, since a negative number
will still break the loop. We can use another operator when reducing the number of
petals as a shortcut. Removing the need to write the name of our variable twice, we can perform simple
mathematical operations on our number of variables using a special
assignment operator. In this case, the minus
equals operator can change this line of code to number of petals
minus equals one. These shortcuts exist for all basic arithmetic
operations plus equals multiply equals
or divide equals, and can be used with any number or variable as the
other operand. There is one more operator that exists for basic
arithmetic as well, the modulus operator, represented
by the percentage sign. Modulus performs a
division operation, but doesn't return
the quotient as the result like the
division operator does. Instead, it returns the
remainder of the division. This entire script, other
than the story being told, is really just determining
if the number of petals on the flower is
an even or odd number, but is doing so in the most
inefficient way possible, counting down each
individual petal. So let's comment out
the entire wall loop and instead use modulus
to speed up the process. Knowing that all even numbers
are divisible by two, we can use number of
petals modulus two. The only possible result as a remainder of this
division will either be zero if the number of petals is even or one if the
number of petals is odd. So our script could be
simplified to just if the number of petals modulus
two is equal to one, then the lady is happy. Otherwise, she's sad. And since we understand how integers are interpreted
as conditions, we don't even need to perform
the comparison operation here and can just shorten it to I number of
petals modulus two. The result of the
modulus operation could be assigned directly to the Boolean variable to get the same result without the
need for an if statement, and the zero or one will automatically be converted to
false or true accordingly. And we could go further by removing the variable
and just using the modulus operation directly inside the turny statement. In the next lesson,
we'll learn about collections of
related variables. I'll see you in the next lesson.
6. Arrays & For Loops: Hello, friends. Today we'll consider how we can organize
large amounts of variables, especially when they hold
related information. I've already gone ahead
and created a script named census and overridden the base nodes definition
of the ready function. For our hypothetical
scenario in this lesson, we are imagining a census
worker who needs to record how many people live in each house on a single
road in their town. We can declare a single variable to hold all this information, Let's name it Main Street. And the type of this
variable will be an array. An array can hold a list of variables of any of
the other types. In this case, the type of the variables we are
looking for are integers, representing the number of
people who live in each house. We can specify the
type of contents of an array using square
brackets after the type name array and putting the type of the contents
inside the square brackets. Now this array can only hold
integers as its contents. Before we can access the
contents of an array, it needs to have a size, the number of variables that
are contained inside it. Unlike other
variables, arrays are more complex and have
their own functions. We can access these functions by following the name of the
variable with a period, then the name of the function. In this case, the
resize function, which sets the number of
addresses inside this array, representing the number
of houses on Main Street, which we will say is six. Our imagined town is
a little unusual in that the house numbers on
Main Street start at zero. So our census worker
starts at the house at zero Main Street and
knocks on the door to ask the resident of the home how many people
live in the house. We can store a value in an
array starting with its name, then using square
brackets to mark the address of the
specific integer, in this case, address zero, then using the
assignment operator. We can assign an integer
to that address. So let's say that the
house number zero on Main Street has five
people living in it. Moving on to the next house on Main Street address number one, someone answers the knock
at the door and tells the census worker that there are two people living
in that house. At the next house,
address number two, the house is under construction
and has a for sale sign, which means this house
is clearly unoccupied, and our census worker records the number of
residents as zero. At number three Main Street, nobody answers the door, despite the fact that
there is a car in the driveway and
signs of occupation. What might we consider the census workers should record as the number
of residents? We might think that
it could be zero. But how would we
differentiate between an unknown number of occupants
and an unoccupied house? If we consider that the
number of occupants of a house should never be
considered a negative number, we could use the number
negative one to mean that the number of occupants of the
house is an unknown value. And at number four Main Street, there is, again, no
answer at the door. But the house also
appears to be neglected, and likely nobody has lived
there for quite some time. We could assume that the
number of occupants is zero maybe used the same negative one value for an unknown number of occupants or create more
negative value codes for different situations
like using negative two to mean probably
unoccupied but not confirmed. Address number five on Main Street doesn't
even have a house, but it's just a vacant lot. So again, this could be
represented with a zero or another negative value to
encode some sort of meeting. Let's print out the results of the census and see
what they look like. We can see our array
represented as a comma separated list of numbers encapsulated
in square brackets. This does a decent
job of representing the number of people living
in each house on Main Street. But what if we wanted to use this data to get
a total number of occupants of all the houses
on Main Street or average? If we calculated the sum
of all of these numbers, the unknown numbers being represented as negatives
would skew the results. One of the best benefits
of using arrays to store related information in this way is how easy it is
to loop through it. Using the keyword four, we next write the name
of a new variable, one which will represent each individual element
inside the array, followed by the keyword in, then the name of the array. Like functions, if
statements are wa loops, this is followed by a colon and an indented block of code
which will be repeated. These lines of code
will be repeated once for each of our six
houses on Main Street, and we can name the
variable house, as it will be used to access each individual house
inside the loop. Let's say we first want to find the average number of occupants who live in an occupied house. That would mean adding up
all the positive numbers then dividing by the
number of occupied houses. So we'll need three
new variables, one for the total
number of occupants, another for the number
of occupied houses, both as integers, and a floating point number
for the average. For each house in Main Street, we can check if the
house is occupied, if the number stored
in the array at that address now stored in the house variable is
anything larger than zero. If this is true, then
the total number of occupants in occupied houses
can be increased by house, and the number of occupied
houses incremented by one. After all six houses on Main
Street have been checked, the loop breaks, and we have
our two integer numbers. So we can calculate the
average by dividing the total number of occupants by the number of
occupied houses. Then print out the results. There are plus occupied houses, plus occupied houses
on Main Street, with an average of plus average
occupants plus occupants. But if we run this code, the average is represented
as an integer, three, which we know is not correct, since there are a total of seven occupants in
two occupied houses, the average should be 3.5. This happened
because we performed a division operation
with two integers, and the result of the operation
will also be an integer, even though we are assigning it to a floating point
number variable. Godot also provides a warning against using integers
in division operations. To resolve this, we can change our integer variables to floating point numbers
in their declarations, or we can change their type locally before
performing the division. Much the same way as we changed integers into strings
before appending them, we can change an integer into a floating point number
before performing a division. Changing the result
of the division to a floating point
number as well. Changing the type of a variable in this way is called casting. We are casting an
integer to a float. Now we can see the results we were expecting with an average of 3.5 occupants
per occupied house. A benefit of GD Script, being a loosely typed
language is that we do not need to specify the
types of our variables, including the types
contained within an array. If we allow our array to
contain any type of variable, we can change how our census
worker records information. Let's assume address zero remains the same
with five occupants. But address one also provided the names of the two
residents who live there. Instead of recording the
occupants as a number two, we could record their
names in a string. So Main Street
address number one is occupied by Jordan
and Ashley Smith. House number two was unoccupied, which we could
represent as a zero. But we might also
consider recording it as a boolean instead and give it a value of false to
mean that the house is under construction and can't
possibly have occupants. At number three,
nobody was home, but there are definitely
people living there. We just don't know how many. We could also store
this value as a boolean and give
it a value of true. At number four, it really
seems like nobody lives there. We may want to market
as zero occupants. At address number five, since there isn't
even a house there, we might actually consider
using null as the value, since null represents the
absence of any value. We can use the keyword null to assign this to the
array at address five. Now our printed array
looks quite different, containing variables
of all different types instead of just integers, and we can discern more
information from it. Arrays can even contain other
arrays as their contents. So let's change our
list of names from a single string to
an array of strings. We can create this
array at the same time it is being assigned
using square brackets, then populating it with a comma separated list of
strings as its contents. So if we wanted to know how many people are living at
Address one on Main Street, we could print that out as
Mainstreet one dot SIE. In the next lesson,
we'll learn more uses of arrays to create more
dynamic data collections. I'll see you in the next lesson.
7. Stacks & Functions: Hello, friends. Today we'll
continue working with arrays in different ways by setting up a
game of Old made. I've already gone ahead
and created a script named Old Made and overridden the base nodes definition
of the ready function. In order to play old made, we need a deck of cards, which is a collection of variables which we can
hold inside an array. Since the game of
Old Made doesn't really care about the
suits of the cards, we can ignore that information, and each element in the
array only needs to contain the cards rank from ace
to king as an integer. To generate our deck,
we can use a four loop. But instead of
using the four loop to iterate through an array, it can just be used to count
through our card ranks. Starting with four,
we can declare a variable to represent
a card's rank, followed by the keyword in, then specify a number. If we were to consider
King to be a number, it would be the 13th rank. For now let's just print out rank so we can
see what happens. The four loop starts with a rank value at zero
and counts up to 12, repeating this loop and
print statement 13 times. For the sake of sanity, it might be a good
idea to consider card number two to be
represented by rank two, and thus Ace would be one, and king would be 13. While starting a
four loop at zero is really helpful for
iterating through arrays, since their first
index number is zero, it isn't that helpful
in this case. We can change the way
a four loop counts by replacing the 13 with a
function call, range. Function calls are followed by parentheses
containing arguments. The arguments for
this function are the lower and upper
limits of the range. The lower limit will be
included in the range, but the upper limit
will not be included. So range 114 will contain
all numbers 1-13, and we can see that is
printed out in the output. Another way of
adding information to an array instead of using the square brackets and index number is to use a
function named append. This append function accepts an argument of the value
being added to the array. This will position the
value being added to the end of the array,
wherever that may be. And since there are four of
each rank in a deck of cards, one for each suit, we should repeat this line of code
four times for every rank. We can do this with
another four loop, this time starting at
zero and counting up to, but not including four. Printing out the deck, we can
see our array has four of each number 1-13 for
a total of 52 cards. But to play old made, we want to remove
three of the queens, or in this case, 12. So in our four loop that is iterating through
the card suits, let's check if the rank is 12. And in this specific case, we only want to allow
112 to be added, using the keyword break to instantly break
out of this loop. Break will only end the most indented loop
it is currently in. So we will end the suit for loop but not the rank for loop. This will then increase
the rank to 13 and proceed with adding
four thirteens to the deck. And testing it out, we can
see how there is only 112. The first thing that happens in our game is shuffling
the deck of cards, which in GD script is a simple built in
function of rayse. We only need to
call this function, and we can see that the cards
get shuffled in our deck. We will also need
another variable, an array of hands of cards
to represent our players. Each hand inside the hand array needs to hold multiple cards, so they are also arrays, which we will need to initialize
before we can use them. So counting from zero up
to but not including four, we can append a new
hand to the array. Our hand starts off as an empty array represented
by square brackets. If we imagine our deck of cards, the front of the cards being the side with the rang and suit, the back of the
card being blank, then we might also imagine
the deck being face down. The front of the array is
the bottom of the deck, and the back of the array
is the top of the deck. The process of
dealing out the cards can be done by iterating
through our deck, but we do not actually need to reference specific cards at all, since it doesn't really matter
what they are only that the card is taken from the top of the deck and
handed to a player. So to remove a card from
the top of the deck, we can call another function. This one is called Pop back. It not only returns the
last element in the array, but also removes
it from the array. We then want to add this
card to the player's hand, and we'll start with player one, assuming player
zero is the dealer. Just like we can use pop
back to remove a card, we can also add a
card with pushback. The card we are pushing
onto the back of the player's hand is the same card we are popping
from the back of the deck. So we can simply put this function call
inside the brackets of the function call and use its return value as the
argument to the push function. Using pushback is the
exact same as append. The append function is
a legacy of strings, since they are usually stored
as arrays of characters, while push and pop come from using stacks or cues
like we are doing now. To deal our next card, we'll need to increment the
hand that is being dealt to. So let's store that
in a variable as an integer and give it
an initial value of one. Replacing the one in our array
index with our variable, we'll need to increment it by one after each card is dealt. But this will obviously
just count up to 51, and there are not so many hands. So we'll need this
number to return to zero each time
it reaches four. This can be accomplished using our trustee modulus operator, assigning hand to be
modulus equals four. But if we want to allow our code to work with
any number of players, it would be better to replace four with the size
of our hands array. To see where we are so far, let's print out our hands
by iterating through the hands array and
printing out each one. Then print out the deck. We can see our four arrays, and the deck is empty. We can even change the
number of players and the cards will deal out to
the correct number of hands. Our ready function is
getting rather large. We can organize our code better by declaring
our own functions. Separating our logic
into small chunks, we have a section that creates
our initial deck of cards, one that creates
the player's hands, and one that deals the cards. Using the keyword funk, we can give each of
these blocks of code a name followed by
parentheses and a colon. Initialized deck,
initialize hands, and deal. Then the ready function can call each of these
functions in sequence. Our functions can also
accept arguments, such as the number of
hands we want to have. Let's pass four as the argument. To write our function
to accept the argument, we need to add a
matching parameter inside the parentheses of
the function declaration. Much like declaring a variable, it has a name and
optionally also a type. Since we are using this
to resize an array, it's a good idea to restrict
it to be an integer. We can then use the
parameter inside the function much the same way we would use
any other variable. The last thing we need to
do to start playing would be to remove all of the pairs
from each player's hand. So iterating through each
hand in the array of hands, we can call another function to remove the pairs
from that hand, passing the hand as an argument. Giving this function
a definition, we can then accept a hand
as an array parameter. We will need to iterate through
the hand with a variable. Let's call it card one. But we can stop before
we get to the last card. So we'll only go from zero up to the size of the
hand minus one. We can then iterate
a second time in much the same
way, but this time, start at the card after card one and go all the way to
the end of the hand size. Inside these 24 loops, card one and card two now contain every unique pair
of cards in our hand, so we can easily compare the two cards to see
if they are a match. If they are, they can be removed from the hand,
starting with card two. If we removed card one first, then the index of card two would actually
change as a result, since all the cards
would move one position. If we find a match, we don't want to continue
this loop or either loop. But we want to restart
the entire process again. A simple way of doing this
would be with a while loop, a Boolean variable
for whether or not we found a pair with a
default value of true. While a pair was found, we can immediately
set it to false. This allows us to
enter the loop but not stay here unless
a pair was found. In other languages, you could do this with a do
Wile loop instead. If we find a pair, we can set the variable to true, then also break out of both loops resetting
the Wile loop. If both four loops complete without finding
a matching pair, found pair will be false
and the Wa loop will break. We can repeatedly run our
simulation and see that none of our players' hands will ever contain
matching pairs, and the players are ready to start playing their
game of old Mad. In the next lesson, we'll use another form
of flow control. I'll see you in the next lesson.
8. Match & Return: Hello, friends. Today we'll use another form of flow control to simplify complex conditions. I've already gone ahead
and created a script named months and overridden the base nodes definition
of the ready function. Let's start by
declaring a variable to hold the current month
and set it to be January and a second variable to hold the number of
days in the month, which will be left
as zero by default. We can print out a simple
sentence stating that the number of days
in January is 31. But first, we want to
populate our variable with the number of days
based on the value of month. So let's write a function
that can do just that. Assign the number of
days in the month to be returned from a
function we will write, which will accept the
month as an argument. Giving this function
a definition, accepting the month as a
parameter of type string, it also needs one more piece of information, a return type. The type of value returned
by a function is defined after the parentheses with
a hyphen and greater sign, an arrow, followed
by the type name. In this case, it is an integer, representing the number
of days in the month. If we do not specify
a return type, the return type for any function we write is null by default. Inside our function, we
need to return an integer, which means we should declare an integer at the top of it, and it will need to hold the
number of days in the month. Then at the bottom
of the function, we will return the value
held in this variable. Between these two lines, we need to populate
our variable with the correct number of
days based on the month. If the month is January, then the number of days is 31. We learned previously
that we can provide an alternative
with the se keyword, but that only provides us with two options out of
the 12 we need. We can use another keyword, if, short for if to provide another condition which will be checked if the original
condition was false. So else if the
month is February, then the number of days is 28. And we can repeat this as
many times as we need to, providing a case for
every possibility. But many of these options
have the same outcome, either 30 days or 31 days. We could vastly improve
the efficiency of this if statement by combining
the most common outcome, 31 days to be the default case and replace all of them with a single else
keyword at the end. If the month is not February
or any month with 30 days, then the number of
days must be 31. All of the cases resulting
in 30 days can also be combined into a single statement
using the or operator. If the month is September or if the month is April or
if the month is June, or if the month is November, then the number of days is 30. But the statement is quite long. With more cases, it might
run off the screen. For simple case by case
statements like this, there is a cleaner
and easier to use keyword than the I
statement called match. Match is followed by
the name of a variable, a colon then an
indented block of code. Inside a match block, we can specify any number of specific cases for the
value of the variable. Let's start with February. The case is also followed by a colon and another
indented block of code. This code will only be run if the value of
month is February. We can group cases together
in a comma separated list. So our I statements
condition can be shortened into just a
list of four months. If the value of month is
September, April, June, or November, then the number
of days will be set to 30, and our block can
be replaced with a wildcard case represented
with an underscore, setting the number
of days to 31 if the value of month is anything
else not specified above. This is the same as using
default in other languages. Each of these blocks of
code are called branches. And in GD script, a match
statement will only ever execute the
uppermost branch whose condition was met. In other languages, this is similar to a
switch statement, but differs in that
switch statements can execute more than one branch if multiple conditions are met. Because of this restriction, there is no need to include
breaks in match branches. If we wanted to print out the number of days
in every month, we would probably
want to use a loop, but we can't simply
count or increment a string like January to
somehow become February. So it is common in code when representing things
that exist in a sequential order
to put them in a numbered list called
an enumeration. An enumeration in GD script is just a group of
related constants. So we might have a constant for January with a value of one, another for February with
a value of two, et cetera. But using the keyword Enum, we can name the
group of constants, let's name it months
followed by braces. Inside the braces, we can
group the constants together, and they will each
be assigned a number automatically based on their
position in this list. Anything contained
within the braces does not need to
stay on one line and can be broken up into whatever format you prefer
to make it more readable. Indentation doesn't matter here, but usually when writing
anything encased in braces, most coders will indent
to the contents, so it looks similar
to a function. While I prefer the braces
to be lined up vertically, many coders prefer to put the opening brace on the
declaration line like so. Just like Constance,
enumeration entries are conventionally named
in upper snake case. Now that we have an enumeration, the month can be restricted to the type of this enumeration. We can also assign it a
value from the enumeration, starting with its name,
followed by a period, then the entry in the list. The month being appended to the string output is no longer a string itself and must be cast to a string before
it can be appended. The argument being passed to the number of days is no longer a string and is now a month, and we can alter our function to accept a month as
a parameter too. Since month is now a
month, not a string, we'll need to change
our conditions to match using the name of the enumeration
followed by a period, then the entries in the list. But when we print
out the results, we see that instead of January, our month is written
as the number zero. While the type of our
variable is a month, which is an enumeration, the actual value is
treated as an integer, its position within
the enumeration. Since January is
the first entry, its value as an integer is zero. February would be
one, et cetera. This allows us to modify the value of month with
simple arithmetic, so January plus one
becomes February. If we wrap this code in a
loop that repeats 12 times, we can iterate through
all 12 months with ease. But what about writing the
name of the month as a string? We could do this in a
couple of different ways. Our enumeration type has
a property named keys, which is all of our entries
as an array of strings. If we index this array
using our integer variable, it gives us the entry name as a string matching
that number. But following the Upper snake
case naming convention, we may not want to print
out the name this way. We could rename all of our enumeration keys in
Pascal case instead, or we could declare a constant containing
the array of strings, holding all our months names in a more presentable format. Then use the month as an
index into this array. Another thing we can do
with enumerations is manually set the values of the entries using the
assignment operator. Let's say we want January
to be one instead of zero. The rest of the entries
will automatically update their values to
increment from one, so February is now two. But this no longer matches
the constant array indices. So if we wanted to use this to write the
name of the months, we would need to either add an empty entry for index zero, Or subtract one from the month as it is being used
to index the array. In the next lesson, we'll
learn more about how the engine uses and
executes our scripts. I'll see you in the next lesson.
9. Scene Tree & Inheritance: Hello, friends. Today
we'll learn about how the engine uses the scene
tree to run our scripts. I haven't yet attached a script to the root
node of the scene tree, but we'll instead start this
lesson by adding more nodes. Right clicking on the root node, then selecting add child node, still just a normal node. We can rename it to
be president and pretend our scene tree is
the structure of a company. The president node is indented because it is a child of
the scenes root node. With a node selected,
we can also use the shortcut Control
A or Command A to add another child node. Let's name this one
operations manager, and it is indented another level because it is
a child of the president. Let's now attach a new script to the president node
and name it employee. Overriding the base nodes definition of the
ready function, we can simply print the name of this node and append is ready. This script can be attached
to more than one node. Let's attach the same script
to the operations manager node by dragging it from the file system tab
to the scene tree. Then run the scene
to see what happens. We can see that the
operations manager is ready, then the president is ready. Both nodes that have the script
attached run the script, and since they have
different names, the output was different. We can also see that
the operations manager was ready before the
president was ready. To understand why, we'll
need to add more employees. The operations manager will oversee a number of operators. So I'll add another
child node to the operations manager
and name it operator one. Then attach the employee script. We can right click
on any node except the root node and select duplicate to create
a copy of it. The duplicates name will automatically include
an increasing number, so we can have operator two, three, and four, et cetera. Since the node we duplicated
had a script attached, the copies created also have
the same script attached. Since the operators have the same indentation
level and are all children of the
operations manager, they are called siblings, and siblings are not allowed
to have the same name. Our company should have
more than one department, so let's duplicate the
entire operations department by duplicating the
operations manager. All of the child nodes
are also duplicated. Since the operators are
not direct siblings, they're allowed to keep
their original names. Let's rename the manager
to be marketing manager, and they will have just two marketers
working on their team. Parent nodes can
also be collapsed to hide their children or
expanded to show them. All of the employees
of this company have the same employee
script attached to them. So let's run the scene and get a better idea of the order in which the scripts
will be run. We can see that the first operator is
the first to be ready, followed by each
operator in sequence, and finally, the
operations manager. The same follows for the
marketing department. Then finally, the president of the company is the
last to be ready. How is Gadot deciding
this order of execution? The engine starts at the top of the scene tree and
works its way down, but it does not
consider any node to be ready until all of its
children are ready. So the president is
not ready because the operations
manager is not ready. The operations manager is not ready because the
operators are not ready. The operators are
ready in sequence, since they have no children. Once all of the
operators are ready, then the operations
manager is ready. But the president still
isn't ready because the president has another
child who is not ready. The marketing manager was not ready until all of the
marketers are ready, and finally, the president
is the last to be ready. Since the root node doesn't
have a script attached to it, its ready function is that of a default node, which is empty. Besides ready, there are
other functions of node we can override to be executed
later after a node is ready. The most common being process also preceded with
an underscore. The engine will try
its best to run the process function
of every node in the scene tree 60 times per second using the default
project frame rate. So process uses a
parameter named Delta, which is a floating point
number to represent the number of seconds that have passed since the previous frame. So most of the time, it will
be one 60th, but not always. We do not need to use this
parameter for anything today, so we can proceed the name of this parameter with
an underscore, so Gudo knows that we don't need to use it and shouldn't
bother with it. But in order to override
the process function, it must have the same number
and types of parameters. Let's tell our employees to
do some work every frame by printing out a statement saying their name appended
to does work. Running this scene, we see that all of our employees
are working very hard, writing their print statements
60 times per second. This is not the same as an
infinite loop as work is actually being done the way it is intended and
nothing is broken. Let's make this a
little easier to understand by only
allowing each employee to do one unit of work using a Ban variable has done work with a
default value of false. In the process function, if the employee
hasn't done any work, then they will do work and
change the value to true. One 60th of a second later, since the variable is true, they will not do work anymore. But if we declare this variable inside the process function, then it will only exist inside the process function and only for one single
iteration of it. Next frame, when the process function is
run the second time, the variable is declared again and still
defaults to false. So this will have no effect. For our variable to persist, it needs to exist outside the context of the
process function. So we'll declare it outside the function without
any indentation. Variables declared here can be accessed anywhere in
the entire script. This concept is called
scope and is one of the main reasons why we use
indentation in our scripts. So we can see which variables
exist within which scope. Running the scene now,
we can see each of our employees declares that
they are ready to work. Then only once
everyone is ready, they each do one unit of work, but now in a different order. The order in which nodes
in the scene tree execute their process functions follows the exact order of the scene
tree from top to bottom. Parents do not need to wait for their children like
the ready function. We may want all of our
company's employees to be similar but not
exactly the same. The work of a manager isn't really the same as
that of a worker. We can use inheritance to create different behaviors
for similar objects. In the same way we are writing
scripts that extend node, but changing how they get
ready and how they process. First, we need to
give this script a name that we can
use as a reference, using the keyword class name, followed by a name that describes the behavior
of the script, usually the same as
the script name, but written in
upper Pascal case. Now that the engine knows
this is an employee, let's create a new script
and name this one manager. But instead of
inheriting from node, this time we can
inherit from employee. Because our manager
script extends employee, everything written in
the employee script will still apply to this one. But we can add more
or make changes. A manager is still an employee, but the way they do
work is different. We should replace the
script attached to our manager nodes with
the new manager script. Let's override the employee's definition of the
ready function. So when a manager is ready, they won't just say
they are ready, but they will say that their
entire department is ready. In order for both
of our managers to be able to output the name
of their departments, they will need a
variable to hold the name of their
department as a string. But how can we
populate this variable to hold different values
for different departments? If we proceed our
variable declaration with the tag at export, this variable will become accessible by other
parts of the GADoEngine, namely the inspector panel. In order for a variable
to be exported, it must be declared at
the script scope level. Selecting the operations
manager and the scene tree, there is now a field in
the inspector panel where we can give the department
variable a string value. This way, the operations manager and the marketing manager can each have their
own value for this variable that we
can easily control. Running the scene, we can see
that the nodes which have the managers script attached now declare that their
department is ready, while the other
employees all use the previous definition
in the employee script. The process function in the
employee script is also inherited by the managers and
is run the same as before. So let's change our
manager's process function. Managers will say
something different. Name plus manages workers
to do their work. The has done work variable
from the employee script was also inherited and does not need to be declared
here to be used. In fact, we can't declare a
variable with that name since it would cause a conflict with the inherited variable
of the same name. Let's run the scene
again and see how our managers now do their work differently
from other employees. In the next lesson, we'll learn
more about using nodes in the scene tree and object
oriented design principles. I'll see you in the next lesson.
10. Abstraction & Encapsulation: Hello, friends.
Today we'll explore how to write classes that are easier to use and understand. Imagining a car as our
frame of reference. Let's create two
nodes in our scene, a driver and a car as
a child of the driver. Then attach a new script
to each of the same name. Cars are very complex
pieces of machinery, and most people who drive them have no idea how
they actually work. They only understand how
to operate their car. Let's start in the car script and give it a class name of car, then add a variable for the year of the
car as an integer. Another for the make of
the car as a string, and another for the
model also as a string. Exporting all of these, I'll set my car to be
a 2007 Honda Civic. When creating a new class, be sure to save the script
before trying to use it. In the driver's script, the driver will need a
reference to the car, which we can store in a
variable of type car. Nodes can access other
nodes in the scene tree, most often their children using another tag similar to
export called on ready. Using the tag on ready, we can assign the variable
value after the node is ready, which means all of its
children are also ready, but before calling the ready
function of this node. We can assign it to
the return value of a built in function, Get node, which accepts a
node path as a parameter. If we specify the name of the child node in
quotation marks, we now have a reference to the child node stored
in a variable, and we can access its
properties and functions, including those in
the attached script. Overriding the definition
of the ready function, we can access the car node and print out its
year, make and model. The ready function is executed
after the on ready tags. A shortcut exists for the get node function
to make this easier, the dollar sign, followed
by the same node path. Et's switch back to the car script and
add another variable. This one specifying whether or not the engine is
running as a Boolean. As a driver, we know if
the engine is running and we have control over whether or not the
engine is running, since we can choose to start or stop the engine using the key. So we would consider this
variable to be public. Another property of our
car might be the number of cylinders in the
engine as an integer, and I'll assign it to
have a value of four. This is something that the
driver doesn't need to know, can't access easily and should never be
allowed to change. So unlike the public variable, this one would be
considered private, and we can mark it as private by proceeding its name
with an underscore. In the driver script, we can
set the value of engine is on to true by putting the key in the ignition
and turning it. But first, we should be
pressing the brake pedal. Let's use a function to set pressure being applied
to the brake pedal, which we will assume is up to a maximum of 20
kilograms of force. So to start our car,
we should probably set the brake pressure to
somewhere around 10 kilograms. Back in the car script, we will need to define this function which sets
the brake pressure. Like the engine is on variable, this would be called
a public function, since it is being used
by another script. This function will accept
a floating point number as the parameter
representing the amount of pressure being
applied to the pedal. But what does the
car actually do when we apply pressure
to the brake pedal? From the perspective
of the driver, the driver chooses to
apply a certain amount of pressure to the pedal without ever knowing the exact position. From the perspective of the car, the position of the
pedal is what is being measured and
used to stop the car. The car uses the position of the brake pedal to adjust
the amount of pressure being applied to calipers which hold brake pads against
the wheels of the car, not to mention the complexity of anti lock brakes
or other systems. So if we were actually
programming a real car, we would need to write
several complex functions here that convert
the simple action of the driver applying
pressure to the brake pedal to the complex mechanics
of squeezing the calipers. This is part of the
internal functions of the car and not anything
that is controlled or accessible to
the driver through any means other than applying pressure
to the brake pedal. So we would consider these
to be private functions, since they are only meant to
be used by the car itself. And we can mark our
private functions by proceeding their names
with an underscore. Notice how this is the same as the process and ready functions. The way GD Script uses private variables and
functions is more like the way other languages
use protected ones in that they are
accessible to inheritors. GDScript does not actually
enforce these privacy rules, however, and scripts can
still access private members. These conventions
exist purely to facilitate better class
structure and design. Another way of using
comments in GD Script is to create regions of our
scripts that are related, using the region keyword followed by the
name of the region. We can end a region using
the keyword and region. So let's make a
public region and a private region
in our car script, separating what should be accessible to the driver
and what should not. To keep things simple, let's say that the
driver only needs to be able to turn
the engine on or off, apply pressure to the throttle, apply pressure to
the brake pedal, or turn the steering wheel. What actually happens
inside any of these public functions is the internal mechanics
of the car itself. The driver does not need to know how any of it works
to operate the car. For example, when
pressing the throttle, what is actually
happening is that the car opens an air intake
valve on the engine, letting more air and
oxygen into the engine. So the car needs to use a
sensor to measure the amount of airflow and the amount of oxygen that is now
flowing into the engine. Then use that to
adjust the amount of fuel being injected into
the engine's cylinders. All of these functions
are private, since we do not need
to know what they actually do in order
to drive the car. Way our car moves would be
in the process function, and we'll need another private
variable named phase to represent which of
the four phases each cylinder is currently in. In the car's process function, if the engine is running, we can iterate through each of the
cylinders of the engine. Adding together the
cylinder and the phase, then modulus four, we get a different phase
for each cylinder. Matching this number, we
can tell each cylinder to perform a different
phase, the intake stroke, compression stroke,
combustion stroke, or exhaust stroke, each accepting the
cylinder as an argument. Then increase phase and cycle back to zero
when it reaches four. Each of these functions can then control the intake valves, exhaust valves, fuel injectors, and spark plugs of each
cylinder to run the engine. The amount of force
being produced by the combustion strokes of
each cylinder would then move the car forward while the
brake pads try to stop the car and the steering wheel position changes
its direction. The car's position would then
be moved by the direction, speed, and Delta time. Don't worry if none of
this makes sense to you because that is precisely
the point of this lesson. You do not need
to understand it. Since all of this is in the
private region of the script, the driver does not need to know how any of this works in
order to drive the car. They only need to know how to use the public region
of the script. The separation of public
and private enforces two important principles of
object oriented programming. Encapsulation is
the public region, providing access to
public variables or functions that are easy to
use and simple to understand, and abstraction is
the private region, hiding the details of
complex systems or mechanics which
do not need to be understood to be
used effectively. You will know if your
classes adhere to these principles if
you can hide all of the private contents and still easily understand how
it is meant to be used. So our driver can now
simulate driving the car by accessing only the public
regions of the car script, from pressing the
brake and starting the engine to
releasing the brake, pressing the throttle, turning the steering wheel, et cetera. These principles are not only important for our own scripts, but also allow us to
use scripts written by other developers without fully understanding how
they work internally. They also make R
scripts more flexible. Imagine if we swapped out our gas car with
an electric car. Would anything in the driver's
script need to change? Only the car script
would need to be changed for an
electric car script, since all of the
public variables and functions would still exist
in the electric cars script. Likewise, we could also change the driver script to
an autopilot script, one which only needs to be given a destination and will automatically be able
to operate the car. In the next lesson,
we'll learn even more about object oriented
design principles. I'll see you in the next lesson.
11. Polymorphism: Hello, friends. Today
we'll cover the last of the object oriented programming
principles, polymorphism. Extending the concepts of
inheritance and abstraction, we can write classes that represent a more
abstract concept, a collection of more specific
classes that are related. Let's use animals as
an example by creating a script named animal
that extends node, but we don't need to attach this script to any nodes
in the scene tree. Here we can give the
script a class name, then define properties and behaviors that are
common to all animals, like a variable to hold the plural of the
animal as a string, and another for the name for
a grouping of this animal. And let's also define a
function for the animal to move, eat, or speak. While we can easily define the plural of animal as animals, there is no word for
a group of animals. We also can't define
how an animal moves or eats without knowing
what kind of animal it is. So the bodies of these
functions can remain blank. But we might consider some
behaviors to be common enough that they should have
a default, such as speaking. While there are many
different mammals that make a wide
variety of noises, the majority of animals don't, including fish and insects. Most people would not
describe them as speaking. So when telling any
random animal to speak, we might consider the default
behavior to be silence. So let's add some
different animals to our scene tree,
starting with a cat. We can now use inheritance, like we have before
to make any number of different animal scripts that inherit from animal like cat. We'll then override
the ready function to give the variables which are inherited from animal a value that is
appropriate for a cat. The plural would be cats, and a group of cats
is called a Clouder. The behavior functions
can also be overridden to change the silent
speaking behavior to meow. Cats walk on four
legs to move around, and I'll say that they eat fish. The actual contents of these variables and
functions are not relevant, but the focus is on how
they are inherited from the abstract concept
of an animal to a more specific
instance of a cat. We can repeat this process to make many more
different kinds of animals to add to our scene
tree like a bird or a fish. And giving each a new script
inheriting from animal, they can each provide their
own unique values for the variables and
implementations of the functions they inherited. The plural of bird is birds. A group of birds
is called a flock. They fly to move around, they eat worms, and
they chirp to speak. I'll get a head start
on my fish script by copying the contents
of the cat script. And the plural of fish is fish. A group of fish is
called a school. They swim to move around. They eat algae, but
they don't speak. So by omitting the
speak function, fish will use the
inherited definition from the animal
script for speaking. So far, all we've done is use the object oriented principles we already know to inherit, abstract and encapsulate
the properties and behaviors of animals. But all of this allows us
to use polymorphism by treating all animals as if they are equal and interchangeable. First, let's add a script
to the root node of our scene and call it Zoo,
inheriting from node. Our zoo is full of
different animals, but it doesn't matter what
those animals actually are. Overriding the definition of the ready function for our Zoo, we can easily iterate
through our list of animals and treat all of them as if they
are just animals. A quick and easy
way to store all of our animals into
a single variable, let's call it animals is to use a function of node
called Get children. This returns an array of nodes, all of the child nodes of
this node conveniently organized into an array in the same order
as the scene tree. Because this relies
on the children, we must use the tag at on ready. Since we know that
all animals have the same properties
and behaviors, we can iterate through
each animal in our array of animals and tell each
one of them to speak. And despite treating
them all as animals, each will speak in their own
unique way as defined by their own particular
scripts or use the definition inherited from animal if they don't
have their own. We can create as many levels of inheritance as we want to. So let's create another
abstract script for mammals, inheriting
from animal. Mammals are a
subcategory of animals, including all animals which have mammary glands and use
them to nurse their young. These are unique properties
and behaviors of mammals that do not
apply to other animals. So we can add a new variable to the mammal script
for the number of mammary glands this animal has and write a new
function called nurse. Again, the contents of the functions are not
relevant to this lesson. This script will also
need a class name in order to be inherited. Switching over to
the cat script, since cats are mammals, and so we can give the
number of mammary glands a value for cats,
which should be eight. And we can add new
mammals to our zoo, as well, like a monkey. Monkeys are also mammals, but they only have
two mammary clans. I'll once again
copy the contents of another script to
make things faster. The plural of monkey is monkeys. A group of monkeys
is called a troop. They move by climbing trees. They eat bananas, and I'll
say that they say oh. Our Zoo script can now take advantage of this new
inheritance structure to apply unique conditions
specifically for animals that are mammals. We can directly use class names in conditional
statements to say, if the current
animal is a mammal, then we will execute some extra code
exclusively for mammals. I'll just print out
some extra lines describing our mammals. And we can see our output is both consistent for all animals, but also has something unique specifically for
the two mammals. You can imagine how we could
change our list of animals in our zoo to be any
different kinds of animals, and we could alter our
structure of inheritance to categorize them as
precisely as we need to. In the next lesson,
we'll learn about another data collection we can use to group related
information. I'll see you in the next lesson.
12. Dictionary: Hello, friends. Today we'll use an alternative
to the array that is sometimes used sort
of like a class without any functions
called a dictionary. Imagine a row of
lockers like you would find in a high
school or a gymnasium. If we declare an
array of variables, we might consider
the array index of each to be the
lockers number. Let's say there are ten lockers, and we can access each
individual locker to put something inside, starting at locker zero, one, and so on. What we put inside each
locker does not matter. It can be an integer,
float, string, boolean, null, or an
instance of a class. And we can print
out the contents of the lockers to see them. Now imagine that when we put something in
one of the lockers, we don't just close the
door of the locker, but also put a pad lock on it. This padlock requires a three digit number
combination to open. Previously, if we wish to retrieve the contents
of the locker, we only needed to know
the lockers number, walk over to it, and
take the contents out. Now we can ignore the
lockers number, and instead, we need to know the combination
to open the padlock. Let's say that the
combination is 149. As an array, assigning
something to number 149 would mean that the array would
have to have 150 lockers. If we were to change the type of our array to a dictionary, this would still work
except we don't need to set a size for the dictionary before we can access
its contents. Notice how the
output has changed. Dictionaries are represented by braces instead of
square brackets, and each entry is a number
representing a key followed by a value of what is contained in the locker
locked by that key. There are also no entries
in the dictionary for any key which was
not assigned a value. As a dictionary, this number
is not an index or address. It is a key that we
use to open a lock. And just like the contents of the locker can be any data type, the keys we use to
lock them can also be any datatype with
only one restriction. Every key must be unique. Otherwise, we're unable to find the locker that
matches the key. So let's try locking up a locker with a
different kind of lock, one that uses letters
instead of numbers. So the key of this dictionary
entry is a string, but its value is a number. The complete opposite
of this locker, whose key is a number
and value is a string. Let's lock the next
locker with a spin lock, which requires three
numbers to unlock it, which we could
store in an array. So our dictionary entries key
is an array represented by square brackets containing a comma separated list
of three integers. Maybe this locks number
includes a decimal point, so the key is a float. And somehow this
locker is locked by photosensitivity and can only be opened if the lights are off. So its key is a boolean. We can even use
custom classes as either our keys or
values in dictionaries. Let's write a new class for key, which will not need
to inherit from anything since it won't
be attached to a node, nor will it be inherited. Our key class needs a class name and we have a variable
that describes its teeth. The implementation of this
class is not relevant, but we can pretend it is a key. Back in the locker script, we can store an instance
of our key in a variable. Let's call it key
one of type key by assigning it to
the class name of key Nunu creates a new key, which is assigned
to our variable. We can now use this key to create an entry in
the dictionary. We can also create more keys
to create more entries. As every key we create
is considered unique. The output now displays the keys as reef counted followed
by a long number. The names we have used in other classes were
inherited from node. But since key doesn't inherit from node, it
doesn't have a name. Ref counted is short for
reference counted object, and it's the base
class for GD script. If we write any class which does not inherit
from anything, it will inherit from reef counted and be assigned
a random long number. We can ignore this
and just understand that our keys are
reference counted objects, meaning Gadot is
automatically keeping track of each unique instance
of each object we create. But what do we want to
store in the lockers? Just like arrays can
contain other arrays, dictionaries can also
contain other dictionaries. Matching how dictionaries
are printed out, we can create our
own dictionaries on the fly using braces. Each entry starts with
a key followed by a colon and then the value
that matches that key. Common use of
dictionaries isn't to create anything like
this locker scenario, but instead to create
something more like a class. Albeit one without any
functions, only variables. Let's say that this locker
contains a plush toy, which we will represent
with a dictionary. And this plush toy
belongs to a set of collectible plush toys that are similar but varied
in their own ways. Each of the plush toys is a different type of animal
with a color and a name. Each of these can be
keys in the dictionary. Let's say that this locker
contains Bruno the brown bear. And the next locker contains
Arthur the White Goat. We can see our
dictionary plush toys inside the dictionary
of lockers. It so happens that color is also an already defined
class in Gadot. So instead of storing
our colors as strings, they can be stored as colors
using the class name color. As you can see,
there are lots of predefined colors for us to use, and we can tell by how they are named in upper snake case, that these are constants
of the color class. We could even flop this
around and pretend that the plush toys contain an NFC or near field
communication tag, making them the key
to access the locker. And we can put whatever
we want inside the locker just as before, including
another plushy. Let's make another plush toy
to store inside the locker, which is unlocked
using Arthur as a key. This time, it's cutie,
the gray elephant. So re imagining this scenario, there is an NFC reader
near the lockers, and we are holding a plushe
with an NFC tag inside, something like what you might
find in an escape room. If we hold the plush
toy against the reader, we need to check which locker matches the plush
toy and unlock it, if any locker matches at all. But if we try to access a key in a dictionary which has not
been assigned a value, we will cause an error. Much the same way we
would if trying to access an array
index out of range. We can check if a dictionary
contains an entry matching a specific key
using the function has, then passing the
key as a parameter. This allows us to check
if a dictionary entry exists before trying to access its value,
avoiding the error. If the dictionary
doesn't have the key, I'll print out a
different message. Instead of defining the
same dictionary twice, it would be better to store in a variable and use
the variable twice. In the next lesson,
we'll explore some different techniques
that you can use when you get stuck or
need more information. I'll see you in the next lesson.
13. Debugging: Hello, friends.
Today we'll go over a few different strategies
that you can use to help you when you
inevitably get stuck. I've already
attached a script to the scene's root
node named debug. You've probably noticed the
autocomplete feature that is available if you specify
the type of your variables. This not only allows
you to see all of the properties and
functions of the class, but after selecting a function, you can also see the types of all the functions parameters so you can provide
matching arguments. A, when you know the types
of all your variables, this makes mismatch
errors clearly apparent, and you'll be warned of the error without
having to run it. Without strict types,
the engine doesn't know what is mismatched until
attempting to execute the line. If you want to browse through a built in class like color, we can easily access the
information by clicking on either of the buttons in the upper right
corner of the panel. Online Docs opens a tab in
your default web browser to the GADO API matching the version of the editor
that you're using. Here you'll find basic
information about GADO tips on getting started, detailed tutorials
on the basics, community links where you can
find additional resources, and a complete class reference. As you can see, there
are a lot of classes. And selecting any of them, we can see what
they inherit from and what other classes
they are inherited by. Each has a description
of what it does. Links to tutorials
on how to use it, a list of properties
and methods. When talking about classes, we tend to refer to variables as properties and
functions as methods. There are also signals, something Gadot uses for automatic communication
between nodes, enumerations and constants,
like we have used before. All of these have
their own subsections and descriptions of
how to use them. Back in the GDA Editor, search Help opens a window in GDA where you can see
a list of every class. Selecting a class,
we can see all of the same information
that is available from the GDA website
in our editor, so we can access it offline. Returning to our script, we can even access
this information easier by holding down the control or command key
than clicking on a class name, bringing us straight
to the reference information for that class. The same can be done for specific functions
and variables. Clicking on them not only opens up the reference
page to the class, but also brings us
to the function or variable that we clicked on. There are also global
functions that we can use, mostly to perform
mathematical operations like clamp, for example. If we use this function, we can see the parameters
it is expecting and we can use it to clamp
our variable to be 0-1. Holding the control or command key and clicking on the name of the global function
will allow us to access the reference
for this function, complete with a description
and how to use it. These are all contained within
the global scope class. Okay I'll add another variable and another line to my script. If we run our scene, we
can view the scene tree of the running simulation through the scene panel by clicking
on the remote toggle. This is not the
same scene tree in the local tab that we
build to define the scene, sort of like a blueprint. This scene tree is
the one that was constructed by the
engine to run the scene. Selecting any node, we can see its properties
in the inspector, and we can see that
the ready function has already run since the colors
have different values. We can change the values of
any of these properties too. We can also use print
to print out whenever we want to see what our
variables hold at any moment. And it helps to be as descriptive as possible
with your print statements, so there is no confusion about where they
are coming from. But if we change things up
and use the process function, I'll just change the color
gradually over time, then add a print statement here. Since this process function is running 60 times per second, the print statements value as a debugging tool is
greatly diminished since the output log is just going
to repeat the same message faster than we can possibly
read it or react to it. We can pause the
simulation while it is running using the pause
button or pressing F seven. This allows us to see the
remote scene tree as it is at this moment and review
the contents of the output panel with
the simulation paused. But this still
isn't very useful, as several frames will
be completed between the time when the simulation starts and when we can pause it. If we click to the left
of any line of code, we can add an
automatic breakpoint indicated with a red circle. When we run the current scene, the simulation will
pause when it reaches this line before executing it. This is much more
helpful if we want to see our simulation run
one frame at a time. Now we can see how each frame the color gradually changes
one frame to the next. Let's have our process function call another function to
perform a simple task. I'll just change the color gradually by reducing
its red value. So now we should see
the color gradually change every frame becoming
less red and more blue. With the breakpoint still at the first line of the
process function, I'll run the scene. It stops here, and we can see
that the next line of code to be executed is indicated
with a yellow triangle. While the simulation
is paused like this, we can tell it to execute only a single line
of code at a time, rather than an entire
frame by selecting step over from the Debug menu or
using the shortcut F ten. This will execute
only the line of code which is pointed at
by the yellow triangle, then immediately pause again. So we can see the changes made only by that
single line of code, and we can see the
yellow triangle point to the next line
waiting to be executed. We can continue running our script line by
line if we continue stepping over until
the process function ends and the frame is complete, returning to the top of the process function
to run a second time. Now we can watch each individual
change to the color as its blue value is increased separate from when the
red value is decreased, even though they happen
during the same frame. In the case of function calls, there is another option to
step into the function. If we select this option
or use the shortcut F 11, the yellow arrow will instead step into the function being called and execute that one line at a time until its completion. When the function is complete, the yellow arrow returns to the original function to
continue executing that one. Using these tools, we have a very focused view of what our classes
are doing and when, how they are behaving and we can watch them in
as slow motion as we need to to figure out what is going wrong at what
specific moment in time. If you can't solve
a problem yourself, the GDA community is very helpful and always
willing to lend a hand. There are Discord servers
for GoDO developers, including my server where my students help each other out. The link to join
is in my profile. There are also online forums on websites like Redit where
you can ask questions too. You now have a basic
understanding of most of GDScrip syntax and the tools needed to explore these
concepts further. When you're ready, please ask
me for a discount code to any of my game project
courses, good luck.
14. Setup: Hello friends. If you
haven't done so already, you'll need to download
the GdoGameEngine from Gudo engine.org. Then extract the contents
of the zipped folder. Be sure to put this
unzipped folder somewhere safe on your computer. Open Gudo when you're ready. Hopefully, you've completed the intro to programming series, so you have a basic
understanding of the editor and how
to write scripts. Click the plus Create button
to create a new project. Our objective in this
course is to create an entire game using only user interface
nodes in a single scene. You can name your project
anything you want, such as UI course, unnamed UI game, or if you have a name for your
game, you can use it here. Godot will automatically make a new folder for your
project in documents, or you can click Browse
to change the location. I like to have all
of my GDOPjects in a subfolder of documents. This project won't involve
any complex rendering, so the renderer won't matter, but I'll use compatibility
so I can export to Web. And GODO provides
Version Control support with it by default, which is very
helpful, and I highly recommend learning how to
use it for your projects. Since our game should contain only a single scene using
user interface nodes, let's start by creating our
root node as user interface. User interface nodes all have green icons to make them easily identifiable
in the scene tree. We can rename our nodes by
clicking on them after they're already selected or right click on them and select rename. Let's name this
node title screen. Then save this scene in our project either through
the main menu scene, save scene or using the
shortcut Control S or Command S. Despite the fact that the root node is
named title screen, we are intending to make this
entire game in one scene. A common name used in this case for the
scene would be main, so that's what I'll name Mcene. And with only one scene, we can just leave it in the
project's resource folder. If you're not sure what I
mean by user interface nodes, select the root node
of the project. Then take a look at its
properties in the inspector, expand the theme section, click on the down
arrow beside theme, then select new theme. This scene now has
a theme which will change how all of its
control nodes are displayed. Click on the new theme to
open it in the bottom panel. Expanding this panel to get a better view of
all its contents, we can see quite a wide variety of user interface
nodes previewed. Label buttons of various types, editable text, numbers,
and sliders, separators, a progress bar, tabs, a tree, and a scroll bar, all of
which is contained in a panel and several containers used to organize everything. Some control nodes are
not included here, including one which can be used to display two D textures. If you haven't decided on a
game for your project yet, try to come up with
something you might try to make using only
these components. We are now ready to start
our new game project. In the next section,
we'll create a basic title screen
for our game. I'll see you in
the next section.
15. Label A: Hello, friends. With
our project started, we're going to start our game
with a basic title screen. The most basic thing we need for a title screen for our
game would be the title. Let's start by
adding a new node to the scene tree by
clicking the plus button, right clicking on the root
node and selecting add child or using the shortcut
Control A or Command A. To complete the challenge of using only user interface nodes, we'll only use the nodes that
are sorted under control. This control is
also the same type as the root node of scene. We can see in the
description that control is the base class for all graphical
user interface controls. These nodes will
adapt their positions and sizes based on their
parent control nodes. The control most often
used to display text is named label indicated
with a tag icon. We can also see the inheritance
of the label class as each node type is indented below the type it inherits from. Any node or class that inherits from another
will have all of the same inherited properties plus its own extra properties. So the label node is a node, but it is also a canvas item, a control node, and a label. Each of those come with their own properties and behaviors. We can see that the
label node is indented below the root node as
it is a child of it. The root node is
the label's parent, so the label will adjust its position and size
based on the parent node. We'll see what that means soon. Let's rename the label node to title and have a look at its
properties in the inspector. The first property is the text that the
label will display. So we can enter the
game's title here. There are also
several options for controlling the text
alignment, wrapping, justification, paragraph
separation, clipping, ellipses and tab stops. None of these are really
applicable for our title, except maybe uppercase
if you would prefer to change all lowercase letters
into uppercase letters. The properties under
displayed text are only applicable for situations where there is too much text to display
all of it at once. And BD options are available for languages that are displayed right to left instead
of left to right. None of this is important
for our game's title. Let's press Play and see
our title displayed. Since no scene has been selected for the main
scene of this project, we are prompted to set it. Press select current to set this scene as the
game's main scene, and we can see our game run, displaying the title in the top left corner
of the window. Press the stop button
to end this simulation. This works, but it
isn't very good. We probably want to move the
title and increase its size. Looking further at the
properties of the label node, these are all of the properties
of the label node itself, but there are more sections of properties if we scroll down. The next section holds
all of the properties that the label node
inherits from control. If we expand the layout section, then the transform section, we can see the size and position properties
of the label node. If you can't see this
rectangle in your two D view, adjust it until you can using the mouse wheel to pan and zoom. This blue rectangle
is the display area of the game as it will be
rendered when we run the game. Notice how the red X
axis overlaps with the top side of
the blue rectangle making it appear magenta. The green Y axis
overlaps the left side, making it cyan, and the origin point of the scene
is in the top left corner. If we click and drag the title away from
the top left corner, its position X and Y values in the inspector are updated to
reflect its new position. Notice how moving the
label down increases the Y position and moving
it up decreases it. Let's put the title somewhere around the center of the screen. Then press the play
button to see the change. The title is now displayed
in the center of the screen. But what happens if
we resize the window? The position of the
title is determined by its distance from the top
left corner of the window. So if we make it
bigger or smaller, the title is no longer in
the center of the window. Press the reset button to return the label back to the
origin, position 00. You probably noticed the green X at the origin when
selecting the label node. These are four anchors, all pointing to
the same position. Let's change the layout mode of the label node from
position to anchors. This adds another property named anchor presets,
which we can change. If we change the anchor
presets to full wreck, this is the same
as the root node with an anchor in every corner. If we select the root node, the anchors are all pointing to different corners
of the screen. R select the label node. The rest of the
options will anchor the label to different
locations on the screen in corners centered or stretched on one axis
along the screen. Let's anchor the title to
the center of the screen. Now, if we hit Play, then resize the window, the title remains centered even if the size of
the screen changes. Can also customize how
these anchors affect the node by changing
anchor presets to custom. This allows us to change
the anchor points, defining them as percentages of the screen's width or height. Since anchors are displayed
pointing at the corners, each of these anchor
point settings will affect two anchors. I'm fine with keeping the
title centered horizontally, but I would prefer
if it were moved up slightly to about one
third of the screen. Adjusting the top anchor
point to one third, the top left and top
right anchors move up. I'll also set the bottom
anchor point to one third, so all the anchors point
to the same position. And Anchor offsets can be used to offset the node's position from the
anchor point by set amount. This can lead to the node size being larger than the text, and so the label's
alignment property will affect where it is drawn. I'm fine with having the label centered on the anchor point, so I'll reset all of my
anchor offsets to zero. The grow direction will
affect how the node is repositioned relative to the anchor point
based on its size. If told to grow to the left
or right, top or bottom, we can see how the node resizes and repositions to
accommodate the text. A game title will often have a unique font and font size from anything else
in the project. In cases like these,
we can ignore the theme we created
in the first lesson and instead expand the
theme override section of the control properties. Before we can change the font, we need to import a
font into the project. I downloaded this font
from Google Fonts. We can import files
into the project by dragging them into
the Godot Editor window. We can now set this as
the font for the title. We can also change
the font size. I'll set mine to 49. Styles aren't often used for
labels, but to demonstrate, we can add a style box flat to add a background
color to the font. Clicking on the
style box allows us to change the color among
many other options. Like I said before, this isn't really something that's
often used for labels, and we'll go over style
boxes in more detail soon. I will remove the style box from my title label by clicking
on the reset button. I'll instead add theme overrides for the font color,
shadow color, and outline color, making the font white and the
shadow transparent. Then add constants for
the shadow offsets, outline size and
shadow outline size. Since the title
is only one line, line spacing would
have no effect. As mentioned earlier,
parent control nodes affect the size and
position of their children. As we saw before, the
title screen node is anchored to the full rectangle
of the display area. But if we resize it, its
children will recalculate their sizes and positions to fit the size and position
of their parents. In this case, the
title remains centered horizontally and one third from the top of the title
screen's area. We can reset this back
to the way it was by resetting the anchors
of the title screen. We now have our game's title displayed on our title screen. In the next lesson,
we'll add a background. I'll see you in the next lesson.
16. Label B: Hello, friends. Before
moving on to textures, we can explore a little more of what labels have to offer. None of the changes made in this lesson will be
safe to the project, and I will just revert back to the end of the previous
lesson before moving on. Any video titled B
will follow this rule, providing only extra
information about the previous topic, but
can be skipped over. Selecting the label node, we have a property
named label settings. Much like the theme resource
from the first lesson, we can create a new label
setting resource here, but it will only
affect this label. Creating this label
settings resource, we'll ignore the theme overrides and use these settings instead. Here we can change
all the same settings that are available in the theme, spacing, font, font size, color, outline, and shadow. Et's add a second line
of text to the title. If for any reason, the size of a label node is
larger than the text, the alignment properties will determine where
the text is drawn, whether it is on the
left, center, right, or filling the entire width, top, bottom, et cetera. If the text is too long to fit inside the designated
width of the label node, we can use Auto rap
mode to change how the text is automatically
moved to the next line. Arbitrary will just cut off at whichever character happens to be at the end of
the label's width. Word will try to wrap at
spaces between words, and word Smart will break a word if it doesn't
fit into a single line. If the meaning of
any property is not obvious and you want more
of a detailed explanation, hovering the mouse over the property name will
provide a tool tip. I'll use some Lormipsum as placeholder text to explain
the next few settings. Justification flags
can be used to change how the fill text
alignment is implemented. Starting in Gudo version 4.4, you will be able
to change the new line character to
anything you want. Clip text can be used
to prevent any text from being drawn outside the boundaries of
the label node. If clip text is activated, then text overrun behavior can be changed to alter how the
clipping is implemented. Trimming characters
or words even adding ellipses to
replace characters or words The ellipsis character can also be changed
to anything you want. Normally, tab characters in the text area are ignored when the label renders the text, but you can also add tab
stops to your labels. Clicking on the packed float
32 array to expand it. Click the plus add element
button to add a tab stop. Then set the value
as a decimal number. Multiple tab stops can
be used to sort of create charts or columns
within the label node. Returning to orm Ipsum to help demonstrate displayed text, if we have a large amount
of text in a label node, then we can customize
the amount of text that is drawn
using these properties. Lines skipped will hide lines of text starting
at the beginning, while max lines visible will hide lines of text
starting at the end. Negative one is used to mean
unlimited in this context. Visible characters can
be used to control the exact number of characters
that will be drawn. This is directly tied to
the visible ratio property, which is the same
thing, but expressed as a percentage of
the whole text. Changing one value will change
the other automatically. If both character visibility and word wrapping
are being used, then how word wrapping
is implemented can be changed using visible
character behavior. For example, this word would not fit on the first line
if it were all drawn, but it does fit only because of the number of
visible characters. Changing the visible
characters behavior to characters after shaping, the word is wrapped
to the second line regardless of whether or not
it is completely visible. The glyph layout
settings are only used if the layout direction
is set right to left, and the BD settings are for languages specifically
write from right to left. I'll now revert the project back to the way it was
before this lesson. That's all of the label options. In the next lesson, we'll
move on to texture Rx. I'll see you in the next lesson.
17. Texture Rect: Hello, friends. In
the previous lesson, we added a title to
our title screen. In this lesson, we'll
add a background image. I found this image that I
think suits my game idea nicely on free pic.com
made by UpClac. Make sure you check the
attribution requirements and licensing of assets you find
online before you use them. I've downloaded this image
and we'll import it into Gadot so I can use it as my
title screens background. To prevent my project
resource folder from getting too cluttered
and disorganized, I'll create a folder
for imported assets. Then inside that folder, create another for funs. Then drag each file into the appropriate folder so
I know where to find them. To draw this texture, we'll need to add a new
child node to our scene. The node we need to
use to draw images on screen is the
texture wrecked node. Let's rename this
node background. Then populate its
texture property with the imported image. We can no longer see the title. The image is being
drawn over it. By default, each node in the scene tree is drawn
from top to bottom. So the title is
being drawn first, then the background is
being drawn over it. We can rearrange our nodes
easily by dragging them. So the background is sorted above the title in
the scene tree. This way, the title is
drawn over the background. This image is much larger than the display
area for our game. We want the background image
to cover the entire display, so we'll change its anchor
presets to full wrecked. This moves the image to be centered within
the four anchors, but it is still too large. If we change the expand mode
of the texture wrecked node, we can tell it to ignore
the size of the texture and instead use the size that is
specified by the anchors. Now the image covers the
entire display area, even though the aspect
ratio of the image does not match the aspect ratio of
the display area exactly. It's hard to tell
with this image, but it is now distorted,
being stretched vertically. Other options exist to force the image to resize
to fit the width, ignoring the image's height, suitable for fitting into a horizontal arrangement
or fitting the height, ignoring the image's width, better to fit inside of
a vertical arrangement. Either of the proportional
options attempt to do the same as the
previous options, but also maintain the
images aspect ratio. With fit width proportional, the image does cover the entire display area while maintaining
its aspect ratio. Next, we have the
stretch mode options, defaulting to scale, which will allow the image
to stretch any way that is necessary to meet the demands of the expand mode and anchors. Tile mode is for images that are smaller than
the size provided. So let's swap the image
out for the Gudo icon. Now the area is filled
with tiles of the image. Undoing this change, keep will ignore the
expand mode and force the image to keep its
original dimensions and aligning it with
the top left corner. Keep centered will also keep the original dimensions
of the image, but center within its area. Keep aspect will allow the image to shrink or grow
to fit inside the area. Switching back to Ignore size, since the aspect
ratios don't match, the image doesn't cover
the entire screen. Keep aspect centered
does the same, but also centers the
image within the area. Keep aspect covered
will resize the image, maintaining its aspect ratio, but ensure that it
covers the entire area. So now the left and right edges of this image are being cropped. If you want to make sure
the entire image is shown, you would probably use
keep aspect or keep aspect centered and change
the background color to fill the unused area. If you don't mind
cropping the edges, using keep aspect covered is also a good option for
background images. The flip horizontal and
flip vertical toggles will just flip the image if that's something
you wish to do. Let's press play and
see how it looks. Remember that if we
resize the game's window, the anchors will change and affect how the
image is drawn. And while the image will scale
itself to fit the window, the title will only
reposition itself, since it is being drawn
to a specific font size. But we can change some settings to have more control
over this behavior. From the main menu,
select project settings. Then look under
display for window. Here we can edit our
viewport dimensions, changing the dimensions of the game window when
we run the game, and also the dimensions of the blue rectangle
in the editor. It's a good idea to
set this to match the dimensions of your
target publishing platform. The anchors haven't updated yet, but selecting or resizing the nodes will force
them to update, and we can reset their anchor
presets back to full rect. I we can change whether or not the game opens
in a window or full screen, but don't change
this setting until after you have some way
of exiting your game. You can also change where the
window will be displayed on screen by setting the initial
position type to absolute, then changing the screen
coordinates for the window. And also which screen
it will be displayed on by changing initial
position type to center of other screen, then specifying
the screens index. These options will only work
on an exported project, not when we run
them in the editor. We can make the
window borderless. And even change whether or not
the window can be resized. This is one way we
might fix the issue, but not a very
flexible solution. The settings under stretch
provide better alternatives. We can change the
stretch mode of the entire game from
disabled to Canvas items. Remember that all control nodes
inherit from Canvas item, so they will all be
stretched with the window. Another setting exists for stretching the entire viewport, which will include the
output of three D cameras. When performing this stretching, we can change whether or not the game's display will
keep its aspect ratio. Ignore will allow the game to squish and stretch
any which way. Keep width or keep height
will allow the game window to scale only as much as the
width or height will allow. Expand will let the games
display grow and shrink, still maintaining
the aspect ratio, but will expand to fill
the entire window. Scale can be used to
automatically reduce all canvas items to half size or up to
eight times their size. Useful for developing
for platforms that have drastically different
resolutions than your development machine. And scale mode can be set
to integer to restrict the scaling to only dividing or multiplying by whole numbers. This is particularly useful for pixel art since it won't
cause any pixel distortion, attempting to stretch or shrink pixels by
fractional amounts. Handheld orientation
settings are available for mobile development to
restrict to portrait, landscape, reversed, or use the device's sensor to
determine the orientation. And VSync mode can be
changed to determine how the machine's GPU is synchronized with the
display refresh rate. These settings are not
available when using the compatibility
renderer and won't be applicable to anything
we do in this project. We now have a background
image for our title screen. In the next lesson, we'll
add a start button. I'll see you in the next lesson.
18. Button A: Hello, friends. In
the previous lesson, we added a background
image to our title screen, and this lesson we'll
add a start button. Adding a new node to
the title screen, just like we have done before, this time we are looking
for the button node. Similar to the label node, a button can contain
text, but also an icon. Let's name the buttons start. Then also put the same
text in the text field, so it will be displayed
on the button. There is also an icon field, which we can populate with the Gadot icon to
see how it works. The button is drawn as a rounded rectangle encompassing both the text and
the icon provided. The flat toggle removes any textures or colors
from the button, drawing only the text and icon. However, the entire buttons
area is still clickable, and there is still
a focus style on the button that will be
drawn when it has focus. Expanding the text behavior
and icon behavior sections, we can change the
alignment of the text and icon to shift how they
are displayed on the button. These changes are much more apparent if the size
of the button is larger than it needs to be to accommodate both the
text and the icon. Some of the same
behavior settings are available as the label node
to control text overrun. Auto wrapping. And clipping if the button's size is too small to
accommodate the text. And the icon can be expanded to fill the
size of the button. BD options are also available for languages that are
written from right to left. Base button is a class that
all buttons inherit from, even buttons of other types. We'll go over these settings
in button lesson B. For now, let's edit
the properties inherited from control, starting with the layout. I would like my start
button to be below my title at two thirds
of the screen's height. So switching the layout mode
from position to anchor, I'll change the anchor
presets to custom. Then move the left anchor to 0.5 and the top anchor to 0.667. The right anchor can't be further left than
the left anchor, so it is automatically
changed to the same value. Likewise, the bottom anchor can't be above the top anchor. The button is currently to the bottom and right
of the anchor, so I'll change the grow
directions to both. Then reset the anchor offsets to center the button
about the anchor point. With the button positioned, the next step would be to
edit its theme properties. If you want the
button to be unique, then using the theme
override section for this button would
be appropriate. But if you want multiple buttons to share the same
theme properties, it would be better to only
have to set them once. Selecting the root node, we created a theme
in the first lesson, or you can create one now
if you haven't done so yet. Let's change the default font and font size for our theme, and we can see the effects of this change in the
theme preview. While this theme is a
property of the root node, not the button, making changes to this theme will
also affect the button. This is because the theme
is passed down through the scene tree from parent to child in another
form of inheritance. If a node uses theme override
properties like the title, then these settings
will override the inherited theme properties. Taking a look at
the theme panel, our theme preview contains a button node with the
default theme properties, except that the
font and font size have been changed to
the new settings. By clicking the Plus button, we can add node types to
the theme like button. This adds several properties that we can add to
the theme and edit. Starting in the color section, we can change the
color of the text and icon while the button
is in different states. The normal font color of buttons needs to contrast
with its background. I'll just use the
default light gray. Often a disabled
button will have gray text, sometimes
even transparent. Hovered buttons might
lighten the color. Pressed is sometimes darker, and I'll also add
an outline color. All of the same color options are also available for the icon, which will be multiplied by the icon texture's colors
to produce the result. In the constant section, we can add the amount
of pixels separating the text and icon when they
are horizontally aligned. As well as a maximum
width for the icon, the icon's height
will automatically be adjusted keeping
its aspect ratio. And we can specify the outline
size for the text, too. We haven't covered
style boxes yet, but if using style boxes
of different sizes, you can add this property and set its value to one to force the button to always use the size of its
largest style box. We can change the font for
all buttons if we want to, but I'll just use my
default theme font. I will, however, make
the font size for my buttons a little bit bigger
than my default font size. In the icons section, we can specify a default
icon to use for all buttons. The icon set in the
buttons properties will override this icon
if it is provided. So if you want most
every button in your game to have the same
icon, you would set it here. But you could also
have special buttons with unique icons, too. The next section is for styles, where we can draw a different
background for the button, other than the
default transparent black curved rectangle. Starting with
normal, this is what the button looks like when
it isn't doing anything. Adding it to the theme, we have multiple options for
how we provide styles. Style boox texture
is used to draw imported images to use as
the buttons background. I have these button images
which I will import into Gadot with the assets
folder already selected. That way, they are imported into that subfolder
automatically. Click on the style box to
open it in the inspector. Then set the normal texture
to the normal style box. I don't want to modify
the texture in any way, but the text and icon don't really fit well into
the background image. So I'll add content margins
to force the icon and texture away from the sides into the flat interior
of the button. Moving on to the pressed style, we can add another
style box texture. But this time populate the texture with the pressed
image of the button. Then add the same content
margins as normal. And repeat this to add
disabled and hovered. I don't have a unique
texture for Hover press, but we can quickly
duplicate a style box by clicking and dragging the one
from pressed into the slot. Selecting the button
in the scene tree, we can preview what the button looks like when
it is disabled by checking the disabled toggle in the properties inherited
from base button. Running the game,
we can also see the button style change
when hovering over it and pressing it. However, once pressed,
the button now has focus, which gives it
this white border. If you want to remove this, you can provide another
style box for focus. Or alternatively, we can just add a style box
empty to remove it. This button doesn't
do anything yet, but we'll have to wait
for another lesson. We now have a start button
for a title screen. In the next lesson, we'll
add social media links. I'll see you in the next lesson.
19. Button B: Hello, friends.
Before moving on, we'll go over a few more
features of buttons. Remember that none
of the changes made during this
lesson will be saved. We covered the disabled property of the base button class, but there are many more
properties listed here. Toggle mode will turn the
button into a toggle, meaning once it is pressed, it will stay pressed
until pressed again. Button pressed can
be used to set the default state of the button while it is being
used as a toggle. If it's not a toggle, the button cannot
be set to press. We can change whether the button is considered to be pressed when the mouse button is released or when the mouse button
is first pressed. And also specify which mouse button is being
used to click the button. If keep pressed
outside is turned on, then clicking the button and moving the mouse outside
the boundaries of the button will maintain its pressed state until the
mouse button is released. A button group can be created to turn a toggle button
into a radio button, meaning only one button
can be pressed at once. We'll see how this works when we use it in
a later section. And lastly, the button can
be assigned a shortcut. Let's use the Enter key as a shortcut to press this button. Expanding the shortcut
by clicking on it, we can see that it
contains a list of events, which we also need to
click on to expand. The list is empty, so
it has a size of zero. But we can click the plus add element button to add
an event to this list. Clicking on the drop down, we will use an
input event key to react to the player pressing
a key on their keyboard. Clicking on the input
event key to expand it, there are a lot more
properties here, but we only need to press the configure button to open an event configuration window. This window is actively
listening for input events, so pressing any key on the keyboard will
automatically select it. We can also add the requirement
that they hold down other keys like
Shift Alt, control, windows or
automatically detecting the control or command key based on the user's
operating system. Pressing Okay, will fill in the required information for us. Shortcut feedback will change whether the button's
style changes when the shortcut is used, and shortcut and tool
tip will automatically add a tool tip to the button
that shows the shortcut. Leaving both of these on,
let's see how it looks. Hovering over the button,
the tool tip tells us that Enter is a shortcut
key to press this button. Pressing the enter key on the keyboard also
presses the button. If we turn these options off, the tool tip is removed, and while the enter key
is still a shortcut, it provides no visual feedback that the button
is being pressed. Selecting the root node
and taking another look at the style box texture
options for our button. I'll swap out my texture with the Gadot icon for
demonstration. Texture margins can be added
to create a nine slice. Nine slice textures
can conveniently be resized with the corners of the image retaining
their original size, stretching the edges
along a single axis and stretching the interior along both axes to fill
the extra space. How these axis stretches are implemented can be changed in
the axis stretch settings, either stretching the
image, tiling it, or a combination of tiling and stretching to attempt
to remove seams. Expand margins can
be used to make the style box larger than its
area should normally allow, expanding it in any of
the four directions. The sub region is used to slice sprite sheets into
individual textures. Clicking on the
Edit region button opens a region editor window. This window allows clicking and dragging to
choose the region, adjusting the edges
and corners as needed. There are options for pixel snapping for greater precision. Grid snapping if
your sprite sheet is evenly sized and spaced, you can specify the grid size and offset to match
the sprite sheet. Then select one or more grid
cells to form your texture. Or auto slice will automatically find the textures for you, and you can just select
one by clicking on it. Modulate can also multiply the entire texture
by another color, adjusting it to a
different color. This is often used to add tint, or if your texture is
entirely grayscale, then modulation can be used
as the main source of color, creating a wide variety of colored style boxes
from a single texture. Other options are
also available for creating style boxes
in the theme editor. All of the other style
box options also have the same content margin options
as the style Box texture. Style boox empty will draw nothing and has no
unique properties. While Stylebox line will draw a single horizontal line
across the top of the button. Clicking on the Stylebox line, we can change the line color. Thickness. Switch it to vertical and grow the
line in either direction. Using negative values here
will shrink the line. Style box flat will draw a
simple colored rectangle. Clicking on the style box flat, we can change its color and
skew it in either direction. If we add a border width, then we can also change
the border's color. And blend the border into
the button's background. Toggling draw center will remove the background to
draw only the border. If we add corner radius, then the rectangle will curve
like it did by default. And we can change how many
edges are computed to create the curved corners with the corner detail property. We can add a shadow behind
the button, change its color, size, and offset it in
both directions too. And anti aliasing
can be disabled, or we can change its size to make the edges look smoother. That covers all of
the button properties and style box options. In the next lesson, we'll
add social media links. I'll see you in the next lesson.
20. Link Button: Hello, friends. In
the previous lesson, we added a Start button
to our title screen. In this lesson, we'll
add social media links. Let's start by adding a new
note to the scene tree. This time, it is a Link button. We'll name this node after
the social media platform, in my case, Patron. Then also populate the text field with the
same information. Copying and pasting the
desired URI into the field, nothing more is required to make the Link button do
exactly what we expect. Running the game, we can
click on this Link button to open a browser window to
the URI we specified. The underline
option allows us to change whether the text
is always underlined, only when the mouse is
hovering over it or never. The same BD options
are available for languages that write
from right to left, and we have all of
the same properties inherited from the base button class if we
want to use them. This works, but isn't what I want for my social media links. I would prefer to have the
platform's icon beside the text and have both
clickable as one button. To do that, I'll need
a texture wrecked node to display the icon. I'll rename this node icon and use the Gadot icon as
a placeholder for now. I don't want the icon
to be this large, so I'll set it expand
mode to ignore size and stretch
mode to keep aspect. Then scale it down to a
more reasonable size. Next, instead of using the Link button node
to display the text, I'll use label node. I'll rename this node handle and put my handle
in the text field. Then move this to the
right of the icon. I need to bundle these
two nodes together, make them act as
one single node. We can do this by using
another node as their parent, like the Link button node. Group selecting the two nodes, we can drag them over
the Link button node to reparent them to
the Link button. So they are now children
of the Link button node. By moving the parent node, both of the children
move along with it, maintaining their
relative positions. If we extend the size of the Link button node to
cover the two children, this entire area becomes a clickable button that links to the social
media platform. We can also anchor
the Link button node to any location on screen, and it will automatically adjust the relative positions
of the child nodes. An easier way to
be more exact with the horizontal alignment
of controls and keep them consistently
spaced is to use another control node that
serves exactly this purpose. This node is called a
horizontal box container. It will automatically
arrange all of its children horizontally with consistent
spacing between them, making this the child
of the link button, then the icon and
the handle children of the horizontal box container. The icon and the handle now have their layout mode
set to container. Since they are inside
of a container, the container is responsible for determining their
size and position. So these properties
are disabled. The children are also forced to collapse to their
smallest allowable size, which means the icon has been
squished down to nothing. If we change the expand mode
to fit with proportional, this expansion mode is
ideal for horizontal boxes, making sure that the
texture's width matches the height allowed by the
horizontal container. Let's select the root
node so we can see the theme editor and add the horizontal box
container to the theme. This node type has only
a single property we can set in the constant section, which is the number
of pixels that will be used to separate
the nodes children. Feel free to change this value
and see what looks good. Selecting the horizontal
box container, its size was originally
40 by 40 pixels by default and expanded to cover
its children horizontally, but the height remains 40. If we click the reset button, the container will attempt to
collapse as much as it can. Since the label
node's text size is demanding 31 pixels
of vertical space, the container is
also 31 pixels high. This height is also
given to the icon, whose own settings set its
width to match its height. Since we now know the exact size we need for this
icon and handle, we can copy these numbers into the custom minimum size
property of the link button, ensuring that it will always encompass the entirety
of its children. Then reset its size property to force it to
collapse to this size. We now have a social
media Link button that includes both an
icon and a handle, and the entirety of
both can be clicked. Let's create some new asset
folders, first for icons, and another inside that for social media icons to keep them separate
from other game icons. Then import some
social media icons with this folder selected. We can then replace the Gdo icon with the appropriate icon. Selecting the link button node, we can duplicate it using Control D or Command D to
add more social media links. Each duplicate will add
an increasing number to the name since sibling nodes
cannot have the same name. All of the child nodes will
also be duplicated but retain their original names since they are not siblings with
their duplicates. We probably want these links
to be arranged vertically. We can do this with a
vertical box container that works just like the
horizontal box container, but arranges its
children vertically. So let's add that as a child of the title screen and
rename it to socials. Then group select all of the link button nodes and reparent them to this
vertical box container. The container automatically arranges them vertically for us. I'll rename each link button to the appropriate social
media platform. Now that we can see
all of our links, let's change their
URIs. Their icons. And their handles, if necessary. Since my ink buttons are
now different widths, the vertical box container will expand them all to
the largest width, making several of
these link buttons much longer than they should be. Group selecting
all of the links, we can change the
container sizing property from this pop up
menu in the preview window, changing their
horizontal container sizing from fill
to shrink begin. So each link button
will collapse its width to the minimum
size we set earlier. These container
sizing properties can also be found in
the layout section of the properties
inherited from control by expanding the
container sizing section. Both horizontal and vertical
options are available, collapsing towards the
beginning, center, or end of the container or filling it entirely
in either direction. Let's anchor these
social media links to a corner of the screen
where they look good. I'll put mine in the
bottom left corner. It's not a good idea
to have these kinds of icons or text touching the
edge of the window like this. We usually prefer to
have some margins. We can easily offset
the position of the container node by changing its anchor presets to custom, then adjusting the
anchor offsets, adding a positive value to the left side to move it away from the left edge
of the window, and a negative value to
the bottom to move it up. Selecting the root node so
we can see the theme editor, just like with the
horizontal box container, we can add vertical box
containers to the theme to change the number of pixels used to separate their children. I would like to
have most vertical box containers in
my project have the same uniform
pixel separation as my horizontal box containers. But I'll use a theme
override to make this particular vertical
box container an exception and double
the separation to spread these links
out a little more. And we can change the order of the links easily by clicking and dragging them around
to reorder them in both the scene tree
and the container. We now have social media
links on our title screen. In the next lesson, we'll add
a variation to our theme. I'll see you in the next lesson.
21. Theme Type Variation: Hello, friends. In
the previous lesson, we added social media
links to our title screen. In this lesson, we'll add
a theme type of variation. Let's add a new label to our scene for testing
purposes only. One without any theme
overrides, unlike the title. We'll be using lots
of these to display all kinds of text and
numbers on screen later. Selecting the root node so
we can see the theme editor. We can add a new type to
our theme, the label node. Like we've done before, we can change the colors constants. Font, font size, and
style, if we want. I'll just use the default
font and default font size I've already set for my theme. This creates a consistent
look and feel for all or at least most of
the text in our game. The title being unique, uses theme overrides to define its own special
theme properties. While I like the stylish
font from my game's title, text, and start button, these social media links should be very clear
and easy to read, especially because my name
is so difficult on its own. So I have imported a second font to use only for these links, Roboto, while the
rest of the text for my game should remain
in the stylized font. We could select each
handle label and use the theme override section to change their font like
we did with the title. But what if we want to change it or we want to add more
of these link buttons? We would then have
to edit each of the nodes theme overrides to
make sure they all match. It would be better to use
our theme to set them all at once while keeping them separate
from other label nodes. But we've already added the
label type to our theme. So in cases where we want to
set a theme for some nodes, but not all nodes
of the same type, we can add a type
of variation to the theme by simply typing
our own name into this field. I'll name it handle, since that's what I'll
be using it for. This new type has no
properties yet because the theme editor doesn't
know what the base type is. We have to go to
the last page and set the base type
for this variation. And the base type is the type of the nodes we want
to effect, label. We can either type the
name into this field or click the plus button to pick the base
type from a list. This will add all of
the same properties to this type variation as would be available to a label node. So we can add the same colors, font color, outline
color, shadow color, constants like outline
size and shadow offset, font, which I will
change to roboto. And font size, which
I would like to be slightly larger than
the default at 28. None of these changes have
affected any of our labels, the title, text, or the handles. Selecting the handle nodes, we can even group select
all of them together. Then expand the theme section. Next, clicking the
Edit button beside the variation field and type in the name of our
theme type variation. Just like that, all of these label nodes will now
use the theme type variation, so we can adjust
their unique settings collectively while keeping them separate from other
labels in the project. Changing the font and font
size might also cause the horizontal boxes to change their size to
accommodate the text. So I'll quickly adjust the custom minimum sizes of
my ink buttons to match. And I'll now delete
this extra label node since it isn't needed
for the title screen. We now have social media links
that are easier to read. In the next lesson, we'll add a background
for the main game. I'll see you in the next lesson.
22. Panel: Hello, friends. In
the previous lesson, we added a theme type
variation to give some nodes a consistent theme that differs from that of other
nodes of the same type. In this lesson, we'll add a basic background
for our main game. Normally games would
have a different scene for the title screen
and the main game, but in this project, we're
doing everything in one scene. By default, the
background is just gray. If you want to use an
image for your background, you would use a texture
rec node to draw it. I don't want just
a simple image, so let's start by adding
a new node to our scene. This time, a panel node
and rename it game. Panels don't really do anything more than a standard
control node, except that they also
draw a style box. Let's set its anchors to full wrecked so it covers
the entire window. The default style box for a panel is the same as a button, just a semi transparent black rectangle with
rounded corners. Before importing the
image for my panel, I'll create a new
asset folder for styles and another one
inside that for button. Then move all of my button
styles into that folder. Then another folder for panels, and I'll have that
folder selected when importing the
panel style textures. Selecting the root node so
we can see the theme editor, let's add panel to the theme. Panels have only one theme
property, their style box. The same options are available for the panel as there
were for buttons, empty, line, flat or texture. Let's use a style box texture, then click on it to open it in the inspector and assign the texture as one
that was imported. With the default settings, the texture is stretched to
fit the size of the panel, which doesn't look very good. Switching the axis
stretch property to tile, the image will be tiled instead of stretched to fill
the allotted space. Using tile fit will do both
tiling and stretching in such a way that the
entire region is covered with whole tiles
instead of fractions. This image is a little too busy, it might be too distracting, so I'll use the rope
border image instead. If we add some texture margins, the image will be sliced into nine different textures
that will be used to build the style box with the corners retaining their
exact pixel dimensions, the edges being stretched
or tiled to connect the corners and the center being stretched or tiled to
fill the remaining area. Now that we have a basic
background for our game window, we would need to have it
hidden by default when the game first runs so we
can see the title screen, which we can do by clicking on the eye icon beside
it in the scene tree. But since we will
spend most of our time developing the game editing
the contents of this panel, it would be better if
this were visible to us, and the title screen
was hidden instead. So we want to swap the game node with the title screen node, making the game node
the scenes root node. Right clicking on the game node, select the option
make scene root. The game node is
now the root node with the title screen
as a child of it. So the title screen is
originally drawn over the game. To switch from the title
screen to the main game, all we need to do is hide the title screen by turning
off its visibility. However, now the panel theme is no longer being applied
to the panel node, since the theme is a property
of the title screen, I will only affect the title
screen and its children. With the title screen selected, expanding the theme section, we can click on the arrow beside the theme and save it
as a project resource. I'll just leave it in the
project's resource folder and name it after the font. Trade wins. This theme is now saved in our
project's resource folder. So we can select the game node, expand the theme section, and use the drop down
to load this theme. Now the theme is being applied to the entire
project like before, since it is a property
of the root node. We can also remove it from
the title screen node since it is being inherited through the scene tree anyway. Next, we'll need to attach
a script to the game node, either by selecting
it and clicking on the attached script button or right clicking on the node and
selecting attached script. We'll name this script
the game manager and create a new folder to hold all of our project scripts. Then create the script
in that folder. The script will be written in GD script and
inherit from panel, the type of the node it
is being attached to. This automatically
switches our view to script view and opens the
script we just created. This script only needs to turn the visibility of the
title screen on or off. To do that, we'll need a reference to the
title screen node. Right clicking on the
title screen node, we can access this
as a unique name as long as no other node in the scene tree has
the same name, which will be indicated
with a percentage sign. Then clicking and dragging
this node into the script, holding down the control or command key while
releasing the mouse, we will store a reference
to this node in a variable. I'll proceed the
name of the variable with an underscore to
mark it as private, meaning it is only meant
to be used by this script. Overriding the ready function. The blue arrow icon beside the function tells us
that this function is overriding the definition
provided in the node class. This function is called when the node and all of its
children are ready, we can tell the title
screen to show itself. Selecting the start button, we can switch the inspector
panel to the node panel, which gives us a list of all the signals
this node can emit. The signal we want to react
to is the pressed signal, which will be emitted when
the button is pressed. Double clicking on the signal or right clicking and
selecting Connect. We can connect this
to the game node, which will automatically
generate a new function for us on Start Pressed. The green connection
icon to the left of this function tells us that a signal connection will
call this function. When the start
button is pressed, we will tell the
title screen to hide. Running the game, the
title screen is shown, and when we click
the Start button, it hides, revealing the
game panel underneath. We now have a background
for our game window. In the next lesson, we'll
add a fade transition. I'll see you in the next lesson.
23. Color Rect: Hello, friends. In
the previous lesson, we added a background for
our main game window. In this lesson, we'll
add a fade transition to switch between the title
screen and the main game. Currently, the title screen
is displayed immediately, and pressing the start button
cuts to the game panel, which is a bit jarring. It would be better if we
transitioned gradually, which we can do easily
with another node. The color wrecked node. Let's rename it fade. The color wrecked node simply fills its boundaries
with a solid color, which we can change
in the inspector, so we will set it to black. Then set its anchors to full wreck so it covers
the entire window. This is how most
games start end and change scenes with just
an empty black screen. We can adjust the transparency known as Alpha of
this color to turn it invisible gradually over time and back to solid black
again whenever we need. Let's hide it so we can see our game while we're
working on it. Attaching a script to this node, saving it in the scripts folder, the purpose of the script
will be to fade in and out. Anytime we are adjusting
a value over time, we can use a built
in class of Gadot, a tween, which we will
store in a variable. Adding a function, let's
call it too clear. The function can return a signal that will be emitted
when it is done. The first thing we need to
do is create a new tween. Then assign this created
tween to our variable. This is a function
of the node class. Any node can create a tween. Then tell the tween
to tween a property. This function has four
parameters. Which node? We can use the keyword
self to say this node that the script is attached to the
fade node, which property? As a string encapsulated
in quotation marks, we specify the name of the
property, which is the color. The final value of the property, which is the color clear. Let's just write that
in all caps for now. And lastly, the
duration of time. Let's say 1 second, for example. We can then return the
finished signal of the tween, which will emit the signal automatically when it finishes. Clear is undefined,
so let's give it a definition by declaring it at the top of the
script as a constant. A constant is the
opposite of a variable. While they both
store information, a constant never changes. They are conventionally
named in upper snake case, and we can assign it
the value of a color. Then specify in brackets
zero red, zero green, zero blue and zero Alpha, which is black, but
also fully transparent. Switching over to the
game manager script. Let's give the
fade a unique name and add it to the game
manager as a variable. It can be told to
show itself first. Then after the title
screen is shown, we can fade to clear to gradually reveal the
title behind the fade. When the start
button is pressed, we can first fade to black before hiding the title screen, then fade back to clear. But hiding the
title screen needs to wait for the fade
to black to finish. Since we are returning a
signal from this function, we can use the await command to pause this function until
the signal is received. So we also need to write the two black function
back in our fade script. This function will do the exact same thing as
the other function, but only changing
the final value that the color property
is being tweened to. Instead of duplicating
all of the code, it would be better to make
this a private function, than have both of the
public functions call it. We'll name it tween
color and change the final value argument of the tween property
function to final color. Then add final color as a parameter of the
function declaration. Both too clear and
too black can now return the return
value of tween color, passing clear as an argument or color dot black since black is already defined
for us in the color class. If we were to call the
two clear function, then the two black function, while the tween is
currently busy, it may cause some
unwanted behavior. So anytime this is possible, we should first check
if the tween has already been created
and if it is running. If these are both true, then we can kill the tween
before creating a new one, preventing any
conflict from arising, having two tweens trying to
change the same property. If we aren't quite sure we want to use 1 second as the duration, we might want to turn
this into a variable too. Let's name it duration and
set its value to 1 second. Then add the export tag to
the front of the declaration to allow us to give this variable a different
value in the inspector. Selecting the fade node
in the scene tree, we can see the duration
variable has a value of one, but we can change it
to whatever we want. I'll try 2 seconds and
see how that looks. Running the game, the window starts completely black
but fades to clear, revealing the title
screen behind it. We can't currently click
on the Start button because we are actually clicking
on the fade color rect, even though it's transparent. The button isn't responding
to hovering over it either. Selecting the fade node, we need to look
in the properties inherited from control for the mouse section and change its filter
property to ignore. Now the mouse signals will pass through the fade node to
the nodes underneath it. So let's try running
the game again. If we press the start
button, it fades to black, hides the title screen, and fades to clear once more, revealing the game panel. We now have a fade
transition for our game. In the next section, we'll start working on
the actual game. I'll see you in
the next section.
24. Menu Bar: Hello, friends. In
the previous section, we created a title screen for our game that can transition
to the main game. In this section, we'll start working on building
the actual game. Let's start by adding a new
node to the game panel, a menu bar node. And sort it behind
the title screen. The menu bar node doesn't
draw anything by itself. So if you want a visual
representation of the menu bar, you would need to put this node inside another panel
or texture wrect. Then use the texture or style
box to draw an actual bar. I'm fine without
drawing an actual bar, so I'll delete this extra node. How the menu bar actually
works can be a bit confusing. If we ever need more information
on how any node works, we can always check
its documentation by right clicking on it and
selecting open documentation. We'll switch descript view and open the documentation
for this node. We can read from the description that this node will create menu buttons for each pop up
menu node added as a child. Returning to two D view, let's add a child node to the menu bar of
type pop up menu. This isn't strictly
a control node, but it's close enough. Simply by adding the
pop up menu node as a child of the menu bar, the menu bar automatically adds a menu button with the
same name as the node. Let's rename this
node something more commonly seen on a
menu bar like file. I'll also add another for
settings and one more named. You may need to make
some other changes before the menu bar will update. The menu buttons are overlapping with the game panel's border, which doesn't look very good. It would be better if it were moved further away from
the edge of the window. We could move it manually using either its transform position or anchors and anchor offsets. But I wouldn't want any of my game elements
touching the border, so it would be better to apply margins all around that
will apply to everything. The panel node
isn't a container, so it can't control
the positions or sizes of its children. Adding content margins to its
style box will be ignored. So let's add a container
to the game panel, specifically one that will add margins, the margin container. Then make that the
direct child of the game panel with the menu bar a child of the
margin container. Selecting the root node so
we can see the theme editor. Let's add margin
container to the theme. The margin container
doesn't draw anything. It only has constants in
its theme properties. How many pixels of margin
space are added to each side? Since the title screen is a sibling of the
margin container, not a child, it will be
unaffected by the margins. Let's also add menu
bar to the theme. The menu bar allows us to
change the font color of all the menu buttons
it creates in all of the various button
states we have seen before. In the constants section, we have the pixel
separation between menu buttons and the
font outline size. We can change the
font and font size, but I'll use my themes,
default settings for those. There are no icons,
and we can change the style boxes for
the menu buttons created by the menu bar in
all the same button states. For my menu buttons, I'll use style box empty as
the normal and copy it into disabled and a style box flat for pressed and copy it into
hover and hover pressed. Opening the style box empty, I'll just add some
content margins to make the button size a little
larger than the text. And for the style box flat, I'll add the same
content margins, some rounded corners,
and darken the color. Then make the hover style box unique so it isn't
tied to the others, then open it and change
it to a lighter color. Let's see how this looks
when we run the game. The menus are all
there on the menu bar. They have a curved
flat style box in a lighter color when hovered
and darker when pressed. Their pop up menus are
currently empty, though. There are a few
options we can use in the inspector to change how
the menu buttons behave. The flat toggle will ignore style boxes for the menu buttons and just draw their text. Turning off switch on hover
will force the player to click on the menu buttons to switch between them
instead of just hovering. And preferred global menu will allow this menu
bar to be drawn by Mac operating systems as the global menu outside
the game window. So if you don't want
that to happen, you can toggle it off here. But if you are using
the global menu, then you can specify where in the global
menu to start adding your custom pop up menus with the start index negative one, in this case, being
used to mean automatic. And, of course, BD
options are also available for languages that
write from right to left. We now have a menu bar
with several empty menus. In the next lesson,
we'll populate the pop up menus with
several options. I'll see you in the next lesson.
25. Popup Menu: Hello, friends. In
the previous lesson, we added a menu bar to our game. In this lesson, we'll add
options for the pop up menus. Selecting the pop
up menu node and taking a look at the
properties in the inspector, the bottom is an expandable
section named items. Here we can add each
of the options that will be available within
this pop up menu, like New Game, save,
load, and exit. These items can be
rearranged by clicking on the arrows or by dragging
the hamburger icon. I'll make a new asset folder for menu icons and import
some menu icons. Then assign each menu
item its own icon. For settings, I'll add another item and make this
one checkable as a checkbox. This would be appropriate
for something that can be turned on or off, like mute, for example. Then I'll add another
pop up menu as a child of this pop up menu
to create a sub menu, which I will name difficulty. The items in this pop up menu will be my difficulty settings, each checkable as
a radio button, meaning only one can
be selected at a time. Easy will be checked by default. Easy, normal, and hard. In the help menu, I'll add an item for about
with another icon. Then another item
that says support. By clicking on the
separator toggle, this menu option won't
be a clickable button, but a separator between
related groups of menu items with its name being written in this
separator as a label. I'll then add another item under the separator which
will be a link to my Patroon with the
social media icon. Selecting the root node so
we can see the theme editor. Let's add pop up
menu to the theme. Font accelerator color is
used to display shortcuts, but we don't have the option to add those in the inspector. Our pop up menus also
don't have titles, but the rest of the font color options should be familiar, along with extra font color
settings for separators. In the constant section, our pop up menus don't
have a closes button, but the horizontal
separation can be used to add space between the icon
and text of each item. The icon max width will
be useful for limiting the size of the icons to match the font
height of the text. There won't be any indentation
in these pop up menus, but padding can add extra pixels to the start and
end of each item, giving just a little
bit more space. Font Outline size we are
very familiar with by now, which can also be applied
to text and separators. These pop up menus can't be resized and don't have titles. But the vertical
separation can adjust the number of pixels
separating each menu option. There are properties for
font and font size of the menu options,
title and separators. I'll just leave all of these
and use the theme defaults. The icons page can be used to change the icons
used for checkboxes, closes button, radio
buttons, and sub menus. I have imported some new
icons to use for these, so I'll just replace
all of them. These images are 16 pixels
square and won't be resized. So make sure you're using a reasonably small
image for these icons. Even though our
pop up menus don't have a closed button,
I'll add those two. In the styles page, we can add a style box for the
pop up menus panel. I'll use a style box
texture drawn with the steel plate image and give it texture margins to keep the screws
from being stretched. The hover style box is drawn when the menu
option is hovered over. I'll use a style box flat and make it look the same
as the menu buttons. Since my separator has text, I can use the labeled separator left and labeled
separator right style boxes to change how
the separator is drawn to the left and right
of my separator's text. If the separator
doesn't have any text, then the separator style box
will be used to draw it. Embedded borders will not be
used by our pop up menus. Let's see how these pop up menus look when we run the game. Clicking on any of
our menu buttons, the pop up menu is
displayed with the panel. Each item can be hovered
over and clicked, but none of the options
do anything yet, and the difficulty
sub menu isn't there. This is not done automatically. We have to add it
through a script. So let's add a script to
our settings pop up menu. Saved in the scripts folder, inheriting from pop up menu. Grabbing a reference to
the difficulty sub menu, all we need to do is
when this node is ready, and therefore its
children are also ready, we can call a function of
the pop up menu class. Add sub menu node item, which requires a label, a pop up menu node, and optionally the index number of where the sub menu
will be inserted. We can just use the
node's name for the label and the node itself. By ignoring the index, the sub menu will be
added to the end. Let's add another
script to the file pop up menu so we
can add shortcuts. All we have to do is
export variables of the type shortcut so we can
set them in the inspector. Then when the node is ready, we can check if
these shortcuts have a value before setting them as the shortcuts to
the index numbers of each item in
this pop up menu. We can populate each
of these shortcuts in the inspector with conventional shortcuts we would expect. Control or Command N
for new S for save, L for load, and E for exit. Then we should go back
to the theme editor and add a font color for
our new shortcuts. Now when we run the game, our file menu has shortcuts and our settings menu has a sub
menu with radio buttons. But the sub menu icon is
hidden behind the items text. Unfortunately, the way
these items are laid out, the sub menu icon will only be visible if the item
isn't the longest. To remedy this, we could write some code to manually
edit the width of the panel or we
can rename some of our other items to be longer
than the sub menu item. I'll need to add some more
horizontal separation and padding to accommodate
my large font size. Et's take a look at the
properties of sub menu. The first three options
will determine if the pop up menu hides after
clicking on an option. If we uncheck the hide on checkable item
selection option, then the pop up menu
will not hide when clicking a checkbox or
changing the radio buttons. This might be useful
for the settings and difficulty pop up menus. A state item is similar
to a checkbox but can have more than two states and must be created
through a script. The sub menu pop up delay
is how long the mouse needs to hover over the sub menu before the pop up will appear. Allow search will make
each menu option in the pop up menu searchable with keyboard input while the
pop up menu is shown. The other options
are for integrating this pop up menu with different
operating system menus. Running the game again,
everything is more spaced out, and we can use
keyboard inputs to search the pop up
menu that has focus. We now have pop up menus for
each of our menu buttons. In the next lesson, we'll write scripts for our menu options. I'll see you in the next lesson.
26. Signals: Hello, friends. In
the previous lesson, we added menu options
to our menu bar. In this lesson, we'll
write scripts that will react to each of
them being pressed. Starting with the
file pop up menu, switch the inspector
panel to the node panel where we can see all of the
signals this node can emit. Can react to the ID
pressed signal by connecting it to the
file pop up menu script. So our script will react
to the player selecting any of the menu items or
pressing the shortcut keys. Unlike the button, this
signal passes us in argument. So the function generated has a matching parameter
of the same type, the ID number of the
item which was pressed. Back in the Inspector tab, we can see the ID numbers of each item if we need to
know them or edit them. Inside this function, we can use a match statement to run different code based on
which item was pressed. And keep things organized by simply calling a different
function for each. Let's simply print out statements that say which
function was called for now. After starting a new game, we will need to reset a lot of our control nodes back
to their default states, which would be kind of jarring
if it happens suddenly. So let's grab a reference
to the fade node. Then we can await fade
to black before creating the new save data and
fade back to clear after. It's impossible to predict
just how many things might need to react to the
new save data being created. Instead of resetting
them all here, it would be better
to emit a signal. We can declare our own
signals just like variables. Let's name it reset. Then emit it after we've
created the new save data. Any node in the scene tree can
connect to this signal and reset back to its default state when the player wants
to start a new game. Since this process isn't
happening instantly, we might want to restrict
when it can be called. If, for example, the
player starts pressing new game and load game
in rapid succession, this would cause
unwanted behavior. Let's declare another
variable for whether or not this script is
busy as a Boolean. Then when any ID is pressed, if this script is busy, it will ignore the input. Setting busy to true
before fading to black, then false after
fading to clear, none of these functions
will be able to interfere with each other
after one is called. Loading the game
will be essentially the same as starting
a new game for now. Exiting the game only
needs to set busy to true, fade to black, and then tell
the scene tree to quit. This will close the application on any platform except web. Web games tend not to have
the option to exit at all. Next, let's look at
the settings menu and connect its ID pressed
signal to the script. For now, I'll just
declare a variable to hold the current value
of mute as a boolean, which will naturally
default to false and another for the difficulty as an integer defaulting to zero. This pop up menu has only
one item, the mute toggle. So I'll just confirm
that the idea is zero, then call a function
to set the value of mute to be the opposite
of what it currently is. Giving this function
a definition, accepting the value
of mute as a boolean, we need to set the checkbox
to actually be checked. Hovering the mouse
over any property in the inspector we wish to
access through a script, we can see the property path, item zero slash CHEC. Right clicking, we can also copy this path onto
the clipboard. Then calling the set function, which accepts the property
path as the first argument, we can paste this
into quotation marks. Followed by the value, we want to set it to mute. This will mark the checkbox, but won't actually
mute anything. Switching the bottom
panel to audio, we can see Godo's
audio bus editor, which has only one
bus by default, the Master Audio bus, whose bus index is zero. Most game projects will have a bus for music, sound effects, and voiceovers, so each can be managed as
separate groups. Back in our script,
we can access this master bus from a built
in Singleton audio server. Then access the set
bus mute function passing the bus index, which is zero and the value
of mute as arguments. We'll go over Singleton classes in more detail in
the next lesson. Connecting the ID pressed signal of the sub menu to
the same script, We can set the
value of difficulty to match the ID
that was pressed. Since they're in ascending
order, this makes sense. We then need to set the checked property of all the items, since radio buttons should never allow more than one to
be checked at a time. Using a four loop,
we can iterate through each item in the
difficulty item count. So this will count
zero, one, two. We can then use the same
set function as before, this time on the
difficulty pop up menu, setting the item zero checked property to
be whether or not the value of item is equal
to the ID that was pressed. But we don't want to
only set item zero. So we're replacing zero
with percent S. This marks a position inside the string which will be
replaced with a value. Following the string with
another percentage sign, then the value to replace it with this will set
item zero checked, item one checked, and item two checked with zero
is equal to ID. One is equal to ID, and two is equal to ID. Before we create
the help script, let's quickly throw
together a simple about window by adding
a panel container to the game and rename it about and sort it to be drawn over the
other game elements. Then add a vertical
box container, a label, and a button. The label might
describe the project, its current version, and
who is developing it. The button will be used to
close this window and can be told to shrink to the center instead of filling the
width of the container. We'll then anchor this to
the center of the screen. Connecting the button's pressed
signal to the about node, we don't need a script
attached to the node. We can just pick the hide
function that is defined in the control class by toggling
off script methods only. Have it hidden by default. And give it a unique name. Finally, let's
create a new script for the help pop up menu. And also grab a reference to the About window and connect its ID pressed
signal to the script. Matching the ID, remembering that the separator
also occupies an ID, we can either show
information about our game or open a
URI in a web browser. Using the Singleton OS, short for operating system, using the function Shell Open, then specifying the website. This will not work for
web Build, however. So if the operating
system's name is HTML five, we should instead use the JavaScript Bridge Sileton
to evaluate the following. Window dot location dot href
equals then the website. This will now work
on any platform. Let's run the game and
try pressing our buttons. From the main game, pressing
Control end fades to black. We see the new game message
output, and it fades back in. Loading does the same thing. And saving just
outputs the message. We can check and uncheck
the mute toggle, change the difficulty
of radio buttons, open the about window, close it, and open my Patrion page in the
default web browser. We now have all of
our menu options reacting to player interactions. In the next lesson, we'll add the players save
data and manage it. I'll see you in the next lesson.
27. Save Data: Hello, friends. In
the previous lesson, we connected all of our
menu options to scripts. In this lesson, we'll create the player's save data and manage it with the menu options. In order to manage a Saffle, we'll need to
create a new script that describes what's in it. Let's name it
something like save data and inherit from
resource instead of node. This resource isn't
anything that will be drawn or even added
to the scene tree, but will just be a
collection of data that describes the
player's information as it is needed
to play the game. Starting at the top of the
script before extends, we need to give this resource a class name using the keyword class name
with an underscore. These are conventionally
written in Pascal case. We can then add any variables
we want to this script. Let's add whether or not
the volume is muted, which would be a Boolean and the difficulty
setting as an integer. And also any other variables that might be needed
to play the game, like the player's current
gold, for example. All of the variable
types must be primitives like Boolean,
integer, float, or string or built in classes that contain only
these primitive types, like vectors or colors. Any information you want to be saved must have the export tag. You may want to include some variables without
the export tag, which will not persist
between game sessions. While all of these variables
have natural default values, we can assign them our
own default values, too. Or if we want to include complex algorithms or
random number generation, it would be better to override the initialized function
inherited from object. Godot will automatically call this function for us when
creating the resource, so we can initialize any of
the variables we need to. This script must be saved before we can use it
in other scripts. Next, let's switch
to the file script. Since the resource
has a class name, we can declare a variable
using it as its type. Then the new game menu
option can create a new resource of
that type using the class name
followed by dot Nu. In order to save our game, we'll need to know
where to save it too on the player's
operating system, which is best stored
in a constant. Let's call it path as a string. Fortunately, Godot solves
this problem for us by providing the shortcut
user colon slash slash, which will
automatically point to a suitable location
on any platform. We only need to name the file. Following the name with the
file extension dot TRS will generate a text
resource file that can be read and edited
by anyone in Notepad. Definitely a good idea for
debugging and Indi projects. In order to save our game, all we need to do is access
a built in singleton class, Resource saver, which has
a function called save. This accepts a resource
and a path as arguments. Loading the game is
much like saving, except we assign the value of the save data to another
built in singleton class, resource loader, which has
a function called load, accepting the same
path argument. These singletons
we keep using are just scripts which
contain classes, not unlike many we have written, but they are part of the
Gadot game engine itself. They are called singletons because they are
written in such a way that there can only ever be one instance of
them at any time. This makes them very accessible
because we can directly access the unique instance
directly by the class name, the way we have done here without the need to
store it in a variable. We don't need to know where
it is or find it because the class name itself is a direct reference to
the instance of it, precisely because
there is only one. Most of the scripts we write as developers do not
need to be unique, and it is actually
preferable that they aren't we take the fade
script, for example, there is no reason why we
couldn't put this exact script on another node
in the scene tree and reuse it for a
different purpose. But then the need to reference the node the script is
attached to by storing it in a variable
is the only way of knowing which fade script
we are trying to access, since there would be
multiple instances. It is possible to create our own Singleton
classes in Gadot, but we won't need to
for this project. From the game manager script, let's give the file node a unique name and store a
reference to it in a variable. Then when the start
button is pressed, we can tell the file node
to start a new game. The file script will handle the fading back end for us so
we can remove it from here. Setting the default value
of busy to true will prevent the player
from accessing any of these functions
from the title screen, which is currently possible
using the shortcuts. And we should also disable the start button to prevent it from being pressed
more than once. Giving it a unique name. We don't need to store
it in a variable since we're only going to use it once and can just set it's disabled property
to true when pressed. In the setting script, we'll need to store a reference
to the file pop up menu in a variable in order to
access the save data resource, and we can delete these
temporary variables. When selecting the Mute toggle, we will need to set
the value of mute to the opposite of what is
stored in the save data. We will set the
value of the file save data resource
to match mute, making it the opposite of
what it was previously. Likewise, changing
the difficulty will also need to set the value
in the player's save data. When a new save data is
created or one is loaded, we will need to set these
values to match the save data. So selecting the
file pop up menu, we can connect the custom
signal we made last lesson to the settings menu script to automatically call
a new function. Allowing us to set
mute and difficulty to match what is in the newly
created or loaded save data. Let's try it out by
running the game. Using the shortcuts from the title screen,
they are ignored. Pressing the start button now creates our initial save data. If we select Save from the menu, then a save file is
created on our computer, which we can open in any
text editor like Notepad. We can see the values
of our variables. Back in the game, we can change the values of mute
and difficulty, save the game, and see the changes immediately
in our save file. If we exit the game
and start over, We can load our
save file and see the changes have been made
to the values we saved. We now have all of our
menu options functioning. In the next lesson, we'll keep track of the player's gold. I'll see you in the next lesson.
28. Counter: Hello, friends. In
the previous lesson, we managed the
players save data. In this lesson,
we'll add a counter to the screen to keep
track of their gold. Let's first think about
the overall screen layout and decide where to
put such information. With the menu bar occupying
the top left corner, I think that having players
resource counters in the same horizontal
line as the menu bar makes good use of the extra
space in the top right, leaving the rest of the window available for the main game. So with the margin
container selected, let's first anchor it to the
full size of the game panel. Then add a vertical box
container to organize the menu bar above the other components we
haven't added yet. To the vertical box container, we'll add a horizontal
box container, which will organize
the menu bar to the left and the resource
counters to the right. The resource
counters themselves, having both an icon and a number will also be horizontal
box containers. Let's name this one gold. Then give it a texture wrecked node and a label node
as its children. I have this gold coin icon I will use for my
game's currency, which I will populate as
the texture for my icon. When building a user
interface like this, it's a good idea to have
your counters default to the highest possible
number you want to allow. That way you know how much space needs to be allotted
to accommodate it. I think 10 million should be a sufficiently
large number here. The counter is
currently sorted by the horizontal box container just to the right
of the menu bar, but I would prefer to be in
the top right corner and any additional resource counters I might add to fill
into the left. Selecting the menu
bar, we can tell it to expand horizontally to fill
as much space as it can, pushing the remainder of the
horizontal box's children to move as far right
as they possibly can. Now, if I duplicate
my gold counter, each counter will be
added to the right, pushing the previous ones left. Next, let's add a script
to the gold node, which will be responsible for
making sure that the number displayed always
matches the number of gold in the
player's save data. So let's call it counter and save it in the
scripts folder. In order to access the
player's save data, we'll need to store a reference to the file node in a variable, and to change the
text property of the label node, we'll
need that, too. Then we can write a function
that updates the counter. All we need to do is set the text property of
the label node to be the value of file data
dot gold as a string. Whenever the player starts a new game or loads their game, the reset signal
will be emitted, which we can connect to this function to
update the counter. Next, we'll need to write one or two public functions
that will change the value. I'll write two
separate functions, one that receives the resource, and one for spending
the resource, both accepting an integer
parameter for the amount. When receiving, we'll simply
set the new quantity of the resource to be
plus the amount received, then
update the counter. But we may have decided
that there should be a maximum quantity
that can be displayed. So let's enforce that
restriction here. We'll export another
variable for the maximum quantity
and give it a value. We can then enforce this maximum using the minimum function. The minimum function will return the smallest of
all values provided. So either the quantity of
the resource plus the amount added or the maximum quantity allowed, whichever is smaller. When spending the resource, we first need to check
if the player has enough of the resource
to spend the amount. If the quantity of the resource is less than the
amount being spent, we probably wouldn't want to continue with the
transaction and return. If the player does have enough, then we can reduce the quantity by the amount and
update the counter. We can make this function more useful by returning a boolean, returning false if
the player doesn't have enough or true
if successful. So any game logic that calls
this function can react to the outcome of trying to spend this resource as being
either true or false. For testing purposes, let's
override the input function, which accepts an input
event as a parameter. Then check if either the up
or down keys were pressed. If up was pressed, I'll have the player receive a random number of gold 1-100. Likewise, when pressing down, they will spend a
random amount of gold, and I'll print out the result. Running the game, we can
see that the counter was set to match the initial
value of the player's gold. Pressing down, the
amount reduces until they can't afford
to spend anymore. In which case, the
counter is not reduced and the output
changes to false. Pressing up increases
the counter and eventually caps out at
the maximum quantity. This will work pretty
well for gold, but what if we have
different resources that we want to treat with all
these same functions? We would need a different
script for each resource, which isn't very good design. So let's make this script more flexible and export the name of the resource that
this counter is keeping track of as a string. Remember to set this exported
value in the inspector with the exact same spelling as the variable in the players save data that you
want to count. I'll also change my maximum gold value back to
what it should be. We'll also need to declare another variable to
hold the quantity to. Now, both transaction functions can start with first
getting the quantity of the resource from
the players save data using the G function, which accepts the property
name as a string. So we'll give it
the resource name. We will then perform
our transactions on the local variable instead of directly referencing
the players save data. And after performing
our functions, we can set the variable
in the player's save data in much the same way
using the set function, passing the name
of the property as a string and its new value. We also need to get the
current quantity from the player's save data
when updating the counter. Now we can duplicate the
gold node and have it track a different resource
with no changes to the script, only changing the values
of its exported variables. Et's add another resource
to the players save data, something like robots with
an initial value of one. Then also connect to
the reset signal to automatically update this
new resource counter. Now when we run the game, both counters are
displaying the amounts of different resources contained
in the players save data. Remember to remove or comment out the input function
before moving on. Also, the counters
display should have been updated after setting
its quantity value. We now have resource
counters for our game. In the next section, we'll start implementing some
game mechanics. I'll see you in
the next section.
29. Line Edit A: Hello, friends. In
the previous section, we created a menu
bar for our game. In this section, we'll start implementing some
game mechanics. Let's start by collapsing the horizontal box
container and maybe renaming it something more
descriptive like top. We can then add another child to the vertical box container to fill the remainder
of the game panel. I'll use another
margin container. I'll name this margin
container ship, since I'll be using it to
display information about the player's ship and expand it to fill the remainder of the game panel vertically. Inside the margin container, I'll put a vertical
box container to organize a few
components vertically, starting with a label node
that simply says my ship, so the player knows that
this is their ship. Under that, I'll
put a texture rect to draw an image of the ship, use the Gadot icon as
a placeholder image, tell it to ignore the
size of the image and give it custom dimensions
of 150 pixels squared. And finally, a stat block of the ship's
various statistics. Similar to a horizontal or
vertical box container, we can easily organize large
numbers of control nodes into a grid formation
using a grid container. Let's name this stats
and give it two columns, one for labels and
another for values. For the stats, I'll add
two label nodes for each, the ship's name, type, the number of crew members, and the amount of
cargo it can hold. The labels on the left side of the grid indicating what
type of value it is. The labels on the
right side can be populated with example
information for now, but we want this to be tied to information in the
player's save data. The grid container will
automatically sort its children into the number
of columns specified, keeping all of the rows
and columns aligned. Selecting all three child nodes of the vertical box container, I'll have them shrink
to the center. I would like the
player to be able to change the name
of their ship, so let's change the name label
node to a line edit node. This node allows
the player to edit the text inside it
while playing the game. I'll put all of this inside a panel container so it has
a nice border around it. Then tell the vertical
box container to shrink vertically to the
center of the panel container. From this display, we can
clearly see the information we will need to store for
each ship a picture, name, the type of the ship, both minimum and maximum numbers of crew and cargo capacity. Much like we did with
the player save data, we can create a resource script
to hold this information. Let's first create a new
script folder named Resources, and we can put the save
data script in here. Then make a new folder for ships and create a new script in this folder named ship
inheriting from resource. Here we can export variables
just like we did with the players save data
to describe a ship, including a texture
two D for the image, a string for the type, and integers for
the minimum crew, maximum crew, and
cargo capacity. I'll exclude the
ship's name since it won't be associated
with the type of ship. Be sure to save this script. Right clicking on the
ship resource folder, we can now create a new resource and make as many
ships as we want. I'll make three ships, a cutter, catch and a schooner. Double clicking on the resource, we can open it in the inspector and edit the exported variables, populating the image with
a picture of a cutter, filling in the type, crew limits and cargo capacity variables. Each of our resources
can populate the exported variables with
their own unique values, creating a category of
similar objects for our game that all have the same properties but with
different values. To better organize my scripts, I'll create a
subfolder for UI yes. Where I will place all of my sub manuscripts,
fade and counter. Then another folder
for managers, which will hold the
game manager and another new script
that I will attach to the ship node and
name it ship manager. The ship manager's
responsibility will be to manage everything that
happens with its children, including updating the
displayed pictures and text. In order to do that,
this script needs to know what all of the ships
are that exist in the game. So we'll export an array
of ships as a variable. Taking a look in the inspector, we can see our array
of ships and add three elements to it and populate each with
a different ship. I've designed my
ships to function as upgrades and would prefer them to be sorted
from worst to best. So I'll put the
cutter at index zero, followed by the catch,
then the schooner. Now, all we need to know from the player's save data is which ship the player
currently has. This can easily be
stored very efficiently as just the array
index of the ship. Switching over to the
save data script, I'll add an exported integer for the player's ship index number and have it default to zero. While I'm here, I'll also add a string for
the ship's name. Back in the ship manager script, in order to display the
player's ship information, we'll need a reference
to the file pop up menu to access the
player's save data. I'll rename this
node my ship Info. Then grab references
to the texture wrec, line edit and label nodes. I'll rename each
of these variables so I know they are describing the player's current
chip and also at a local variable to hold the
player's ship index number. Writing a private function to update the player's
ship information, each node will set the appropriate property to
that of a ship in the array, using the player's ship index
number to index the array. I'll display the minimum
and maximum crew together by casting them to strings and separating them
with a hyphen. Then also display
the cargo capacity with its units in tons. When starting a new
game or loading, the reset signal will be
emitted and we can connect to this signal to automatically update the player's
ship information. A after retrieving their ship index
number from the save data. But our ship still
doesn't have a name yet. I will randomly generate ship
names from a list by first declaring a constant
at the top of my script called ship names, which will be an
array of strings. I'll then populate this array with a bunch of
common ship names. In the on file reset function, if the ship doesn't have a name, I'll assign it a randomly
selected name from this list. Indexing the array
with a random integer between zero and the size
of the array minus one. The last thing we
need to do is allow the line edit node to change
the player's ship name, which can be done by connecting its text submitted signal
to the ship manager script. This will be omitted after
pressing the enter key. There are also signals
for every time the text changes or when the text size
limit has been reached. In our newly generated function, there are two possible
situations we should account for if the line edit
text is empty or not. Since the new text was
passed to us as a parameter, we can easily put it
in an if statement with true being not empty
and false being empty. If the new text is not empty, we can assign it to the
player's ship name. But if it is empty, we
can do the opposite, reverting the text
in the line edit back to the previous
value of the ship's name. This will prevent the player from having a ship with no name. It's also a good idea to tell the line edit node
to release focus, regardless of the outcome. The ship name can be
read directly from the player's save
data when updated. Returning to two D view, we can see that our
players ship information is populated with
placeholder information. When we run the game,
everything is automatically replaced with the information associated with the cutter ship, and the ship was assigned a
random name from the list. We can click on the line
edit node to change the name of the ship and press Enter to submit the change. Attempting to name the
ship nothing is rejected. Saving the game, then
starting a new game. The ship's name is
replaced with a randomly generated one and
loading the game, our saved ship name is restored. We now have our players ship information displayed and
can edit the ship's name. In the next lesson, we'll
go over the options available for line edit
properties and theming. I'll see you in the next lesson.
30. Line Edit B: Hello, friends. In
the previous lesson, we allowed the player to
change the name of their ship. In this lesson, we'll
go over the options available for line edit
properties and theming. Selecting the line edit node, let's take a look at its
properties in the inspector. Using the text property, we can give it a default value. If the text property is empty, then the placeholder text is displayed instead with
a different font color. We can align the text
to the center, right? Or filling the entire line
will space words out evenly. We can restrict the length
of the entered text. It's a good idea to test how
much text can be comfortably displayed using a wide letter
like a capital M or W. Then use that to judge
the maximum text length. If the line edit
is not editable, the player won't
be able to click on it and edit its contents. If expand to text
length is enabled, the box will expand to
accommodate the text inside it. Enabling the context menu
will allow the player to right click on the
line at at node to display a context menu. We can see this in the theme editor without running the game. The context menu
is automatically themed to match our
game's pop up menus and include all of the
typical stuff we would expect to see like cut, copy, paste, select all,
clear, undo and redo, a sub menu for changing
the direction of the text and options for displaying and inserting
control characters. If you do not want to include this context menu, you
can turn it off here. For console and
mobile platforms, virtual keyboard
integration is available, and there are multiple
options of virtual keyboard, specifically for inputting
numbers, decimals, phone numbers, emails,
passwords or URLs. A clear button can be included, which when clicked, will remove all the text from the line edit. The shortcut keys
that are displayed in the context menu will be used even if the
context menu is not. These can be disabled here. There is an option
to disable pasting using the Middle Mouse
button on Linux platforms. You can also change whether
or not the player can select text using either
the mouse or keyboard. Deselect on focus
loss will deselect whatever text was
highlighted when the line edit node loses focus. Drag and Drop selection can
be used to control whether or not the player can click and drag text within this line edit. An icon can be assigned to
the right side of the node to indicate better to the player
that this text is editable. A pencil is common, but I'll use a feather icon to fit the
aesthetic of my game. If there is both a right
icon and a clear button, the clear button will
be drawn instead of the right icon anytime there
is text to be cleared. Flat works just like buttons, removing all styles
from the node and only drawing
the text and icon. Draw Control characters is the same toggle option that was available in the context menu, allowing control
characters to be drawn. Lastly, select all on focus will automatically
select all the text when the node first grabs focus. Under the carat category, we can tell the
carat to blink and specify the interval of
time between blinks. The carat column is the character index of the
carat's current location. We can see it if we
force the line edit to display the carat
even without focus. If using the line edit for secret information
like a password, we can turn secret on, and every character
will be drawn as the secret character
specified here. Displaying the carat made
grapheme and BD options are also available for more
complex writing systems outside of the Roman alphabet. For my game, I will have
the placeholder text as name your ship with a maximum length of 16 characters and allow the box to expand to
accommodate the text. I'll keep the right
icon as the quill, select all of the text on focus, and have the carat
blank turned on. Other than that, the rest of the options are fine
as their defaults. Selecting the root node so
we can see the theme editor, let's add line
edit to the theme. In the color section, we
can change the color of the carat and the clear button as it is normally and pressed. We can change the font
color of the normal text, placeholder text, selected text, and uneditable text, as well as the outline
color of all of them, and the color of the box that is drawn behind selected text. In the constant section, we can change the
width of the carat, set the minimum
character width for the line **** box
measured in Ms, and the thickness of
the font outline. Like everything with text, we can change the
font and font size, but I'll leave them as the
default for the theme, and we can override the icon
used for the clear button. The clear button
colors will be used as modulation on
this icon texture. Lastly, in the style section, we can provide style boxes
for how the line edit looks normally when it has focus
and when it is read only. I'll use style Box
flat for normal. Then click on it to edit it, add my usual seven
pixels of corners, borders, and content margins. I'll have the same color as the background with a
darker border color. Then I'll duplicate
the style box flat to the focus option, make it unique, then
open it up for editing. When it has focus, I'll change the border to a lighter
color and add a shadow. We can see how our changes
look in the theme preview. For read only, I'll just
use a style box empty. Let's also add the grid
container to our theme. Just like the horizontal and
vertical box containers, the grid container only has constants for its
theme properties, the number of pixels separating
its rows and columns. Don't forget to test
how your changes look by running your game. We now have our line edit node configured to match the
aesthetic of our game. And the next lesson will allow the player to
purchase a new ship. I'll see you in the next lesson.
31. Option Button: Hello, friends. In
the previous lesson, we allowed the player to
change the name of their ship. In this lesson, we'll let the
player purchase a new ship. I'll just be recreating
a very similar set of information on the right
of this panel container, displaying the stats of a ship upgrade that is
available for purchase, starting with a
vertical box container, which I will name
ships for sale, then put both of these into a horizontal box to
organize them side by side. With both of the
child notes selected, telling them to
expand horizontally, they will share the
available space evenly. Inside ships for sale, I'll copy over the label
texture wreck grid container and all of its label nodes. I'll have ships
for sale shrink to the center vertically to match the layout of the left side. Changing the label to
say ships for sale. But I'll change name to cost, change the left label
to a texture rect, and use my coin icon instead of text to indicate that this
is the purchase price. I'll set it to fit
proportional to width and keep its aspect ratio to match the height of
the node to the right. Then change that back to normal label node and populate
it with a sample number. Then move these to the
bottom of the grid. I'll then also add a button to the bottom to allow the
player to buy this ship. And shrink it to the center horizontally to
match the layout. It might not make much sense to only have one chip for sale, so I'll change the ship's
type to an option button. This is a button
which will present the player with several
options to choose from. This option button is inheriting its theming
properties from button. I'm fine using my button
theme for the buy button, but I would like the option button to have a
different style. Selecting the root node
to see the theme editor, let's add option
button to the theme. This will allow us to override the theme properties which the option button
inherits from button. Most of the theme properties
for Option button are identical to that of
the normal button node, allowing us to
change the font and icon colors of all
the button states. Font outline color,
font and font size, and style boxes for each
of the button states, too. I'll use style Box flat for my option button and make it look just like
the line edit node. Then duplicate this
style box to pressed, make it unique, change the border color to
a lighter shade. Use it for hover, hover
pressed and focus. Then I'll just use a style
box empty for disabled. We can see how our
changes look in the theme preview window
without running the game. In the icons section, we can change how the down arrow looks by providing
a different icon. Just like with the pop up menus, this icon is 16 pixels squared
and will not be resized, it is important
that the icon you replace it with is
properly sized. There is also a theme
property for an icon that will be drawn to the left
of the text on the button. I'll just drop in another icon to show you what
that looks like. In the constant section, there is a property for setting the size of the button to match its largest style box in case your style boxes
are different sizes. This will work when my
option button is disabled, since the empty style box
is smaller than the rest. The arrow margin is
how many pixels are between the down arrow and
the right side of the button. Horizontal separation
is how much space is between the left side icon
and the button's text. Icon max width can be used to limit the size
of the left icon. It has no effect
on the down arrow. Modulate arrow, if given
any value other than zero, we modulate the arrow icon
with the font color of the buttons text in all of
its various button states. And the outline size draws an outline around
the text as we know. I don't want to
use the left icon for my game, so I'll delete it. Selecting the
option button node, let's take a look at its
properties in the inspector. The expandable
items section works just like the one we use
to build our pop up menus. So I'll add an item for
each ship in my game. Each item can be assigned
an icon if you wish, disabled or used as a separator. The ID number is
used to identify which item in the list
is currently selected. With selected as negative
one, no item is selected. Fit to longest item will size the entire button to best accommodate the item
with the longest text. Turned off, the
button will resize to fit the text of only
the selected item. And and the Allow
reselect toggle will allow the player to select the same item which
is already selected, emitting the selected
signal again. Many of the same options
are also available, which were inherited from
button and Base button that we've already explored. It's clear each of my
ships are going to need another property,
their purchase price. So switching to ScriptVew, I'll add a variable to the ship script for
the price of the ship. Remember to save the script. Then give each ship resource a different value
for their price. I'll rename my button By. Let's connect the By buttons pressed signal to the
ship manager script. And also the item selected
signal from the option button. There is also a signal
which will be emitted when an item in the pop up menu is focused if you need it. Just like we did with the
players ship information, we'll populate the
information on the right side of the screen
with the information of the ship which is selected from the option button
based on its index. So we'll need to
store references to the texture wrecked
option button and label nodes
plus the By button. Since I sorted all
of these nodes into a horizontal box container, all of these paths
are no longer valid. I could rename all
of my nodes to have unique names and use
unique name references, but I'll just add the horizontal box container to
the path instead. We can copy almost all
of the lines of code from updating the player's
ship information into the on type value selected function to update the information about
the ship for sale, simply changing
the array index to the index of the ship
which was selected. This will only work if the array indices of the
ship array are the same as the ID numbers of the items in the option
buttons pop up menu. We can guarantee
this by generating the option buttons items
ourselves from this script. Overriding the ready function, we can iterate through each of the ships in the ship array, then add an item to the option button,
the type of the ship. Since the ship array is
indexed from zero counting up, adding each item to
the option button, the ID numbers will
match exactly. But we must remove
all items from the option button and have it start the game empty
for this to work. When a new game is started
or a game is loaded, we retrieve the index of the
ship the player already has. I do not want to allow
the player to purchase the same ship they already have or one that is of lower quality. So I'll count from zero up to the size of the ship
array and disable each item whose
index number is less than or equal to the
player's current ship index. I'll then select
the next upgrade available to the player
if there is one. If the player's current ship is the last index number
of the ship array, there are no more upgrades, so I'll tell the option button
to select negative one, and all of the options
will be disabled. Otherwise, the option
button can select the item whose index is one more than the player's
current ship index. Since I'm allowing negative
one to be selected, this is obviously not
a valid array index, so I'll need to add a
condition to the on type value selected
function to accommodate it. And instead remove all
of the information. The last thing I want to do
is disable the buy button if the player doesn't
have enough money to buy the currently
selected ship. Grabbing a reference to
the player's gold counter. I'll simply set the value
of buy dot disabled to be the opposite of
gold dot has enough, passing the value of the
currently selected chip. This function doesn't exist, so let's switch
over to the counter script and add it quickly. Copying the definition of spend, we simply need to return
whether the quantity of the resource is greater than or equal to the
amount in question. While I'm here, I'll uncomment the input function so I can
cheat to get more money. Finally, when pressing
the buy button, we'll call gold dot spend, passing the price
of the currently selected ship for sale, and putting this inside of
an I statement allows us to react to whether or not the player has enough
gold to do it. This should never happen
since we disabled the button preemptively if
they don't have enough gold, but it's fine to
have redundancy. If the transaction
was successful, we'll set the player's
current ship index to the currently selected ship. Then update the player's
ship information and the ships for sale, too. Otherwise, I'll print out an error message so I
know this happened. When setting the selected index of the option button
through our script, the item selected signal
will not be omitted. But we can easily call our
function directly instead. Passing the selected
index, we just set it to. Running the game, the
player starts with a cutter and the catch
is displayed for sale. Expanding the option button, the player is not allowed
to purchase another cutter, but they can also
see the schooner. Buying the catch, the
player's ship information is updated and the schooner
is displayed for sale. Expanding the option button, the schooner is now
the only option. Giving myself more money, I'll re select the schooner
to activate the buy button. If we purchase it, now there
are no more ships for sale. I should also disable the buy button if there
is nothing to buy. We now have ships for sale that the player can buy
as an upgrade. In the next lesson, we'll create multiple different pages of information that the
player can switch between. I'll see you in the next lesson.
32. Horizontal Split Container: Hello, friends. In
the previous lesson, we allowed the
player to purchase bigger and better ships. In this lesson, we'll
allow the player to resize regions
of the game panel. This ship management
page from my game is clearly divided into two
regions of information, the player's current ship and ships that the
player can buy. Organized into a
horizontal box container, neither of these occupy the entirety of the
space provided for them, so they are expanding to
split the space evenly. In cases like this, there
is a control node we can use to give the player
more control over the layout, allowing them to resize
the division of a space between two control
nodes, a split container. Split containers can be
horizontal or vertical, but can only have two
child nodes and will split the available space between them and also allow the
player to adjust it. If I rename this node, all of my node paths will break. So I'll just leave
the name as is. It doesn't look like
anything has changed, but if we run the game and hover the mouse over the center
of the game panel, there is now a bar that
we can click on to grab and drag to the left or
right to resize each side. Each side will only be allowed
to shrink to the point where its own child
control nodes can't collapse any further. Selecting the horizontal
split container, let's take a look at its
properties in the inspector. The split offset can be used
to adjust the position of the split with zero
being where it would naturally sit after the
first child control node, negative moving left and
positive moving right. Collapsed will ignore
the split offset, collapse the first
child control node, and position the split
immediately after it. Dragger visibility can be
used to hide the dragger, making it unusable or both hide and collapse it removing the pixel separation
between the child nodes. Selecting the root node so
we can see the theme editor, we can add the horizontal
split container to our theme. In the constant section, we can remove the auto
hided behavior of the grabber by changing
the value to zero, which will make the
grabber always visible. Minimum grab thickness
is the pixel width or height of the clickable area generated to surround
the grabber, since the grabber icons
are typically very thin, and separation is the number of pixels that separate the
two child control nodes. In the icon section, there are three
different grabber icons, but we only need to
change the top one. The horizontal and vertical
grabbers are properties of the parent class
split container and will not be used by this
horizontal split container node. Running the game, we can
see that the grabber is no longer auto hiding when
the mouse isn't touching it. The area separating
the two child nodes is wider than it was before, as is the clickable area. We now have our margin container split into two
resizable regions. In the next lesson,
we'll give the player multiple pages of information that they can switch between. I'll see you in the next lesson.
33. Tab Container: Hello, friends. In
the previous lesson, we allowed the player to
resize part of the game. In this lesson, we'll
allow the game to display multiple different
pages of information. My game window is now
looking more full, and I would not be able to squeeze in more
mechanics without overwhelming the player with too much information being
on display all at once. To solve this, let's
make the node which is below the menu
bar a tab container. The description tells us
that the tab container will create a tab for
every child control, displaying only one at a time. Making the ship node a
child of the tab container. It becomes a tab within
the tab container. I'll add several more tabs by adding more children
to the tab container. All of them will be
margin containers. I'll have one to let the player manage the crew of their ship, another for buying
and selling cargo, and one for navigating
to another port. The tabs generated by
the tab container can be rearranged by changing
the order of its children. With the rearranging
of the scene tree, the ship manager
script will have disconnected its path to the
gold resource counter node. I can fix this by giving
the counter a unique name and using the percent
sign annotation instead of the node path. Returning to two D view and
selecting the tab container, let's take a look at
the properties of the tab container
in the inspector. The tabs are aligned to
the left by default, but they can be moved
to the right or center. They can also be moved to the bottom of the tab container. Current tab is the
child index number of the tab which is
currently being displayed. If there are so many
tabs that they do not all fit across the length
of the tab container, then the extra tabs
will be clipped and navigation buttons provided to let the player
scroll through them. If clip tabs is disabled, then the tab container will expand to accommodate
all of its tabs. Turning off tabs visible will
hide the entire tab bar. The tabs still exist and
can be used through script, but the player will not
be able to click on them. All tabs in front can
be used to draw all of your tabs over the contents
instead of underneath. I'll demonstrate what
this looks like later. If drag to rearrange is enabled, the player can click
and drag tabs to rearrange their positions
in the tab bar. If this setting is
turned on and you have multiple tab
containers visible and you want the player
to be able to click and drag tabs from one tab
container to the other, then you can set all of
the tab containers to have the same tabs rearrange group ID to allow this
sharing of tabs. With the default value
of negative one, tabs will not be allowed to relocate to any
other tab container. With used tabs for
minimum size turned off, the tab container will resize to accommodate the contents of
each tab as it is selected. Turning it on, every tab will have the size of
the largest tab. Tab focus mode can
be used to control how the tabs in the tab
bar can grab focus, defaulting to both mouse
and keyboard controls. You can restrict
to only allowing mouse clicks to give
the tab bar focus, so pressing the tab
key on the keyboard or arrow keys will not allow the player to switch
focus to the tab bar. But if the tab bar has
focus from clicking, then the arrow keys can still be used to navigate between tabs, or none can be used to never allow the tab
bar to grab focus. The player will still be able to click on tabs to
switch between them, but they will not grab focus, so the arrow keys will not
be used to switch tabs. If deselect enabled
is turned on, then the value of the
current tab can be set to negative one to have every
tab become unselected. After configuring the properties
for my tab container, I'll have it vertically expand to fill the space
of my game window. Selecting the root node so
we can see the theme editor, let's add tab container
to the theme. Starting in the icons section, we can override
the icons used for the increment and decrement
buttons the player uses to scroll through the
clipped tabs and their highlighted
counterparts which are displayed when the player
hovers the mouse over them. We can also change the
drop marker which will be displayed between tabs while
dragging and dropping a tab, and a menu icon, if this tab container has
been given a pop up menu, as well as its highlight. In the colors section, we can provide a modulation
color for the drop marker. A font outline color and font colors for every state of
the tabs in the tab bar. Before we continue with the
rest of the theme options, let's run the game and see
how these settings work. Since my tabs are being clipped, we can click on
the increment and decrement buttons to
navigate through them. I can click and drag
tabs to rearrange them, and the drop marker is
drawn and modulated to indicate where the tab will be positioned if I release
the mouse button. No menu icon is
displayed because this tab container has not been assigned a pop up menu yet. I'll change the drop marker
modulation back to white. I'll now delete the excess
tabs in my tab container. If we add a pop up menu as a
child of the tab container, it will not generate a new tab, since the pop up menu node
is not a control node. I'll put it as the top child of the tab container to
make things easier. And just put one item in the pop up menu
that says, Hello, If we attach a script
to the tab container, I won't be keeping this
script in my game, so I'll just leave
it in the main resource folder with
the default name. Overriding the ready function, we can call the tab container
function set pop up, which accepts a pop
up as an argument. So we can pass that get child with the child
index of zero, the top child, the
pop up menu node. Next, I'll switch to the
ship manager script. Tabs can not only have
text but also icons. But just like the pop up menu, they must be added
through a script. So let's export a
variable that will be the ship tabs icon
as a texture two D, which we can populate
in the inspector. Then in the ready function, knowing that this is a direct
child of the tab container, we can call Get parent to reference the parent
node of this node. Then call the function
of the tab container, set tab icon, accepting the index number of the tab and the icon texture
as arguments. So I'll pass it the index number three and the exported texture. Returning to the theme editor
in the constant section, we can give this icon
a maximum pixel width. The number of pixels separating the icon from the
text of each tab, the font outline size and how many pixels of margin space are added to either
end of the tab bar. As always, I will
ignore the font and font size settings and use
my default theme settings. In the style section, we can change how
the tab container, tab bar, and tabs are all drawn. I will set all of them with
different style box textures. Panel is the background fill of the tab container where the
active child will be drawn. This is usually drawn seamless with the
selected tab style. This image has rounded corners, so I'll add texture
margins of 16 pixels. I'll copy this over to the
style for the selected tab, but make it unique
since I do not want the tabs to have the rounded
corners on the bottom. Expanding the subregion section, then clicking on the
Edit region button, we can use only a portion of the provided image to
draw the style box. With grid snapping,
I can slice off the bottom 16 pixels to
remove the rounded corners. Now the selected tab looks like it is connected
directly to the panel. I'll also remove the
bottom texture margins. This will move the text and icon down on the selected tab, making it appear more as
though it is above the others. Copying this two unselected
and making it unique, I'll substitute the
texture with a darker one, then put the bottom
texture margin back in, moving the text and icon up. Then repeat the process for
adding different textures for Hard focus, and disabled. The tab bar background can
also be given a style box, so I'll use a shadow. Running the game, we now
have a stylized tab bar, and the ship tab even
has a ship icon. The menu icon is displayed
over on the right, and we can click on it to
bring up a pop up menu. I have no plans to
use this menu for anything in my game,
so I'll delete it. Remove the script from
the tab container and delete the script from
my project resources. If your tabs are very ornate in their design and you
give them expand margins large enough to overlap with the contents
of the selected tab, This is when all tabs drawn
in front can be used to draw all of the tabs in front of the content of the selected tab. I'll revert these changes
back to the way they were. We now have multiple tabs that the player can
switch between. In the next section, we'll use more new control
nodes to add more mechanics. I'll see you in
the next section.
34. Tree A: Hello, friends. In
the previous section, we allowed the player
to manage their ship. In this section, we'll allow the player to manage their crew. Switching to the crew tab, I'll add a similar
node structure to this tab as the ship tab, starting with a horizontal
split container containing two vertical
box containers, one for the crew the
player currently has, and the other for
people they can hire, both expanding horizontally and filling the space vertically. Then on the left side,
I'll add a new node called a tree and have the tree expand to fill
the space vertically. If we select the root node and take a look at
the theme editor, there is a tree here we can see populated with different
types of tree items, including plain
text, editable text, a checkbox, a roller box, and a list of options. The last three are also sorted as children of
another tree item. There is a scroll bar that
can be used to scroll through the tree items if the tree is taller than the allotted area. Parent tree items
can be collapsed or expanded to hide or
show their children. I thought this would be
a neat way to represent the chain of command for
my ship's crew members. Selecting the tree again, there is no way to populate
it with tree items from here. It must be done
through a script. Recognizing that
this is the same way the scene tree works in the
scene tab of the Gudo Editor, I can recreate exactly what I want in my tree
using basic nodes. With the top of the
hierarchy being the captain, followed by the first mate, the second mate,
and the third mate. I olden times, they may have
also had a fourth mate. I can rearrange these nodes so the first mate has
authority over the second, third and fourth mates and add more children
to each of them. The second mate is in
charge of navigation, so they will have a
navigator, helmsman, Brelman, topman and seamen
under their command. The third mate is in charge
of safety and combat, so they'll have the
boatswain or boatswain, a boat builder in
modern English, carpenter, surgeon, and
gunner under their command. And finally, the fourth
mate, if there was one, would be in charge of
logistics and resources, so they would command the
cook, butcher, and purser. I may also want to allow paying passengers on
board the ship, too. Attaching a script
to the crew tab, I'll export an icon for it, populate it with the
crew icon for my game, and set it as the tab
containers tab icon for this tab in the ready function the same way as the ship tab. Then I'll grab a reference
to the tree node. Let's write a function which
adds an item to the tree. We can add an item
to the tree by calling its function
create item, which accepts optional
arguments for the parent of the item being created and
the child index number. Ignoring these
arguments, this item will be created as
the root of the tree. If we want to change
anything about this item, we need to store a
reference to it in a variable of type tree item. I'll name it new item. Then we can call the
set text function on this newly created tree item, which requires a column
number and text arguments. The tree currently
only has one column, so the column index is zero, and I'll put the
text I want to set it to as a parameter
of the function. Knowing that I'm going to
have a lot of tree items, I'll store them in a dictionary, then assign the tree item at index of text to
be the new item. So now to add the
captain to the tree, all I have to do is
call my function passing the name of the
tree's top child node. If I want to add the rest
of them to the tree, I also need to specify the parent tree item when
calling create item, so that can be another
parameter of the function. When calling this
function for the captain, the parent tree
item will be null. Now I need to traverse the hierarchy I've
built in my scene tree, adding each item to the tree
with the correct parent, which seems complicated, but can be simplified
using recursion, meaning a function
which calls itself. To keep it even more simple, I'll keep the recursive
logic separate from adding a tree item and do
it in a separate function, which I will name populate tree. The first parameter
of this function will be the current
node in the scene tree, and I'll also need to know the parent tree item
if there is one. I can then call the AD
tree item function, specifying the name
of the node as the text and passing along
the parent tree item. I'll then store this in a local variable returning it from the ADTree
item function. Using a four loop to iterate through all of this
node's children, we can simply call this
populate tree function again on the child node and give it the parent tree item generated
from the current node. If the current node
has no children, this four loop will not run. The ready function only needs
to initiate this process, starting with the
tree's top child node and no parent tree item. Running the game, the
tree is populated with the exact node
structure from mycin tree. Each item in the
tree can be selected and parent items can be
collapsed or expanded. Let's select the
tree node and take a look at its properties
in the inspector. Columns can add extra
columns to the tree, giving each tree item multiple text entries,
one for each column. I'll have three
columns for my tree to display roles, names, and pay. Column titles are
hidden by default, but can be made visible
with this checkbox. Allow reselect will emit the selected signal again if the player clicks
the selected item, and we can change whether or not the ight Mouse button can
be used to select items. Allow search will let the player navigate the tree
items by typing, matching the text of each item. Hide folding will hide the
collapse or expand arrows, and enabling recursive folding will allow the player to hold down the shift key to expand or collapse children
recursively. Hide root will hide
the root tree item. In my case, that
would be the captain. The drop mode flags
can change how the player drags and
drops data into the tree, but that is beyond the
scope of this course. Select mode can change whether the player selects
a single item, multiple items, or
a row of the tree. Horizontal and
vertical scrolling can also be enabled or disabled. Now with multiple columns
and titles visible, I'll add titles to
each of my columns by calling the tree's set
column title function. Then also set the last
column to not expand. So the role and
name columns will occupy the majority
of the tree space. After grabbing a reference
to the file pop up menu, we'll need to connect
the file reset signal to the crew manager script to
react to the player either starting a new game or loading
their game and populate the remaining two columns of the tree with their crews
name and pay rates. Since the tree items are
stored in a dictionary, it would make sense
to also store the player's crew data in
a matching dictionary. For now, I'll just initialize
this dictionary to already contain a skeleton crew for the ship, starting
with the captain. Each member of the crew needs multiple pieces of information, so that can be stored
in another dictionary, giving the captain
a default name and a pay rate of zero gold. Then I'll add in a navigator, helmsman, topman and seaman, each with a name and pay rate. Back in the crew manager script, when the file is reset, we can populate the tree with
the player's crew members, but we may also need to erase ones that
are already there. So iterating through
every role in the tree, we'll check if the player's
crew has that role. Then call another function, specifying the tree
item we are changing, along with the name and pay
rate we are changing them to with blank values in the case that the player
doesn't have that role. Writing this function,
accepting the same parameters, we can just set the
text of the tree item at the other columns to
be the values passed in. Now the tree is automatically populated with the names and pay rates of the player's crew, but I would like the
captain's pay to be blank and their
name editable. Setting the tree roots
item to be editable at column one is pretty
simple with a function call. Then when populating
a crew member, I'll only set the text of the second column if the
item is not the root item. Connecting the item edited signal to the crew
manager script, we can react to the player
editing their captain's name. This doesn't provide
us with any arguments, but they are stored
in the tree class and can be retrieved
with get functions. In the case of my tree, only one tree item is editable, so I don't really need
to know anything other than what the player entered
for the captain's name. If it's not empty, I'll change the captain's name in
the player's save data. Or if it is empty, revert the tree item
back to the value. This works as long as
every role is unique, but I may want to
have some roles with multiple crew members. In these cases, I'll
change the value stored in my save data
from a dictionary describing one crew
member to an array of dictionaries describing
a list of crew members. I'll give the player
multiple seamen and top men to run basic sailing
operations of the ship, each with unique names, but I'll leave
their pay the same because my ship is unionized. Back in the crew manager
script, when creating the tree, I'll need to add as
many tree items as the maximum number of duplicate roles as there
could possibly be, which I will indicate
in the scene tree by adding X followed by a number
to the name of the role. Then when populating the tree, I'll search the name
of the node for the letter X and store
that in a variable. If the letter X is not found, it will return negative
one as the result. Declaring new variables for the role and quantity
of that role, the role defaulting
to the node name and the quantity
defaulting to one, if X is found, then I can parse these values out
of the node name using the substring function
with the role being before X and the
quantity after it. Then call Ad tree item
with the variables. Add tree item will need a
new quantity parameter, and if the quantity is
anything larger than one, then the dictionary entry for that tree item will need to
be an array of tree items, much like the crew dictionary. Creating the tree
item can then be wrapped in a four loop
counting up to quantity. And based on whether or
not the quantity is one, either set the
dictionary entry to the item or append it
to the array of items. I don't want a whole bunch of blank repeated roles
in the tree, though, so I'll set the tree items visibility to only be
true for the first one, then false for the rest. When the file reset
signal is received, different code will
need to be run based on whether or not
the role is an array. If it is an array and the player has that role
in their crew dictionary, then the quantity of that role will be the size of the array. Iterating through all of the roles that are
available in the tree. If I is less than quantity, then the tree item
at index role I can be populated with the player's crew data
at the same indices. Otherwise, they'll be blank. And the item's visibility
can also be set to true if either I is zero or I
is less than quantity. So at least one of
each role will always be displayed even when the
player doesn't have them yet. Running the game, we can
see that the player now has multiple crew members assigned to the top Men and Seamen roles. We now have a tree node
displaying the player's crew. In the next lesson,
we'll go over the theme options available
for the tree node. I'll see you in the next lesson.
35. Tree B: Hello, friends. In
the previous lesson, we used a tree to display
the player's crew. In this lesson, we'll explore the theme options available
for the tree node. Before that, though,
I'm going to change this name parameter to crew name to get
rid of the warning. With the root node selected, let's add the tree node
to the game's theme. Starting in the
constant section, we can set whether or not the horizontal lines
separating each tree item will be drawn by changing the draw guide's property
to zero or not zero. The same can be done for the relationship
lines which show the parent child relationships of tree items more clearly. The relationship line
width can be changed here. And we can provide different
relationship line widths for child relationship lines as well as parent
relationship lines. We can also separate the
parent relationship line from unselected sibling
relationship lines by adding a pixel margin. The horizontal separation is the pixels of space between
columns of the tree, as well as the left side of the tree if folding is disabled. Tree item cells can
be given pixels of margin space in
all four directions. Item margin will also provide extra indentation to
the left of tree items. Outline size is the font outline we are very familiar
with by now. Scroll border and
scroll speed are for altering the behavior of drag
and drop scrolling only, not how the scroll bar
naturally behaves, so we will not be using this. We can add horizontal
pixel separation between the tree items and
the vertical scroll bar, as well as vertical
pixel separation from the horizontal scroll
bar if there is one. And we can give the scroll
bars margins as well. Vertical separation
adds pixel separation between each tree item. Tree items can contain
multiple buttons, and we can use this
property to change the horizontal separation
between the buttons. Icon max width can
be used to limit the size of icons
provided for tree items, and their height will
automatically be adjusted to maintain
their aspect ratio. I'll export another icon from my crew manager script and add it to the root of
the tree at column one, so the player can
clearly see that they're allowed to edit
their captain's name. Running the game, the
captain's name now has an icon to indicate to the player that
it can be edited. Back in the theme editor
in the colors section, there are font colors for
default tree item text, disabled font text for when a tree item is editable
or a checkbox, but is also disabled, the font outline color, the font color of
selected tree items, and also the column
title font colors. We can change the color
of the guidelines here, and we can also change the color of the relationship
lines and have different colors for the child relationship and parent
relationship lines. Drop position color
is the color used to indicate where dragged
items will be dropped, and custom button
font highlight is only used for custom buttons when the mouse is
hovered over them. We won't be using either
of these options. There are different font and font size options for the tree items and
the column titles, but I'll continue to use my
theme defaults for those. In the icon section, we can change how the expand
and collapse arrows look, as well as checkboxes when
they are checked or unchecked, enabled or disabled and
indeterminate states, too. Useful for parent treat items containing lists of
checkboxes as children. So the parent can display
the indeterminate checkbox to indicate that some but not all of the
children are checked. This behavior is not automatic though and must be scripted. The select arrow
icon is drawn to indicate option buttons
that display a pop up menu, and the updwn icon is used for spin boxes which edit
numbers within a range. The player will be able
to click the up arrow to increase or the down
arrow to decrease the value. To create these different types of interactable tree items, I'll go to my populate
crew member function. Then I'll call the function
set cell mode on column two, which is the pay column and
change its mode to check. If the value of
pay is not empty, then I'll set its editable
property to true. Otherwise, I'll set it to false. Now we can see that the tree
items in the pay column are checkboxes and only enabled if the player has a crew
member for that role. Cell mode icon will display
only an icon with no text, and custom can be used to create custom buttons, but
we won't be doing that. If we set it to range mode, then we can use it
to either create an option button by providing
a comma separated list of options as the text property or a numerical range which can
be edited as a spinbox. We can configure the spin
box range properties by calling set range config, providing the column,
minimum value, maximum value, and step value, and set the current value
by calling set range, providing the column and
the value as a float. If the value of pay
is empty, though, I'll change the cell
mode to string and just display it as the
empty uneditable text. Running the game, the player can change the pay rate
of their crew members within ranges of one
to nine gold per day with steps of
one gold per click. This interaction will also emit the tree item
edited signal. So I'll need to check if
the item that was edited is the tree's root item before
editing the captain's name. I'll also have to write some
additional code to change the crew member's pay rate in the player's save data here. Without knowing the
role or array index of the crew member
whose pay was edited, there's not much I
can do other than to iterate through all of them
and check which one it was. So iterating through
all of the tree items, I'll check if it's
an array first. If it's not an array and is
the item which was edited, then I'll set the
pay of that role to the new range value,
cast to an integer. If the role is an array, then I'll have to search through that array until I find
the one which was edited, now giving me the array index, which I can use
to set the pay of the crew member in that role
and array index number. Back in the theme editor,
in the style section, we can change the style box
panel drawn behind the tree. I'll just use an empty
style box to ignore it. As well as the focus style, which will be drawn over the entire tree when it has focus, which I will also remove
by setting it to empty. We can change the style
boxes for the title buttons normally when hovered
and when pressed. I don't want my titles
to act as buttons, so I'll also use empty
style boxes for these. The only style box
I want to use for my tree will be when a
tree item is selected. So I'll set it to
a style box flat, give it a shadowy color
with rounded corners, and copy it into the style
box for selected focus. The tree node can automatically include horizontal and
vertical scrollbars, so we might also want to
add those to the theme too. In the icons section, we can provide icons
for increment and decrement in the normal
highlight and pressed states. Keep in mind that with
the vertical scrollbar, increment is the one which
points down at the bottom. I do not want to include these
buttons in my scroll bars, but I want to use them to define the clickable mouse area. So I'll add them as
placeholder textures and give them a width of
eight pixels, but no height. Therefore, nothing
will be drawn by them. Then in the style section, I can set the scroll style
box to a flat style box. Make it a very
narrow black line by adding an invisible border
on the left and right sides. I'll then copy this into
the focus style as well. For the grabber, I'll
make it the same color as my buttons with rounded
corners and a shadow. Copying this into the
other two styles, I'll then make them unique, set the color of the
highlight lighter, and the pressed darker. All of these same
options are also available for the horizontal
scroll bar as well. We now have our tree node themed to match our
game's aesthetic. In the next lesson, we'll allow the player to hire and
fire crew members. I'll see you in the next lesson.
36. Scroll Container: Hello, friends. In
the previous lesson, we explored the theming options available for the tree node. In this lesson, we'll allow the player to hire and
fire crew members. I'll start by adding a button
below the tree node which says fire and shrink it towards
the center horizontally. Then connect its pressed signal to the crew manager script. We can request which
item is selected from the tree node and
store it in a variable. If nothing was selected, or if the selected
item is the captain, which in my game represents
the player themselves, then pressing the fire button
shouldn't do anything. So if something was selected
other than the captain, I'll get the role
which was selected as the text in column zero of
the selected tree item. Then split my logic based on whether the role allows
multiple people, either erasing the role from the player's crew or checking if the size
of the array of crew assigned to that role is
not zero before removing the person at the same index as the array index of the
selected tree item. This is only necessary
because I still have the role represented
as a blank entry, even if the player doesn't have anyone assigned
to that role. After firing the crew member, I can just call on
file reset to update the crew tree with the new
information. Let's try it out. We can fire any of the crew
members except the captain. Attempting to fire blank
entries does nothing. In the save data script, I'll remove the initial
crew except for the captain and initialize
the arrays as empty. Next, I'll need to
generate a list of people looking for work
that the player can hire. So I'll add a grid container to the right side of this panel and a button below it that says hire shrink to the
center horizontally. I'll have the grid
container expand to fill the space both horizontally
and vertically. Giving the grid
container three columns, I'll add two label nodes and a texture rect as
the column headers. Name them and populate
them with role, name, and my coin icon. I'll have the roll
and name labels expand to fill as much of
the space as they can, collapsing the pay
column to the end. I'll need to write
a script which populates this grid with
potential candidates. But first, I don't want to be restricted by the
amount of space here. I'll need scroll bars
like the tree has. The tree node includes
scrolling automatically. But if I want my grid
container to have scrolling, I'll need to add another
node, a scroll container. The scroll container can
only have one child node, which it will clip to fit inside its control rectangle and provide scroll bars if
the child does not fit, which I will expand to
fill the space provided. We can see the effects
of the scroll container by duplicating the child
nodes a bunch of times, so it no longer
fits vertically and keep adding text until it no
longer fits horizontally. Selecting the scroll
container node, let's take a look at its
properties in the inspector. Follow focus will tell the scroll container to
automatically adjust the scroll position to keep the focus control node
visible at all times. Both the horizontal and
vertical scroll mode have multiple options. The default option auto only shows the scroll
bar when it is needed, but we can change
it to either always show or never show or disabled, we'll expand the
scroll container to fit its child node
in that dimension, removing the need
to scroll at all. A dead zone can
be added to touch scrolling to adjust
its sensitivity. Expanding the scroll section, we can change the
default scroll position either horizontally or
vertically by pixel, as well as change the step
increment or decrement, which will move the
scroll position when the player
clicks those buttons. I have removed the increment and decrement buttons from
my scrollbar theme. Now I'm ready to
populate this grid with prospective crew members waiting to be hired by the player. I'll center the text in
my column headers to prevent them from being clipped
by the scroll container. So I'll connect the hire buttons pressed signal to the
crew manager script. First, I'll grab a reference
to the grid container node. I'll also need to know all the possible roles
that can be hired, which I'll store in an array, and a list of all the
random people I generate, which I will also store
in another array. Then after generating
the crew tree, set the possible
roles to be the array of keys from the tree
items dictionary, removing captain and passenger from the potential roles
which can be hired. I'll then call a
different function to populate the higher grid. The first thing I'll
do when populating the higher grid is clear its contents with
another function. After that's done, I'll
repeat a number of times, let's say 15 and append a new person to the array
of people looking for work. This entry in the array
will be a dictionary, containing a role, name, and a pay rate with the name and pay rate
being label nodes, initializing them with
their class name dot neu. The role will be a checkbox, which is another node that
inherits from button. This way, the player can check
the checkboxes of everyone they want to hire
before clicking on the hire button to
hire them all at once. All three of these nodes have a text property which
needs to be populated. I'll just give them
all random values, starting with the role being a random selection
from possible roles. I'll pick a random name
from a constant array of strings the same way
I did the ship names, providing a large list of
potential names in my script in a region tag that I can collapse to keep it from
getting in the way. For their pay, I'll just use a random integer 1-9
cast to a string. All three nodes can be added to the higher grid as children. Clearing the grid
is as simple as iterating through the
array of people who are looking for work and
calling Q free on all three of their dictionary entries,
then clearing the array. If we run this now, the
checkbox has not been themed, and it's inheriting its theme
properties from button. So let's add checkbox to the game theme and make
it look more appropriate. Starting in the style section, check boxes have all the
same styles as buttons, which I will remove by setting them all to style box empty. Then in the icons section, along with the same icon
inherited from button, there are checked
and unchecked icons enabled and disabled, and also a set for
radio buttons. As always, I'll
leave the font and font size to use the
default theme setting. The same font and icon
color options are also available for the checkbox as they
were for the button. Note that the icon
modulation colors only apply to the icon property, not the checked or
unchecked checkboxes. In the constant section, aligned to the largest style box we are familiar with by now, same as outline size
and icon max width. Check vertical offset will
offset the vertical position of the checkbox icons by a
few pixels either up or down. Horizontal separation
adds pixels of space between the check
Box's icon and its text. If the checkbox has both
a checkbox and an icon, the horizontal separation
will be applied to both. I'll remove the icon
from my checkbox. This looks much better.
Back in the script, when the player clicks
on the hire button, since my game has
rules about not having multiple people
in the same role, I'll create an array
of everyone who was successfully hired and
a string for the role. After iterating through the people who are
looking for work, I'll then iterate
through everyone who was hired and remove them from the array of people
looking for work, then update the crew tree. Inside this for loop, I'll check if the person's role checkbox has been pressed, and if so, set role to be
the text from that checkbox. In order to know if this
person can be hired, the logic will differ if the role allows
multiple people or not, which I know based on
whether the tree item dictionary entry at the index
of that role is an array. Starting with unique roles, if the player's save data crew dictionary doesn't
have that role, then I can set the value of
that role to be this person, creating a dictionary of their name and pay
rate as an integer. If it is an array, though, I'll have to compare
the number of people assigned to that role in the player's existing crew with the maximum number allowed. And if they still have room, then append this new
person to the array. If either the player
doesn't have room for more of this role or the unique
role is already filled, then I'll continue to the next person who is
looking for work. If this person was
successfully hired, I'll free all of the nodes
associated with them from the scenery and append them to the array of
people who were hired. This will suffice for allowing the player to hire
these crew members. Attempts to hire crew
positions which are already filled or at
capacity will be ignored. But I do think it's
important to enforce the maximum crew capacity of
the player's current ship. So I'll write a
function which counts the player's current crew,
returning an integer, iterating through every entry in the player's crew dictionary, if the entry is an array, adding the size of the array, otherwise adding one and
returning the result. When clicking on
the hire button, I'll add a variable
for the maximum crew. And with each iteration through the people
looking for work, I'll check if the
player has reached their crew capacity and break out of the
loop if they have. I'll need to retrieve
this information from the player's current ship. So giving these
tabs unique names, I'll grab a reference to the ship manager
using add on Ready. And request to the
player's crew limits, setting the maximum to be the
first element of an array. The ship manager script then needs a function
which returns a two integer array of the player's current
ship crew limits, simply returning an array
of two numbers filled with the minimum crew and maximum crew of
their current ship. Now the player is prevented from hiring more crew than their
current ship can hold. The player can now
manage their crew from this crew tab with
mechanics for hiring, adjusting pay rates, and
firing crew members. In the next section we'll allow the player to set
sail to another port. I'll see you in
the next section.
37. Radio Button: Hello, friends. In
the previous section, we allowed the player
to manage their crew. In this section, we'll
provide them with a map of different ports
that they can sail between. Like the crew tab, I'll start by switching
to the map tab, adding a script to it, call it the Map manager and save it in the
manager scripts folder. Then export an icon as a
texture two D and set it as this tab's icon by calling the set tab icon on
this node's parent, the tab container, passing
the icon as an argument, and populate the exported
icon with a map icon. Switching back to two D view, I'll start by adding a vertical box container
to this tab, containing a texture
wrecked node, populated with the texture of
a map of the Caribbean Sea, set to fit proportional
to its width, keep its aspect ratio, and expand to fill
the space vertically, and shrink to the
center horizontally. On this map, I'll add a control node to
use as a folder to hold all my ports and have it anchored to the
full wreck of the map. Then I'll represent each
port with check boxes, starting with Havana, Cuba, then Kingston Jamaica,
Porto Prince Haiti, and Santo Domingo in
the Dominican Republic. These checkboxes are
naturally set to toggle mode, so the player can
check or uncheck them, but I only want them to be able to have one checked at a time, meaning these should
be radio buttons. We can create radio buttons
by clicking on the drop down labeled button group and
creating a new button group. We don't need to
do anything with this button group other than save it as a project resource. Then assign all of the buttons to also use the
same button group. Buttons in a button
group will use the radio button icons instead
of the checkbox icons, and only one button
in a button group will be allowed to be
toggled on at any time. I'll have the Havana Cuba button disabled as it will be the
player's starting location. And thus, they will
have three choices to select from for their
first destination. I would like to customize
how these buttons look without affecting other
checkboxes in my project. So I'll add a new theme
type variation to my theme, call it port, and set its
base type to checkbox. If any port is disabled, whether it is checked or not, I'll set its icon to an anchor, since that will indicate that is where the player
currently is. An unchecked radio button
will be a sale icon, and a checked radio
button will be a red X, indicating that is the
player's selected destination. I'll also reduce
the font size of these buttons so they don't take up as much
space on the map. Selecting all of my port
buttons in the scene tree, I'll switch them all to use the port theme type variation. Then reset their size
properties to be as small as possible while
containing their text. To keep my button text
from overlapping, I'll have Kingston
and Portal Prince reverse their layout to
be from right to left, so the text appears on the
left of the radio button icon. To ensure that the buttons remain in the correct positions, even if the map is
resized for any reason, I'll set the buttons
to anchor mode using custom anchors and position their anchor points to the
center of their button icons. I'll then repeat this
for each of my ports. Selecting one of
the radio buttons, taking a look at its signals, it would not be a good
design to connect each of these toggled signals to a different function
in the script. That would make adding more
ports very cumbersome. But note that the
argument being passed by the signal is a Boolean
named toggled on. In the map manager script, we can react to any button being toggled with
a single function. Let's first declare
a variable to hold the player's current
port as an integer, which will naturally
default to zero, and another for
their destination. I'll give it a default
value of negative one, which will mean
that the player has not selected a destination. We can then write a function
to set the destination, first accepting the
toggled on parameter to match the argument passed
by the toggled signal. Then adding another parameter, the port ID of the button
which was pressed. If the button is
being toggled on, then the destination
will be the port ID. If the button is being toggled off and the port ID
is the destination, then the destination
has been removed by the player and should be
set back to negative one. To connect the buttons toggled
signal to this function, we need to expand
the advanced section of the signal
connection dialogue. Here we can add extra
arguments to the signal. The type of argument we
wish to add is an integer, and for Havana, I'll
leave its value at zero. We can then go back to the
list of nodes in the scene, select the map node, and pick a function to call
as a reaction to this signal. The function being called
must have parameters whose types match those
emitted by the signal. Since this signal
has toggled on as a boolean and we are
binding an extra integer, set destination must have a Boolean parameter followed
by an integer parameter. We can then repeat this process
for every radio button, changing the value of the bound integer argument
to a different value. For simplicity, I'll use each button's child index
number in the scene tree. Back in two D view, I'll also add a margin
container to the bottom of the map anchored to
the bottom wide and put inside this margin container a label to
indicate the distance from the player's current port to their destination measured
in days of sailing, and a button to embark on their journey shrink to the end
of the margin container. I'll then note the position and size of the margin container and add a color wrecked node as it's sibling with
the same anchors, size, and position, colored
a semi transparent black. The map image is very busy, and adding this
tint will just make the label and button
more noticeable. I'll have the label invisible and the button
disabled by default. Back in the script,
I'll grab references to the ship manager and these
two nodes using add on ready. Then also define a
constant array to hold the distances between each of my ports measured
in nautical miles. Creating a matrix of integers, the reflexive
positions will be if the current port and
destination are the same, so the distance would be zero. And I'll just
populate the matrix with some realistic values for the sailing distance
in nautical miles between each of these cities. The symmetric values
should be the same, assuming you can sail along the same path
in both directions. Then after setting
the destination, if the destination is
either negative one or it is the same value
as the current port, then I'll hide the
distance label and disable the embark button. Otherwise, I'll set the text of the distance label to the correct numbers
of days sailing, then enable the embark button. To access the
player's save data, I'll need a reference
to the file node two. To calculate the number
of days of sailing, I'll use the sealing function, which rounds a float up to the
nearest integer and divide the sailing distance
in nautical miles by the player's
current ship's speed. For this to work,
I'll have to add this get speed function to
the ship manager script. So returning afloat, I'll return the ships indexed by the
player's current ship ID number. And I didn't include a speed
value for my ship resource, so I'll open the ship
resource script and export another variable
for the ship's speed. Then give it a default value of 100 nautical miles per day. I can then give each ship a
different speed if I want to, and the player will
be able to make these journeys in
fewer days of sailing. I would also like the
player to be able to toggle off their destination
by clicking on it again, which is not allowed by default. If we double click
on the button group in the project resources, we can toggle on the Allow
press property to accommodate this behavior. Let's try it out. The player can click on any of these radio
buttons on the map, selecting it as
their destination, and the number of
days will update, and the embark button enables. If we press the button which
was already toggled on, we can toggle it off too. And now there is no destination, so the number of days is hidden and the embark button disables. We now have a map
with several ports the player can choose between
as their destination. And the next lesson will allow the player to control
the ship's anchor. I'll see you in the next lesson.
38. Check Button: Hello, friends. In
the previous lesson, we allowed the player to select
a destination from a map. In this lesson, we'll let them
control the ship's anchor. Below my map, the
vertical box container will hold a horizontal
box container, and that will hold
a check button which will be used to
control the ship's anchor. Like the check box,
the check button also inherits most of its properties from button,
including its theme. Selecting the check button, let's take a look at its
properties in the inspector. I'll start by removing the
style theme properties inherited from button by
checking the flat checkbox. We can add text to the check
button and also an icon. I'll use the text to indicate to the player that this is used to control the ship's anchor. Like other buttons, we can adjust how the text
and icon will be drawn relative to the
check button's position and how much space is available. Next, let's select the
root node so we can see the theme editor and add
Check button to the theme. The check button inherits
many of the properties we are familiar with from button
like font and icon colors. In the constant section, we can align everything to the largest style box by setting it to anything
other than zero. Add some vertical offset
to the check button, horizontal separation
between the icon, check button, and text. Set a cap for the maximum
width of the icon. And an outline
size for the text, all of which we've done before
with other control nodes. As always, I'll
leave the font and font size as the theme defaults. In the icons section, we can change how the check
button looks when it's checked, unchecked, or disabled, as well as provide an icon which will be used for all
check buttons across the project unless overridden by the individual check
buttons icon property. And finally, in
the style section, we have all the same styles which were inherited
from button, which I will remove by replacing them all with style box empty. Selecting the check button node, I'll have my button
pressed by default. I'll rename my
anchor node and the ports folder before I
use them in my script. We'll need to respond to
the player clicking on the check button by connecting
its signal to a script. Instead of the pressed signal we would normally
use for button, we'll need to know
whether the check button is being toggled on or off, so we'll connect the
toggled signal instead, which passes the toggled state of the check button
as an argument. Connecting this to my
MAP manager script, I can easily react to
the signal based on whether or not the check button is being toggled on or off. Switching to the
save data script, I'll add the is
anchored variable as an exported boolean and
default it to true. Let's also connect the file reset signal to the
script so we can initialize this tab with the appropriate information
from the player's save data. I'll need references
to my anchor node and the array of child nodes
under my ports folder. All I need to do for
the anchor is set its button pressed property
to match the new save data. I'll also need to
iterate through all of my ports and set their
disabled property to match whether or not this is the player's current port and set none of
them to be pressed. The check button reacts to being hovered and clicking on
it weighs the anchor. Clicking on it, again,
drops the anchor. We now have a check button
controlling the ship's anchor. In the next lesson, we'll allow the player to
control the sails. I'll see you in the next lesson.
39. Vertical Slider: Hello, friends. In
the previous lesson, we added a check button that
controls the ship's anchor. In this lesson, we'll use a slider to control
the ship's sales. I'll start by adding
a vertical slider to the horizontal box
container below my map. Unlike the check button, the vertical slider doesn't
have text or icon properties, so I'll also add a label node, which I will populate
with the word sales, so it matches the style
of my anchor control. Let's select the
vertical slider node and take a look at its
properties in the inspector. The editable checkbox
will determine whether or not the player can change
the value of this slider, and scrollable will change
whether they can do so using the mouse wheel while hovering the
mouse over the slider. Tick count will add
horizontal lines to the slider to indicate
segments like a thermometer. I'll have three
ticks on my slider, and the ticks on borders
checkbox will change whether the first and last tick are drawn for the maximum
and minimum values. The range properties are also
important for this node, allowing us to specify what the minimum and maximum
values of the slider are, as well as how much the slider can change in its
value with each step. Since my slider is being used
to control the ship sales, it will only have
three possible values, zero, half or one for full sale. These range properties are
also used by other nodes, like the scroll bar, which
uses the page property, but sliders don't use
it, so we can ignore it. Value is the current
value of the slider, so we can change what
it is by default, and it will
automatically snap to the step values between
our minimum and maximum. Exponential edit will skew the value to be exponential
instead of linear, but only if the minimum
value is greater than zero. Rounded will always round the value to the
nearest integer. Allow greater or
lesser will allow the value to go past the
minimum and maximum values. But the slider will not
indicate that it has. We'll learn more about these properties in the next lesson. Let's select the root
node so we can see the theme editor and add the vertical slider
to our theme. All of the theme properties
for the vertical slider are also available for
horizontal sliders. Since the slider has
no text or icons, it has no colors,
font or font size. So we'll start in the
constant section. If center grabber is
anything other than zero, then the grabber
will be positioned within the vertical slider space based only on its center point rather than the entire
image dimensions. And grabber offset can add some horizontal displacement of the grabber to the
left or right. On a horizontal slider, this same property will be
a vertical offset instead. In the icon section, we can provide different images for the grabber in its normal, disabled and highlighted states, as well as the image used to draw tick markers
along the slider. I'll use a rope naught as my grabber and a cross
beam for my tick. In the style section, we can change how
the slider is drawn, as well as the filled area below the grabber normally and
when it is highlighted. For a horizontal slider, this would be the area to
the left of the grabber. I'll have my slider be a thin brown line
representing a mast, and the fill will be
a style box texture which I will populate with a picture of a sail and expand it both
left and right, so it looks like an
actual ship sail. I'll then copy this,
make it unique, and change the texture for
when it is highlighted. To give this sale more room, I'll override my
theme setting for the horizontal box
container to have 21 pixels of space
between child nodes. Selecting the slider node, we need to react to
the player using the slider by connecting one
of its signals to a script. If you want to react to the
value being changed only after the player has released the mouse button and
stopped dragging, then connecting the drag ended signal would
be appropriate. But I want to react
to the value being changed even while the player is still holding down
the mouse button. So I'll connect the
value change signal instead and connect it to
my map manager script. I'll store the value of the sales slider in the
player's save data. Grabbing a reference
to the sale node, I can also set its value
when the save data is reset. I'll also need to add this variable to the
save data script too. Let's try it out. The vertical slider
reacts to being hovered and we can change its value
using the mouse wheel. We can also click and drag the grabber to change its value. We now have a vertical slider controlling the ship's sails. In the next lesson, we'll let the player embark towards
their destination. I'll see you in the next lesson.
40. 6 4 Progress Bar: Hello, friends. In
the previous lesson, we added a vertical slider
to control the ship's sails. In this lesson, we'll
use a progress bar to indicate how far along the
ship is in its voyage. I'll add a progress bar node to the horizontal box
container under my map and have it expand
horizontally to fill the space, then position it between the
anchor and sail controls. Let's take a look at its
properties in the inspector. We can change the direction of the fill and change the value
to see the effect of this. We can also toggle the
percentage display on or off, and the indeterminate toggle will show us that something is progressing without
any indication of how far it has progressed, which we can see if we also check the preview
toggle under Editor. All of the same
range properties are here and work the same
way as the slider. But if we allow the
value to be lesser than the minimum or
greater than the maximum, the value will be indicated
by the percentage, but not by the fill
of the progress bar. Let's select the root
node so we can see the theme editor and add the
progress bar to our theme. Like any node that has text, we can change the font and font outline colors and set the outline size in
the constant section. As always, I'll
leave the font and font size as the theme defaults, and the progress bar
has no icons to change. In the style section,
we can change both the background and
fill of the progress bar, which I will populate
with flat style boxes. I'll use the same blue
background color as my panel, round the corners and add some content margins
to make it a bit bigger. Then copy this for the fill, make it unique, and change
its color to green. To embark on our journey, we'll need to connect the embark button's pressed
signal to the script. At the top of the script, I'll need a reference
to my progress bar. I'll also declare
a boolean variable for whether or not the
player is currently sailing, the amount of time
that has elapsed as a float and the number of
days at sea as an integer. To start sailing, I'll set
the boolean variable to true. Time elapsed and
days at sea to zero. I'll also iterate
through the rest of the tabs in my tab
container and disable them, since these should only be accessible at a port,
not while sailing. I'll then write another
function to arrive at a port, accepting a port
ID as an integer. After setting is
sailing to false, I'll enable the port that
the player sailed from, change the current port
to the destination, and disable the
new current port, then set destination
to negative one, meaning there is no destination. I can then reactivate the other tabs since the
player is now in a port. Then set the maximum value of the progress bar to the distance between the current port and the destination and
disable the embark button. To sail from the current
port to the destination, I'll use the process function to gradually fill the
progress bar over time. If the player is
not sailing or is anchored or the sails
are set to zero, then no progress will be
made and I'll just return. Otherwise, I'll increase
time elapsed by Delta, and if time elapsed is greater than the
number of days at sea, then I'll increment days at sea and pay the crew
their daily earnings. I'll next add to the
progress bar's value the player's current
ship's speed multiplied by their sale
value multiplied by Delta. This will result in the
ship traveling its speed in nautical miles every 1 second and with 1 second
representing one day. If the value of the progress bar reaches its maximum value, then they have arrived
at their destination. I'll need to write
this payout function in my crew manager script. Grabbing a reference to the crew manager using add on Ready, then switching to the
crew manager script. First, declaring a variable for the total payout
as an integer, I'll iterate through
the player's crew. If the crew role
is not an array, I'll add the pay for that
role to the total pay. If it is an array, I'll
iterate again through each person in that role and add all of their
pay to the total. I'll need a reference to
the gold resource node so this script can request the player spend
this total amount. And finally, spend the amount of gold necessary to pay
all crew members. If the player does
not have enough money to pay out all of their crew, this would be a good time to end the game
with the game over. I added an S to my
variables name here. I'll need to fix that. When
clicking on a port check box, I'll first check if the
player is currently sailing, and if so, press the button that belongs to the destination
port and return. This will prevent
the player from changing their destination
while sailing. If they aren't sailing and are
setting their destination, I'll set the value of the
progress bar to zero. Let's try it out. The
player can select a destination and click the embark button
to start sailing, but they won't make any progress
toward their destination if they are anchored or if
their sails are not unfurled. The ship's speed can be adjusted with the
sail at half or full. Upon arriving at
their destination, the tabs and checkboxes can
be interacted with again. Once at a port, the player
can recruit crew members, and sailing to another port, the crews pay will be deducted
for every day at sea. We now have a progress
bar showing how far the ship is from
its destination. And that covers almost all
of Godo's control nodes. The few mechanics left to complete this game
would only use more of the same nodes we have already used elsewhere
in the project. Now you should be ready
to complete your project. If you've been following
along with this project, see if you can complete
this game's market tap and add a game over
screen. Good luck.