Transcripts
1. Intro: You've started learning god. You've taken tutorials and
have made smaller projects, but you're ready to take
it to the next level and create a full game
with professional level, code, structure, and design. This course will teach
you how to build a complete pixel platform
from the ground up and how to build a scalable game with reusable code that can be applied to games
of other genres. You will learn how
to build a character that the player can
control and design levels, track the player's
progress through your game and respond
at checkpoints, create a combat system with
enemies and a boss fight, and learn scalable
design principles for Gadot that you can apply
to any Gadot project. When you're done, you'll
have a template of a complete game that you can
populate with more levels, more mechanics, more
items, and more enemies. If you need any help, our
discord server is full of other students and
game developers who can answer any
questions you may have. Click the website link
and my profile to join. We will be starting with a
blank project in Godot version 4.2 and using free
assets from its dot IO. But you're welcome
to follow along with your own project using
different assets. Additional notes and
videos have also been added to update new features
in Godot version 4.3. Let's get started building
your first complete game.
2. 0-1 Introduction to Programming: 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 Gadot 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. 0-2 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. 0-3 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 se
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 not, 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 Is. 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. 0-4 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 a 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 booling variable. He loves me to be its opposite. So he loves me, is assigned
to naught 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 WW, 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 WWloop
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. 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. 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. 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. 0-5 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 worker 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 on 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 mark
it 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. 0- 6 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. Range 114 will contain
all numbers 1-13, 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. 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. 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 while loop. If both four loops complete without finding
a matching pair, found pair will be false and
the while 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 Maid. In the next lesson, we'll use another form
of flow control. I'll see you in the next lesson.
8. 0-7 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 lansinn 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 se 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. So 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. 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. 0-8 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 scene's 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. So 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. 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 Bolan 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
and 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 Gadot engine, 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 scenetre, 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 manager's 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. 0-9 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 G 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 GDScript uses private variables and
functions is more like the way other languages
use protected ones in that they are
accessible to inheritors. GD Script 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. 0-10 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. 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. 0-11 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 datatype, 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 will 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. 0-12 Debug: 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 Gdo 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. 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 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 GDA 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, and good luck.
14. 1-0 Setup: Oh friends, welcome
to my course. I'm making a complete two D
pixel platformer in Godot. To get started with
the Cado engine, you'll first need to download
it from their website, head over to Godoengine.org and download the
latest stable version. For this course, I'll
be using God version 4.2 Once the download
is complete, just unzip the folder
and run the EX file. Be sure to relocate these unzipped files to an appropriate location
on your hard drive. I'd like to have the
engine pinned to my task bar for easy access. There are no
complicated installers, hubs, or store interfaces
to muddle through. If you've ever worked
with other game engines, you'll probably be surprised at how simple and easy
it is to use Good. By comparison,
when you're ready, click on the New Project button. Give your project a name. This is often the same as
the title of your game, but often games start without having an official title yet. Developers will use things
like Untitled Platform or Untitled Pirate game until an official
title is chosen. Select where on your hard drive you want the project
to be stored. I recommend making
a sub folder in documents named
Godot Projects and give each of your projects its own dedicated folder within that folder to
keep them organized. Since the game we
will be making uses simple two D pixel
art in two D scenes, we can use the
compatibility renderer which supports desktop, mobile and web platforms and is the fastest at rendering
simple scenes like ours. Lastly, God will optimize compatibility with Github
version control by default, which is very useful
when you're done, click Create and It to create the project and
open it for editing. The project opens in an
empty three D scene, but we're making a two D game. Under the Scene tab, which
is in the upper left corner. By default, click on two D scene to create
a new two D scene. This will automatically switch our editor view to two D mode. If you ever find your
editor is in three D mode, you can always switch with the buttons at the top
of the editor window. Cado. Every scene is
defined as a tree of nodes. The scene itself is
defined as a node two. It is a node two D, this is the scenes root node. We build our scenes by adding
branches to the scene tree. Click on the plus button under the Scene tab to add a
node to the scene tree. As you can see, there are a
lot of different node types. Type, label into the search bar and add a label
node to your scene. Select your new node
A nodes properties can be edited in
the inspector tab, which is on the right side
of the editor window. By default, type any message
you like into the text area. When you're done,
save your scene by selecting scene from the
menu bar, then save scene. You can also use the keyboard
short cart control S, or command S, give
your scene a name. The naming convention for
Godot is to use snake case, which is all lower case with underscores
instead of spaces. Click the save button to
save your first scene. When you're done, hit the Run Current Scene button in the upper right corner
of the editor window. It's the one with the play
icon on a clapper board. This isn't a very exciting
scene yet, but it works. Click on the stop icon
to stop the scene. Under the file system tab, which is located in
the lower left corner, by default, you can see a list of all the files
that make up your project. Every new Godot project has only one resource file provided by default,
the Godot logo. Click and drag the
logo into your scene. This added a sprite two
D node named icon to the scene tree and set the
icon as the sprites texture. This is just an image
being drawn on screen. Let's get ready for the next
section of the course by making a solid floor for our character to
walk and jump on. For that we'll need
to add collision. Right click on the icon node
and select add child node. Using the search bar, look
for static body two D node. This adds the new node as
a child of the sprite, but it is showing a warning. If we hover over
the warning icon, it will give us
more information. This node can't collide with anything because it doesn't
know what shape it is. To do this, the static body two D node needs
another child node. Adding another node like before this time search for
collision shape two D node, the warning icon on the static body Tu T note has disappeared. But the newly added
collision shape Tut node has another warning. It asks us pole to please
create a shape resource for it. This is done in the inspector
panel using the drop down menu labeled Shape
Select New Rectangle Shape. This has satisfied the warning. But looking at the scene, we can see that the
collision shape doesn't match the icon. If we want the
collision to cover the entire icon, we
need to adjust it. You can click and drag on the red circles to resize
the collision shape. If you want more
precise control, you can click on
the rectangle shape two D resource to expand it, which exposes text fields where you can enter exact
values for the size. If the collision
shape is off center, you can expand the
transform section under node two D to
adjust its position. You can also adjust this value
manually by clicking and dragging the Red Cross in the center of the
collision shape. Select the icon node
and rename it to floor. Since we no longer need to make any adjustments to
the child nodes, we can collapse this node by clicking on the
collapse arrow. Now let's make this
into an actual floor. Adjust your view within the viewport until you can
see the entire blue border. Since it is overlapping
with the red x axis, the top of the box
appears magenta and the left side overlapping with
the green Y axis appears. Can this blue box is
the boundaries of the screen when we run the game with the floor node selected, adjust its position, size, and shape so it covers
the bottom of the screen. Holding down the Alt
or Options button will move only the selected
node and its children. We've just created a
functional floor for our games testing environment out of nothing more than
the Godot icon. This type of work flow is
common in game development. Using placeholder assets to
create a minimal project, which can be used to prototype and demonstrate game mechanics. But in order to make a game that looks good, we'll need assets. I've built this course using a free asset pack called
Treasure Hunters, created by Pixel Frog. From it, these assets are released under a Creative
Common Zero license. You can distribute,
remix, adapt, and build upon the material
in any medium or format, even for commercial purposes. Attribution is not required. You can download the entire
asset pack for free or optionally support the artist by paying whatever amount
of money you choose. Once the download is
complete, unzip the folder. Importing the
assets is as simple as clicking and dragging them
into the project window. I'll also be using sound effects and
music downloaded from Freesound.org The Victory War and Battle Music loops library was composed by Little
Robot Sound Factory. The Battle sounds pack is
created by Luke Sharpals. These sound and music files are under an
attribution license. They are still free to
use for any purpose, but you must give
appropriate credit to the creator of these assets and provide a link
to the license. Pay close attention to the licensing and
attribution requirements of assets you include
in your games. Importing these assets works
the same as the artwork. Just click and drag
them into the editor. However, these audio files
are much larger than the images and we won't be
using very many of them. I recommend only importing these assets as you decide
to implement each one. Now we have a
functional floor with collision and all the art assets we need to start the
next section which will be implementing
a character. I'll see you in
the next section.
15. 1-1 Character: Hello friends. In
the first section, we started the project, imported some assets and created a functional floor
with collision. In this section, we'll
focus on creating a character for the player
to control and play with. To get started, we'll first
build the node structure of a typical character
in the scene panel. Click the plus button to add a new node to
the scene tree. Search for a character body
node two D, and create it. I'll rename this node to
the name of my character. Just like with the floor,
there's a warning. This node has no shape. So it can't collide or
interact with other objects. We already know how to
remedy this problem. Simply add a collision shape two D node and define its shape. The warning reminds
us that we must create a shape resource
in the inspector. Most characters in
games use capsules as their collision shapes
because they are easily adjusted to encompass
the overall shape of a humanoid and are simple
for the engine to work with. By default, new
nodes are added at the scene origin in
the upper left corner. Let's zoom in on the new nodes. We also need to be able to see what the
character looks like. For that, we'll add
another child node to the character, A sprite, two D. We saw in the previous
lesson that there is a texture property in the
inspector panel for sprites, but this one is empty browsing through the treasure
hunters asset pack. Select a character
sprite to use. By default, I don't want my character to be equipped with the
sword by default. I'll select from the without sword folder and pick idle one. Now we can see the
character as it will be drawn in the game,
but it is blurry. That's because Godo's
default project settings are not optimized for pixel art. To fix this select project from the menu bar, then
Project Settings. There are a lot of
project settings and depending on your
version of Godot, they may not be organized
or named the same way. If you have trouble finding
any project settings, there is a search
bar at the top of the window labeled
filter settings. We need to find the settings for rendering two D textures, then change the
default texture filter from linear to nearest. This will stop the graphics
engine from trying to smooth the texture and simply
draw as is on screen. With the changes made, we
can close the window and now the character is drawn in
perfectly defined pixels. When working with pixel art, this is generally the
preferred aesthetic. With the red X and green Y
axes marking the scene origin. We should assume
that the character's origin will be at their feet. Let's drag the spread up until the red X axis acts as a floor where the
character will stand. Make sure that they
are also centered about the green Y axis. To make adjusting the collision
shape easier, I'll re, order it to be
below the sprite in the scene tree but still
childhood to the character. This also determines what order the nodes are drawn on screen. With the collision shape
in front of the sprite, it's easier to move and adjust its size to match the character. Remember that the
bottom point of the collision capsule
should line up with the character's feet and the red X axis for the character to stand
cleanly on the floor. And should also be centered
about the green Y axis. In two D games like this,
it is generally considered good practice to
make the collision shape smaller than
the character. This will prevent the
player from getting hit by attacks they
would perceive to be near Mrs. and allow them to get closer
to the environment. Before the next step, make sure your file system panel is focused on the root
resource folder. New resources will be created in the last folder
you interacted with. In the file system panel, you can collapse
any open folders and click on the blank space. If your insurer with that done right, click
on your character. We can take any branch in our scene tree and save
it as its own scene. I'll name this new scene after
the character it's already done since it will use the node's name as the new
scene name by default. Now Roger is their own scene separate from the scene
we've been working in. Roger's node in
the scene tree has a clapper board button and the child nodes
have been hidden. Clicking on the clapperboard
opens Roger's scene. This allows us to edit
Roger in isolation. Any changes we make here will be transferred
to all scenes containing Roger
in the main scene. We can now move Roger from the scene origin down to the floor where they
will be drawn on screen. If we run the current scene, we can see Roger hovering
above the floor, but he should probably fall down to the floor from gravity. In order to make Roger
behave like a character, we need to add a script switch to Rogers scene and
select the root node. Then click the scroll with a plus button to add a new script. It's good practice to write common behaviors as
generically as possible. This script should not be Roger. Instead, I'll name
the script character and write the script in a way that it can apply
to any character, not just Roger Godot offers a default template for scripts attached
to certain nodes. We'll see what this
template has in it. By default, the
script will inherit the behaviors of the node
type it is attached to, in this case a character
body two D. We will be writing all of our scripts in Goido's native
language, Godot script. Click Create to create the new script and attach
it to the node. The main panel will
automatically switch to script view and open
script for editing. As you can see, the template already has several features. The character has a speed
and a jump velocity. These are measured in
pixels per second. There's also a
gravitational force which is being set by
the project settings. In the physics process function, we can see that the
gravity is applied to the character if they
are not on the floor. A jump button has been
implemented adding jump velocity. When pressing UI, except
which is the Spacebar, there is directional control for left and right
directions allowing the character to move and stop. Move inside is a
built in function which will take the
character body's position, velocity, and external forces. Then determine where
the character will be after delta time has elapsed. With the default
settings, this code will run 60 times per second. If we run the current scene, we will see Roger fall
from the scene origin. But this is Rogers scene, it's not meant to be played. Switch back to the main
scene and switch the view to two D. Playing this scene, we can see Roger immediately
fall to the floor. Pressing the spacebar
causes Roger to jump. Pressing the left
and right arrow keys will move Roger left and right. This implementation of
controls isn't very good, but does offer a starting point from which we can
build our game. Now we have a basic
character being drawn on screen with basic
controls and physics. In the next lesson, we'll focus on improving our
player controls. I'll see you in the next lesson.
16. 1-2 Input: Hello friends. In the
previous lesson we've created a basic character with a spray collision physics
and basic input. In this lesson, we'll set up better input handling to
control our character. Let's start this lesson
with some housekeeping. As you can imagine, our game will contain a lot of
different resources. And it is important to keep them organized so we can quickly and easily find any
resource we need, right? Cooking on the root folder
or in a blank space. Create a new folder and name it. Scripts, then drag the character
script into the folder. Then repeat the process
with the scenes. But we have two different
types of scenes. We can further
distinguish them with a sub folder inside the scenes folder named characters and place Roger in this subfolder. When you're done,
switch to script view. Taking another look over the default template
provided for the character body two D script doesn't really fit our needs. We named this script Character and plan to apply this to
every character in our game, most of which will not be
controlled by the player. It's important to isolate
and separate functionality into different scripts
for this script, which we are using to describe the attributes and
behaviors of characters. We should remove
any input handling. I'll comment these lines by pressing control K or command K. Applying gravity
and movement input during the physics
process is still useful. But let's add some
public methods to the script to describe exactly
what a character can do. In our game, a character can
face left or face right. For now, we'll just
leave the body of these methods empty by
writing, pass or return. A character can
run which requires a direction to know if they
are moving left or right. We can represent the
direction as a float, which is a number that
allows decimal values. This will not only be useful
for knowing the direction, but also allow variable speed. We will need to
store this value in a variable so it can be used during the next physics process. Let's define a private
local variable in our script called
underscore direction, then set its value
in the run function. We can also edit the
physics process to use this variable instead of
the one it was using before. A character can also jump, but we don't need any
parameters for this one. If the character
is on the floor, then add jump velocity to
the character's y velocity. These will be our
public methods which other scripts can use to control any character
in our game. We will add more methods
to the script as we add additional behaviors
to our characters. Other methods like
physics process, which do not concern
other scripts, are marked with a
preceding underscore, just like the underscore
direction variable. These are not meant to be
used by other scripts, they are only for this script. If another script wants to control a character's direction, they should call the
run method instead, because the run
method is public. This creates one
universal set of clearly named behaviors to describe how a character
will act in our gain. A practice known in
programming as encapsulation. The contents of these
methods, variables, and of the physics process are not important
to other scripts. The other scripts don't need to know how the character jumps, They only need to know that
this is a character and it can jump hiding. The details of how
functionality is implemented is known in
programming as abstraction. These practices will make your scripts much
more organized, easier to understand, use, and adapt for other purposes. This is starting to look like
a better character script, but we've cut off the ability of the player to
control the character, because we want
that to be done by a separate script switch
to the main scene. If you're not there
already, right click on your character
and add a child node. This node doesn't need any type. It can just be a basic
node and name it Player. We are only using it to attach an extra script
to the character. Next in the file system panel, let's write click on
the scripts folder to create a new script
and call it player. Then attach it to the new node. Click on the script icon to
open the script for editing. The purpose of the script will be to receive input and call the appropriate methods in the character
script as a result. Which means that we will need to reference to the character. If you recall, we attach this script to a child node
of the character node. We can easily get a reference to the character node by declaring a variable at
the top of the script. We will name this variable
underscore character, because only this script
should need to access it and assign its
value to get parent. Starting the declaration
with at on ready will allow us to assign a value to this variable after the
node has been created, entered the scene tree and
is ready to start running. Meaning that the
node will know who its parent is by
that point in time. In order to receive
and process input, we will use two
different methods, underscore process
and underscore input. The input method is more efficient for handling button
press and release events. This method takes one parameter, typically named event, and
its type will be input event. All we need to do is
check the event to see if the player
pressed the jump button. If they did, then we will
tell the character to jump like the physics
process Underscore Process takes delta as a parameter, which is a float
type representing the amount of time which has passed since the last process. Usually one 60th of a second. But since we aren't going to be using the value
of delta here, we can prefix its name
with an underscore, letting Goodot engine
know that we don't need this value in the
process method, which will run every
frame regardless of whether or not input
was given or changed. In any way, we can check the status of
our movement input. This will allow us to
handle both key presses and analog stick axes more
easily and interchangeably. We will call
character do run here passing in the argument
of input get axis, run left as the negative value and run right as
the positive value. You may have noticed that the default inputs were UI accept, UI left, and UI right. These are built
in input mappings meant to control
user interfaces. Since we have used different names for
the input mappings, we will need to add these to the project settings
before they will work. Open the project settings and switch the tab to input map. If you click on the Show
Built In Actions toggle, you can see the input actions
which were used previously. You can re hide these mappings
with the same toggle type. Run left into the
add new action field and click the Add button. Then repeat for run right and jump to map each of
these to a button. Click the plus
button on the right. This opens a pop up
window where you can select any input for
keyboard, mouse, or joypad. You can use the
search bar to filter the options or use a listening field to
detect it automatically. For run left, I'll
use the A key. Then repeat the
process to add a key for run right and jump. You can also add
multiple buttons to each mapping to make your
games work seamlessly. With a game pad, I'll
use the left stick to run and bottom face
button for jump. When you're done, try
running the scene and testing out your new input mappings to control
the character. Everything should work
the same way as before, but now the input handling and character controls are being
handled by separate scripts. Using this method, you can
imagine how the character the player is controlling
can become interchangeable. The input handling script
could also be replaced with an AI script to control other characters in
your game quite easily. We now have better
input handling for controlling a
character in your game. In the next lesson,
we'll focus on improving the feel of the locomotion
and jumping mechanics. I'll see you in the next lesson.
17. 1-3 Locomotion: Hello, friends. In
the previous lesson, we improved our character's
input handling by isolating it into its own script separate
from our character. In this lesson will make
the locomotion controls of the character feel
better to the player. If you run the main scene
and pay attention to how the left and right movement
feels, it is very rigid. The movement speed is
instantaneous and constant, which is not how humans or any
other objects really move. Open up the character script to simulate more
realistic movement. We can actually use variables taken from real world physics. In reality, an object
will accelerate gradually up to its maximum
speed and accelerate as well. Here at the top of the script, Godot has defined a constant value for the characters speed. Let's add two new
variables here to represent acceleration
and deceleration, which we are measuring in
pixels per second squared. These can be private
preceded with an underscore and will
be of type float. We'll give them initial
values for now. I'll just use the
same value as speed and adjust them later after
testing how they feel. Now the physics process
will need to use these values to calculate
the character's velocity. If we first look
at the else block, this is where the character will decelerate when the player
isn't providing any input. All we need to do here is
change speed to deceleration. This useful function called move toward will start at
the value of velocity x and move toward zero an
amount of deceleration. This works well for both
left and right direction, since coming to a stop will mean moving the velocity toward zero. But if you look above,
the gravity value is being multiplied by delta. Remember that this code
runs 60 times per second. And we are providing our rate of deceleration in units of
pixels per second squared. We can multiply the value of deceleration by delta to adjust its value to match the
amount of time which has elapsed since the
last physics process. Then we can rewrite
the F block to use the same function
and apply acceleration. This time we are
starting at velocity x, moving toward speed
times direction at a rate of acceleration
multiplied by delta. Remember that we can use this script for every
character in our game, and we may not want
every character to have the exact same
constant speed. First, let's change
speed from a constant to a private variable and edit the acceleration
formula to match. Next, we can add at
export to the front of these variable declarations
to make them accessible to other parts of the Godot
engine, namely the inspector. If we switch to the
character scene and select the root node, you'll now see
these variables in the inspector and can
edit their values. In this way, each character can define their own movement, speed, acceleration, and
deceleration values. They do not all need to be
the same constant values. Switch back to the
main scene and try playing with
these new values. You can see how the
character accelerates up to speed gradually and
likewise decelerates. Since we used the same value as the characters speed for
acceleration and deceleration, we know that the time
it will take to reach top speed is exactly 1 second. Likewise, the amount of
time it takes the character to come to a complete
stop is also 1 second. We can make adjustments to
these values quickly and easily from the inspector even while the scene is playing. This allows us to
quickly test and feel how different values
for our variables will affect the game and see
which values work best. I think that the
character should reach top speed in half of a second and come to a stop
in a quarter of a second. We'll increase the values of acceleration and
deceleration by a factor of 2.4 accordingly,
let's try it out. That feels better
to me if you have your rate of acceleration set lower than your
rate of deceleration. However, if you try to switch
directions while moving, the redirection will
feel very unnatural. This is because you expect the character to
decelerate down to zero, then accelerate in the
opposite direction. But the code as written says
that they should simply accelerate from the wrong direction to the
right direction. To make this feel better, we can change the
if statement to first check if
direction is zero, in which case the character
should decelerate. Next, knowing that the
direction is not zero, we can check if
velocity x is zero, meaning that the
character is stationary and tell them to accelerate. We also need the
condition if the sine of direction is the same as
the sine of velocity x, meaning they are
already moving and the player wants to keep
moving in the same direction. Our last possibility is
if neither direction nor velocity is zero and
their sines do not match, meaning the player
wants to move in the opposite direction
the character is moving. In this case, we
can instead move towards speed at a
rate of deceleration. This will feel much
more natural when the player tries to turn
around while moving. Let's try it out much
better before we move on. I personally don't really like thinking in
terms of pixels, especially because these could just be placeholder assets. What if you decided you wanted
to make this game using a different asset pack that uses 64 pixel tiles
instead of 32 pixels? Now, all of your mechanics
aren't going to work the same, and every value needs
to be adjusted to match the new pixel
size of your assets. Instead, I prefer to
think of my mechanics in units of tiles per second,
pixels per second. This would be a project
wide constant value, which a lot of different scripts will need quick and
easy access to. For this, we can create
a new script folder called auto loads, then create a new script within that folder
called global. This script doesn't
need to run any code. It will only be used to hold variables when they are
used by the entire project. Like this one, we can declare our variable
in this script, PPT, or pixels per tile. It is an integer, a number
without decimal points, and its value will be 32. But how do other scripts
access this value? Open the project settings and switch to the auto load tab. This is a list of scripts which will
automatically be loaded when your game starts and will remain there
until it is closed. They can also be accessed by any other scripts
by their node name. Click on the folder icon to open a browser and find
your global script. The name of the script will be used to automatically
generate the node name. Click the Add button and
add it to the list of auto loaded nodes in
the character script. We can adjust the values
of our new variables to be defined in units of
tiles instead of pixels. It makes more sense in my
mind that the character moves at a speed of
eight tiles per second, rather than 300
pixels per second. If I choose to switch
to an asset pack which uses 64 pixel tiles, all of my mechanics will
still function the same. All I would need to change is the one value in the
global auto load script. If you prefer to use
pixels as your main unit, feel free to continue doing so. When we start our
game. Cado will automatically run a method on every node when it is ready, aptly named underscore ready. In this method, we simply multiply each of these values by global dot to convert
them into pixel units. We now have our character accelerating and running
more realistically. In the next lesson,
we'll improve the jumping and
gravity mechanics. I'll see you in the next lesson.
18. 1-4 Jump: Hello friends. In
the previous lesson, we improved the character's
locomotion mechanics. In this lesson, we'll make similar adjustments to the character's
jumping and gravity. If we run the main
scene and try jumping, you'll see that the
character jumps very high and floats in the
air for a long time. It doesn't really feel good taking a look at the
character script. The jump velocity is another arbitrary constant value which doesn't really
fit our needs. Instead, let's define our
characters jump in terms of how many tiles high they
should be able to jump. This will be an exported
private variable named underscored jump height
with type float. And I'll use a default value of 2.5 while not exactly realistic. This allows the player
to jump on top of a platform that they could
also choose to walk under. We also need to multiply
this by global bt. In the ready method,
before the game starts, we will need to calculate
the velocity that the character will need to
reach that jump height. We can replace the jump velocity constant with a private
variable to hold this value. Since it will be
calculated at run time, it does not need to be exported. This calculation can be
added to the ready method. Two, the jump velocity
can be calculated using the square root of jump height multiplied by
gravity multiplied by two. We can't use square root on a negative number, it
has to be positive. Luckily, in two D games, the y axis is inverted. Gravity is already expressed
as a positive value. However, our result for the jump velocity is
also a positive number. As a result, we
need to multiply by negative one to make the
character jump up, not down. Now we can edit the jump method
to use this new variable. If we test it out, the
character will jump exactly 2.5 tiles high when we
press the jump button, but the jump is still
slow and floating. To fix this, we can open the project settings and
alter the value of gravity. Either by filtering the settings or by clicking on physics two D, we can find the default
gravity setting. This is set to 980 pixels
per second squared. I will instead type calculation
directly into the field starting with 9.8 which
is gravity on Earth, multiplied by tile
size of 32 pixels, then multiply by eight, giving me a value of 2508 0.8 which is approximately 2.5
times what it was originally. This will make
everything in the game feel heavier and fall faster, which will appear more
realistic to the player. Now the jump feels much better, but I would like to give
the player the option of controlling how high
the character jumps. There are a lot of
different ways in which games implement
this feature, like measuring how
long the jump button was held before releasing. Adding an initial jump force, an extra force every frame
for a short time after, et cetera, done poorly, this can lead to frustrating, unresponsive controls
or characters feeling like they're
wearing a jet pack. My personal favorite
solution to this is very simple and feels very
responsive to the player. Simply stop the player from moving upward when
the jump button is released in the
character script. Under the jump method, we
can add a stop jump method. Remember that the y
axis is inverted. In two D games, the zero value for Y is at the
top of the screen, and positive values move down. If the characters velocity
y is less than zero, meaning they are moving up, we can set it to zero to
stop them from moving up. Then gravity will start
making the move down. The player will perceive
this as controlling their jump Ppe by holding and
releasing the jump button. We just need to call this new method from the player script. When the player releases
the jump button, if event is action released, jump, then character stop jump. Now let's try it out. We
can easily control how high the character jumps by releasing the jump button before they
reach their maximum apex. This is easy to implement and effective for the player to feel like they
have full control. The last thing I want to address in this lesson is
that the player has just as much control over the character's movement in the air as they do on the ground. Realistically, there would be no such thing as air
control while jumping. But in most video games, air control exists to keep the player in
control of the character. It can be especially useful in platforming games for allowing the player to avoid pitfalls or redirect dare to
land on platforms. Some air control can be good, but it shouldn't be the same
as movement on the ground. To add more specific air
control to our character, we can define another exported variable for it at the top of the script as a float
type and give it a default value of 0.5 All we need to do is multiply this by acceleration when the player
is not touching the floor. To simplify the physics
process method, let's divide it into
two separate methods, physics and ground physics. These can both be private
methods in the physics process. If the character
is on the floor, then apply ground physics, else apply air physics. Then proceed with
move and slide. Ground physics will not
change from what we already have in the air physics method. If direction is not zero, we can move velocity x toward speed times direction
at a rate of acceleration times air
control times delta. This will still allow the player to have some control in the air, but reduced by half. Let's keep these variables
separate from the ones above by also exporting
categories for them. These variables will be exported
into the jump category. The variables above will be exported into the
locomotion category. Let's try it out. Like
with the other variables, you can make adjustments
to the value and see what it
feels good to you. We now have our character
jumping and falling more realistically and with better control for the player. In the next lesson,
we'll animate the character to
match its movement. I'll see you in the next lesson.
19. 1-5 Animation: Hello friends. In
the previous lesson, we improved the overall feel of the character's
jumping mechanics and gave the player
better control of their jump height
and trajectory. In this lesson, we'll add animations to the
character scene, open the character scene, and get a good view
of the character. First, let's make the character face the direction
they're moving in. If you select the sprite two D node and look
in the inspector, expand the offset section under sprite two D. The
property we want to change is flip H. For horizontal clicking
on the checkbox, we can see that the
sprite is flipped. Hovering the mouse over the name tells us how to
access this property. In our script, the property
name is flip underscore H, opening the character script. We created methods for
face left and face right, but we haven't used them yet. All we need to do is
modify the value of the flip H property of
the sprite two D node. We can get a reference to the sprite two D node at the top of the script using
at on ready var, we'll name it underscore sprite. This will be of type sprite two D. Then assign the
value of this variable to be dollar sign sprite two D. Whichever node this character script
is attached to, it will search through the
child nodes to find one named sprite two D and
assign it to this variable. Keep in mind that for this
script to work as intended, every character we
create must have a child node named
sprite two D. Otherwise, this variable will
not contain a value. Now in the face left
and face right methods, we can use this
reference to the sprite two D to alter the
flip H property. If the character is facing left, then the flip H property
should be true. If they are facing right,
it should be false. But these functions are
not being called anywhere. The best place to
call them would be at the start of the
physics process. It doesn't matter if the character is on the
ground or in the air. After all, if the sine of
direction is negative one, then the character
should face left. If the sine of direction is one, then the character
should face right. We don't need to do anything if the sine of direction is zero. Now when we run the main scene, the character will face the direction of
the player's input. This is a good start,
but let's also start working on
animating the character. Right click on the
sprite two D node in the scene tree and
add a child node. The node we need to search for is an animation player node. This node will allow us to change aspects of the
character over time, particularly the sprite texture. Adding this node will
automatically open the animation panel at the
bottom of the editor window. Click on the animation button, then click New Name, the new animation idol. Now you can see a timeline
in the animation panel. Each aspect of the character
which is being changed by the animation will be
listed on this timeline. Click the plus button to add a new property to the animation. The property we
need to change is the sprite two ts texture. In order to mark the
positions within the timeline where the
sprite will change. We can add a keyframe
by right clicking on the property track and
selecting insert Key. Once a keyframe is
added to the timeline, we can easily move it around. Clicking and dragging, if
it isn't in the right spot. Clicking on a key frame we can set its value
in the inspector. The idle 01 sprite is already populated into
the texture field. Since that is its current value, I think that these
assets look best. With a frame rate of
ten frames per second, I will want to change
the sprite's texture every one tenth of a second, meaning that the total
animation length will be 0.5 seconds. The timeline is currently
showing 15 seconds, which makes editing
only the first segment of the entire
timeline difficult. Let's zoom in using the slider
in the lower right until we can easily see every 0.1
seconds on the timeline. So let's add four
more key frames and populate each with
the remaining sprites. In order, we need to reduce the length of the
animation currently set at 1 second down to 0.5 seconds. We want this animation to loop. Click the Animation Looping
button to the right. We can preview this animation by clicking on the play icon at the top of the animation
panel. This looks pretty good. Next we need to run animation. Click the animation button
and select Duplicate. Then name the new animation run. Expanding the asset folder which contains the
run animation, we can see that it
contains six sprites. We'll need to expand the
length of the animation to 0.6 seconds and add
another key frame. Then populate each key frame
with the correct sprites in order When you're done, preview the animation to
make sure it looks good. Repeat the process three
more times to create the animations for
jump, fall, and ground. Paying attention to the length
of each animation based on the number of sprites
provided in the asset pack. However, these animations
are not meant to loop, so be sure to turn off
looping for these ones. We now have our character
animations all set up. In the next lesson, we'll
build a state machine to synchronize the animations with what the character is doing. I'll see you in the next lesson.
20. 1-6 State Machine: Hello friends. In
the previous lesson, we set up animations for
our characters movement. In this lesson, we'll implement those animations and synchronize them to the
character's behavior. But before we do that, let's quickly add another
node to the main scene. A camera two D node. This will help us quickly get a better view of our
character since we can reposition and zoom it in by editing its properties
in the inspector. Now when we test to the scene, we'll have a better view of the results of these animations. Now to implement the animations, we'll open the
character scene and add another child node to
the animation player. This node is called
an animation tree. The animation tree node
gives us a warning, no root animation node
for the graph is set. Looking in the inspector panel, there is a field
labeled Anim player. Clicking where it says a sign, we can select the
animation player we made in the previous lesson. The purpose of this animation
tree will be to control which animation is playing
at any given point in time. Using the drop down
labeled tree root select new animation
node state machine. This will open the newly
created state machine in the animation tree panel at the bottom of the editor window. Before we start editing the
state machine, however, let's take a look at the asset
folder for our character. The animations are
split into two folders labeled with sword
and without sword. The contents of each of these folders are
all same animations, with the exception of the
character holding a sword. Or in order to avoid
redundant effort, I'll be planning ahead with the structure of this
animation tree to accommodate features which will not be implemented until
later in the course. To get started with
our state machine, we will first right click
in some empty space in the animation tree panel and
select Add state machine. Then name this new state
machine without sword. We can also do this
again and create another state machine
named With sword. We can then click the
transition button in the upper left corner and add a transition from start
to without sword. Now when the game first starts, the animation tree will
default to playing animations for our character
without the sword. We can ignore the sword
state machine for now, switch back to selection
mode and click on the pencil icon to open the without sword state
machine for editing. Inside the state machine, we can create another state
machine named movement. Then add the transition from start to movement
as the default. This layer of the tree will be used later for
another purpose. Open the movement state
machine for editing once more. Create another state machine, this time named locomotion, and create a transition
from start to locomotion. Then open the locomotion
state machine for editing. Finally, we can add animations
to the state machine. Right clicking select
add animation, then idle and repeat to add run. Create a transition
from start to idle, making it the default animation. Then add a transition
from idle to run and from run back to idle. The purpose of this
state machine will be to change the animation
between these two states. We can set the
conditions under which these transitions will
happen by clicking on them, then editing them
in the inspector with the transition from
idle to run selected, expand the advance section and select the
expression text area. The expression entered into this text area must
produce a Boolean value, either true or false. The condition under
which we want the character to
switch from idle to run is if velocity is not zero. Likewise, the condition for
transitioning from run to idle will be if velocity
x is equal to zero. We also need to tell
the animation tree which node is controlling
these advanced expressions. Selecting the tree
in the scene tree. Then looking in the
inspector panel, we can change the advanced
expression base field to the character's root node. Now we can run the main scene and see the animation tree work. Switching the character
animation between idle and run based on the
character's x velocity. To incorporate the rest
of the animations. Return to the
animation tree panel and navigate to the
movement layer of the state machine Right
click to add the jump, fall and ground animations create transitions from
locomotion to jump, jump to fall, fall to ground, ground to locomotion
and locomotion to fall. Then we can set the conditions for each of these transitions. The characters animation
should change to jump if their velocity y
is less than zero, then switch to fall if their velocity becomes
greater than or equal to zero followed by
ground if is on floor. Since this is a built in method of the character body two node, we can access it and it
returns a Boolean value. There is no condition
for transitioning from ground to locomotion. Instead, expand
the switch section and select at end
from the dropdown. This will allow the
ground animation to complete before
transitioning to locomotion, but impose no other conditions. Lastly, if the player
walks off of a ledge, we want the animation to transition from
locomotion to falling. That will be true
if not is on floor. But the transitions from
locomotion to jump and fall will both be true at any
time the player jumps. We can give the jump priority
by changing its priority 1-0 Switching back to the main scene and running
the current scene. We can now see the
animation tree at work, as it will automatically
transition the character between
the various states. Now we have the character's
basic movements animated. In the next lesson, we'll add dust and sound effects to
some of these animations. I'll see you in the next lesson.
21. 1-7 Effects: Hello friends. In
the previous lesson, we used a state machine to synchronize the characters
animation with their behavior. In this lesson, we'll add
some sound and visual effects to add polish to the animations. Starting in the character scene, let's add a new node, an animated sprite two D, and rename it to dust particles. A warning tells us
that we need to create a sprite
frames resource in order for this node to work
with the new node selected. Looking in the inspector, expand the animation section. Clicking on the drop down
labeled sprite frames, select new sprite frames, then click on the sprite frames resource to open it for editing, which will appear in the
bottom of the editor window. Here we can see a newly created sprite frame animation
named Default, with a frame rate of
five frames per second. In the file system panel, search through the
asset pack to find the dust particles folder group, select the fall sprites and drag them into the
animation frames area. Pressing play to
preview the animation, we can see what it
will look like. We don't want this
animation to loop, turn off looping by cooking
the animation looping button. Expand the offset section
and adjust the y offset until it is level
with the red x axis, which will be our ground. It's important to use
the offset field and not the transforms
position in order to get the particles to
appear on the ground. I'll also change the frame rate to match my character
animations, which had a frame rate of
ten frames per second. Now when we hit play, the
animation will play only once. And we can see how it will look in the game in relation
to the character. The purpose of this animation
will be to play once when the character lands
on the ground and disappear when
it is finished. These types of effects
look best when they exist independent
of the character, as if the dust is part of the world and should not
follow the character around. Let's right click on
the dust particles node and save this
branch as its own scene. This doesn't really belong to any of our existing folders. Let's make a new folder
for it named dust. Name this particular scene, fall dust cooking on the clapper board, icon
in the scene tree. We can open the fall dust scene in order to make the
particles disappear. After the animation is complete, we will need to attach a
script to the root node, clicking on the ad
script button name the new script dust. We don't need to use any
of the default templates. Uncheck the template
checkbox and make sure the script is
saved in the scripts folder. This will start the script
off with nothing more than extends animated sprite
two D line at the top. But before we actually
write any code with the animated sprite
two D node selected, look at the inspector panel
and switch to the node panel. Signals should be
selected by default. This is a list of signals which the node can
use to trigger behaviors either in its own
script or in other scripts. We can use the
animation finished signal to know when the
animation is finished. And run a method in our script. Right click on
animation finished. Connect to open a new window. Make sure that the dust
particles are selected as the recipient of the
signal. Then click Connect. You'll notice that Do has
automatically generated a method in the
script which will be triggered when the
animation is finished. There's a green connection
icon to the left of the method declaration to let us know that this method is
being triggered by a signal. Also in the scene tree, the signal icon is
displayed next to the dust particle node to let us know that this node has
a signal connection. The only thing we need to do in the script when the
animation is finished, is tell the engine
that we are done with this node and it can be removed. This is done using a built
in method called free. We also want to tell the
script to automatically play the animation when
it is added to the scene. Overriding the
underscore ready method, we can simply call when this
node is added to the tree, it will play once and remove
itself when it is finished. We can now duplicate
this scene to create the similar dust particle scenes for jump and run particles in these new scenes,
replace the sprites in the animation with the correct sprites from
the asset pack. We now have three different
dust particle effects we want to generate under different
conditions for our character, but they can all use one method. Switching back to
the character scene, we can now delete
the dust particles node since it is not meant
to be part of the scene. Then switch to script view and
open the character script. Let's add a new private
function called spawn dust, which will take a
parameter named dust. The type of this parameter
will be a packed scene allowing us to use the scenes we just created as the
argument for the method. First, we need to
instantiate the scene, then change its position so it is in the same
location as the character. Next, add it to the scene tree by first getting the character's
parent node, then adding the dust
as a child node. By making the dust a
sibling of the character, it will exist in the world independent of the character
and not follow them. As soon as it is added
to the scene tree, the animated sprite will
play its default animation, then Q itself to be removed when the animation is finished only a fraction of
a second later. To trigger the jump
dust particles, we can call this method
directly from the jump method, passing in jump dust
as an argument. Then declare what the jump
dust is at the top of the script as an exported
variable of type packed scene. Selecting the
character's root node and switching back to
the inspector panel. We can then populate
it in the inspector by clicking and dragging the
scene into the exported field. The run and fall
particles do not have a simple place in our script
where they can be spawned. However, what we can do instead is select our
character's animation player, open the ground animation, and add a new track. This time a call method track. Then we can access the
character's root node and call the spawn particles method at the start of the animation. By adding a keyframe. Selecting the key frame
will allow us to pass arguments to the method
which we can then populate. By clicking and
dragging the fall dust. We can do the same thing
with the run dust particles, adding them to the
run animation. Remember that the run
animation will loop, so new particles will spawn every time the
animation loops. For the fall and jump particles, it doesn't really
matter which way the character is facing. But for the run particles, it is important that they appear to move behind the character. We can set the animated
Steph property to match that of the character at the moment of its creation. Let's try it out.
Running creates dust every 0.6 seconds
of continuous running. Jumping and landing also create their own
unique dust clouds. For another bit of polish, we can also add sounds
to our animation tracks, Switch back to the
character scene and add a new node
to the scene tree, an audio stream
player two D node. With this node in the tree, we can now open
the jump animation and add a new audio
playback track. Selecting the audio stream
player node to be the target. Placing a keyframe at the
start of the animation. We can then populate this
keyframe with an audio stream. Browsing through the
sounds provided in the battle sounds
packed by Luke Sharps. There are a few
appropriate sounds to choose from which fit
this character nicely. Import the sounds you
like into and place them into a designated sound effects folder to keep your
project organized. Then populate your animation
keyframe accordingly. To get the run animation
looping over shoe sounds. Make sure that you modify
the start offset and end offset to select a sample which is shorter
than the animation. Let's try it out now. The character makes a
grunting noise as they jump, An impact sound when they land, and footstep noises
while running. Throughout this first
section of the course, we have created a fully
animated character with both audio and visual effects and fun platforming mechanics. In the next section, we'll start working on building
levels for our game. We'll see you in
the next section.
22. 2-1 Tilemap: Hello friends. In
the last section, we created a fully
animated character with platforming mechanics. If you've completed
the assignment, you should now have a full
cast of four characters. And we'll have figured out
that you can duplicate the player node to control more than one character
simultaneously. If you were able to complete
the intermediate challenge, you'll also have some AI scripts controlling one or more
of the characters. With the more
advanced challenge, you'll probably have all
of the character nodes, child to the player node, with the character
being controlled, able to be switched
with the button press. In this section, we'll
build a level for our game. Our first lesson, we'll start with building the level terrain. Add another node
to the main scene. The node we are looking
for this time is tile map. A tile map will allow us to quickly add and modify
sprites in our level, which will be organized into
tiles of a specific size. For a tile map to work, it needs a tile set. As you can see in the
inspector from the drop down, Label tile set,
select new tile set, then click on the tile set
resource to expand it, exposing some
fields we will need to edit our tiles are square. But you can see that there
are a number of other options to make working with other
tile shapes just as easy. The first thing we need to
change is the tile size. The tile size of
the asset pack I'm using is 32 pixels in
both width and height. It is important that
you set this property before adding any
tiles to the tile set. Because we are using this
tile map for terrain, we'll also need to add a
physics layer for collision. A terrain set will make
editing terrain much easier. The terrain set can
hold multiple terrains. We need to add a terrain
to the terrain set. You can think of each terrain as a different biome or
construction material. Let's name this first
terrain island. Each terrain type
can be assigned to a different color to distinguish
them from one another. Since we're only
going to be using one terrain type per now, we can just use
the default color. This color won't be
used in the game only while we are
editing the terrain. It is only for our
benefit as a developer. With those changes made, we can turn our attention
to the bottom of the editor window where a new tab has opened
for the tile map. The message tells us that
we need to configure a tile set first switch to the tile set
tab at the bottom. Here we can add
the tiles provided in the asset pack
to this tile set. Either by clicking on
the plus button or clicking and dragging the image file from the file system. Tab code will offer to automatically create
tiles for us. Select Yes. If you're sure you've
set the tile size correctly, otherwise select no. The tiles should now be lighter and have
an orange border. Selecting the eraser
tool will allow you to remove tiles from the tile
set by clicking on them. Likewise, turning off
the eraser tool will allow you to add tiles to the tile set by
clicking on them. Switching back to
the tile map tab, we can now select
and draw any tile from the tile set into
the two D view by clicking Goode provides
an orange grid matching the tile size
to make it easier. The tile map editor also
provides all the usual tools for selecting painting lines,
rectangles, bucket filling. You can also eye drop, select, erase, rotate, flip,
and randomized tiles. Make a basic floor, Place your character over it and position the camera
to view the results. Then play the scene.
You'll notice that the terrain has
no collision yet. Switch back to the tile set tab. Switch to select mode and group select all the tiles by clicking
and dragging over them. Let's first add collision to our tiles by expanding
the physics section. Then physics layer zero. This tool will
allow you to create complex collision
shapes for each tile, but we only need basic squares. Clicking on the ellipse icon, select Reset to
default tile shape, or alternatively
press the key to simply make the collision shape match the shape of
the square tiles. Now all of our tiles
will have collision, just like the Codell icon we were using as
the scene floor. This is great, but building
levels by populating individual tiles
into the tile map will be extremely tedious. Fortunately, Godel provides a very easy to use terrain editor. Switch to paint mode. This allows us to paint
properties onto the tiles. The property we want to paint
is the terrain property. Particularly in terrain to
zero, the island terrain. You may want to expand the panel or zoom in
on the tile area. For this part, each
tile is divided into nine smaller squares
which we can paint to the terrain color by clicking
or erase by right clicking. Every tile which is part of this terrain should have its
middle square filled in. In order for the terrain
editor to work as intended, we need to paint the edges of every tile which is meant to be drawn next to another tile. When you're done,
there should not be any duplicates in this tile set. Every tile should have
a unique pattern. Switch back to the tile map tab. Switch to the terrains
tab at the top. And our island terrain
should be listed as an option selecting that you'll see every tile
from the tile set listed, along with two extra options, continuous mode and path mode. Select continuous mode,
then draw terrain freely in the two D view by clicking and erased by right clicking. Each tile will automatically correctly connect to
its adjacent tiles. You should be able
to draw terrain of any shape you can imagine and the proper tiles
will automatically be selected for
each tile position. If there are any
inconsistencies, double check your terrain
property painting for errors. Using this tool, we
can quickly and easily generate the terrain for
every level in our game. We now have basic terrain
in our game that we can use to build the
foundation of our levels. In the next lesson, we'll add some decorative vegetation to make the environment
feel more alive. I'll see you in the next lesson.
23. 2-2 Decorations: Hello friends. In
the last lesson, we set up basic terrain with collisions and terrain editing. In this lesson we'll add a few decorations to our terrain to make
it more interesting. Like the tile map switch to the tile set tab at the
bottom of the editor window. There's another tile set
graphic in the asset pack which belongs in this
tile set named front, palm, bottom, and grass. Let's add that to the atlas. These decorative
elements don't really need any physics or
terrain editing. The grass can simply
be drawn over areas randomly about your level where you think it
will look nice. The roots, however, are meant to be drawn over terrain tiles. But if we try to do that, it will replace the
terrain instead. To accomplish this,
we need to add another layer to our tile
map for these roots. Selecting the tile map
in the scene tree, we can collapse the
tile set resource and expand the layers section. Here we have one layer
which has no name. Let's name this layer terrain, then click the Add
Element buttoned, Add Another Layer, and
name this one roots. To make this layer drawn
in front of the terrain, we can edit the z index,
increasing it to one. Since the terrain
is on layer zero, the roots on layer one will
always be drawn over top. The drop down in
the upper right of the tile map tab now contains
our two different layers. This allows us to add root
tiles to the root layer, which we can then paint over the terrain tiles on
the terrain layer. When each layer is being edited, it will appear in full color, while all other
layers will be faded. But these roots also need trees. Let's also build the trees. If you want trees to
have collision and be an immovable object
that acts like terrain, the most appropriate
node type to use would be a
static body two D, which we will name Palm Tree. This is the same node type we originally used for
the Godot icon. This particular node can
still have collisions, but will not be moved by
collisions with other objects. It will remain static. This tree will
have an animation. So let's add an animated sprite, two D as a child node
of the palm tree. Create a sprite frames resource
for the animated sprite. Then open the
resource for editing. Click and drag the palm tree
images into the animation. Change the frame rate
and hit play to preview the animation. This looks good. Let's make sure this animation
automatically plays by default by clicking on the Autoplay button at
the top of the panel. Let's save the Palm tree node as its own scene and sort it into a new scene
folder for decorations. Now the palm tree can
be edited in isolation, and all changes made to the
palm tree scene will apply to all copies of the palm tree With the palm tree
node selected, Click and drag it
to move it around the level and position
it over the roots. We can make this a lot
easier by turning on grid snapping with the button at
the top of the two D view. We can also configure
the snapping options by clicking on the ellipse icon. Let's make sure that objects
snapped to multiples of 32 pixels to
match the tile size. But the palm tree
doesn't want to line up because the origin of the
tree is in its center. For the tree to line
up with the tiles, the origin needs to be in the corner selecting the
animated sprite, two D node. Adjust the offset to have
the sprite draw 16 pixels, or half of a tile down into
the right of the origin. Now the palm tree can easily be moved around to match
the terrain tiles. The static body needs
a collision shape. In order to collide
with anything, we need to add
another child node, a collision shape, two D. To make this palm tree
more interesting. Let's also make it function as a platform for the
player to jump on. Set the collision shape to
be a segment shape two D, this is just a line which we can edit by moving
the two points. Make the line segment line up with the top
of the palm tree. Let's try it out. It
functions as a platform, but it would be nice
to be able to jump through the bottom instead
of colliding with it. I would like the player to not hover so far off the
edge before falling. Adjusting the grid
snapping settings to eight pixels
or quarter tiles. I'll make the line segment only cover up half the full
length of the tile. If you want to have the
player able to pass through the bottom of the platform
and land on top of it. There's a simple built in implementation for
that behavior. In Godot, simply check the one way collision
checkbox in the collision shape
two D properties. This draws an arrow
in the editor showing us in which direction the collision will be applied. That is much better. Now I can
jump through the bottom of the palm tree platform
and slide off the edge instead of standing
on an invisible barrier. Something that might
be bothersome to some players who like the
pixelated art style in particular is that the
player is not being drawn on the exact same pixel
offsets as the environment. If I position the character
next to some grass, you can see that the pixels of my character do not always
line up with the pixels of the grass in the
project settings. We can adjust these
rendering settings to force all images being drawn on screen to be drawn at exact
pixel coordinates, which is what the pixel
art fans would prefer. These settings are
hidden by default. Click on the advanced settings
toggle under Rendering. A new option for
two D is revealed. Here we have options
for snapping transforms and vertices to pixels. This might cause the player to hover one pixel above the floor. If that's the case, adjust their collider so they
stand cleanly on the floor. Now when we hit play, the character and
everything else in our game will always be drawn snapped
to pixel coordinates. We now have grass and
animated palm trees decorating our pixel
perfect island. In the next lesson, we'll
add a script to our camera, which will follow the
player allowing us to make levels that are
larger than one screen. I'll see you in the next lesson.
24. 2-3 Camera: Oh friends. In the
previous lesson, we decorated our island
with vegetation. In this lesson, we'll
have the camera follow the player
around the level. The easiest way to
accomplish this would be to simply child the
camera to the player. But this approach is very restrictive and can
cause motion sickness. Camera movement
should be more subtle and show the player what's
ahead of their character, having the player character
in the camera as siblings, we will need the
camera to react to the player's actions and have its own scripted
behavior in response. Let's start by creating a
new script for the camera. We can just call it camera and make sure it's saved
in the scripts folder. This camera will need to
know what it is following. Let's export a private
variable which we will call underscore subject. The type of this
doesn't need to be a character body
two D. Instead it can be something
more generic like a node two D. If you look
in the scene tree, you'll notice that
the nodes have different colors, most
of which are blue. These blue nodes are
all node two D's, but are more specific
types of node two D's, including the camera itself. There are two D characters, two D static bodies, two D animated
sprites, et cetera. Selecting any of
them, you'll see in the inspector that they
have the properties labeled node two D as well the transform property which
contains their position, rotation, scale, and skew. By setting the type of our
subject to node two D, the camera will be able
to follow anything in our scene that qualifies
as a node two D. Since we only need
to know that it has a position for the
camera to follow it, Click and drag the
player character into the exported field for
our camera subject. In the process method, which will run 60 times per second, we can simply set
the position of the camera to the
position of the subject. This will be the same
behaviors when the camera was childhood to the character
without the vertical movement, severely reducing
motion sickness. It would be more helpful
if the character wasn't centered in the
camera's view though. But instead, it should look
ahead of the character and slightly up so that
the player can see more relevant
parts of the level. Like with the animated sprites, we can add an offset to our camera script to shift it
from its original position. We'll make this an
exported private variable, underscore offset with
the type of vector two. A vector two
contains two floats, an x and a y value. It is used to define
anything in two dimensions, including the position variables that we've been working with. Now the process
method can simply add the x portion of the offset
to the camera's X position. If we position the character
exactly at the scene origin, we can move the
camera to give us an idea of where we
think it will look good. Then use the camera's
current position as the offset value. Alternatively, you could
define the offset in tiles and multiply it by Google
Ppt in the ready method. Now the camera will follow the player's horizontal movement while looking ahead
to the right. But what if we don't want
our levels to be so linear? What if the player
wants to go left? The camera should look to
the character's left too. The camera will need to react to an action performed
by the character. We have two functions in
our character script, which are called whenever the
player faces left or right, but these are called
constantly with every input. Let's only call
these functions when the character actually
changes direction. To do that, we can export
a private variable to store whether or
not the character is currently facing left. As a Boolean. The face left and face right methods will set this value to be true
or false accordingly. Then at the start of
the physics process, we can add conditions
to only call face left if the character is
not already facing left, if they are facing left. Now we have our face left
and face right methods only being called when the player chooses to
change their direction. We should also call the
appropriate face left or face right methods during
the ready method based on the value of
the exported variable. This also has the benefit
of making this function work with the characters whose
sprites are facing left. By default, the camera script can now react to the character changing directions
using signals. At the top of the
character script, we can create a
custom signal which we will name Changed direction, and add a parameter telling the camera whether the character is currently facing left. Now the face left and face right methods can
tell this signal to emit passing in whether or not the character is now facing
left or not as an argument. With the character notes
selected in the scene tree. Switch to the node panel, Right click on our new
signal and select Connect. Then select the Camera.
Note from the list. The camera will listen
for this signal. We can rename the method which will be
called as a result. To put it in terms
that are easier to understand within the
context of the camera, let's name it on Subject
changed Direction. The method is added to the
camera script automatically. Now we can change the cameras offset to look left or right. Since we know which
direction the character is facing To preserve
the value of offset, let's create another
private variable named look Ahead distance. Which starts off as a copy of underscore offset X set during the ready phase
with at on ready. Then we can set the
look ahead distance to be the X offset, but multiplied by
negative one if the subject is facing left or positive one
if they are not. Then use the look
ahead distance in the process function to
set the camera's position. Now the camera
will look ahead of the character no matter which
direction they're facing. But what if we want our levels
to go up and down as well? We can use a similar
method to have the camera react to the
subject emitting a signal. This time moving the camera when the character lands at
the end of their jump, adjusting the camera to a new height in the
character script. Let's add another signal
called landed and pass a parameter called floor
height, which will be a float. How do we know when
the character lands? Well, we have the handy
is on floor method. If we store this value
in a private variable, we'll call it was on
floor as a Boolean. We'll record it here. Above move and slide. Then after move in slide, we can check if the
character wasn't on the floor and now
is on the floor, which means this is the exact
moment when they landed. Let's call a new private method here called underscore landed. Writing a definition
for this method, we don't really need to do
anything else here right now, besides emit the landed signal passing in our Y position
as the new floor height. Telling the camera to
listen for the signal, the camera can then call a new method called
on subject landed. All we need to do is
adjust the value of the camera's Y position to be the new floor height
plus the Y offset. We now have the camera
following the player, allowing us to make
levels that are larger than what we can
fit on a single screen, but the movements are jarry. In the next lesson, we'll
make the camera move gently between these
positions using tweens. I'll see you in the next lesson.
25. 2-4 Tween: Hello, friends. In
the last lesson, we move the camera around, following the
player as they move right, left, up and down. In this lesson, we'll
have the camera gently sweep between
these positions. The simplest way to
gradually adjust values over time
is to use a tween. Let's declare two new
private variables. At the top of our camera script, we have two different
signals triggering two different behaviors
in our camera script. We want these to stay
separate from each other. The first variable will be
called look ahead tween, the second floor height tween. Both are of type tween. Starting with the
look ahead tween, when our subject
changes direction, we can set the value of look
ahead tween to create tween. This is a built in
method we can use to create our tweening behavior
quickly and easily. We then tell this look ahead
tween to tween a property starting with which
node the property belongs to which is itself. Then we designate in quotation marks the
name of the property. We will tween the value of
look ahead distance to become this value we already calculated
over a duration of time. For now we'll say 1
second. This looks better. But if the player changes direction while the
camera is panting, we shouldn't have two
tweens created at the same time competing
over control of the camera. It doesn't appear to
be causing any issues. But we can eliminate the
possibility by first checking if the tween already
exists and is running. If this is true, then we should kill it before
starting another one. This tween is currently linear and has a
duration of 1 second. But we can easily customize it to create a wide
variety of effects. First, let's export a
float variable called underscore duration and give
it a default value of one. Then plug this variable
into the tween method call. We can now adjust the
speed that the camera will pan by simply changing the value of this variable
in the inspector. Remember that you
don't need to stop the game to adjust
values in the inspector. I think this should
be a bit slower in case the player turns
their back to an enemy, but doesn't want them
immediately removed from view. For example, to change how
the camera moves over time, we can add easing and
transition types to the tween, causing it to move slower or faster at the beginning or end. Even bounce or move beyond
the defined limits. Let's export a new private
variable named trans type. The type of this variable will
be tween transition type. Then when we create the tween, we can also call another method, set trans pass our
transition type as the argument to this method. We can do this because both the create tween and set trans methods
return the tween. As a result, the
create tween method creates the tween
and returns it, which is then used to call the set trans and
returns the tween again, which is then assigned
to our variable, selecting the camera in the scene tree and
looking at the inspector, you'll see that the transition
type default is linear, meaning that the value
progresses evenly over the duration from the drop down. There are a variety
of options to alter how the value
will change over time. We can also add another
variable called easing type, similarly typed tween ease type. This will alter the
transition to affect only the beginning of the
end of the tween or both. Just like with the
transition type, we can also set the easing
type in the same line of code. When we created the tween, try experimenting with
different durations, transitions, and easing
types to see how they feel. We can use the same technique
to gradually adjust the camera's current
floor height over time after the subject lands. Instead of using a hard cut, first we need to store the current floor height
in a private variable. Then when the subject lands, we can copy most of the same code from when
they change direction, only changing the tween
variable from look ahead to floor height and also changing the property name
that we're tweening the value. We will tween two is the same
as the method parameter. Then we can edit the process
method to set the cameras y position to be the floor
height plus offset y. Now the camera will
gently pan up or down after the subject lands
adjusting to a new floor height. If you want these two behaviors to have different durations, transition types
or easing types, that is as simple as exporting
more variables for them. I would like the
horizontal painting to be slower and more gentle, but the vertical to be
quicker and more reactive. I'll have a longer duration
for the horizontal, but also have the ease type
to be both in and out. For vertical, I'll have a shorter duration
and only ease out. The camera moves faster at first and slower after we. Depending on which version
of do you're using, there might be a
problem with moving both the camera and the
character at the same time, creating a jittering effect. To fix this, try opening
the project settings. Looking under display window for V sync at the bottom
and disable the setting. Now we have our camera,
showing the player exactly what they need to see
in a smooth, gentle manner. In the next lesson, we'll add an animated background
to our scene. I'll see you in the next lesson.
26. 2-5 Background: Hello friends. In
the previous lesson, we finished our camera's
behavior script, so it follows the
player character. In this lesson, we'll add an animated background
to our level. The level background
needs to give the impression that
it is very far away. Otherwise, the
environment will appear very flat and two
dimensional to the player, we can create an
illusion of depth by adding a child
node to the camera. A node two D, it will have a position value as well as a
Z index, a name background. Then expand the ordering
section under canvas item and set the Z index of this
node to be a negative value. The level terrain is using
the default Z index of zero. A negative number will be drawn behind depending
on how many layers of depth you want in
your environment. You may want to put
the background z index farther back than
just negative one. I'm going to put the
background at negative ten, leaving negative one through
negative nine available for anything that needs to be in between the background
and the terrain. Even though I'm not
planning on doing that, it's just better to leave
the possibility there. Now, every child node we add to this node will automatically follow the camera and be drawn behind everything
else in the level. The asset pack contains several sprites we can use to assemble a background
environment. We can simply click and drag the images for the
background into our scene. And Godot will automatically
turn them into sprite two D nodes group selecting
all the new nodes. Make them all children
of the background node. By dragging them into it, I'll just stretch the sunset, the sky and the water images so they cover the entire
viewport of the camera. Let's make the background
its own scene so it can be edited in isolation and save
it into the scenes folder. Then click on the clapper
board icon to open it up. In the same folder, there are some sprite animations to
make the water twinkle. Let's add three animated
sprite two D's into our seam. We can then populate each of these with a sprite
frames resource. And populate the sprite frames with each of the three
sprite animations. Also, make sure
that they autoplay their animations looping
with the correct frame rate, then position each in the water where they
will look nice. When you're done, you
can save your work and see how it will look
by running the main scene. Next, let's add some
clouds into the sky. These aren't animated,
so we can just drag them into the two D view to
make sprite two D nodes. If you want to adjust how
the sprites are layered, instead of adjusting the Z
index value of each sprite, we can also use the scene
tree to re order the nodes. Nodes with the same
Z index will be drawn from the top of the
scene tree to the bottom. Simply organizing the list
of clouds by their size will also a sort
their order and put the smallest cloud in the back and the largest
cloud in the front. Wow, wow. This largest cloud is also already larger than the camera's viewport and
has these hard edges. It's designed this way
to be looped around on itself using a technique
called parallax scrolling, duplicating the sprite node. We can place two of them in our scene and move them
gradually to the side. Then when one moves off screen, we can reposition it
to the other side, causing it to loop infinitely
in the background. This will further add
to the illusion of depth that the
background is far away, but also create the illusion
that there is wind. Let's add a script to the
root node of this scene, which will create the
parallax scrolling effect. First, we'll need to get a reference to the child
nodes which we want to move, we can use at and ready to
store these in variables, making sure that we
put the exact names of the nodes after the dollar
sign in their assignments. Let's also export a variable
for scrolling speed, which is a float based on
the palm tree animation. It is implied that the wind
will be blowing to the left. Let's use a default
value of negative 100 in the process method. We can then add a scrolling
speed multiplied by delta to the exposition
of the clouds, causing them to move left
at a constant speed. But we're going to do this
to five different clouds. So it would be better to
make this method taking a node two D as a parameter along with the distance it is
being scrolled as a float. Then we can just add the
distance to its position. Now the process method
can call this method, passing in each cloud along with the scroll speed
multiplied by delta. To calculate the
distance per frame, we can further improve
the illusion of depth by also having the clouds
move at different speeds, With the larger clouds
nearest to the player moving faster and the smaller clouds
further away moving slower. But avoid using
speed multipliers which are multiples
of each other. This will form a very
obvious pattern and break the illusion if one cloud speed is exactly half of another. For example, the player will notice it helps to use
prime numbers for. The last thing we need
to do is check if any of our clouds have moved
completely off camera, then move them over
to the other side. If we position one big
cloud in the middle, the other is now where it will be when we want it
to loop around, which is also the pixel
width of the image. Let's make this
width value exported variable float type with
a default value of 448. Checking if the position x of a cloud is less than
with times negative one. We can then move it to the
right by width multiplied by two to put it on the other
side of the second big cloud. This will also make
the other clouds periodically loop around
on their own two. Let's see how that
looks beautiful. However, as soon as your player
resizes the game window, the illusion is broken. Opening the project settings
under display window, we can set a desired
resolution for our game. More importantly, we can change the stretch mode to
stretch canvas items, which is everything
in our two D game. We definitely want
to keep our aspect ratio the same even if the
player resizes the window, A new feature recently added. We can also restrict stretching to only scale by
integer amounts. This can stop our pixels from being drawn at
inconsistent sizes. Once you've decided on
a resolution setting, make sure your background
fits the new settings, and it should be impossible to see outside your background. Now we have a nice cloudy sunset in the background of our level. In the next lesson, we'll add
water into the foreground. I'll see you in the next lesson.
27. 2-6 Water: Hello, friends. In
the previous lesson, we added an animated
background to our scene. In this lesson, we'll add
water into the foreground. For this lesson,
I've downloaded and modified another sound
effect for water splashing. This sound effect was
created by Yin Yang Jake 007 on Freesound.org Just
like the background, we can start by creating a
root node for the foreground. This time using an area two
D node and name it water. Then set at z index
to a positive number, so it will always
be drawn in front, leaving room for extra
layers in between. The assets for water
in the asset pack have an animation for waves and a flat square
for everything else. Let's add an animated sprite two D node for the
surface of the water. Create a new sprite
frames resource. Add the sprite
frames autoplay loop and then set the frame rate, add an offset or move
the animated sprites transform so that
the water's origin is in the upper left corner. This will make it easier to
line up with the tile map and also provide the Y value of
zero being the water surface. Then also add the bottom
sprite as a sprite two D node. While the bottom can easily be stretched to fill any area, the surface will need
to be duplicated and repeated to keep the
scene tree organized. We should create
another node two D, to act as a collapsible folder holding these animated sprites. The area two D needs a shape to define how
its collision will work. Water physics needs two
different behaviors. When something is
on the surface of the water and when it
is under the water, we can use a line segment drawn over the
surface of the water, since that will tell us if something is touching
the surface or not. To make things easier, make
sure that the origin of the water object is in line
with the surface collider. Let's also quickly
duplicate one of our dust particle scenes
to create a splash scene. Then delete the sprite frames
in this scene and replace them with the splash one
sprites from the asset pack. In order for the water
to react to collisions, we'll need to create
a water script. This script will need
to react to two signals when a body enters it
and one body exits it. We can simply connect
these signals to the script itself using
the default names. The body parameters of these methods are
of type node two D, as you can see from
the signal list. But in order to define how our characters will behave
in relation to the water, we need to know if
this node two D named body is a character or
if it is something else. We'll have to give
our character script a class name before
the word extends. In the first line, add
class name, character. This is now a word
that other scripts can use to refer to this
script as a general type. Back in the water script, when a body enters the water, we can now check if the
body is a character, Then call a method we
haven't defined yet, which we will name enter water and pass in the y
position of the water, which is the water
surface in the body. Exited method. We can perform the same check to see if
the body is a character. Again, when a character exits, meaning that it is
no longer touching the line segment covering
the surface of the water, One of two things
could be happening. Either they are now
above the surface of the water or they
are below the surface. Since we know the Y position of this node is the
surface of the water, the body also has a Y position. We can compare the two values to determine which case this is. But also adding global PPT
divided by two or a two tile. If the body position is less than or equal to
the water surface, that means the body has stopped making contact with
the surface of the water and is entirely above
the surface of the water. Since pixels per tile is
defined as an integer, but position y is
defined as a float, Godel will warn us that decimal
values will be ignored. We can remedy this by casting
the integer value into a float value here and the character is
exiting the water. Otherwise, the character is below the surface of the water. And we should call
a different method, which I will name dive. Switching to the
character script, we can add these three
method definitions to our public methods. Enter water, which
takes a float value representing the surface of
the water as a parameter. Also, dive and exit water. The purpose of these
public methods is to let the physics process and jump methods know where the character is and
how they should behave. We'll need to add some
new private variables. The water surface
height as a float, and two booleans telling us if the character is in the water and if they are
below the surface. Each of our new methods can now set these variables
appropriately. Enter water sets the water
surface height tells us that we are in the water but not below the surface exit. Water will set is
in water to falls. Dive will simply
set below surface. To be true for the physics, we need to consider whether the character will float or sink, as well as how much being in water restricts their movement. We can add a new category of exported variables to
our script named Swim. For the sake of simplicity, the value which determines whether or not a
character floats, we can call density and give it a default
value of negative 0.1 This value will be in comparison to
the density of water. A value of zero will
have perfect buoyancy, less than zero will float, greater than zero will
sink to restrict movement. We can also define
a value for drag, which will be multiplied by forces to reduce
their effectiveness. And use a default value
of 0.5 For starters, when an object first reaches
the surface of the water, there is an impact
force reducing their velocity In the
enter water method. If our velocity y
is more than zero, we can multiply it by drag To
simulate this impact force, just like we did with the
ground in air physics, we can first check in
the physics process, if the character
is in the water, then apply water physics, making sure to pass delta, changing if to else
on the next line, checking if they
are on the floor. Horizontal movement in the water will behave much the
same as on the ground, but with the deceleration and acceleration also multiplied by drag to reduce their
effectiveness. Since I copied this
line from above, I need to change deceleration
to acceleration. The vertical movement will depend whether the
character is below the surface of the water or if their density is
greater than zero. In either of these cases, we will need to move their Y
velocity towards buoyancy, which is gravity
multiplied by density, and also apply
drag to slow down. The rate of change
is calculated using gravity times drag times delta. If the character is touching
the surface of the water, then we want to make
them bob up and down. Which can be divided into two situations with
another if statement. If the characters
center of buoyancy is above or below the
surface of the water, then adjust the y velocity to move up or down accordingly. We can simplify this to
be if the position y of the character minus
one quarter of a tile is less than the
surface height of the water. For simplicity, we can just copy the formula from
above multiplied by negative one
if they are below the surface of the water to move in the opposite direction. The last thing we need to do is alter how jumping works
while in the water. If the character is in the
water and below the surface, then the jump velocity
should be affected by drag. If the character is
not below the surface, then they should have
the full jump force so they can easily
jump onto land. We can also emit the landed signal here
to adjust the camera. Also when they enter the water to also the stop jump method should be disabled
while in the water. While we have the
character script open, let's copy the spawn dust method and use it to create
a splash method. In the water script, we will need a reference
to the packed scene, which contains the splash. Renaming our variables
and methods accordingly. Since the water is creating the splash and the
water doesn't move, the splash can be childed
to the water, no problem. The splashes y position
will default to zero, which is already
the water surface. And its x global position can be set to the value passed into
the method as an argument. This will be the
position of the body which entered the water
to create the splash. We can set the splash X
position to match this exactly. The water also needs
an audio stream player two D to generate the
splash sound effect. Populated with the modified
file I downloaded from Freesound.org The script can get a reference to this child node during the ready phase, then tell the sound
effect to play. We can generate a splash
anytime a body enters the water passing
the body's position. And also when a character body
chooses to exit the water, positioning the water where
the player can access it and remembering to populate the splash packed scene
in the inspector, we can give it a quick test and make any adjustments we wish to the character's density
drag or the calculations of buoyancy or the popping at the surface until it feels good. With the water fully functional, let's make it encompass the
entire level's foreground, stretching the bottom,
duplicating the surface, and editing the
collision shape to match the floor we created in the very first lesson may
still be in your way too. Now we have the water
in the foreground of our level and characters
can swim in it. In the next lesson, we'll structure our level
and add boundaries. I'll see you in the next lesson.
28. 2-7 Boundaries: Hello friends. In
the previous lesson, we added water into the
foreground of our scene. In this lesson, we'll set up the structure for our
levels and our boundaries. First, the tile map is currently triggering the splash noise
When we run our scene, adding a condition to the body entered method can return
if the body is a tile map, preventing this
code from running. In this case also, some of the water
animations may not have been synchronized
when running the scene, which is only a temporary issue caused by having the animations
playing in the editor. If you're seeing a seam
on the scrolling clouds, you can also reduce the
width property by one pixel. We should also adjust the remaining
characters colliders so they can stand
cleanly on the floor. Our scene tree is now full of
many different components. The label and floor nodes are no longer relevant.
Let's delete those. We should separate all
the nodes that will be used to build different
levels into one branch, making all of our
decorations into children of a node two D. These become
easier to organize, do the same thing
with our enemies, making them all children of a node two D to keep
them organized. Since these are all components
of our games level, we can create a new area two D to hold them and name it level. Then make the tile
map decorations, enemies and water children
of the level node. For the sake of simplicity, let's temporarily set
the level's origin to be the player's starting location and move the player
to the scene origin. Then adjust all the child
nodes of the level to match save the level
as its own scene, which belongs in a new
folder named levels. We can name this level one, one. The player character Camera and background will be used for every level since
the asset pack does not contain any other
assets for backgrounds, depending on how you plan
to design your game, the background may also
be part of the level. We can now open the
level one one scene. Since we made the root node of our level into an area two D, that means we can
give it a shape. Let's define our
level as a rectangle. And adjust the size and
position of the rectangle to define boundaries to which the player and the
camera will be confined. And adjust the D bug color of this collision area to
be more transparent. It doesn't affect our
view of everything else. Creating a new script
for the level, we can get a reference to the collision shape two D
node during the ready phase. The purpose of this
rectangle is so the player and the camera
will know where to stop. We'll need to get the
minimum and maximum values allowed within the confines
of this rectangle. Let's write two methods, get min and get max, which will be public and
return a vector two. The minimum value will
be the collision shapes position minus half of its size. But to access the size, we need to first
access its shape, then the rectangle,
then the size, and repeat for the
maximum value. To avoid repeating this
code multiple times, we should also define
this as a variable, name it half size. Returning to the main scene. The purpose of this scene is to facilitate communication
between the player character, the camera, and
the current level, allowing us to easily swap out the level with a
new one as needed. You can rename this scene and its root node to
reflect this purpose. In a simple game like this, you can call this the
main scene scene, or the game scene. If your gameplay loop includes other gameplay
besides platforming, you may want to name
the platforming scene to distinguish it from
other gameplay scenes. To manage the rules of
how this gameplay scene will function, we should
give it a script. The name of the script
will usually be the name of the scene
followed by the word manager. This manager's job is to make sure that all
of the child notes have all of the
information they need to do each of their unique jobs. In order to prevent
the player character or the camera from leaving
the boundaries of the level, they need to know what
those boundaries are. Since this scene will be
capable of running any level, it will need to request
the information about the level's
boundaries from the level script and
pass that information to the player character script
as well as the camera. At the top of the
manager script. Let's first get
references to the level, the player and the camera
inside the ready method. We can then get the
boundaries from the level and set the boundaries for the
player and the camera. Now both the character and
the camera scripts will need public methods for
setting these boundaries, then also enforce these
boundaries on themselves. Starting with the
character, we can simply create two new variables of type vector two and store the minimum
and maximum values. Write a set bounds method
which takes two vector two and sets the values
of the private variables. Then at the bottom of
the physics process, we can restrict the
character's position to be contained within these limits
using the clamp method, providing the current value
a minimum and a maximum. It's up to you if you want
to impose these restrictions on all characters in your game or just the player character. But I think that the
enemies should be free to leave the level
boundaries in my game. I'll make this last
step contingent on whether or not these
boundaries have been set or not by also adding a Boolean variable
named is bound. When the set bounds method is called on the
player character, it will also set this
variable to true, but other characters
will remain unbound. The camera will be
slightly more complicated since the camera itself
also has a shape. Copying the same variables and set method into
the camera script. We can edit the
values of min and max to accommodate the
size of the camera's view. We can retrieve this
value using get Viewport wrecked size divided by zoom and reduced by half. Then add this value
to the minimum and subtract it
from the maximum. It's almost always
better to store any calculated value in a variable rather than
calculate it more than once. Then we can clamp the
camera's position the same way we did
the characters. Let's try it out.
The cameras view is contained within
the level boundary as is the character's origin, and it works on all sides. We can keep all of Roger on screen by applying
the same logic, using the size of the sprite. But because the sprite isn't centered about the
origin of the character, we need to calculate the x
and y values differently. Now Roger will only be allowed where the entire sprite
will remain on screen. Throughout this section,
we have developed many of the features that
will be used to generate levels for our game. In the next section, we'll add collectible treasures and a user interface
to our main scene. I'll see you in
the next section.
29. 3-1 Data: Hello friends. In the
previous section, we built many of the
structural components We need to build
levels of our game. In this section, we'll create
collectible treasure for the player to collect and
disperse it through our levels. If you completed the assignment, you should have two
basic levels built with different terrains,
decorations, and boundaries. As you completed this task, you probably also
made adjustments to be a better fit or
feel for your game. For example, I slowed down the scroll speed
of the clouds to negative 32 pixels
per second because I didn't like how they looked
when the camera tweeened. As the camera turned around, I reduced gravity by about
half of what it was before and altered the values for
all the characters, locomotion and swimming. I also made a small
correction to the splash sound effect
in the water script. Since the audio
player two D node reduces the volume based on
proximity to the camera, the sound will get
more quiet the further you were from the
beginning of the level. To fix this, I set
the global exposition of the audio stream
player node to the exposition passed into
the method the sound will originate from the same location as the splash at full volume. While tutorials should always be the last thing you
build for your game, I'd like to take the
opportunity to go over some level design principles
for intuitive learning. For the first island level, I started the player off with an immediate obstacle that
they must jump over, followed by a higher obstacle
and an even higher one. After that, this should be
sufficient for the player to understand that
they can hold down the jump button to reach
the top of the second, but not the third obstacle. I introduced the tree as a one way platform in
a safe environment, and require that the
player learn how to use it before reaching the
next section of the level. Here the same trees
are available, with slightly more
risk being introduced, having to jump
from tree to tree, and potentially
falling in the water. If they fail, the
risk isn't real, though only perceived
by the player. I've created three different
paths from this part, with the easiest being
the path forward, and the other two potentially
leading to rewards. All are plainly visible, teaching the player that
they should be looking out for more branching paths
like this in the future, the next time I hit
something like this, the player will see
the reward first, but be required to turn
back to get it again, teaching the player what
they should be looking for. This is the first time I
used a background tree. Because I didn't
want the player to think that they could
jump on this tree, fail, and falsely learn that
all trees are not platforms. By this point, the player has experience using foreground
trees as platforms. This tree is positioned
to be jumped onto from the tree to the
left or from the ground. This teaches the player that
only background trees are not platforms to proceed, they must ignore the background tree and jump from the ledge. Instead, in case the
first tree didn't work, I followed it up with a
second tree meant to lure the player into trying to jump onto it. And instead, following. Here's another secret cave. But this time the
player can't see it. If they have learned from the previous parts of
the level that they should explore into the
water, they will be rewarded. All of these entrances into the water are clearly
marked with rays of light, two, which help grab the player's attention and
make the water look inviting. I have placed foreground and
background trees together to test the player's ability to
jump up to the highest tree. The trees are further
distinguishable by their coloring and layering in front of or
behind the player. Using consistent
colors or lighting to attract player's
attention is called using visual language or affordances
to tell the player that they can or
should do something at that location or
using that object. Many games use these techniques to indicate climbable ledges, breakable objects,
interactable NBC's. Or to tell you when
you should use a specific weapon or ability. Moving the first
level out of the way, replacing it with a ship
level and renaming the nodes. I can then play the
other level instead. By the time the player
reaches this level, they will have a good
understanding of the platforming mechanics
and don't need to be taught. I instead focus on creating the overall look and
feel of a pirate ship, shaping the entire level
to represent a hull. And added extra tiling to hide the background until the player reaches above the water level. I decorated specific rooms and dead ends with
barrels and bottles, And positioned staircases in a thematic way and prevented the player from
ever leaving the ship. Before the player can
collect any treasure, we will need to create a script to know how much
treasure they have. We can put this script
in the Autolodes folder and name it Data. But instead of extending node, this time we need to extend. We can remove the template. The purpose of the
script will be to hold all the information about a player's progress in the game. To get started, let's
just add an integer, which will be the number of coins the player has collected, and another integer to represent the number of lives the
player has remaining. Exporting these variables
will allow us to view them in the inspector
while the game is running. If we need to, the default
value for an integer is zero, which is fine for a player's
coins as a starting value, but not so nice for their lives
at the start of the game. We should define exactly what this resource will hold
when it is first created. Underscore in it, we can set the initial values
for each variable, as well as perform any
other logic we want. As part of the resource
initialization, I would like players
to start my game with zero coins and three lives. In order to reference this
resource by its type, we need to give it a class name. Let's name it Data
with a capital D. As our game becomes
more complicated, this data resource will contain many more variables
of different types to track the players progress. Now we have a place to
store this information, but we need the
scripts in our game to be able to access it freely. Like we did with the
global auto load, we can create another auto load whose responsibility is to
access and manage the data. Let's name this file will contain a variable
called data with a lower cased of type
data with an upper case D. The purpose of
the script will be to give the rest of the game access to the current data file, but also manage the data file. Here we will need to write
methods for starting a new game saving
and loading two. We're really only interested in creating a new data resource. We should set the value
of data to be data, new data new will create a new data resource and automatically trigger the
init method we just wrote, setting the lives to
three instead of zero. For now, we can simply call the new game method when
this node is ready. Opening the project settings, switch to the auto load tab, Then add the new file script
to the list of auto loads. The default node name will work fine if we run this scene. We can switch back to God
while the game is running. Then switch from local to
remote in the scene tab. This will show us the scene tree for the game that is
currently running. Selecting the file
auto loaded node, and looking in the inspector, we can see that it
has a data resource. The name of the data
resource is object ID, followed by a long number. Clicking on the resource, we can view and
edit its contents. Any of our scripts can access
this information easily. To demonstrate, let's open
the game manager script. And after doing this usual stuff with the level boundaries, simply print out the value of filed lives Running the scene, we can see number three
printed into the output panel. We now have a custom
resource set up to store our players information that
we can access at anytime. In the next lesson, we'll create coins that the
player can collect. I'll see you in the next lesson.
30. 3-2 Coins: Hello friends. In
the previous lesson, we created a custom resource to track the player's
progress and data. In this lesson, we'll
create collectible coins. We can disperse throughout our levels for the
player to collect. For this lesson, I've imported a set of coin
clicking sounds from Freesound.org made
by Val Inspire with a Creative Commons license. Let's start by opening
our first level and creating a new node two
D folder for treasure. Coins are physical objects
that exist in the world. We'll need to detect collisions. We may also want them to
be affected by gravity. The node which defines
these behaviors best will be a rigid
body two D node. Let's rename it to silver coin in order to
be able to see our coin. Let's first add an
animated sprite to it. Create a new sprite
frames resource. Find the silver coin sprites in the asset pack and populate
the default animation. Set this default
animation to autoplay, looped and set the
frames per second. We can also add a second animation to this
animated sprite by clicking on the add
animation button name. This animation effect, then populate it with the coin effect sprites from the asset pack. This animation should
not be looped. This is the animation
which should play when the coin is collected
before it will disappear. Switch back to the
default animation. So we can see the coin size and shape clearly in the editor. Now that we can see our coin, as with all physics objects
of these collisions, we will need to give
it a collision shape, creating a circle
shape resource. We can resize it
to fit the size of the coin you may need to
turn off grade snapping. Collisions with coins also typically generate
sound effects. We should add an audio
stream player to de node to it as well and populate it
with the coin Cl sound effect. Let's save the silver coin
branch as its own scene and put this in a new scenes
folder named treasure. Like everything else,
coins will need a script to control
how they behave. We should start organizing our scripts into
designated folders. I'll create a nude folder
for treasure scripts. We can start our
script by grabbing references to the sprite
and audio player nodes during the ready phase With the coins root node selected, connect the body
entered signal to itself so the coin can react to collisions
with other bodies. When a body enters
the coin collider, we should first play
the sound effect. Then we want to check
if what the coin collided with is anything
besides a character, and if it is not a character, return so it is ignored. If this is indeed a character, we can tell the animated sprite to play the effect animation. Then this node to be
removed from the scene. With a free, this code will
all run in a single frame. We won't get to see the
animation play out. We can use a new
command here called a weight to pause this code
until something happens. The thing we want to
await will be when the sprite animation is finished with the coins
root node selected. We'll need to change a few of the default settings for
the rigid body node. By default, the
body entered signal will only happen when
the colliders overlap. But since physics
are being applied to both the character and the
coin, this will not happen. The characters collider will push the coin collider instead. We can change these
settings to allow the signal to happen when
the colliders make contact. By setting contact monitor to true max contacts reported to any value greater than zero, but we should really
only need one next. Expand the deactivation section. Most people wouldn't
want pixel to rotate, so we can lock the
coins rotation by clicking on this checkbox. Collectible coins in most games fall into one of two categories. Those that hover in the air
and those that are tossed around after breaking a
box or defeating an enemy. We can make the first
option by setting the freeze property to true and changing the freeze
mode to Kinematic. Now the coin will not
be affected by physics, including gravity, but will still collide with
other objects. If we want to turn on gravity or have the coin tossed around, we simply set freeze to false and apply whatever
forces we want to it. We also want to make
sure that our coins are only collected by
the player character, not the enemies or
any other characters. The simplest way to achieve this is using collision layers. So far everything in
our game has been using the default collision
layer, layer one. Using different
collision layers, we can easily control
which objects react to or ignore
other objects. Let's let the default layer, layer one, represent
the terrain. Since most, if not all, objects will need to collide
with that, by default, we may want to have
different types of environments with
different types of collisions in the future, let's just let the world use the first eight collision
layers opening Rogers scene. We can change his collision
layer to layer nine and let the second block represent
our player collision layers. Likewise, we can put the
enemy characters on layer 17. And let the third block represent our enemy
collision layers. Finally, the coins
can be on layer 25, with the fourth block
representing treasure. The mask section of collision layers are what this
object will collide with. The only thing we
want the coin to collide with will
be the environment. And the player which
we have set to layers one and nine coins will now ignore collisions with enemies
as well as other treasure. I will make the player
character collide with enemies, enemies collide with the player
as well as other enemies. We should also open the project
settings and give each of these collision
layers names so we can easily remember what
we've set them aside for. The names will be displayed
as a tool tip when hovering over the collision
layer in the inspector. There is a second set of coin sprites in the asset
pack for a gold coin two. Let's duplicate our
silver coin scene and rename it to gold coin. And replace the
silver coin sprites with gold coin sprites if we want different coins to have different denominations. We can also add an
exported integer value to our script and give
it a default value of one for my game. I'll make gold coins have a
value of ten silver coins. While unlikely, it is possible that after the player
collides with the coin, they could collide with the same coin a second
time before it disappears. To prevent this, we should set the collision mask
of the coin to zero, removing any possibility
that it will collide with anything
after it's been collected. Placing one of each
coin in our level, the player can now collect them. We can also place water
on a separate layer from the terrain and have it collide with both characters
and treasure, and name the layer in
the project settings. We now have collectible
coins of varying value that the player
will be motivated to collect throughout our game. In the next lesson, we'll create a user interface with a counter to show the player how
many coins they have. I'll see you in the next lesson.
31. 3-3 User Interface: Hello friends. In
the previous lesson, we created coins of different value for the player to collect. In this lesson, we'll display how many coins the player
has on the user interface. In the main platform max scene, we can add a user
interface for displaying all kinds of important
information the player needs. This information should
be independent of the camera and always displayed in front
of everything else. Will you might be
tempted to make this a child of the camera like we
did with the background. There is a no type for creating this easier called
a canvas layer. Every two node n arsene
has a draw order based on its set index and its
position in the scene tree. But after everything has been processed by the camera
into a single image, all of that becomes the
default layer, layer zero. A canvas layer will by default, draw a whole new
image onto layer one. Over that, canvas layers are populated with
a new node type. We actually used one in
the very first lesson. These are called control nodes
and are marked in green. To display numbers
on the screen, we could use a label node, which can display any
text that we want, but it wouldn't really fit
our pixel art aesthetic. And our asset pack comes
with nice number sprites, so let's use those instead. We can add control nodes
to the canvas layer called texture Rets to display
these sprites for us, let's add a texture
for the one digit, another for the ten digit, and populate them
both with zeros. By default, you will need to set a limit on how many digits you
want to display on screen. Following the conventions of games like Mario
and Donkey Kong, I'll cap the number of
coins at 99 and give the player an extra reward
for every 100 collected. We should also add
an icon texture to tell the player
what this number means and populated
with the image of a coin to avoid manually rearranging
individual control nodes. On the canvas layer, there are also nodes provided by Godot, whose purpose is to organize
their child nodes for us. Let's add a horizontal
box to the canvas, name it coin counter, and set our other nodes
as children of the box. They are automatically
rearranged to fit horizontally within the box. I'll position this counter 32 pixels away from the corner. And increases scaled two, it's much easier to
notice and read. We can then edit the settings of the individual controls to determine how they will be drawn in relation to their
parent container. I would like the icon
to be displayed, fitting proportionally
to its width. And scale up to fit the size
of the parent container. The digits, I would
like to double in size so they're
easier to read. Knowing that there
are five pixels wide, I'll set their custom
minimum width to ten pixels. Fit them proportionally
to their height. And keep their aspect ratio while also keeping them
centered in the parent. Notice how the canvas controls are being drawn
here in the water. But if we hit play, they are displayed in the upper
left corner of the window. If you recall from
the first lesson, the image being
drawn on screen by default is this blue rectangle. If it helps, you
can hide the level, character and camera
while working on the canvas by clicking
on the icons. Remember to unhide them when you're ready to test your game. Try experimenting with how your counter is
displayed on screen, and check how it
looks while playing. To make the counter work, we'll need to write a script for it. I'll put this in a new
scripts folder for UI and name it Counter. Since the nature of a coin
counter has nothing to do with the organization
of a horizontal box, I'll have it extend the more
generic type of control. Instead, we will
need references to each of our texture
recs that we wish to change set during
the ready phase. We will also need a list of
textures to swap them with, representing each of our
numbers 0-9 We can store these in an exported array of texture two D's named
underscore digits. An array is similar to a list, in this case a list
of two D textures, or pictures of numbers
looking in the inspector. We can click on
this exported array to expand it and give
it a size of ten. The array elements
are conveniently numbered zero through nine. These are the indices we use to access the individual
elements of the array. Populate each array index with the matching digit
from the asset pack. This script needs a
function we can call to set the number which
is being displayed. I'll call it set value and take an integer
as a parameter. Knowing that this counter is only meant to display a number 0-99 Let's first
enforce this rule. By clamping the value
between these numbers. We can set the texture being displayed by
the ones texture k, to be digits at an index. What is the index? It is the
same as the digit itself. But how do we extract
the one digit? We can use an operator common in programming called modulus, represented by the
percentage sign, which returns the remainder
of a division value. Ten will return the one digit, which is the array index of the texture we want displayed. We can then also
set the tens digit by performing division
instead of modulus. Now we need the
player colliding with our coins to somehow
update our user interface. But these exist in
different scenes, making signals or method
calls somewhat difficult. To bridge this gap, we can delegate this responsibility to the game manager
since it is easily accessible as the root node and has access to
all the children. Opening the coin script. When the coin is collected, we can get a reference
to the scene root using dollar sign root followed by game which is the name
of the scenes root node. Then call a method in
the game manager script which we haven't written yet. We'll collect coin and pass in the value of the
coin being collected. Switching to the
game manager script, we will need a reference to the coin counter during
the ready phase. The type of this doesn't
need to be a horizontal box. Since we aren't accessing anything specific
to that node type, we really only care that this is some form of control node. First, we can give this collect coin method
of definition, taking the integer value of
the coin as a parameter. Next, we will add the value
of the coin to the file data. Then update the
counter on the UI, setting its value to the
file data's new coin value. Also, this updating of the counter should happen when the seam is first loaded too. Let's also test the counter by setting its value to
something random like 77. Running the scene,
we can see that it was indeed set to 77. And when we collide
with a silver coin, the counter goes up by one. Collecting the gold coin
increases it by ten as well. Feel free to test setting the counter to any
random number, a negative number, or
something higher than 99. But remember to
delete this line. When you're done, God gives us a warning that we're
dividing by an integer. Clicking on the warning
takes us to the coin script, where we are indeed
dividing by an integer, and decimal values
are being ignored. Since this is on purpose, we can add an extra line
here at integer division. To ignore this
warning, we now have the user interface displaying how many coins the
player has collected. In the next lesson, we'll add a collectible life item
and track that as well. I'll see you in the next lesson.
32. 3-4 Lives: Hello friends. In
the previous lesson, we created a coin counter to display on the
user interface. In this lesson, we'll add a collectible life
item to our game. Let's start on our
first level scene. The asset I would
like to represent, the player's lives in my
game is a golden skull. And I don't think it
would look good to have it affected by
forces like gravity. Unlike coins, I want it to appear more ominous
and mysterious. It will always be something that floats until it is collected. We'll use an area
two D node to define its basic properties and
rename it to Golden Skull. Then add an animated sprite. Two D, create a sprite
frames resource and populate it with the
golden skull sprite assets. Autoplay loop and set
the frames per second. Then like the coin, add
a second animation named Effect and populate it with
the skull effect sprites. Switching back to the skull and getting a good view
of it in the editor, we can add a collision shape to it, which will be a circle. I'll adjust the
sprite one pixel to the left and apple bit
so it's better centered. And adjust the Circle
glider to fit. Add an audio stream
player two D node. The sound effect
that I've selected for the skull will be hud, open, made by a new age soup. But I trimmed it down
slightly in an audio editor, it's still longer
than the animation, so I'll need to add an
empty frame or two on the end of the animation
so the sound can finish. Then save the skull branch as its own scene position, the skull where it can be
collected during our test, open up the skull scene and add a script to the root node. Many of the behaviors of
the skull will be very similar to the coin script
we wrote previously. Let's start by
copying and pasting the coin script into
the skull script. My skull script
doesn't need a value, since every skull will
represent one life. But it will still
have a sprite and a sound effect when
a collision happens. We can still do all of
the same logic except the part where we
tell the game manager that the player
collected a coin. Let's separate that into a
separate private method called underscore collect and call the method from the same spot. Switching to the coin script, we can make the same
changes to the format. Now everything in
these two scripts is almost identical except the contents of this
one collect method and they extend
different node types. If we select the
coin and the skull, we can see that they both have properties of a
collision object two D. We don't want to write this exact same
script Every time we create a new type of
treasure to add to our game, we can define a more
abstract script named treasure and extend
collision object two D. Here we can write all
the common behaviors of the various types of
treasure for our game, copying all of the contents
into the new treasure script. The default behavior of the collect method
can be empty since treasure is too abstract of a concept to know what we should do with it
when it's collected. The purpose of this
method is simply to tell Godot that everything that is
treasure can be collected. Give this script the
class name treasure. Since both coins and skulls are different types
of collision objects, we can make them extend
treasure and gain all the useful benefits we put inside of the
treasure script. Now in our coin script we have coin specific things like value. We can override the behavior of being collected instead of doing nothing to instead tell the game manager that
a coin was collected, a method which is overriding another method is indicated
with this blue arrow. You may have noticed
that this arrow appears anytime we have
overridden the ready method. Likewise, when the
skull is collected, it will tell the game manager
that a skull was collected. Now, no matter what kind
of treasure is collected, these same behaviors will automatically happen
for all of them. But we must be careful
and make sure that all treasures have
these child nodes, and the animated sprite must
have an effect animation. Treating different types
of objects as all having one similar set of behaviors
is called polymorphism. All coins and skulls can
be treated as treasure. The only difference will be what they do when they're collected. Any new kinds of treasure we add can also follow
this pattern. Switching to the
game manager script, we can simply add a
collect skull method, increasing the
player's lives by one. When this happens, duplicating the coin counter reference, we can add a lives
counter reference, then also update the UI
when a life is added. Since the ready method is doing two different unrelated tasks, we should separate them into different methods to
keep them organized. Let's write private
nit boundaries and It UI methods which
perform each of these tasks in isolation and are called by
the ready method. I'll follow the conventions
set by Mario and Donkey Kong, awarding the player
with an extra life after they've
collected 100 coins. When filed coins is greater
than or equal to 100, subtract 100 coins,
and add one life, and also update
the lives counter. Next, we can duplicate
the coin counter node on the canvas layer and rename
it to Lives Counter. To put this in the
upper right corner, I'll change the layout direction property to right to left. Since my coin counter was 32 pixels from the left
side of the screen. I can mirror this by subtracting 64 pixels to add the same
padding on the right side. Reversing the layout direction also reversed the ordering
of the child nodes. I like the icon being
on the right side, but the one and tens
digits are reversed. I'll drag them into
the correct position. I've edited the zero one dog
to fit better on the UI by removing the particles above
the skull image to create this zero zero dot Png to
use as the icon texture. Lastly, in the cases where
the coin is not frozen, I want it to remain stationary while it's being collected. Currently, Rogers collision
with the coin adds force to the coin's rigid body
causing it to move while the effect animation
plays in the coin script. In the collect method, we want to access the freeze and freeze mode properties
of the rigid body two D. But these are inaccessible to this script since it is extending
collision object two D and Godot doesn't
like us changing collision settings while
a collision is happening. We can solve both
of these issues by using a built in method
named call deferred. The call deferred
method will tell Godot to execute this after the
collision is finished. Processing call deferred takes a string argument of the name of the method we want to call. In this case set free enabled the value we want to
set it to, which is true. Then I also need to
set freeze mode to a value which is defined
inside the rigid body. Two D class freeze mode, static. A static rigid body
will not move from either forces or collisions,
which is what I would like. This will stop the coin
from moving after it's collected with the skulls
root node selected. Set the collision layer to the treasure layer and the collision mask
to the player layer. Also connect the
body entered signal to the skulls on
body entered method. We now have a life counter
and the player can gain more lives by
collecting skulls or coins. In the next lesson, we'll create a treasure chest full
of pirate booty. I'll see you in the next lesson.
33. 3-5 Chest: Hello friends. In
the previous lesson, we added a collectible
skull treasure to our game. In this lesson, we'll create a chest full of treasure
that the player can open. I will have a treasure chest
be a stationary object that appears to be in the background so the player can
walk in front of it. When they do, the
chest will open and throw some amount of
coins in the air. Starting from the level scene, let's add an area two node
to the treasure folder. Having more
complicated behaviors than other items in the game. An animated sprite
won't be sufficient. Like our characters,
we will need to use a sprite two D node, then use an animation
player to control it and an animation tree
to control the player. Let's first set
the default sprite for the sprite two D node. I'm using the chest sprites from the merchant ship folder, not the one from the
Palm Tree Island folder. I want one where the
chest isn't locked, just closed inside
the unlocked folder. I'll pick sprite number three. We can also add a
collision shape, which should be a rectangle and stretch it out to
cover the entire chest. And an audio stream player
to add some sound effects. Rename the branch node chest and save this as its own scene
in the treasure folder. Our test will need a
script to control it, which should also be in the
treasure scripts folder. The chest will have
an exported variable to know if it is open or not. Since the default value for a bullion is false,
the chest is not open. The chest will also need
to know what's inside it. Let's add an integer
variable to specify the total value of
all the coins inside the chest and give it a
default value of one. It wouldn't make sense to
allow this number to be a negative number or some
ridiculously high number. We can restrict the range of values of the exported
variable by adding underscore range and specify both a minimum and
a maximum value. Now this number can't be set to anything negative
or way too high. We can also add a variable named underscore booty as an array of treasure to represent
the actual coin nodes inside the treasure chest. In order to make these coins, we'll need packed scenes of both the silver coin
and the gold coin. Inside the ready method, we can then write a
simple algorithm to instantiate the coins needed to add up to the total value. While the total value
is greater than ten, we can reduce the total value by ten and instantiate
a gold coin. This newly instantiated
coin can be added to the booty
array with push back. This will repeat
until the total value becomes less than ten or
will be skipped entirely. If it was already less than ten, then repeat the process. As long as the total
value is more than zero, we can reduce it by one and
instantiate a silver coin, then push it onto the
back of the array. By the end of this process, the total value will be zero, and the chest will
have a bunch of coins representing that
amount ready to spawn. When the player opens the chest, we can then go through
the booty array one item at a time using a for loop, set each item's global position to the same global
position as the chest, but also moved up one tile. Turn the freeze property to false physics will be
applied to the coin. We can apply a random
bit of force to each coin to make them scatter up into the air
when the chest opens. To do that, we'll need to create a random number
generator variable and initialize it with a
random number generator new during the ready phase. Then add an impulse to the item. The direction of the
impulse should be up one tile multiplied
by a random number. I'll set my random range to be 5-10 Then also add vector two, right one tile, and multiply by a random number between negative one
and positive one. Negative numbers will make
it go left instead of right. This should produce a
satisfying fountain of coins. When we need to add the
item to the scene tree, I would prefer to
make it a sibling of the chest, not a child. I'll get the chest's parent first and then add the
coin as a child of that. When this is done, we can clear the array since the
chest is now empty. Selecting the animation player, we can start by making a
closed idle animation, which only needs a track
for the sprite two D, sending its texture to the
same sprite it already has. Then reduce the length of the
animation to 0.1 seconds. Duplicate the closed
idle animation and create an open
ideal animation. And change the sprite
to number eight. We can then make a closed
animation changing the sprite, counting down 8-4 so the animation length
will be 0.5 seconds. We can also add
an audio track to these animations and have
the chest play closing. Sound The sounds I'm using for opening and closing
the chest were created by the frisbee. If piece on Freesound.org duplicating the
closing animation. We can make an open
animation simply reversing the sprites and
changing the sound effect. But in the open animation, we can also add a method
call track to the animation. Adding a key frame during the
frame when the lid is open, we can call the plunder
method to spawn the coins, switch the animation tree, set the advanced
expression base node to be the chests root node and the anim player property
to the animation player. Then create a new state machine
for the animation tree. We can add all four of our animations to
the state machine and connect them with a
cycle of transitions. The transition from
closed idle to open will be
triggered by is open, then open to open idle at
the end of the animation. Likewise, open idle to close
happens when naught is open, close to close idle at
the end of the animation. This structure allows us to initialize the chest
as either open or closed when the scene starts based on the value
of is open as well. By making more than
one transition from start and setting
their conditions, select the root
node and populate the coin packed scenes by dragging them from the file
system into the inspector. We also need to set the collision layer to
put the chest on the treasure layer and have it look for collisions
on the player layer. And set the Z index of the
chest to be behind the player. Switch to the node
panel and connect the body entered signal
to the chest itself. Adding an on body
entered method, if this body is a character, then we should set
is open to true, which triggers the
open animation, which in turn triggers the plunder method
spawning the treasure. Place a treasure chest
somewhere in your level. Try clicking the Is Open
toggle to open and close the chest and edit the total value of coins
which will be spawned. When the chest opens, hip play and try opening the chest and collect your
hard earned coins. We now have a
treasure chest which spawns a burst of coins
for the player to collect. The next lesson
will add a lock to the treasure chest and a key the player can use to unlock it. I'll see you in the next lesson.
34. 3-6 Lock & Key: Hello friends. In
the previous lesson, we created a treasure chest that spawns a fountain of
coins when opened. In this lesson, we'll
lock the chest and hide a key somewhere in
the level that opens it. Let's start by duplicating the silver coin scene
to make a key scene. Open up the new key scene, select the animated sprite and swap out the sprites of the
coin for the key sprites. And the key has its own
effect animation too. I'll just change the keys sound effect to a
different coin. Sound The key will need a new script
inheriting from treasure saved in the
treasure scripts folder. All we need to do is tell the game manager script
when the key is collected. The treasure script will
handle everything else. Replacing the script
on the root node with this new key script is as simple as clicking and dragging
it onto the node. Since it is still
a treasure script, the signal behavior
is still connected to switching to the game scene. I will have only one key
per level in my game. Instead of counting keys
like we've done with coins, I'll just use a
single texture to indicate whether or not
the player has the key. I'll add a texture wrecked
to the user interface, populate it with
the default key. Sprat, doubled in scale to be more visible and
position it below the coin. Then click on the
icon to hide it, since the player
doesn't start with the key in the game
manager script, we can set a reference to this
texture in the ready phase when the player
collects the key. We can set a variable in the file data that the
player has a key to true. Also set the visible property
of the key icon to true. We should also create
another method for when the player uses the key to set filet has key to false
and hide the icon on the UI. Switching to the data script, we can add the has key variable and set its default value
in the init method. Place a key somewhere near
level and try picking it up. In order to lock the chest, we'll need to add another
bulling variable so the chest knows whether or
not it is currently locked. We can then alter the logic
in the collision code. If the chest is locked and
the player has the key, then set is locked to false and tell the game manager that the player
has used the key. If the chest is not locked, set is open to true. I'm not using eLseF here
because I want the chest to both unlock and open
in one collision event. Selecting the animation player, we will need to add
more animations. Let's start by duplicating the closed idle animation to make a locked idle animation. And change the sprite
to the idle sprite. Then duplicate the
close animation to create a lock animation. Changing the sprite 3-2 to one, shortening the length of the
animation to 0.3 seconds, and playing a different sound. This time I'm using key twist in lock made by Karen Keegan on Freesound.org We can then duplicate this animation
to also create an unlocked animation
reversing the sprite order. Switching to the animation tree, we can add these new animations
to the state machine. And connect them. With
a similar loop of transitions like we did
with open and close. The chest will transition
from closed idol to lock. When is locked is set to true, then from block to locked idle at the
end of the animation. Likewise, it will change
from blocked idol to unlock when is
locked is set to false, then to closed idol at the end
of the unlocked animation. We can then also
start the chest in the locked idle state
if is locked is true, but the conditions for
starting the chest as closed and locked
can both be true. We'll add the condition
for closed idle, that the chest must
be not open and not locked in the level scene. We can now alter the
chest's locked state by clicking on the toggle. Keep in mind that a locked chest must be unlocked before
it can be opened. An open chest must be closed
before it can be locked. This works, but I
don't like that. The lock simply disappears. The asset pack has
an unlocked padlock as a separate asset,
so let's use it. Duplicate the key scene To
create a padlock scene, change the type of
the animated sprite two D node to a
sprite two D node, since the padlock
has no animations, rename the nodes accordingly and populate the sprite
with the padlock asset. Change the collision shape to a rectangle and resize it to
fit the shape of the lock. Remove its collision
with the player, and reduce the Z index of the
padlock to match the chest. I'll also make the
default setting of the phrase property false, since I always want the padlock to be affected by physics. And remove the script
by clicking on the script button
with a red X on it. The padlock is not treasure and should not be collected
by the player. Opening the chest script, we can add an exported packed
scene for the padlock, just like we did with
the plunder method. We can define another method
to throw the padlock. Since the plunder method could be dealing with quite
a lot of coins, we instantiated them
during the ready method. Then when the player
opens the chest, the coins already
exist in memory and are simply being
added to the scene tree. This is a more
responsible way of instantiating large
amounts of nodes. But since the padlock is
just one single thing, we don't need to
be concerned about instantiating it at the
moment when it's needed. Inside the throw padlock method, we can define a new variable for the instantiated padlock. Then perform the same
logic as the coins repositioning the lock to
match the chest location. Applying an impulse force and adding it to the scene tree, I'll remove the randomization
of the upward force. It just gets tossed to
the side a little bit. Switching to two D view, I'll quickly add the
padlock image to the scene so I can reposition it where I want it to start, then look at its
transformed values to get the x and y positions. After writing down negative
four and negative seven, I can delete the sprite node, switch back to the script view, and set the padlocks
initial position to be the chest position
plus vector two, negative four, negative seven. Now the padlock will appear in the exact position
where it looks like it matches the locked sprite
and will be tossed aside. Remember to populate the
packed scene by dragging the padlock from the file system panel into the inspector, select the animation player, switch to the
unlocked animation, and add a method call track during the frame when the
padlock has disappeared, we can call the throw
padlock method. Now when the player collects the key and touches
the treasure chest, the padlock is tossed aside, the chest opens, and the
player is showered with coins. We now have a key that can
unlock our treasure chest. In the next lesson, we'll add
an end goal to our levels. I'll see you in the next lesson.
35. 3-7 Map: Hello, friends. In
the previous lesson, we locked the treasure chest and added a key to unlock it. In this lesson will create
an end goal for our levels. I'll use the small maps as
the goal for my levels, since this will easily imply to the player that
Roger is following each map segment to find the next to assemble a
complete treasure map. It's a simple enough concept that I won't need to explain it. And it also provides a
framework for level structure. Let's start by duplicating the skull scene to
create a small map. One scene rename the root node. Select the animated sprite and swap out the skull sprites. For the small map, one sprites. I'll use the in and out sprites together to create
the effect animation. Then change the
collision shape to rectangle and resize
it to fit the sprite. I'll use the paper sound made by brevceps as the sound effect. Just like with all
other treasure, we can give it a
treasure script named Small Map Inheriting
from treasure. All we need to do is tell the game manager that
the map was collected. Switch the script
on the root node for the new small map script. Then duplicate this scene three times to create small map. Two, three, and four. Swapping the sprites in
the default animation for the matching sprites
from the asset pack for small map four. However, I'll change
the effect animation since when the player
collects this piece, they have completed
the entire map. After the in effect plays, I'll play the big map
unfolding animation followed by the idle animation, the folding animation,
and then the out effect. This will let the player know
that the small maps have combined to form one larger map and that their
objective is near. When the player
collects the small map, the level is over. And I would like to
reward the player with a short fanfare followed by the screen fading
to black before returning to a level
selection menu. Let's switch to the game
scene in two D view. We can cover the screen in
black with a node called Color Wrecked and name it, fade in the inspector, change the color to black. Then expand the
layout section and select full wrecked
from the anchors. Preset dropdown to make sure that this is
always in front, either make sure it is
positioned at the bottom of the user interface branch or give it a higher Z index
value than everything else. We don't want this to block
mouse inputs on UI controls. Behind it though, Also expand the mouse section and tell the color wreck to
ignore mouse input. Now the entire screen
will be black, which is ideally how every scene in our
game should start. It's in our way as we
develop this scene. Let's have it hidden by default to make it easier to
copy into other scenes. Let's save it as its own scene, then open it up to get this
black rectangle to fade, it will need a script which belongs in the UI
scripts folder. Just like we used tweens
to move the camera, we can also use tweens to adjust other properties
over time, such as color. First, let's define two colors
at the top of the script, one for black and one for clear. Black has red, green, and blue values of zero
and an alpha value of one. While clear has
an alpha of zero, alpha is a measure
of transparency. We'll also need a private
variable for the color tween. Let's start by fading to clear since the rectangle
is currently black. Just like with the camera,
it's a good idea to first check if the tween already
exists and is running, and if so, kill it. Then create the tween.
There's no need to bother with transitions or
easings here, linear is fine. Then tween a property
on this node itself, the color property,
with the target value of clear over a
duration of 1 second. We will want to know when
this behavior is complete, so we can return a signal from this method to let us know when the color
tween is finished. Specifying a return type
for a method declaration in Gudo script is done after the parameters indicated
with an arrow. Since fading to black will be identical except for
changing the color, Let's change this
method's name to fade and take a color
as a parameter, then use that color
as the target value. We can then write fade to clear, to simply call fade passing
in clear as an argument. Likewise, write fade to black. Calling fade, passing
black as the argument. Both of these methods can return the same signals
returned by fade. Switching to the
game manager script, we will need to set a reference to the fade during
the red phase. The very first thing we should do when the scene is loaded is set the visible property
of the fade to true, so the player won't see
anything but black. Then when all
initialization is complete and the game is ready for
the player to start playing, we can tell the fade to fade to clear and await the signal
for when it's complete. At that point, we should give the player the ability to
start playing the game. Let's set a reference to
the player node during the ready phase and call a
method we haven't defined yet. Set enabled passing
true as an argument. Switching to the players script, we can add a private
Boolean variable named underscore is enabled. Also define the set
enabled method, Taking a Boolean parameter which sets the private variable. Then check this value before processing any inputs returning. If the player is not enabled back in the game manager when the player
collects the map, we can then remove control from the player by setting
enabled to false. I would like to play a
victory fanfare here too. So I'll add an audio
stream player node named Fanfare and populate it
with a short music track. The fanfare I have selected is Game Success Fanfare Short
by L Boss on Freesound.org After the player
has been disabled, we can play the victory fanfare. Wait for the fanfare to finish. Wait for the fade
to fade to black. Then switch scenes. Place a goal at the
end of your level. Hit Play. The scene is loaded with a black
screen and fades in. The player can't move until
the fade in is complete. Collecting the
treasure at the end of the level removes
control from the player, triggers the fanfare
and fades to black. We now have a way to end our levels and a nice
gentle screen transition. In the next section, we'll give the player health and
let them take damage. I'll see you in
the next section.
36. 4-1 Damage: Hello friends. In the
previous section, we created a bunch of different treasure for the
player to collect. In this section will
give the player health, allowing them to
take damage and die. If you completed the assignment, you should have your levels populated with treasure for
the player to collect and placed strategically
to manipulate their behavior if you
completed the challenges. You may also have
an exported array of packed scenes in
your chest script that adds extra treasure items to the booty array and spawns
extra treasure items. Or have a minimum and
maximum coin value, with the chest generating a
random integer between them. Then instantiating coins that
equal that random value. You may have all seized the
color diamonds to create new treasure and given them a unique purpose for your game to give characters some health. Let's start in the
character script, where we can add a
new exported variable to set the characters
maximum health. This should be restricted to
only be a natural number. Something 1-100 should be fine. I'm probably only going to have a tax deal, one
damage at a time, 100 will be a lot, and I'll set the
default value to five. We can then also declare a second variable representing
their current health. To keep things simple,
I'll assume that when the characters are loaded into the scene, they are
at full health, setting their
current health equal to their max health
During the ready phase, We can then write
a public method which deals damage
to the character, taking the amount of
damage as a parameter. For now, we'll just reduce the current health by
the damage amount, then print out the current health so we can see the result. Most games like this include a brief period of invincibility
after taking damage. The simplest way to
achieve this would be to turn off collisions
during that time. But the character's collider
isn't just taking damage, it's also colliding
with the environment. We can separate these
responsibilities by adding another child
node to our characters, an area two D, called a Rt box. The Rt box's only responsibility
is to be detected by damage sources and
be turned on or off without affecting other
aspects of the character. It will also need
a collision two D child node to
determine its shape. To differentiate it from the character's
normal collisions, we can change the color of the Rt box to something
else like green. It doesn't necessarily
need to be identical to the
character's capsule, but it's a good
place to start with the hip box node selected. Change the collision
layer to be layer ten, making this the
player hurt layer. Anything we want to hurt,
the player should mask layer ten to look for
the player hurt box. To test this out,
we'll need something simple on our level which
can hurt the character. We'll create a new packed
scene using the spikes asset. This will be a static
body two D node, since it won't move or
be affected by physics. Add a sprite two D node and populate it with
a sprite asset. Add a collision shape to it
and make it a rectangle. This will be a walkable
solid surface that the characters collider will treat as if it were
part of the terrain. It should span the entire
width of the tile, but only come up to about a
quarter of the tiles height. This will also prevent the
player from getting hurt if they touch the spikes from the side instead of the top. This is a part of the level
but not the terrain or water. Let's make it layer three, a new layer for hazards. We can then add another
collision node to deal damage, which will be an area two D and let's rename it to hip box. Depending on how precise you would like to be
with your hip box, you may want to use
a custom shape here. Let's add a collision polygon
two D node as a child of the hip box in the inspector. We can see that the polygon
is a packed vector two array. Clicking on it to expand it, the array has a size of zero
which is currently empty. We can simply click anywhere in the editor to add a
vertex to the polygon. I'll add one at each spear point and another near the right
edge to give it some volume. Then click on the initial
vertex to close the polygon. This is now a custom
collision shape that if the character
touches, they will get hurt. But notice how it's divided into two smaller shapes,
red and purple. That is because
collision detection in video games is complex and
requires a lot of computation, it is much easier to
detect collision inside of a convex polygon than
a concave polygon. Concave polygons will be divided into convex polygons by Godot. By having the collision polygon match the height of
this third spike, we have doubled the
amount of computation required to detect collisions
with these spikes, since the engine needs to
check if the character is touching the red polygon
or the purple polygon. To simplify this, let's remove the vertex
of the third spike, which in our array is
element number two. The polygon is now convex and
completely colored in red. And we can simplify this even further by removing
the last vertex. This hip box needs to
look for the player hurt layer layer ten if you want enemies to take
damage from spikes two, also layer 18, Save the spikes branch
as its own scene. You could choose to put this in its own folder labeled
traps or hazards, but since there are
no other assets that would fall into
those categories, I'll just save it
as a decoration. I will, however, make a new scripts folder
named environment, then move things like
camera level, parallax, and water into it before adding a new script
named Hazard. Then attach this script to
the root node of the spikes. The purpose of the script
will be to react to a character's hurt box
colliding with the spikes. Hit box, selecting the hit
box switch to the node panel and connect the area entered signal connecting it to
the spike's root node. This will change the
name of the method to be on hit box area entered, which is a little more
descriptive of what's happening. Getting the parent of the
area gives us the character, then we should tell that
character to take damage. Since this script could
be used for any number of hazards or you may want to
adjust its damage on the fly, let's make the amount
of damage dealt into an exported variable and default value of
one should be fine. Give it a range so it can't be set to something
negative or zero. Open the project settings and
name the collision layers, Hazard, player hurt,
and enemy hurt. Place spikes somewhere in your level and try
jumping on them. You can see in the
output panel that Roger now has four health remaining
with each collision, the number is reduced by one. Also, the player and
other characters can be configured
to walk on top of the spikes or through
them based on their collision masking
with layer three, we now have a spike hazard
that damages the player. In the next lesson, we'll have the character react
to taking damage. I'll see you in the next lesson.
37. 4-2 Reaction: Oh friends. In the
previous lesson we created spikes that
damage the player. In this lesson will make the character react
to taking damage. You probably noticed
when we are making characters that there are hit animations in the asset pack. Let's implement those now. In the character scene with the animation player selected, add a new animation named hit. Populate the hit animation from the spikes from the
without sword folder. Also add a sound
effect if you like selecting the animation tree. Edit the without
sword state machine. We can add the hit animation
here and transition to it. If the character is hit then transition back
when they are not hit opening the character
script will need to create the is hit
variable of boolean type, then set it to true when
the character takes damage. The value needs to be
set back to false to transition the character out of the hit animation
back to movement. The animation itself can
perform this function. But in order for the
animation to access it, the variable must be exported. Let's make a new category for this in health named Combat. We can then add a
property track to the hit animation to set the value of is hit back to false at the
end of the animation, the character will now play the hit animation once
when they take damage. This will interrupt any movement animations
that were happening because it is
transitioning out of the movement layer of
the state machine. Let's try it out. It will
be nice if the character continues to take damage
without requiring them to leave the hit box collider
and enter it again. If they stay on the spikes, they will continue
to take damage. Also, there should be
a brief window after taking damage when the
player is invincible. We can solve both of these
problems with one solution. Turning off the player hurt box and turning
it back on again, selecting the hurt box node. The properties at the top of
the inspector panel labeled Monitoring and Monitorable can be used to determine if
collisions happen or not. Since this is a hurt box, it is passive and isn't
looking for anything. Monitoring can be set to false. Its purpose is to be
found by hit boxes. It should be monitorable. Turning monitorable
to false will prevent hit boxes from colliding
with this hurt box and turning it back to
true will trigger a new collision with the
spikes which have a hip box. We can then do the opposite. Set it to monitor for the pert layers but
not monitorable. There is a simple node type
in Godot for timing purposes, a timer, which we can add to our character
and name it Invincible. The default time is 1 second, which is fine, and we want this to only count
once at a time. Check the one shot checkbox. During the ready
phase, we can set a reference to the
character's box and the timer when the character takes damage. We can set monitorable to false, Start the timer, await
the time out signal, and set monitorable
back to true. But Godot won't let us turn the collider off
during a collision, we will use set deferred
to tell Godot to set monitorable to false after this collision
is complete. If you want to have other
things in your game that make the character
invincible for a set time, We can also make this into a public method taking a
float time as a parameter. Then pass this into the
timers start method. Now the take damage method can call the become
invincible method, passing 1 second as a parameter. But I don't want enemies to become invincible,
just the player. I'll add an exported
combat variable for the amount of time the
character is invincible after taking damage restricted to be non negative
with a default of zero in the take damage method. I'll then check if
this value is not zero before calling the
become invincible method, using this time as a parameter. If enemies aren't going
to use this timer node, they shouldn't have it
in their scene tree. Instead of setting the reference to the timer using ready, I'll instead use
the ready method. Check the value of
invincible duration. And if it is not zero, then set the reference
to the timer. Now only the player
character needs to have the timer node and will have an invincible duration
value set higher than zero. But the enemy
characters still need to have Rt boxes added to them. Otherwise, their scripts
will try to set references during the ready phase and cause an error because
they don't exist. Their herd boxes will also belong on layer 18
instead of ten. Then give Roger an
invincibility duration longer than 0 seconds. Trying it out, the character standing on the spikes
will continue to take one damage every second as we see it counting down
on the output panel. Lastly, I would like
the character to get knocked away from the
source of the damage. Let's modify the take
damage method to also accept the direction of
the force as a vector two. Then set the character's
velocity to be this direction multiplied
by five tiles. The hazard script will
then need to calculate this direction and send it to the character
when hurting them. This can just be the
characters global position minus the hazards
global position. For the hazard to know
its global position, it must extend no two D. But we don't want distance to be a factor
here, just the direction. Putting this in brackets, we can call the
normalized method. Normalized, We'll take
the vector and make it longer or shorter to make
it have a length of one. Let's try it out. The character hitting the spikes is hit
with a backward force, has a hit animation, and becomes invincible for 1 second
before getting hit again. We now have characters
reacting to getting hit. In the next lesson, we'll
add the player's health to the UI and give them a potion
item to recover health. I'll see you in the next lesson.
38. 4-3 Recovery: Hello, friends. In
the previous lesson, we had our character
react to taking damage. In this lesson, we'll add the player's health to the UI
and create health potions. First, let's make
a health gauge. The assets in the asset
pack are divided into three separate
images for the gauge and a flat red
color for the fill. The health gauge
itself will just be a basic control node
with two children, one for the background
and another for the fill. The background node can
be a horizontal box Automatically sorting the three
background images for us, which are all texture wrecks, Populate the textures with the assets from the asset pack. You can hide the other
branches of there in your way. Selecting the background
horizontal box, expand the theme
override section in the inspector and set separation to zero pixels so the images are drawn
without any gaps. The fill is just
another textuect with this flat red color that we can manually resize and
fit into the gauge. Selecting the health
gauges root node. Let's scale it up four times
to make it more visible. I think it would look good
in the bottom left corner, I'll set the anchor
presets to be bottom left. And adjust the pivot offset y to be at the
bottom of the gauge, which is 40 pixels. Then set the x position
of the gauge to 32 pixels to add the
same padding as the coin above just to layout and check out looks
while playing the game. Zooming in on the
new health gauge. Select the Fill Textureect. To use the fill, we can set the expand mode to ignore size, allowing us to manually set
the size to whatever we want. Click and hold on
the size X value and move your mouse left, reducing the X value, which we can see, also makes the gauge look like
it's depleting. When the X value is zero,
the gauge is empty, and when the x value is
75, the gauge is full. We can use this to display any amount of health the player has by turning it into a
percentage of 75 pixels. Creating a script for
the health gauge. We'll name it Gauge and save
it in the UI scripts folder. We can get a reference to the fill during the ready phase. Like the counters, a
gauge really only needs one method to set the
value being displayed. It would be best
to take this value as a percentage
with a float type. All we need to do to display
this percentage is set the size of the fill to be
75 multiplied by percentage. A percentage in
programming is usually represented as a
number 0-1 unlike in math where it is 0-100 This simplifies our calculations
to be more efficient. If we want to make this
script more flexible, we can export the value of
75 pixels as a variable named max pixels and set
75 as the default value. Switching to the
character script, we can create a new signal for when the character's health
value changes and give it a parameter which is the percentage of the
character's remaining health. Then when the character
takes damage, we can emit that signal. To calculate the percentage of the character's
remaining health, simply divide current
health by max health, But both of these values
are defined as integers, which means that the result will always be either zero or one. If we change one of
their types to float, the result will then
be a float two. We can then connect
the player character's health changed signal
to the health gauge. But we don't want to
call a new method. Clicking on the pick button, we'll present a list of the health gauges methods
we want to call set value. In order to do this,
both the signal and the method should
have the same parameters. In our case, afloat, Remember to unhide
the other branches. When you're ready to
test, let's try it out. The gauge starts full, but jumping on the
spikes reduces the gauge by one
fifth each time. Remember to position the
fade at the bottom of the UI branch if you want it to be drawn over
everything else. Now let's help the
player out with a health potion to
recover that lost health. Like we did through
the last section, we can simply add a new
treasure item using the red potion assets and create a new script for it
inheriting from treasure, copying one of the
other treasures. Rename it red potion
and open it up. Rename the root node to match replace the
animation sprites with the red potent and the effect sprites with
the potion effect. I'll center the
sprite and change the collider to a capsule
to better fit the image. I'm using my gulp recording
made by Miley Joe Moss, 1,996 for the sound
effect of the potion. The different colors
of the potions probably won't just be
adjusting a variable, but instead have a completely
different function. And we'll need their
own unique scripts. Instead of having a
generic potion script, I'll name this red potion
inherit from treasure, then swap the script that is
attached to the root node. We can export an
integer variable for the amount of health
that the potion recovers as an integer. I'll set mine to have
a value of three. I don't want to bother
the game manager with relaying this
information from the potion to the
character since the character collided
with the potion. Instead, I'll make it small change to the
treasure script, storing the character
that collided with this treasure
in a variable. Then, after verifying that
the body is a character, save the body as the
character variable. Now the potion
script can directly communicate with the character
when it is collected. When collected, the
red potion will call a recover health method on the character passing
the amount recovered. Switching over to the
character script, we can add this
recover health method to the public methods, taking an amount of health
to recover as a parameter, adding the amount of health recovered to the current health. We should also cap
the current health at max health using
the min function, then emit the health changed signal to update the
health gauge on the Y. We can combine these two lines and perform them
in one statement. Place a health potion
in your level, try taking damage
from the spikes, then collect the potion
to recover lost health. We now have a way
for the player to know how much health
they have remaining, and an item to help them
recover lost health. In the next lesson, we'll add
a checkpoint to the level. I'll see you in the next lesson.
39. 4-4 Checkpoint: Hello friends. In the
previous lesson we added a health gauge to the UI and a portion to help the
player recover lost health. In this lesson, we'll
set locations where the player should
respond after dying. I'm going to delete the other
level from the game scene. Now, since we'll be editing
the structure of levels and all levels will require this new structure to
function properly, other levels will
need to be edited to. So far we've been using the scene origin as the location where the
player will start, then expecting them to make it all the way to the
end without dying. You may instead want your
levels to be longer and give the player checkpoints
where they can respond instead of starting back at the beginning each time. Let's add another
folder to the levels seen hierarchy
named checkpoints. We can then add a
basic empty two D node to this folder named start position it where we want the player to start the
level. If you prefer. You may also want
the level trigger to be in this folder and
rename it to something like end to make a checkpoint. We'll also create
an area two D node in this folder named middle. Give this checkpoint an
animated sprite node. I'm going to use the flag
assets for my checkpoint since the checkpoints origin
point is the location where the character's origin
will be set and the character's origin
is at their feet. The checkpoints
origin should be on the ground where the character's
feet will be positioned. With that in mind,
I'll position the flag so the flagpoles base
is at the origin. Adding a collision shape, I'll just use a line segment
covering the flag pole so the player must actually make contact with the flag
pole to trigger it. I'll also add an audio
stream player to de node, which will play Game Reward
by a Yen Ba on Freesound.org Save the checkpoint
branch as its own scene. I'll put mine in the
root scenes folder since it will be included
in every level of my game. Opening the new
checkpoint scene, I'm going to edit the z index of my flag to be drawn behind the character while being very similar to the behavior
of a treasure item. I wouldn't logically think
of this as treasure and I don't want to
refactor the treasure script to fit this into it. I'll create a new
script from scratch and put it in the environment
scripts folder. Grab a reference to
the audio stream player two D node
during the ready phase and connect the body
entered signal to react to the player
colliding with the flag. Play the sound effect and then set the collision mask to zero to stop any more
collisions from happening. Then we want to
set a variable in the file data to know that the player has reached
the checkpoint. You may want to have
this be a Boolean if you only ever want
to have one checkpoint, like a typical Mario game, or an integer if you want to
have multiple checkpoints, more like a Donkey
Kong country game, an integer is more
flexible and can also be used for the
single checkpoint system. So that each checkpoint will then need a unique
integer to identify it, which the game manager will use to know where to
respond the player. Let's define a variable as an integer to hold this number. Then set the value of filed checkpoint to
be this ID number. When the player touches
the checkpoint, we could export the variable
to set each one manually, but that would be prone to error and more work for
us in the long run. Instead, I'd like to delegate this responsibility
to the level script. First, we need to make
sure the collision mask of the checkpoint is set to
collide with the player. And give this
script a class name so it can be identified
by the level script. Switching to the level script, if we can safely assume that all levels will have
this checkpoints folder, then we can use that to set a variable not to the
checkpoint folder itself, but to an array of nodes. We can set the value to
be checkpoints folder, giving us a list of every node
in the checkpoints folder. Then using a four loop, we can count from zero to
the size of this array. If the note is a checkpoint, set its checkpoint ID to
its position in that list. The level now contains a list of all checkpoints where the
player character could be spawned and every checkpoint knows its position in that list. It will be the game manager's responsibility to load the level and move the player to the
appropriate checkpoint before gameplay starts. Let's also add a method to the level script so
the game manager can request this information named
get checkpoint position. Taking the checkpoint ID as an integer parameter and
returning a vector two position. I'll start with a fail safe case of just returning
vector two zero. Before using an argument
to index an array, we should check that it
is a valid array index, that it is both greater
than or equal to zero and less than the
size of the array. Then we simply return the checkpoint array index at the checkpoint ID and
get its global position. Switching to the
game manager script. After all initialization
has been handled, but before fading in, we should spawn the player. We can now set the
player characters global position by asking the level the location
of the checkpoint, passing filed checkpoint
as the argument. Lastly, we need to add the
checkpoint variable to the data resource with the
default value of zero. The player will start at the top child node of
the checkpoints folder, which is the empty start node. Since we aren't using
the body parameter passed into the
checkpoints collision Godot would prefer
it were flagged as optional by preceding
it with an underscore. Place the checkpoint somewhere around the middle
of your level while making sure that
the level start is the top node in the
checkpoint folder. Hitting Play, nothing
will have changed, but we can now move the player's start location more easily. Also, if we edit the default value of
checkpoint in the data script, we can spawn the player
at the flag instead. We now have a checkpoint
system in place so the game manager knows
where to spawn the player. In the next lesson, we'll
add player death and have the player respawn at the
last checkpoint touched. I'll see you in the next lesson.
40. 4-5 Death: Hello friends. In the
previous lesson we created a checkpoint system
so the game manager can spawn the player at
different positions. In this lesson, we'll add character death and respawn
the player when they die. Starting in the
character script. Just like we capped
the health at max health using Min when
the player recovers. We also need to
stop current health at zero when they take damage, setting current health to max of either current health minus
damage amount or zero. We can then emit
the health changed signal and set the
knock back velocity. The rest of this method will be contingent on whether or not
this damage has killed them. We need to check if
current health is zero. If it is call a new method, die. If the character is not dead, then the is hit
variable will be set to true and they may
become invincible. Before we write the die method, we should declare
a Boolean variable to know if the character
is dead or not. Also create a
signal to emit when the character dies so the
game manager can react to it. In the die method, we can
set the new boolean variable is dead to true that we can use in the
animator Like we did with is hit and emit a signal that the
character has died. It would also be a
good idea to turn off the character's herd box so
they stop taking damage. But this is still
something that is probably happening
during a collision. We will need to defer this until the end of
the current frame. Setting the collision layer of the character to zero
and the collision mask to one will prevent any future collisions with anything other than the terrain. A dead character
will not collect treasure or trigger
a checkpoint. Finally, setting direction
to zero will cancel any movement input that has been given at the
moment of death. In order to respond the player, we also need a public method to revive them from being dead, setting is dead, back to false current health
to max health. We then need to restore
the character's collision setting the Rt box
monitorable property to true. This isn't triggered
by a collision, more likely the game
manager's logic. It's fine to just
set it directly. We can store the values of
the collision layer and the collision mask in variables
during the ready phase, then restore them to
their original values when reviving the character. Since we will be reviving
the player character after repositioning them
back at the checkpoint, it would also be a good idea to omit the landed signal here, which will correctly
position the camera. We changed the character's, we should also omit the
health changed signal to We also don't want the character to be able to do anything
while they're dead. Starting with the
face left method, we should first check
if the character is dead and if so return
preventing this from happening. And do this for any
methods that should not be executed while
the character is dead, including face right,
run jump, and stop jump. I would also like
to make an addition to the game manager script. When spawning the player, we are setting their
position probably on the ground or near the ground
at a location in the level. You can imagine that there
is a possibility that the player character will die and fall from
a great height. At the moment when
this respawn method repositions the character, the character would have
a very high velocity. In order to prevent
the velocity from persisting and potentially
breaking the rain collision, we should set their velocity
to zero at this time. Switching to the
animation player, we can add the dead hit and
dead ground animations. Remember to use the assets
from the without sored folder. Dead hit should have
a cry of pain from the character while Dead Ground places an impact sound
from hitting the floor. With the high probability that
these sounds will overlap, it would be a good
idea to separate vocal sounds from
general sound effects. Adding a second audio
stream player two D node, we can name one voice and
the other sound effects. We can then keep vocal
sounds restrained to only being able to
play one at a time, while also allowing
sound effects to play simultaneously
without interruptions. Setting the max
polyphony property of the sound effects to a value greater
than one will also allow multiple sound effects
to play together two. This format also allows us to adjust the volume
settings of voices and sound effects independently selecting the animation tree. Add these new animations into the without sword state machine. First, playing the
dead hit animation followed by dead ground. Dead hit is played when
the character is dead. Then dead ground will be played after dead
hit has completed, only if the character
is on the floor. In order to revive the player, we need these animations to
also return to movement. When is dead becomes false in the game scene, select the player character node and connect the Did signal
to the game manager. Let's name this
method on player Did. When the player dies, one of two things could happen. Either they respond at their last checkpoint
or they've run out of lives and have
hit the game over state. First, checking if file data
lives is equal to zero. We will then call a
game over method, giving game over a definition. For now, we can just
print out game over. If lives is not zero, then we can reduce it by one, Update the UI, then call
a different method. Return to last checkpoint. To return to the
last checkpoint, we should await,
fade, fade to black. Spawn the character at the
last checkpoint reached, revive them, then
await faded to clear. In both of these cases, I would like to
play a short jingle like with the victory fanfare. Let's add three
exported variables to the game manager script
of type audio stream, which will be named Victory, Death and Game Over. I'm using scene ending
music for death and sad, or scary scene change. Music for game over, both made by Dominic
Trace from Freesound.org We can then tell
the fanfare node to play the appropriate
sound for each situation. Playing the victory theme when the player collects the map
at the end of the level. Playing the death
theme when they die and the game over theme
when they run out of lives. It would also be prudent to disable player
input at the start of return to last checkpoint and enable player
input at the end. I've set the players starting lives to one and
Mac's health to one. For this demonstration, we now have the player
character able to die and respond at the last
checkpoint they reached. In the next lesson, we'll give the enemies attacks
to hurt the player. I'll see you in the next lesson.
41. 4-6 Enemies: Hello friends. In
the previous lesson, we gave the player character a death animation and had
them respawn after dying. In this lesson,
we'll add attacks to the enemy characters that
can hurt the player. I'll use Fierce Tooth as an
example for this lesson. But all characters
will need to have their node structures edited to match before running any tests. Selecting the
animation player node, we can start by
adding the same hit, dead hit, and dead
ground animations for the enemies as
we did with Roger. Then also add anticipation
and attack as well. In the animation
trees state machine, we can recreate the same
hit and death animations as we did with Roger. Structuring the state
machine in layers. Like this allows
the enemy getting hurt or killed to
interrupt their attacks. Editing the attack layer, we can then add the anticipation
and attack animations. Here add a transition from
movement to anticipation, anticipation to attack
and attack to movement. The transition from anticipation to attack will happen at end, as will the transition
from attack to movement. But the transition from
movement to anticipation, which sets off the
chain of animations, will happen when the
character wants to attack in the character script. Let's add the wants to attack bolin to the
combat variables. Then add a public method named attack which sets
its value to true. Each time this method is called, the enemy will want to attack once like the is hit variable needed to
be set back to false, otherwise the
animation would loop. We also need to set
the wants to attack variable back to false
during the animation. The wants to attack
variable will need to be exported so the animation
player can access it. Switching back to
the animation player during the anticipation
animation, we can set the value of wants
to attack back to false. This is sufficient to trigger the animation sequence
for this enemy to attack. But like the spikes,
there needs to also be a hit box looking for
the player's hurt box. Select the attack animation with the first frame selected. That this is what's showing in the editor with a good
view of the character. Let's add another area to D
node to the scene tree named hit box and give it
a collision shape. To distinguish
this collider from the blue environment collider
and the green Rt box, we can give the hit box
another color like red. Select a shape that
you think will best fit the area being
targeted by the attack. I'll use a capsule, then resize it to fit the size and
shape of the targeted area. Be sure to leave the
hip box centered on the character and only move the collision
shape off center. There's also an
effect animation. Let's add an animated sprite toting node as a
child of the hip box. Create a sprite frames resource and populate it with
the attack effects sprites. Unlike other effect animations, we do not want this to autoplay. And also the usual ten frames
per second frame rate, it also should not loop. Also add an empty
frame on the end, reposition the sprite
node so it looks like the effect is
emanating from the attack. When you're done, set the default frame to be
the last empty frame, so it will be initially loaded into the scene as
not showing anything. But then when we
tell it to play, it will play the animation
once and disappear again. We can set the hit boxes
collision layer to be layer 19, which will be the
enemy hit layer, and have it mask the player
hurt layer layer ten. Since it is an active entity, it is monitoring for hurt boxes. Nothing is looking
for hit boxes. They are typically
not monitorable. However, we don't
want this to be monitoring until the
enemy actually attacks. The default value of
monitoring will also be false. In the character script.
It would be a good idea to grab a reference to the hip box during the ready phase. We need to add a hit box node to every character to satisfy the character scripts
requirements before testing and set the
monitoring property to false. Just in case back in
the attack animation, we can add the hit boxes monitoring property
to the animation, setting its value to true
during the first frame, back to false during
the second frame. Then also add a
call method track for the effect animation, telling it to play during the
first frame of the attack. While the animations are divided into anticipation and attack, there are actually three
phases to an attack, an contact, and recovery
when dealing with enemies. The anticipation phase is how the enemy telegraphs that
they are going to attack, giving the player time to react. Followed by contact, which
is the exact moment when the collider is turned on and the enemy actually
hurts the player. Finally, recovery, which gives the player time to react
to being hit or retaliate. Connect to the area
entered signal of the hit box to the
character script. The only area we will allow to collide with a hit
box is a Rt box. Thanks to the
collision layering, all we need to do is ask the
Rt box area for its parent, which will be a character, and tell that character
to take damage. For now, just deal one
damage in the direction of the player's global position minus this enemy's
global position. Then normalized, we should export the damage amount as a combat variable so
it can be modified for each character in the level scene. Find your enemy and attach
a timer node to it. And adjust the timer to
something a bit longer, like two or 3 seconds. And set it to autos, start. Connect this timer's
timeout signal to the enemy's attack method. Now this enemy will
be instructed to attack once every time
the timer reaches zero, and it will repeat indefinitely. When you're ready,
hit play to see the character auto attacking
and try letting it hit you. The this works as long as the enemy character
doesn't turn around. Let's set the sprite nodes flip H property to true to simulate
the enemy facing right. Now the attack animation
will face right, but the hip box and the effect animation are still facing left. Selecting the hip box in
the transform section, click on the chain
icon to unlink the x and y values so they
can be edited separately. Then click and drag from
the scale x property, moving your mouse to the left. As the value approaches zero, the hip box is so
thin it disappears. But as it becomes negative, it is mirrored about the y axis. We can use this in
the character script to flip the hip box. The same time we flip the sprite in the
face left method. Set hip box scale x to one if the sprite
naturally faces left, l negative one and vice versa. In the face right method, we can flip our enemy's
initial facing direction with the exported variable.
To test this out, we now have our enemy characters
attacking the player. In the next lesson, we'll
add a game overscreen. I'll see you in the next lesson.
42. 4-7 Game Over: Hello friends. In
the previous lesson, we gave the enemy's attacks
to hurt the player. In this lesson, we'll
add a game over screen. This will be a control
note over the UI canvas, probably drawn over
everything else, but still underneath the fade, named Game Over Menu. I'll use a vertical box to
sort everything easily, starting with a label that says Game Over so the player quickly understands what's
happening when the player runs out of lives. I'll just present them
with three options retry from the beginning of the level, go back to the level selection
menu or exit the game. I'll add three
buttons for each of my three options,
Select and exit. I'll scale up the whole
thing four times and reposition it to be in the center of the
screen. Using anchors, selecting each of the buttons, connect the pressed signal
to the game manager script, creating three new
methods which will be called when the player
presses the buttons. First, we will need a
reference to the game over menu set during
the ready phase. And set its visible property
to falls to hide it. Then when the game
over state is reached, we can set its visible
property to true. Then pressing any of
these buttons should hide the gameover screen menu by setting its visible
property back to false. If the player would
like to start over from the beginning
of the level, we will need to reset
a bunch of variables, respond all the
enemies treasure, and checkpoints, et cetera. We can simplify this by just unloading and reloading
the same level. Unloading is easy since we have a reference
to the level node. We can just call
underscore level free. In order to reload the level. However, we'll need to know
which level was loaded. Opening the data script, we can add two new variables, one for world and
the other for level, both with an initial
value of one. If you don't want to organize
your levels into worlds, you can ignore that
variable while we're here. We'll also need to reset these other values
when the player starts over from the start of the level adding a public retrying method, we can set coins to zero, lives to three,
checkpoint to zero, and has key to false. Back in the game manager script, we can call file data to
reset the players progress. Then we can set the value
of level to be load, which takes a string argument representing the file path to the resource we want to load. Searching through
the file system tab, find your level, right
click and select copy path. Then paste this path into
the quotation marks. And follow the load call
which returns a resource with instantiate to turn
that resource into a node. Then add that node to the
scene tree with add child, but this will only ever
load level one one. We can replace the ones in the file name with the
variables from our file data, but the variables are integers. And this is a string cast the integers to strings
using STR bracket. As long as every
level is inside the levels folder and is
named in the same format, it will be loaded
by simply setting the world and level
variables in the file data. We can then spawn the player, turn off the player
controls and revive them. But we don't want the player to see any of this happening. We should start by
fading to black. Then at the end, await fade to clear and turn the
player controls back on. The player will be
at the start of the level which has
been completely reloaded anew and they will
retry with three lives. For now, if the player wants to return to the level
selectional menu, we can just fade to black
and reset their file data, since we haven't
built that scene yet. But leave a comment
or printout statement here so we know in the future
to implement this feature. If they press the ex It button, we can await, fade to black. Then call get tree quit
to close the game. Alternatively, you
may want to have this return to the game's
title screen when we have one. For testing purposes, I'll just have the game
overscreen visible. By default the
Quit button works. The level select button doesn't do much yet, but
that's intentional. The retry button works too. But this menu looks bad and doesn't fit the
aesthetic of the game. Let's use the asset
pack to make it better. I'll start by changing the label to a basic control node named Banner and fill it
with texture wrecks, populating them with the
letter images to spell game over and sort them using two horizontal boxes. Resetting the size and changing the separation to two
pixels between each letter. I'll add in five more texture Ts and use the small banner
images to assemble a banner, rearranging them to fit centered about the
banner's origin point and have the words
written on the banner. I'll then tell the
banner to center itself inside the
parent vertical box, selecting the game
over menu node. Expand the theme section and select New Theme
from the drop down. Then click on the theme resource to open it up in
the bottom panel. This display shows us several different UI elements and how they will look using
this theme resource, which we can edit. Let's add button to the theme. Under the style box tab, there are five options for
different button states. Adding all of them to the theme. We can then change how the
button looks in each state. Starting with normal, add
a new style box texture, click on the style
box to open it, then populate it with
the green button. One image. The buttons in
the preview are now green, as are the buttons in our game. But they're stretched and ugly. Then expand the texture
margin section, adding a few pixels
of margin space. The image now looks better. The images in this
asset pack have a larger bottom margin
than the other sides. Since the game over menu is
using this theme resource. All of its children also
have the same theme applied when hovering or
focusing on a button. I'll swap its texture
with the yellow button. We can see how this
behaves by hovering and clicking on buttons
in the preview. When disabled, I'll use the
same green button texture, but modulate the color, multiplying the RGB
and alpha values all by half so it becomes
more gray and faded. When pressed, I'll use the
same yellow button texture, but do the same thing,
modulating with gray RGB values. But leave alpha at full opacity, removing the text
from the buttons. I'll replace them
with icons instead. Then child each button to a horizontal box paired up with another horizontal box to act as the button's label. Each label horizontal
box will have multiple texture Recs for each letter of the word I
want to spell out, telling the horizontal box to shrink and center vertically. For level select, I'll just use an empty text direct
as the space. I would like more space between the buttons and their labels. So I'll group select the
horizontal boxes and edit their separation
values to be 14 pixels. Then I'll put all
three buttons in another vertical box and place that inside a panel
container node named wood. And also add a panel node to
the panel container named paper, Reopening the theme. In the bottom panel, we can make more changes to panels
and panel containers. Adding the panel
container to the theme, we can give it a
style box as well, using prefabs Three
as the image. I want wider margins
on this panel so the buttons don't look
so crowded near the edges. Container nodes automatically resize to fit their contents. Let's also add the
panel to the theme. Panels are similar to
a texture wrecked, but also have texture
margins like the buttons. And I'll use prefab
nine as its texture. This has quite wide
margins at 20 pixels. And unlike the button images where the inside was
just a flat color, the paper and wood have texture
that is being stretched. Expanding the axis
stretch section, we can set the textures
to tile instead of stretch to space
things out more, I'll give the wood panel
a minimum width of 160 pixels and the paper a custom size of
120 by 100 pixels. Positioning the paper
at the right side of the wood and the buttons
vertically centered. The words look like they
are written on the paper. And remove the custom minimum
height of the banner. Now we have a thematic
game over screen. In the next section, we'll give the player a sword so
they can fight back. I'll see you in
the next section.
43. 5-1 Sword: Hello friends. In the
previous section, we allowed the player character
to take damage and die. In this section, we'll give the player a sword and
attacks of their own so they can fight back if you
completed the assignment. All of your levels will now
have the checkpoints folder. And all of your
enemies will have hit death and attack animations. All of your enemies
will be able to attack, The player take damage and die if you completed
the challenges. You might also have
your user interface and game over menu tweening
in from off screen. As well as green
and blue potions that effect to the
character in unique ways. Let's start with a
bit of housekeeping. I would like to sort the
character and players scripts into a new scripts
folder named characters. This main game scene will
be used for every level, not just for the first level. So we can delete the level node in the game manager script. We won't be setting the
value of level using at on ready. In the ready method. After setting the
fade to be visible, we should then load
the current level. Writing a new private method. Load level we can use the same code we wrote
in the retry button pressed method to load the level and replace that with a method call. Then also move it boundaries
and it UI into load level. They will be updated to
match the new values. Also add a line to nit UI to set the visible property of the key icon to whether or
not the player has the key. Now whatever world a level
is set in the file data, that level will be loaded
when the game starts. If the level is
reloaded or changed, the boundaries and UI
will update to match. So far everything we've done in our character script
has been applicable, or at least flexible enough, to be applied to any character. However, the asset pack only has the human
carrying the sword. To separate these behaviors
from the character script, we can create a new script
that inherits from character, which will contain
behaviors that can only ever be
applied to the hero of our game saved in the new
character scripts folder, and replacing the
character script on the root note of Roger. This script will need an
exported Boolean variable to tell us whether
or not the hero is currently holding a sword. We can also give this
script a class name so other scripts can
easily check if something is a
character or a hero. I'll also have a
private reference to the sword that is equipped. Then add a new
public method that other scripts can use to
tell Roger to equip a sword. Taking the sword as a parameter, setting the local private
variable and setting his sword to another public method, telling our hero
to drop the sword. If they don't have
a sword to drop, we can ignore and return. Otherwise, setting
has sword to falls, telling the sword to be dropped. Passing the hero's global
position as an argument, the sword knows
where it should be. Since the hero no
longer has a sword, the sword variable will be
null, which means empty. When the hero dies, we will override the
character's behavior of die to check if the hero has
a sword and if so drop it, then we still want the rest of the die code to be run too. Instead of copying and pasting, we can use super die to call the inherited
definition of die. Following the same
process we've used to create all of our
collectible treasure. Let's make an equivocal sword
for the player character. I want my sword to
have physics applied. Sometimes I'll make it a rigid
body two D like the coins, but my sword will be
more complex than typical treasure items with multiple states and more things to change than just the sprite, more like the treasure chest. Giving it a sprite two D node. I'll set its default sprite
to be sword, idle, zero one. Adding an animation player
and the animation tree, I can create the idle animation so the sword will spin. Then add a collision shape to the branch, giving it a capsule shape size
to fit the swords sprite. I'll put this on
layer 26 to keep it separate from other
items and have it only collide with the train or
the player like the coins. I'll have it unable to
rotate frozen by default, with its freeze mode set to
Kinematic so it doesn't move, but can still have collisions. And report collisions on
contact with up to two at a time selecting the
animation treat. I'll set the Anim
player property to be this animation player and create a new state machine for now. This will just have the idle animation playing by default. And also add an audio
stream player to node, which I will populate with
the unsheathed sound effect, saving this branch
as its own scene. You might want to put this in a weapons or equipment folder, but since there are no
other assets like this, I'll just keep it in
the Scenes folder. I moved the sword off
of the scene origin, so I'll reset its transfer
and property to reenter it. Now we need to create a
script for the sword. While this behaves
similar to treasure, it's different enough that
I would rather not refactor the code and instead just
inherit rigid body two D, connect the body entered
signal to the script. Thanks to the collision layers, it's pretty safe to assume that the only thing that can cide
with the sword is Roger. But I'll check just to be sure if the body is a hero
and can equip the sword. We'll need to define this method and have it return
a Boolean value. Then we can tell the hero
to equip this sword, passing itself as the argument, and become invisible so the player can't see
the sword anymore. Since the character sprite will draw to make the physics of
dropping the sword easier. I'll also unfreeze the ridge
body with set deferred. Grabbing a reference to
the audio stream player two do during the ready phase. We can also play
the sound effect. When this happens
in the hero script, we need to write the equipped sword method
returning a Boolean. We only need to say if the hero doesn't have a sword
and is not dead. Under these conditions, the hero should be able to
equip the sword. Then when the hero
drops the sword, they call a dropped method. Writing that definition, we
can take the position where the sword was dropped from as a parameter of type vector two. I'll need a random number
generator initiated during the ready phase to
give a random float between negative one
and positive one. I'll set the sword's
global position to be that position plus
half a tile up. Apply an impulse force of up eight tiles plus right multiplied by a random number similar to the
effect of the coins. And finally set visible to true so that the
player can see it. We can now duplicate
our animations to create new ones where the
hero is holding the sword, starting with idol, then run, jump, fall ground and hit. Now we can connect our heroes without sword state machine to the width sword state machine
based on the value of has sword opening the width
stored state machine. This top layer will contain the attack state machine connected to the hit
sword animation, since when the player dies, they will drop the sword inside
the attack state machine. For now, just add the
movement state machine and connect it from start, adding in the jump, fall, and ground animations, as well as the locomotion
state machine, connecting them together in the same ways as
everything else. And finally, completing the
locomotion state machine two, placing the sword
somewhere in your level. The hero will now be
able to pick it up and all the animations will
still work the same, only with the sword equipped. And when they die,
the sword is dropped. We now have a sword item that the player can
pick up and equip. In the next lesson,
we'll add sword attacks. I'll see you in the next lesson.
44. 5-2 Attack: Hello friends. In
the previous lesson we added a sword that the
player can equip and drop. In this lesson, we'll
add an attack button and the attack animations
for the player. Let's start by
opening the project settings the input map tab, and adding an attack button. I'll use the left mouse
click J button on keyboard and left face
button for gamepad. Accommodating a variety of plays styles in two
physics layer names. I'll also add player hit, enemy hit, and
sword layer names. Then in the player script, during the input event, if the attack button is pressed, then we should tell the
player character to attack. The character script has
already been set up to process this input setting
wants to attack to true, but players can and will mash buttons even when the desired
action is impossible. Setting wants to attack to
true will trigger an attack when it becomes possible
thanks to the animation tree. But this will happen even if a significant amount of time is passed since the
button was pressed. We should set a
time limit on how long the attack
input is considered valid before ignoring it if the character can't perform
the attack during that time, let's add a timer node
to the hit box named input buffer and set it's time to half a second
and one shot to true. Then in the hero script, we can grab a reference to this node during
the ready phase. Overriding the
definition of attack, we can set wants
to attack to true, start the input buffer timer, await its time out signal. Then set wants to
attack back to false. If no attack was triggered within half a second of
pressing the button, then the button press
will be ignored. The duration of
time can easily be adjusted in the timer
nodes properties. While attacking, I would like
to prevent characters from taking any other actions
in the character script. I'll need another
bullion variable to know if the character is
currently attacking or not. Which I will set
during the animations and set its value to false
when the scene first loads, just in case, like we did
with the hit box monitoring. Then in the same way I check if the character is dead before
performing other actions, I'll also add the condition that they must not be attacking. However, I'll change run so that if they are
dead or attacking, instead of just returning, I'll set the value of
direction to zero. Otherwise behave normally. Characters will have to finish their attacks before
taking any other actions. I'll also add the condition
to the hit box collision that this character must
not be dead and must be attacking to be
able to inflict damage. In Rogers two D view, I haven't given Rogers
hip box node a shape yet connected the signal or set
the collision layer and mask. We can easily connect
the node signal of area entered to the method we are already defined in
the character script. Then set this hip box to be
turned off by default on the player hit
collision layer layer 11 and masking the
enemy T layer layer 18. Selecting Rogers
animation player, we can add an attack
one animation populated with the appropriate
sprites and setting its length setting wants to attack to false
during the first frame, we need to set the value
of is attacking to true while the animation
plays. Then back to false. After it's done, the character
knows not to move or jump. Then set the
monitoring property of the hip box to true during the second frame and
false during the third. Detecting the collision
which will hurt enemies. Using the second frame of the animation as a visual guide, create a new shape
for the collision. I'll use a capsule and set its color to red to match
my other hip boxes. Then set its shape, position, and rotation to fit the
affected area of the attack. Adding tracks to the
animation for the collision. Shape nodes, shape, position and rotation. We can add key frames for each during the first
frame of the animation. Add an animated so to the hip box to create
the effect animations. And create an
animation for each of the three attack effects with an empty frame at the
end as the default value. Just like we did with
the enemy attacks. Position the node ahead of the character so that the sprite animation looks like it will line up with the attack back
in the attack one animation, we can add a track that
plays the effect animation, passing attack one as the first argument
in the play method. Call in the inspector. And
also an audio playback track to play a swipe sound effect, duplicating the
attack one animation. We can create the attack two
and attack three animations, swapping out the
sprite textures, changing the argument pass
to the animated sprite, and populating the audio playback with a
new sound effect. Then create a new
collision shape for the different
attack animations. Readjusting its
positional rotation and shape to match the
attacks affected area. Depending on which version
of Godot you're using. The editor may not display the collider correctly
while you do this, but you can still enter values
manually in the inspector and repeat the entire process
to make attack three. Make sure you create
a new collision shaped resource for
each animation. Oh, selecting the
animation tree. Diving down into the
width sword layer, attack layer, We want to add
our three attack animations. I'd like to transition through these three attack animations in order as long as the player keeps pressing the
attack button, but transition back
to movement from any of them if they stop
pressing the attack button. First transitioning out of
movement to attack one, if the character wants to
attack and is on the floor. Then following the
same conditions through the other two attacks, only switching at the end
of the previous animation. Attacks two and
three will return to movement at the
end of its animation. But the transition
from attack two to attack three
will take priority. If its conditions are met, attack one will transition
back to movement if either the character does not want to attack or if they are
no longer on the floor. Before testing, it would be
helpful to be able to see the collider animating
in the debug menu. Select Visible
Collision Shapes to render collision shapes
while the game is playing. Now if we pick up the
sword in our game, the player will be able to
perform combo attacks with it, each with different animations, collision shapes
and sound effects. And the attacks will hurt
and kill enemies too. We now have the player
character able to perform a three hit combo attack with a sword to fight back
against their enemies. In the next lesson, we'll
add aerial attacks two. I'll see you in the next lesson.
45. 5-3 Air Attack: Hello friends. In
the previous lesson, we added attack animations
for the player. In this lesson, we'll add more attacks that the player
can use while jumping. Starting in Rogers scene, selecting Roger's
animation player node. We can duplicate the attack
one animation to create air attack one and swap out the
sprites for that animation. Swapping out the swipe sound
effect for a different one. We also need to create a new collision shape for
this animation. Adjusting its shape, position, and rotation to match
the affected area. When adjusting these collision
shapes with an animation, it helps to create
a new collision shaped node and
adjust that instead. Then copy the values into the animation key frames
and delete the new node. When you're done, let's rename the animated sprite
two node to attack effects. Then duplicate it to
create air attack effects. Be sure to create a new
sprite frames resource for this new node and create the two air attack
effect animations. The air attack effects aren't positioned the same way as
the ground attack effects. So we'll need to
reposition the node, deleting the track for
the attack effect. We can add a new track for the air attack effects instead, then duplicating this animation. To create air attack two, we can repeat the process, swapping out the sprites, changing the argument past
to the effect animation, and switching the sound effect. Then adjusting the collision
shape to match the attack. Adding these two new animations
to the animation tree, they'll behave very much the
same as the ground attacks, only with the condition that the character must
not be on the floor transitioning from
air attack one to air attack two at the
end of the animation, under the same conditions. And back to movement at the
end of the second attack, transitioning out of air
attack one, back to movement. If the player no longer wants to attack or they
are on the floor, I would like to create the
sensation of suspending in midair while the player is performing these air attacks. We'll override the definition of air physics in the hero script. If the hero is attacking and their y velocity is
greater than zero, which means they're falling, then I will set their
y velocity to zero, instead keeping them in the air for the duration
of their attack. Otherwise, we can use super air physics delta to perform the inherited behaviors from the character script. But since the player can choose to keep attacking infinitely, they can also choose to
stay in the air forever. We can rectify this with
another game mechanic, a Cool down timer. Adding another timer node to
the hip box named Cool Down. I'll leave its time setting
at the default 1 second, but set one shot to true. Grabbing a reference to this node during the ready phase in the hero script so we can reference it in the
animation tree. Later in the attack animations, we can add a method
call track to each starting the
cool down timer at the end of each animation. Calling start on the
cool down timer. Again, with each attack
will simply reset its time value back to 1 second and start
counting down again. In the animation tree, we won't allow the transition
into the first attack of the sequence unless the
cool down timer is stopped. That way the player will be forced to stop attacking after a maximum of three attacks on the ground or two
attacks in the air. The duration of the
cool down can easily be adjusted in the
timer nodes properties. I would also like attacking an enemy from above
to bounce the player. Adding an override
to the hero script of the hit box area
entered method. I'll first need to
check if this hero is either dead or not
attacking and then return if they are
not on the floor. And the global y position
of the area struck by the attack is greater than the global y position
of the hero, so the enemy being
struck is below. Then I will set their y velocity to be jump force divided by two, creating a bounce that will
be smaller than a full jump. And finally, call the
inherited definition to inflict damage. Well, when creating the players
attack animations, I added the new variable
for is attacking, but that variable
wasn't included in the enemy attack animations.
So let's add that in. While the animation
tree state machine lightering helps keep
things organized. It also introduces the potential for animations to
be interrupted, leaving variables set in
ways they shouldn't be. For example, if Roger attacks his attacking variable
and hip box monitoring are both set to true. But then he gets hit
before the animation completes and those variables
are not set back to false. Roger now can't move
and any enemies that come near him will be
damaged by phantom hip boxes. We can reset these variables when the character
takes damage or dies. To prevent this from happening, let's create an attack
interrupt method. Resetting the value of attacking and hip box monitoring
both back to false, and call this method
when the character takes damage if they
were attacking. We now have the player character able to perform
attacks in the air, suspending gravity, and generating a bounce
force on impact. In the next lesson, we'll add AI scripts to have the
enemy's patrol in area. I'll see you in the next lesson.
46. 5-4 Patrol: Hello, friends. In
the previous lesson, we added aerial attacks
for the player. In this lesson, we'll
write an AI script to make enemies patrol an area. I've moved the other
enemies and position fierce tooth in between
two obstacles in my level. And disabled the
auto attack timer for the most basic
enemy behavior. I would like to have
them simply walk forward until they hit
a wall and turn around. Much like how we created the player node to control
the player character, we can create a child node to enemies to control
their behavior. With AI, this node will
require two D positioning. So I'll make it a node
two D and name it Patrol. Giving this node a
script name to patrol. I'll put it in the characters
scripts folder and use the default
template for a node two D. During the ready phase, I'll grab a reference
to the parent node, which is the character
being controlled. Let's also declare a
variable so we know which direction we want
to move as a float type. In the ready method, we can
initialize the value of direction based on whether the parent character
is facing left or not. But the is facing left variable
was marked as private. We should add a
public method that returns its value to
the character script, so we can improperly access it. Then we can set the value of underscore direction based on the return value of
the parent characters. Public is facing left method, This allows us to read the value of is facing left
but not change it. In the process method,
we don't need delta. We should put an underscore in front of it so the
engine can ignore it. The default behavior
will simply be to tell the character to run in
the desired direction. But before we do that, we should check if the character
has hit a wall, which is conveniently
included in the character body
two de node for us. If the character is on a wall, then we should change
the value of direction. We can get the walls
normal vector, which is a vector
that is perpendicular and pointing away from
the walls surface. We're only interested
in the x portion of this vector to know if the
wall is facing left or right. We can also apply sign to this. The return will be either
negative one or positive one. This won't matter in my game, since every wall
is perfectly flat. Their normal vectors will be exactly left or right anyway. But this code will work
with angled walls. Now two, let's try it out. Fierce Tooth is now walking back and forth between
the two obstacles. They even detect the
player as an obstacle. Two, anything that the character body to D
node collides with that prevents
horizontal movement will be detected by
the is on wall method. What if we position fierce
tooth on top of the platform? They will simply
run off the side and repeat the same
behavior as before. While I may want this
behavior sometimes, I probably don't want
this to happen all of the time and may want enemies to turn around
if they see alleged. This is slightly more
complicated since the detection of ledges
isn't provided for us. Adding a child node
to the patrol node. We can use a ray cast
two D node to detect the presence or absence of terrain in front
of a character. I'll position the ray to
be in the empty space in front of the character and
pointing down at the ground. We want the behavior of
this enemy to change based on whether or not this
arrow is hitting the ground. When they turn
around, reposition the arrow to the other side. Back in the patrol script, we can grab a
reference to the cast during the ready phase
and call it Floor Re then set its position
to be half of a tile ahead of the character
in the ready method. Alternatively, you could
export a variable if your enemies have
different sizes and need to position the
ray differently. This is an integer division, but I'm doing this on purpose, so I'll ignore the warning. In the process method. Anytime the character
is on the floor and the ray is not
colliding with anything, that would mean that the
character is facing a ledge. So we can call a
method ledge detected. If a ledge is detected, we can easily swap the
value of direction between negative one and positive one by multiplying it
by negative one. Then reset the position of the ray to be in front of the
character's new direction. Instead of duplicating the code, we can turn the code from
the ready method into a new method and call it anytime we want them
to change direction. Now Fierce Tooth
will pace back and forth without walking off
the platform's edges. Turning on visible
collision shapes in the debug menu will also allow us to
see the floor ray at work while this happens. But what if I want
to make an exception and have some enemies
walk off ledges? We can add a new variable
type to our script, An enumeration often
shorted to enum, which is just a numbered list. Name it, ledge behavior, and then populate this list with all of our options
we want to have. I'll just add two options. The default value at the top of the list will
be to turn around, but I also want to
have the option to walk off ledges two. You might also want
to have things like stop or jump
off, et cetera. The convention for enumerations is to use upper underscore case. You may remember
using an enumeration already which was defined in
the rigid body two D node, when setting its
freeze mode property. With the enumeration defined, we can export a variable using the enumeration
as its type. Then when a ledge is detected, we can match the value of this variable and provide a block of code to run for each. If the alleged behavior
is set to turn around, we should run the same
code from before. But if it is set to walk off, then we can simply
pass or return. Out of this method,
you can imagine how this format can be used to provide any number of
alternative behaviors. Selecting the patrol node. The inspector now
contains a drop down labeled ledge behavior
from which we can select the behavior we want described in terms that are easy for anyone to understand. I'll also move the players start position to
get a better view. Let's tell Fierce Tooth to walk off ledges and see what happens. Save the patrol node as its own scene in the
character's folder. We can now apply the
same behavior script to every basic
enemy in our game. Adjusting how they react to
different terrain obstacles, using casting and enumerations. In the next lesson, we'll give the enemy's player detection
an aggressive behavior. I'll see you in the next lesson.
47. 5-5 Aggression: Hello friends. In
the previous lesson, we created an AI script
that can be used to make any enemy in our game patrol in area and even change
their behavior. In this lesson, we'll write
another script that will allow the enemies to perceive the player and try
to attack them. Let's start in
fierce tooth scene. First step will be
knowing whether or not an enemy can see the player. This will be a
unique behavior to only those characters
we want to be enemies. We should create
a new script for enemies inheriting
from character. We can then replace the
character script with the enemy script on
Fierce Tooth's root node. Let's add a Boolean variable to know if the enemy can
see the player or not. But how do we know if the
enemy can see the player? Much like we did with attacking, we can add another area to node to represent
the enemy's vision. And give it a collision shape. A use a neutral color,
like white for vision. And make it a large
circular area that covers several tiles in
front of them and slightly around
them on all sides. This represents their
entire field of vision. We need to know if the player
character enters this area. Let's adjust the
collision layering. Putting this on layer 20, a new layer for enemy vision
and masking layer nine, so they can only see the player. This area will be monitoring,
but not monitorable. And always on by default. But there may be obstacles in the way blocking
their vision. We can use a cast node to check their line
of sight as well. It doesn't matter where
the target is right now, I'll just put it in the middle. But the origin point of the Raycast should be
from the enemy's eyes. We can distinguish
these two nodes by renaming them for Clarity. Field Division will only
be looking for the player. The line of sight will be
masked by the terrain and the player as well as anything else you would like to
obscure their vision. Next, we can connect the
body entered and exited signals of the area two D
node to the enemy script. These are nicely named for us to vision body
entered and exited. Let's store the
hero in a variable as they enter and exit the
enemy's field of vision. Thanks to collision layers, only the hero should be
triggering this method. But it's always safer
to check first, storing a reference to the hero as they
enter the field of vision and setting
that reference to null as they exit
the field of vision. To check the line of sight, we'll need a reference
to the line of sight nodes set during
the ready phase. Then during the process
method, we don't need delta. If the hero is within
our field of vision, we'll do some maths, but if they aren't in
our field division, then we can't see the
player setting it to false. We can set the
target position of the line of sight to be
the difference between the hero's position and the enemy's position pointing the line of sight directly
at the player character. If the line of sight is hitting something and that
something is the hero, then we can see the player. Otherwise, it must be hitting
nothing or something else, like terrain or an obstacle. We can't see the player turning on visible
collision shapes in the debug menu
and hitting play. Then returning to the editor, we can switch to the remote
scene tree and select Fierce Tooth to observe their variables in the
inspector in real time. Now we can see that as the player enters
the field of vision, the line of sight starts
to track their position. The can C player variable is set to true only
when they are within the field of vision and unobstructed by terrain
from the line of sight. We also need the field
of vision to flip like everything else when the
enemy faces left or right. Grabbing a reference to
the vision node during the ready phase and overriding the definitions
of face left and face right. We can start by calling
their super methods. Then add setting the
scale X of vision to one when facing left or
negative one when facing right. Since the enemies are all
facing left by default. Since casts don't use
the scale property, we will need to add
an extra condition when calculating the
target position. If the enemy character
is not facing left, then multiply the X of the target position by
negative one to mirror it. Once the enemy has
seen the player, we want to pause their
patrolling behavior and have them chase
the player instead. In the patrol script, we can add a private Boolean variable to control whether or not this
character is patrolling. And set its value
to true by default. Then provide public methods for pausing and resuming
this behavior. Setting the value
of is patrolling in each case when paused
Also call character run zero to force them to stop running in the process method. If this character
is not patrolling, then we should return and
not run any of this code. In the enemy script, we
can get a reference to the patrol node during the
ready phase even though it may or may not exist using
a method named get node or null passing in the name of the node we're looking
for as an argument. Then replace each of these three lines with a method call, telling this enemy whether
or not they can see the hero writing this new private method hero. It will take a Boolean parameter of whether or not
they can now see the hero if the enemy
can't see the hero, meaning that the value up to
this moment has been false, but now they can see the hero. This is the exact
moment when the player entered both their field of
vision and line of sight. Then we should set
can see hero to true. If the patrol note exists, then tell it to
pause its behavior. Inversely, if the enemy could
see the hero but now can't, then we can tell the
patrolling behavior to resume in case the hero
is behind the enemy. We can also force
the enemy to face the player's direction
at this moment by comparing their
global positions and the enemy's process method. If we can see the hero, then we should run toward
them using the difference of the hero's global exposition and the enemy's global
exposition to know the direction and
apply sign to it. So it's only ever
negative one or positive one or zero if they are
directly above or below. Back in to view, we can add yet another area to
node to save time. Let's make it a child
of the hip box node and name this one target. I'll make it yellow to indicate danger and make this shape whatever you think makes sense, but probably similar or just a little bit bigger
than the red hit box. Place this on another
new collision layer, layer 21, the enemy
targeting layer and mask. For the player's
Rt box layer ten. This will be monitoring
and not monitorable. We can connect to the area entered signal to
the enemy's script. We don't need the area two D, so we can flag it
with an underscore. Then when the
player's T box enters the enemy's targeted area,
the enemy will attack. Let's try it out.
When Fierce Tooth sees Roger, they give chase. And if they can't see Roger, they will go back to their
normal patrolling behaviors. If they catch Roger,
they will attack. Our enemies can now see and aggressively chase and
attack the player. In the next lesson, we'll create an enemy that can
shoot projectiles. I'll see you in the next lesson.
48. 5-6 Projectile: Hello friends. In
the previous lesson, we allowed our
enemies to perceive the player character
and attack them. In this lesson will create a new enemy with range attacks. I've already started creating a new enemy for this
lesson, the sea shell. So far, the structure of
the node tree is the same. The root is a character body, two D on the enemy
collision layer, masking the terrain layer
with the sprite two D, animation player
and animation tree. A collision shaped node with
a capsule shaped resource, a matching Rt box, hip box, target box, and vision. Make sure each are set to the appropriate collision layer, masking the correct layers and are monitoring or monitorable. I'll start by
adding a node two D named to Projectile origin, which will mark
the exact location where projectiles
will be fired from. And a timer node, which will
tell this enemy to fire. A projectile set to autos start and it will
fire every 3 seconds. The animations have all been populated with the
appropriate sprites. Idle, hit, fire, and destroyed. Destroyed is only one frame. Then the second frame sets the visible property of the
sprite to falls to hide it, the animation tree state machine has the hit and
destroyed animations on the top layer
transitioning based on the hit and is dead variables
of the character script inside the attack state machine. The Seashell defaults to idle, then transitions to
fire when they want to fire back to idle at the end of the animation
and don't want to fire. Let's create a new character
scripts folder for enemies and put enemy and
patrol scripts inside it. Like we created the enemy script by inheriting from character, we can give the enemy
script a class name enemy, then create a new script
named So Inheriting Enemy. Now this script has
all the behaviors and attributes of a
character, an enemy, and whatever we put in
the shooters script to in order to
spawn a projectile, just like we did
with spawning coins, we can export a packed
scene variable, then also export variables to describe the
projectiles behavior, like its speed, which I would like to describe
in tiles per second. I'll set it to ten by default. Also the damage the projectile will do when it hits the player, an integer ranging 1-100
with a default of one. The maximum duration, I'll
allow the projectile to exist before destroying
itself as a float, with the default of 10
seconds. During ready. After performing all the
inherited ready methods, I'll multiply my speed
measured in tiles per second by Cobolpt to change it
to pixels per second. Also grab a reference to
the projectile origin node. Using at on ready, we can
write a method to spawn the projectile which
will be called by the shooter enemy's
fire animation during the exact frame when
the projectile should appear. Creating a temporary variable to hold the newly
instantiated projectile. We can position it at
its origin point and add it to the scene tree
as a sibling of the enemy. If the enemy can
move, the projectile will not be affected by
the parent's movement. It will exist as an independent
entity of the world. We can then tell the
projectile to fire, giving it the
information it needs to do its job direction, speed, damage, and duration. This enemy will only fire
projectiles left or right, depending on which
direction they're facing to trigger
the fire animation. Just like with the
attack animations, we'll have a Boolean
variable wants to fire which is set to true by a public method fire in the
sea shells animation player. During the fire
animation we can set, wants to fire back to false spawn the projectile during the frame when
it looks most correct. To do so in the seashell scene
I'll also create the projectile which will
be an area two D node. I do not want physics
to be applied to my projectiles and would rather calculate their behaviors
mathematically. If you want your
projectiles to use physics, they should use a rigid
body two D as a root node. The projectile being fired by the sea shell enemy is a pearl and put it on
the enemy hit layer, masking the terrain and
the player t layer. Since these are
the only things I want the projectile
to be able to hit. And it will need
a sprite to draw. And a collision shape, which is the circle.
I'll color red. Positioning the
projectile where it should originate
within the seashell. You may want to reposition the projectile origin
node based on how it should look in relation
to the shooter enemy. I'll also add a timer node, which will primarily
be used to limit the duration that this
projectile can exist. I'll set it's one shot to true when the pearl hits
something I wanted to break. I'll add a node two
D named debris. I'll hide the seashell sprite
in collision shape for a better view while I make
the pearl debris using debris as a folder I can
then add child nodes for each piece of broken pearl
as rigid body two D nodes. Since now I would like
to apply physics. The debris pieces will have their own sprites and
collision shapes. I'll position the top half of
the pearl where it matches the original pearl sprite and give it a collision
shape to match. Put them on a new layer, layer four for debris
and mask only terrain, have it unable to rotate and frozen with freeze
mode set to Kinematic. Then duplicate it to create
the bottom swapping up sprite and repositioning it to match the bottom of
the unbroken pearl. Both the top and bottom debris
will be hidden by default. With the node
structure complete, we can save the projectile
branch as its own scene in a new Projectiles folder
and open it up for editing. We can now delete the pearl from the seashell scene and reset its position to be
centered about the origin. Next, add a script to the pearl's root node
named Projectile, saved in the enemy
scripts folder. During the ready phase, I'll grab references to
the child nodes. I know I'll need to access like the sprite, debris and timer. Having already written the fire method call in the
shooter script, we can give that a definition. Accepting a direction
as a vector, two speed as a float, damage as an integer, and duration as a float. We will need to store
all of these values in private local variables,
except the duration. The duration we can simply
use to set the timer. If the duration given was
anything more than zero, value of less than or equal to zero would be considered
unlimited duration. I'll also need a private
local variable to know if this projectile has
been destroyed or not In the process method, if this projectile has
not been destroyed, then add to its
position direction times speed times delta. This will make the
projectile move in a straight line at
a constant speed. Which is the
expected behavior of projectiles in most video games. Connecting the area entered in body entered signals
to the script. The only body this projectile should clide with is terrain. If this happens, it will
tell the projectile to break if it enters an area which should be
the player's hurt box. I'll first try getting
the area's parent, which should be the
player character. After verifying that
this is the hero, I'll tell them to take damage, passing the damage amount and direction normalized
as arguments, then break the projectile. Adding in the break method, I'll connect it to the
timer's timeout signal. It will be called automatically 10 seconds after the
projectile is fired. If it didn't hit anything
when the projectile breaks, I would like for a few different things to happen over time, which could be done with
an animation player, but I would rather do it
with relatively simple code. First, I'll add an exported variable for the texture two D, which shows the projectile
being destroyed. And swap the sprake
for this one D is destroyed variable to true. The projectile stops moving and it's collision mask to zero, so it won't trigger
any more collisions. Then I want to reuse
the timer node, which is currently
being used to trigger the break method on
the timeout signal. Since this is the break method, the projectile has
struck something and that behavior is no
longer necessary. I'll get the timer's
timeout signal and disconnect it from
the break method. Then restart the timer
with a new time, 0.1 seconds, which is the frame rate I've been using
for all of my animations. Awaiting the timeout signal
is 0.1 seconds later, I'll set the sprites
visible property to false, then tell the debris to scatter. Then wait ten more seconds
before telling the engine that this projectile is no longer relevant and can be removed
from the scene tree. Now we can create a
new script to scatter the debris attaching
it to the debris node, which I'll put in the
environment folder. This script only
needs one variable, an array of nodes which
are its children. When told to scatter, we can iterate through
this array with a for loop following the format of how other languages
would write it. For each loop, we can refer to each member of the
pieces array as a piece, Then tell it to become visible, unfreeze, and apply an
impulse force to it. Since the debris pieces
are already distributed to their original positions
relative to the unbroken hole, we can simply use their position as the direction of
the impulse force, then multiply it by one tile. Of course, you can experiment
with different amounts of force being applied
to scatter the debris. May be normalizing
the direction, so their distance from the
origin isn't a factor. When the sea shell is destroyed, we can do the same thing as with the pearl creating the debris. Node two D, this time
with four children. For each of the
four broken shell sprites copying and
pasting the nodes, they will already have
the correct settings. Positioning each at the exact location
where it will match the unbroken shell sprite and adjust their
collision shapes as well. Then attach the debris
script to the debris node. Finally, connect the
timers timeout signal to tell the sea shell to
fire every 3 seconds. Selecting the sea
shells root node, we need to populate
the packed scene containing the projectile and set the sprite faces left and is facing
left to be true. Yes. Then add a
method call track to the destroyed animation for the debris to be scattered. At the same moment when the
sea shells sprite becomes invisible, let's try it out. The shell shoots pearls
every 3 seconds which collide with the terrain and
break generating debris, which is automatically removed. After 10 seconds, these pearls can hit and damage the player. And the player can also destroy the sea shell by attacking
it with the sword. We now have an enemy that can shoot projectiles at the player. In the next lesson, we'll add
a boss enemy to our game. I'll see you in the next lesson.
49. 5-7 Boss: Hello, friends. In
the previous lesson, we added a new type of enemy
that can shoot projectiles. In this lesson, we'll add a boss enemy encounter to our game. I've gone ahead and created a new level for the
boss encounter, which is a pit that once
the player jumps into, they will not be able to
get out of the level. Checkpoint is right
before the boss and the sword is there, so
they will have already. The totem pole is made of
three shooter enemies, which are almost exactly buckets of the sea shell from
the previous lesson. Except that they mask the
enemy collision layer for stacking purposes. The blue totem has a
custom collision polygon making it difficult for the
player to stand on top of it, but still capable
of jumping over it. In their projectile is
a wooden spike which is a duplicate of the pearl with only the sprites and
collision shapes altered. Unlike the pearl sprites, the wooden spike is not symmetrical in the
projectile script. Let's add a new exported
variable, flip with direction. Then when firing the projectile, if flip with direction and direction x is
greater than zero, we should set the scale x two negative one to
flip the sprite, collision shape and debris and set the wooden spikes value
of this variable to true, leaving the pearls false. Unfortunately, these totem
sprites are not centered. To make the individual
totems able to face left or right while staying centered within the totem pole, they will need a
sprite x offset. Then overriding the definitions of face left and face right, I will set the sprites
offset to be this number multiplied by one if there's
sprites face left else negative one and vice
versa for face right. I'll then set the
value of this variable during ready,
before super ready. Since super ready will tell the character to
face left or right, I discovered a
problem with setting the projectiles position before adding it
to the scene tree. So we can fix that by adding
it to the tree first. Also, I don't want
this enemy to have any knocback velocity added
to them when taking damage. In the character script, we can add another combat
variable named stagger as a float with
a default value of five. The other enemies will still have the same
behavior as before. Then when a character
takes damage, we can multiply the knockback velocity by this new variable and set its value to zero for the boss,
removing the effect. How do we create
a boss encounter? Let's start by adding an area
toting node to this level, which when the player enters, we'll start the boss encounter, then set it to be monitoring
for the player layer. I'll give it a rectangle shape and have it filled
the entire arena. I would like the
camera to remain still and center
during the boss fight. So I'll add a new node type A marker two D node and name it Camera A marker two D node is almost the exact
same as a node two D, except that it draws
a crosshair gizmo in the editor so we can
easily see where it is. If we want the camera to stay locked in place
during the bosphte, we will need to be
able to override its default behavior at
a Boolean variable is following subject with
the default value of true in the process method. If the camera is not
following the subject, then we can return to avoid changing the camera's position. Let's create a new
pan variable to perform this panning
behavior using a tween. Then a new public
method, pan to marker, which will take a marker
two D as a parameter and also the duration of the pan with a default
value of 1 second. If the pan exists and is
running, we should kill it. Then create a new one
and tell it to pan the camera's position to the
markers global position. When calling this method, we
want to override the default following behavior by setting is following subject to false. Then we can add another
method to resume following the subject setting is
following subject to true. But if we leave it
at that, the camera will hard cut to the
subject's position. First, we should check
if the camera is currently panning and
kill that behavior. If it is, we can then reverse the calculations of the
camera position from the process method to
instead calculate what the look ahead distance
and floor height values would be at the camera's
current position. Then emit both the changed
direction and landed signals, which will tween the camera
gently back to the subject. Back in the level scene, we can also add a canvas
layer to the level. Unlike the canvas layer
in the game scene, which will be drawn
over all levels, this canvas layer will only
be drawn in levels with the boss copying the
player's health gauge. We can paste it into the
boss encounters canvas, then reposition it to be at the top center of the display. And replace the heart with
the skull, having it hidden. By default, I'll make sure to only show it
during the battle. After the battle is over, I want the level end treasure
to reveal itself. I'll have the
treasure positioned in an impossible
to reach area and invisible in the
treasure script. I'll add a simple public
method named To Reveal, which will take a new position
as a vector two parameter. This method will
move the treasure to the desired position,
Make it visible. Play the effect
animation backwards. Wait for the
animation to finish. Then play the default animation, selecting the boss
encounter node. Let's create the boss
encounter script, saving it in the
enemy's scripts folder. This script will
need a reference to the camera marker and
health gauge nodes set during the ready phase. But also a reference
to the camera itself, which we can reach by starting our path from the root node. I'll also grab a reference
to the level treasure by first accessing
this node's parent, which will be the level. Then get node checkpoints. Alternatively, you
could export this as a variable connecting the body entered signal to the script. This will be triggered by the player entering
the boss arena area. This is when we should
start the boss encounter. Let's also store a reference to the hero in a local variable. When this happens to make tracking the
position and behavior easier To start the encounter, I'll first pan the camera to the camera marker and then display the health
gauge on the UI canvas. Inversely, to end the encounter, we should tell the camera to resume following
the player and hide the health gauge Ending the encounter could
happen if the player dies or if the boss is defeated. If the boss is defeated, the encounter should end
but also reveal the reward. Repurposing the camera marker
as the reward location. We can trigger the
end encounter method automatically by connecting
the body exited signal to it. Since the player will
respond outside the arena, this script will need to
know what the boss enemy is. In this example I'm using, the boss encounter is
actually three enemies. I'll export an array of enemies. I only want to have
one health gauge track the combined health
of all three enemies. I'll have the boss
encounter script handle the extra logic, creating two new variables
for current and max health. Then in the ready method, I can iterate through my
array of boss enemies, adding up their max health
to get a combined total. Then setting current to
be equal to the max. I'll also write a new method here that updates
to health gauge. Starting current health at zero and iterating through the array. Adding up the current health
of all the boss enemies. To get a combined total, we can then calculate
the percentage of total health
remaining casting current to a float and
dividing it by the maximum. Then set the health gauges value using this combined percentage. If the combined total
of health is zero, then the boss has been defeated. We can use the health
changed signal being emitted by the character
script to call this method. But the health changed signal passes a float argument with it, adding that as a
parameter to our method. We can ignore it by preceding
it with an underscore and connect the signals during the ready method when we
calculate the max health. A common mechanic
in games is to have the boss's behavior change at certain percentages
of health. If the boss isn't dead yet, let's call a phase check method, passing the percentage
as an argument. Since this behavior will
vary from boss to bos, leave the body blank along
with another empty method. Decide next attack. We now have all the generic boss encounter structures in place. Let's give this script a
class name so it can be inherited to provide
encounter specific behaviors. Removing the boss encounter
script from the node. We can instead replace it
with an inherited script, specifically describing the
behaviors of the totem pole. In this script, we can define how it decides its next attacks, as well as how and when it
changes between battle phases. I'll keep things
simple by just having a timer note attached
to the boss encounter, which I will grab a
reference to with at ready. Start at when the boss encounter starts and stop at when
the boss encounter ends. Then connect its timeout
signal to decide next attack. The totem pole will decide
what to do every 1 second. Adding in a random
number generator and an integer to store
a random integer. I'll first iterate through
each boss in the boss's array, then generating a random number 0-2 I can match the results. I'll ignore zero, meaning that
the enemy will do nothing. If it's a one, then they
will face left and fire. Or if it's a two, they
will face right and fire. To increase the difficulty. As the boss's
health gets slower, I'll check when the
percentage reaches less than one third and
less than two thirds when these thresholds
are crossed. I'll set the timers
wait time to be slightly shorter so the
totems attack more often. But I'll make sure that the time remains longer than
the animation, allowing you to
finish and return to idle between each attack. I won't forget to populate
the boss's array with the boss enemies.
Let's try it out. The boss encounter is triggered when the player
enters the arena. The camera pans and
locks in place, and the boss's health
gauge is displayed, which tracks the combined
health of all three enemies. If the player dies, they are respond at the checkpoint and the boss encounter is paused until the player re
enters the arena. When the enemies are all dead, the health gauge disappears, the camera resumes
following the player, and the reward is revealed. We now have a boss
enemy which would be appropriate to wrap up a
world of levels in our game. In the next section, we'll focus on menus and progression. I'll see you in
the next section.
50. 6-1 Pause: Hello friends. In the
previous section, we added combat to our game. In this section,
we'll add menus and progression if you
completed the assignment, all of your enemies will
now have unique behaviors. Allowing them to patrol
an area of your level, looking for the player, then
chasing them and attacking. You should also have
a cannon capable of firing a cannonball which
explodes on impact. If you completed the challenges, your enemies may also be
dropping treasure when defeated. The cannon can be operated by either the player or an enemy, and the seashell also
has a bite attack. In this lesson will
allow the player to pause the game and
open a pause menu. Let's start by opening
the project settings and adding a pause
button to the input map. I'll use the escape key, the start button on my game pad. All of our input handling is currently being processed
by the players script. But pausing the
game isn't really anything to do with
controlling a character. Any node in our scene tree can
override the input method. Pausing the game should be the responsibility of
the game manager. In the Game Manager script, we can add an input method which takes an input
event as a parameter. If the pause button was pressed, we should pause the game. The pause method can take
a bulling parameter, I'll call it, should
be paused this way. It can be used to either
pause if the argument is true or unpause if
the argument is false. Pausing is seen in
Godot is very simple, just that the value
of get tree paused, or in this case two,
should be paused. Then when the pause
button is pressed, we can pass the value of get tree pause with a knot operator to switch its value
as the argument. But if we pause the
entire scene tree, including the game manager, then the game manager
will not be able to do anything including
receiving input and unpausing the game. Selecting the scenes root node. Looking in the inspector. Under node, we can expand the process section and set
its process mode to always, even when the game is paused, the game manager will
still be running. Anything you would like
to pause in your game, select the node and set its process mode
property to pile. The default value of
inherit will tell all child nodes to inherit
the process mode of their parent nodes
pausing Rogers node will also pause the player node. Pausing level nodes will pause all their enemies treasure
and everything in them. User interface
controls like buttons, will still work while
the game is paused. But if you're using Tweens
to show or hide them, then they will need to
be set to always process two. Let's try it out. When the pause button is
pressed, the game pauses. When the pause button
is pressed again, it unpaus, this is functional, but I would like to
present the player with a pause menu and some options. We can start by
duplicating the game over menu and renaming
it Pause Menu, grabbing a reference
to the pause menu. During the ready phase, we can set its visible property when pausing or un
pausing the game. I'll quickly edit the
banner at the top of the pause menu to say pause
instead of game over. Then change the retry
button to resume. I no longer want the
resume button to call the on retry
pressed method. I'll edit the signal connection. We can actually have this call
the pause method directly. But the pause method takes
a parameter click on the advanced to goal to
add extra call arguments. The argument we need to pass to the pause method is a Boolean. We can add it to the list and
its default value is false, which is exactly what we want. Clicking connect
the Resume button will now tell the
game to unpause. The level select and
exit buttons can remain exactly the same as they were in the game over menu. But I would like to
add another option to restart this level
from the beginning, duplicating the resumed button. I'll change this one
to be Restart and edit the button label to make this
fit better on the screen. With an extra button, I'll edit the panel style box
in the theme editor. This paper image is
128 pixels in size, but has an empty
border around it of about 12 pixels on all sides. I can tell Godot to only use the sub region of this image, setting the starting
x and y coordinates, the width and height of
the subregion to be used. Then I'll need to adjust the individual menus to have the words
appear on the paper. The retry button from
the game over menu and this restart button are
very similar in behavior. Let's try to combine
them into one method. The only real change I
want to make here is not resetting the
player's lives and coins. We can rename this on retry pressed method to
restart and have it take a Boolean parameter of
whether or not this is a game over with a
default value of false. Then pass this onto the
file data retry method, which I'll change
to be called reset. The level will be unloaded, reloaded, and the player returned back to the
start of the level. All the same way as before. But if the player has pressed the restart button
from the pause menu, we also need to
unpause the game. We can do this when loading
the level in the data script. We can edit the retry method, renaming it reset, and taking the game over
boolean parameter. Now, only if this is
being called from a game over state should the
lives and coins be reset. In the pause menu,
the restart button can be connected to
the restart method. Since the parameter has a
default value of false, we don't need to specify
an argument here. But in the game over menu, the retry button can be connected to the
same restart method. This time specifying
true as an argument. Now, all the same behaviors will be used by
these two buttons, with the only difference
being resetting the players lives and
coins if they have run out of lives.
Let's try it out. Now. When the game is paused, the pause menu is
displayed and pressing Resume hides the pause
menu pausing the game. The restart button will return the player back to the
start of the level, resetting all the
level components back to their original states. The level select button
isn't doing anything yet, Since we don't have a
level selection menu yet and the exit
button exits the game. We can now pause our
game and give the player a menu of options while
the game is paused. In the next lesson, we'll add a title screen to
start our game. I'll see you in the next lesson.
51. 6-2 Title: Hello friends. In
the previous lesson, we added a pause button and
pause menu to our game. In this lesson, we'll add a
title scene and main menu. Let's start by creating
a new two D scene named title saved in the
Scenes folder. We can copy many
of the components from other scenes to
put in the title scene. Like the background, I'll set the scale property of the title scenes
root node to three, matching the zoom
of the game scenes. Camera then reposition
the background, fill the screen and grab the animated sprites of the water surface
from my level scene. Adding a node two D to
hold them as one group. I'll also add in a new
animated sprite two D node containing the merchant ship. Populating each with the
sprites from the asset pack. We can set their animations to autoplay at the
desired frame rate. I'll put the ship behind
the water by re ordering their nodes, Another
for the sail and one for Roger. To then also add a sprite two
D node for the anchor. Adding in a canvas layer node, we can add control notes
to the title scene. I'll use a vertical box to hold the title and another
one to hold the buttons. The title box will contain two horizontal boxes,
one for each word, Jolly and Roger, populating
each with five text directs. I'll use the big letters from the asset pack for the title
spelling out Jolly Roger. I want the letters to always
keep their aspect ratios. I'll scale up the size of
the title eight times, so it dominates the scene. Then duplicate and repeat
to make the other word. I'll move the pivot to be
in the center of the title. Then use custom
anchors to make it horizontally centered
in the screen and one third from the
top of the screen. For now, I'll have my title
screen only have two buttons, New Game and Continue. I would like these
buttons to look the same way as the ones in
my other scene. Using the same theme, but the theme we created is
local to the game scene. Switching over to
the game scene, select any node that
has the theme applied. Like the game over menu. Expanding the drop down, we can save the theme
as a resource file, making it available for
the entire project. I'll save it in the root folder, naming it wood and paper
in the title scene, selecting the buttons
control node. The wooden paper theme
is now available from the quick load menu and its
children inherit the theme. I'll scale the buttons
up four times and reposition them in the
lower left corner. I'll then populate
each button with horizontal boxes full of texture Recs to spell
out their labels, keeping the aspect ratio of the letters spacing each letter two pixels apart. And give the buttons a minimum size to fit their contents. Resetting the size of the label, I'll anchor it to the
center of the button, then duplicate it to make
the continued label. Resetting its anchor. I'll make the buttons just a little bit bigger
and reposition them. I'll have the continued
button disabled by default since it wouldn't make sense to allow the
player to press it. If there's no say file yet, we can drag and drop
in the fade as well, since we saved that
as its zone scene. Let's see how it looks
nice in the file script. Let's add a new
method to check if the safe file exists.
Returning a bull. For now it can
just return false. Just like the game scenes root node has a game
manager script. This title scene will need
a title manager script too. This script will start by
making the fade visible. I'll grab a reference to the continued button
using at on ready. Then ask the file auto load
if a say file exists to load. If a say file exists, then we should set
the disabled property of the continued
button to false, allowing the player to press it. Then tell the fade to fade
to starting the game. Connecting the button pressed signals to the title manager. We can add methods to
handle both buttons. If the player presses
the new game button, I'll tell the file auto load to create a new data resource. After awaiting fade,
fade to black, I'll load the game scene, which will load the
first level of my game using get tree change
scene to file, which takes a string argument representing the file path to
the scene we want to load. Exploring the file system, we can find the game scene
right click and copy its path. Then paste it into
the quotation marks. If they press the
continue button, I'll tell the file out to load. To load the data resource. I would then like to fade
to black and change scenes just like the new game button, but load the level
selection scene instead. Let's make changing scenes
into a separate method. Change scene, taking
a string parameter of the scenes file path. Then both new game and continue button presses can
call change scene, but switch to different scenes. By now you've probably pressed the Run Project button
by accident instead of the Run Current Scene
button at least once and been prompted to set a default scene
for your project. If you haven't set
a main scene yet, you can click select Current to set this title scene
as the main scene. Alternatively, you can open the project settings
and look under the application section and set the main scene here
using the file browser. If we look under
Rendering environment, we can change the
default color that is displayed when Cade has
nothing else to render, Setting it to black to cover any gaps between fading
out and fading in. Now if we hit the
Run Project button, the title scene
fades in from black. Since no say file exists, The continued button
remains disabled, but we can click on
the new game button. This fades to black and
loads the first level, allowing the player to
start playing a new game. Our game now has a
title scene which access the default
scene for our project. In the next lesson, we'll add in the level selection scene. I'll see you in the next lesson.
52. 6-3 Level Select: Hello friends. In
the previous lesson, we added a title screen and
a main menu to our game. In this lesson, we'll add
in a level selection scene. I've gone ahead and
created a basic scene, much like the title scene with just a background
animated sprites of Roger and the ship helm, a canvas layer, and the fade. Like we did with
the theme resource, we can open any level, select the tile map node, and save the tile set as a resource so it can be
shared by the entire project. In the level select scene, I'll add a tile map node and load it with the
tile set resource. Then use the tile map to draw the stern deck of a ship
with Roger at the helm. Implying to the player
that between each level, Roger is sailing his
ship between islands. Let's see how this looks so far. Nice to display the levels. I'll add a panel
container to the canvas. Scale it up to match
everything else, and set its theme
to wood and paper. This container will
contain a vertical box, which will contain a
horizontal box for the title. I'll put four texture Ts in the horizontal box to
spell out the word maps, making sure that they retain their aspect ratios
and remain centered. I'll also center the title as a whole and reduce the
separation between letters. Underneath the title, I'll
add a grid container. This works much like a
vertical or horizontal box, but organizes its contents
into a simple grid. Since the maps are
each one quarter, I'll make it a two by two
grid setting columns to two. Each grid cell will contain another vertical box containing a button and a panel, which the theme has been
set to look like paper. I'll set its minimum
size to 36 by 32 pixels, so it's large enough to
hold the level name. Using another horizontal box
to write one hyphen, one, I'll make sure that the label is anchored to the
center of the paper. Feel free to name your
levels whatever you want for the buttons. I don't want
to use the green button. I can remove it by clicking
on the flat toggle. Instead of using these
green and yellow buttons, I will set the
buttons icon property to be the small
map, one texture. I'll make sure that the button stay centered within
its grid cell. Under theme overrides
expanding the style section. I'll also set the
focus style to be an empty style box so the
button will remain invisible. I'll duplicate level 113 times to make the other
buttons in the grid, Changing the name labels of
each one to match the levels. You can design this interface
to look however you want, accommodating
however many worlds the levels you want
to have in your game. But this is sufficient for now setting the panel containers
pivot to be in its center. I'll anchor it to be two
thirds from the left side of the screen and one,
two from the top. And change the button icons to match the different
map segments. To make these button
icons animate, we can select new animated
texture from the drop down. Since there are eight images
in the small map animation, I'll add eight frames to
this animated texture, then set each frame accordingly. The default values tell this animation to play
one frame per second. We can either change the
individual frame lengths to 0.1 seconds or set the
overall speed scale of the entire texture to ten. The button isn't rendering, so I'll change a property
to force it to update. Repeating this process,
I'll set each of the four map segments to be animated textures for
the respective button icons. I'll remove the excess space at the bottom of the panel
container by resetting its size and give the title more space with
a custom minimum size. Creating a new script for
the level select manager. I'll first grab a reference
to the fade using at on. Ready. During ready, I'll
set the fade to be visible, then tell it to fade to clear. I'll write a custom
method to connect the buttons to On
level selected, which will take the world
and level as parameters, both represented by integers. Connect the button pressed
signal from the level one one button to the
level select manager and pick this new method. Then expanding
advanced, we can add two integer arguments to
the signal connection, the world and the level,
both with a value of one. Setting the values
of file dot data, dot world and level to
be their new values. We can then await,
fade to black, and load the game scene. The game scene will
automatically handle loading the correct level
based on the file data. Remember that in order for a level to be loaded
using this method, it must be in the level
scenes folder and named using the correct
format to be detected. We can then connect the rest of the buttons to the same method, changing the values
of the arguments to match the world and level
which will be loaded, making sure the fate is
drawn over everything else. By positioning it at the
bottom of the node list, I'll reduce the separation of the vertical boxes to make
them take up less space. Then reset the size and position of the menu
for the final time. Let's try it out by
running the current scene. After the scene fades in, clicking on the
level one one button fades back out and
loads the first level. If we do it again, clicking on a different button will
load a different level. Our game now has a
level selection scene, which allows the player to
load any level in our game. In the next lesson, we'll allow the game to save and
load player data. I'll see you in the next lesson.
53. 6-4 Save: Hello friends. In
the previous lesson, we added a level selection scene that can load any
level in our game. In this lesson, we'll
have the players progression data save and load in the file script, we already set up many of the methods we'll need to
complete for this lesson. In other engines and when
dealing with consoles saving, loading is a difficult
thing to learn. But for the purposes
of our game, the Godot Engine provide simple ways to accomplish
exactly what we need. First, we need to know
where to store the file. Let's declare a variable
named path of type string. Starting the path
with user colon Godot will automatically find the appropriate place to
store our say, file. On most platforms we can
give a name to the file. Let's name it auto save. The extension for our
text resource is RS. Since this is a value which
should never be changed, I'll declare the constant
instead of a variable. This is a more efficient way of storing values you
know won't change. Unlike variables which
are designed to change, the main purpose being to
reuse the same value in multiple places
throughout your script without rewriting it every time. The T means text, which means that the file that
is created can be opened, read, and even edited in any text editor which
isn't very secure. But encryption is very ineffective and
generally not worth the extra effort no matter
how many measures you take. If someone wants to
cheat, they will. I would only recommend
bothering with security for online
multiplayer games. To check if this file exists, all we need to do is call
the exists method of the resource loader passing
in the path string argument. To save the game, we only need
to call the save method of the resource saver
passing the data to be saved and the
path for its location. Every time we call save, it will overwrite the
existing save data. Likewise, loading
only needs to set the value of data to be
resource loader load, giving it the same
path to the file. This is a simple implementation of a single file autosave. You could easily
tie the saving and loading commands
to menu buttons. And even create multiple say files using
the same process. The title manager is
already set up to load the players data when the
continued button is pressed. The question is, when
should we save it? We can start by saving the data at the same time it's created. Keep in mind that
this autosave system only stores one, say, file. Starting a new game
will overwrite any existing safe file. So when the new game
button is pressed, if there is no
existing safe file, we can call a new method
to start a new game, creating the new save file, saving it, and transitioning
to the game scene. If a say file does exist, we should ask the
player for consent before overwriting their
existing say file. I'll quickly put together
a basic container with the question
overwrite, save file, and two buttons for yes and no. And set is visible property
defaults by default, grabbing a reference
to the confirmation using add on ready. I'll make it visible when the player presses
the new game button, but a safe file already exists. Then connect the yes and no
button presses to the script. All N has to do is hide this confirmation while yes
we'll start a new game. Because of the
format of my game, I don't really care about what happens during any
particular level. In order to track player
progression through my game, I only need to know when they
complete or exit a level. To keep things simple, I
can auto save the game anytime the player enters
the level selection scene. In the game manager script, we have some buttons
in the pause menu that are still not functional. Let's add in the
transitions from the game's pause menu to
the level selection scene. When they press exit,
return to the title scene. These methods are accessed
from the pause menu. The scene tree is paused when
these transitions happen, when a new scene is loaded, the tree remains paused. We'll need to unpause
the scene tree when the other scenes are loaded using their manager's
ready methods. Since the pause menu now only
returns to the title scene, the title scene should have an exit button to
close the game. I'll put that in the
upper right corner. Connect its pressed signal to
the manager script and have it await fade to
black before calling Get copying the exit button node, we can paste it into
the level select scene and edit its signal to connect it to the level select
manager script. And have it await fade to black before changing to
the title scene. Let's try it out. After
collecting a coin, I can exit the level Returning to the level
selection scene, where the progress has
been automatically saved, closing the game and
running it again. The continued button is enabled because the save
file exists to load. Pressing continue leads us to the level selection
scene, loading any level. We can see that the coin counter was saved and loaded
between sessions, closing the game and
running it again. We can try to overwrite the save file and
be presented with a confirmation before starting a new game with zero coins. Our game now saves
and loads player data anytime the player exits
or completes a level. In the next lesson,
we'll lock access to levels and unlock them when
the player completes a level. I'll see you in the next lesson.
54. 6-5 Unlock: Hello friends. In
the previous lesson we saved and loaded the
player's progression. In this lesson will lock access to levels until
the player completes the previous level to unlock
it in our data resource, we will want to
track information about each level in our game, which are organized into worlds. We can declare a variable
named Progress as an array of worlds containing
an array of levels, a two dimensional
array, a razor empty. By default, we will need
to initialize it to be the correct size for our needs and populate it with
starting values. We will need an array for every world and a value for
every level in each world. If I had two worlds, the array would look like this,
but I only have one. I'll remove the second subarray. We want the first level of
the game to be unlocked. By default, I'll
change the value 0-1 depending on how many
different progress markers you want to keep
track of per level. Remembering their numerical
codes can be problematic. We can define an enumeration to describe the different
markers of progress, starting with the
level being locked, then unlocked, then completed. Then initialize the first
level to be progress unlocked. Adding in a public method
to set a progress marker. We can take the
progress marker as a parameter along with a
world ID and a level ID. Then set the value of progress indexed at world minus
one, level minus one. Since arrays start at zero, but our world and
level numbers start at one using the R equals operator
with the progress marker, giving these a default value. Assuming that most
progress markers will be set for the level the player
is currently playing. But still allowing us to set markers for other
levels if we want to. Since these numbers are
being used to index array, we should validate
them to avoid crashes. For these numbers to be
considered valid array indices, they should be
greater than zero and less than are equal to
the size of the array. Since we are subtracting
one from these numbers. If the array indices are not
valid, we should return. But we can also
output a message to tell us or any other
developer about this error, with a warning stating that the progress marker
index was invalid and display the world of
level ID's that were used. This is a bit operation, meaning that it is not treating this number like an integer, but as a series of
bits, ones and zeros. If we consider each bit to
be one progress marker, then the first
marker of progress, the first bit, locks the level, if it is zero, and unlocks
the level if it is one. The next bit tells
us if the level has been completed,
starting with zero, meaning that it has
not been completed, but then changing to one
to become completed. If the level starts
with a value of 0000, unlocking it becomes 0001, then marking it as complete
will make it 0011. Since it is both
unlocked and completed, we can change individual
bits using bit operators, allowing us to encode a large amount of information
into a single variable. This or operator will
set the value of the bit marked by the
progress marker to one. We can likewise set
the bit to zero using and equals with the
progress marker inverted with the operator adding an additional parameter
to the method definition. We can turn the progress
marker on or off. I'll have the default behavior to set the bit to one or on, since I don't plan
on ever locking anything that has been unlocked
by the player in my game. Since a single variable
contains 32 bits, we can track up to
32 different bits of information per level. This way, adding progress
markers to the enumeration. An enumeration is
just a numbered list. The entry at the top of the list has a default value of zero. The next one, then
234, et cetera. We can change these numerical
assignments if we want to, Giving locked a value of zero, unlocked one and completed two. But I also want to track if the player opens a treasure
chest in my levels. The next progress marker, instead of being
three, will be four. We could have as many as 32 different values in
this enumeration, each time doubling the value
to eight, 16, et cetera, If one in binary is 0012, is 0104, is 100. Each progress marker
will be all zeros, with only one bit
having a value of one, which is the bit
that will be set or unset by the set
progress marker method. We should remove
locked equals zero, since the proper way to
lock a level would be to set unlocked
progress marker to off. We'll also need to check the values of our
progress markers, accepting the progress marker, world and level ID numbers. Same as the set method, but returning a Boolean, We can return the
value of progress indexed by the world in
level ID numbers minus one. Then apply the bit end operator
with the progress marker. We should also validate that the array indices are
valid before using them, just like above, and clarify the warning message
to be more helpful. This will return true if the progress marker is on
or false if it is off, or if the indices were not valid in the level select scene. We can easily control
which level the player has access to by toggling the visibility of
the level buttons, the grid container
will automatically sort the visible contents. In the Level Select
Manager script, we can get an array of the grid containers children
using add on ready declaring a new integer variable as the index into the
array of level buttons. Then iterating through the
worlds in the progress array. Again, through the
levels in each world. We can set the visible
property of each button in the grid container to match whether or not that
level has been unlocked. We'll need to add
one to the world and level to get the
proper ID numbers from their array indices and increment the button
index each time. In the level script, we can export variables to
know which level is unlocked by
completing this level and set their default
values to one. The default behavior will be
to unlock the first level, essentially accomplishing
nothing but not causing any errors. Then in the game manager script, when the map is collected, we can set the progress marker of the current
level as completed. And also set the unlocked
progress marker of the level that is to be unlocked by completing this level. Then transition to
the level select scene in the first level. I'll set its exported values
to unlock the second level. Back in the level script, we can track if the player
has opened the chest, getting a reference to the
key and chest if they exist. Using at on ready
get node or null. Then when the level
is first loaded, If they do exist and the player has opened the
chest for this level, then we can remove the key and tell the chest that
it has already been opened in the chest script. Let's add a public method
to be already opened, clearing the booty array. Then setting is locked
to false and is open to true when the chest
is plundered, Set the progress marker for this level that the
chest was opened. Let's try it out.
Starting a new game, I can exit the first level to
the level selection scene, and only the first
level is unlocked. Returning to the first level, I can finish it and will be returned to the level
selection scene, where the second level
has been unlocked. Returning to the
first level again. This time I'll open the chest and return to the
level selection scene. Entering the first level
one last time to see that the key isn't there and
the chest is already open. We now have the
players progression through the game
being tracked by the, say, file, unlocking access
to new levels as they go. In the next lesson, we'll add background
music to our game. I'll see you in the next lesson.
55. 6-6 Music: Hello friends. In
the previous lesson, we locked access to levels until the previous level had been
completed by the player. In this lesson, we'll
add background music. Let's start in the
title scene and add an audio stream player
node to the scene. We could set an
audio track here, tell it to autoplay
and set the volume. Then do this for
every scene in level. But the scene transitions
would hard cut our music tracks and sound
quite jarring as a result, much like we have the
screen fading to black, transitioning music
should also be faded to provide a better
overall experience. I'm going to leave the
autoplay on for now and volume to the lowest it
will go. Basically muted. Most game developers
who aren't also sound engineers will tend to
think of volume as a float, 0-1 with zero being muted
and one being full volume. But volume is actually
measured in decibels, which do not scale linearly. Doubling decibels does
not double volume. The volume scale
here ranges from negative 80 to 24 decibels. Fortunately, we do not
need to know how this works in order to be able
to use it effectively. Let's attach a new script
to this music node. When the game first starts, the audio is going to autoplay the track with the volume muted. We can start by gently
fading the volume from zero up to
the desired level, declaring a variable to
store the desired volume. In simpler terms that are
easier to understand, a float 0-1 with linear scaling, the audio stream player
nodes volume property is named volume DB for decibels. So we can declare
another new variable, volume L, for linear, to represent the
current volume on a linear scale and give it a default value
of zero or muted. In the ready method, we can ensure that the
volume stays muted by setting the decibel volume
to match the linear volume. Converting it with a
convenient conversion formula provided by Gade
linear to decibels, giving it the linear
volume as the argument. Defining a private method
to fade the volume. We can take the target linear
volume as a parameter. Along with the duration
for the fate effect, with the default
value of 1 second. While the linear volume is not equal to the target
linear volume, we should move the
linear volume towards the target linear volume
by a small amount. For this, we can get
the process delta time and divide by the duration. Then set volume
indecibels to match the linear volume and await the next process
frame to do it again. When all this is done, we can emit the signal to let anyone who is listening for it know that the volume
fate is finished. The way this code is written, it's obvious that the value of duration is meant to
be a positive number. To avoid any possible
errors or infinite loops, we should check if the value of duration is anything less
than or equal to zero. In that case, we
can set the volume, immediately omit the
signal, and return. Since music will likely be included in every
scene of the game, it would be easier to make
this into an auto load script. Moving the script into
the auto loads folder. Opening the project
settings auto loads tab. We can add this to the
list of auto loaded nodes. Then delete the audio stream player node from
our title scene. For any scene manager
to be able to control the background music with
a single line of code, all we need to do is
define two public methods, one to start a track
and one to stop it. Starting with stop track, we'll take a fade
time as a parameter, a float, with a default
value of 1 second. This can just call fade. The target linear
volume is zero, muted, passing the fade time is the argument
for the duration. Then await the
volume Fade finished signal before stopping
the audio stream. Player node to play a track, we can take the
track in question as a parameter along
with fade time. Set the stream being
played to this new track, tell it to play, then fade the volume to
the desired volume for the fade time duration. But what if a track
is already playing? We can add a couple
of extra checks. First to check if music
is already playing, then also check if the
track that is currently playing is the same as the
track that we want to play. If this is the
case, we don't need to do anything at
all and can return. We can also use this opportunity
to ignore null tracks. Otherwise the music node is currently playing
a different track. We should fade out that track before fading in the new one. Calling fade with a
target linear volume of zero and awaiting the volume
fade finished signal, then the remainder
of the method can work the same fading
in the new track, avoiding any hard cuts in each of the scene
manager scripts. We can export a variable to hold the background music
track for that scene. Then in the ready method
before fading to clear, we can tell the music to
play the music track. I populate this variable
with a music track from my imported assets
level select scene. I'll also export a variable for the background music and play the track during
the writing method. I'll use the same background
music as the title scene for the level select scene in
the game manager script. When loading a level, I'll request the music
track from the level. This way each level can set its own background music
in the level script, exporting a music variable and setting a different
track from the other scenes. And I'll add music tracks
to my other levels too. Lastly, I'll also add music
to the boss encounter script, but also declare
a second variable to store the track
that was playing from the level when the
boss encounter starts. I can store the levels
normal background music in the second variable,
then play the boss. Music When the boss
encounter ends, I'll revert back to
playing the normal level. Music Populating
the boss encounter was something more intense
than the normal level. Music When exiting the game, we can also fade out the
background music too. When importing background music, it's important that
it is designed to be looping depending on
where you get your music. Looping may be encoded
into the files themselves and already be able to
loop for these files. I need to open the
import settings and set their loop
mode to forward. Then click Re Import,
so they will loop. Let's try it out. The
title screen fades in with some pirate themed
music press and continue. The music keeps playing
through the scene transition since the music track is
the same for both scenes. Entering the boss level, the
music fades out and fades in with a new track
entering the boss arena. The levels default
background music is replaced with a more
intense music track. When the boss is defeated, the music returns to normal. Returning to the level selection
scene changes the music again and exiting the game fades the music out before
closing the window. Our game now plays
background music unique to every scene and level, even allowing for tracks
to be temporarily changed. In the next lesson, we'll export the game to be
played and tested. I'll see you in the next lesson.
56. 6-7 Export: Hello friends. In
the previous lesson, we added background
music to our game. In this lesson, we'll export the game to be distributed
and play tested. Before we can export Godot
will need to download export Templates from
the editor menu. Select Manage Export Templates. Select a mirror
from the drop down, then click Download and install. These templates allow
your Godot project to be exported to a wide variety
of formats for distribution. Once the process is complete, you can close the export
template manager. In the project settings, we've already given
our project a name. You may also want to
add a description, a version number, and an
icon for best results. Try to use an image that
is 256 by 256 pixels, or at the very least
has square dimensions. We've already set the
first scene to run. If you're working for a
studio or a publisher, you can add an image
here for that too. You may want to
make adjustments to the display settings to better
fit the exported project, such as defaulting to full
screen instead of windowed. If you change any
display settings, make sure you test
the game to make sure your game sold displays
correctly before exporting. In order to change
the game's icon when exporting for Windows, you'll need to
download an RC edit executable from Github. I recommend putting this file
in the Gio Projects folder, since you'll likely
need to use it in most, if not all, of your projects. In the editor menu, open the Editor settings
under export Windows, there's an option for
adding the path to the RC edit executable
setting that Cado will now be able
to edit icons for exported Windows projects if you have a package
signing certificate, this is also the location for providing that
information as well. With all the
settings configured, we can now go to the Project
Menu and click on Export. Click on the Ad
button and select the platform you want
to export your project. Two, I'll choose Web First. We don't need to do much to
adjust the settings here. Exploring the export path. We should create a new
folder named Export, or builds, if you prefer. Then within that folder, create a new folder for each
platform. In this case, Web. The most important thing when
exporting to Web is that the project must be
named index HTML. This is the file name
that web platforms will look for when
embedding your game. If it isn't there,
it won't work. Clicking on Export project, uncheck the export with debug checkbox to remove
things like warning messages. Then click Save. When it's done, we can close the window. Once the project
has been exported, find it in your file browser. Select all the files that
were created by Godot and compress them to a zip
file named index zip. This is the file
you will upload to embed your game into web
hosting platforms like Itch in the embed options
of these platforms, Shared array buffer support may be required for
the game to run. This is one of the
easiest ways to distribute early
builds of your games. Platforms like it also allow
you to sell your games too. But what if your game is
really good and you want to sell it professionally
on platforms like Steam? Back in the project
export window, we can add another platform,
this time, Windows. Here you may want to adjust
some of the settings, like setting how the
game's icon is resized in a pixel art game Scaling using nearest neighbor will
maintain pixelization. You may want to also add
your company's name if you have one name
and description. Copyrighter,
trademarks, et cetera. We'll export this into a separate folder
from the web export, making a new folder for Windows and renaming
the executable to match the name of the game. If you're distributing this
internally for alpha testing, you may want to export a
debug build for beta testing, early access or release builds. You'll want to have debug turned off also under the
debug settings, removing the console wrapper. Then export the
project and save. Once the export
process is complete, you can find the exported
files created by Godot and your File Explorer,
Select the files, compress them to a zip file, and upload the compressed zip to distribution platforms
to be downloaded. The same process can be used
to also build out to Mac, Linux, IOS and
Android platforms. Export templates for
other platforms like Nintendo or Playstation are
not provided by default. You must be approved by
the platform before they will let you use their
SDK to build games. Our game is now exported
and uploaded to distribution sites like It or Steam to be played by anyone. You now have all the
skills required to build a complete pixel
platform and game. You can build more levels, tweak and adjust
things to your liking, and implement more assets
from this asset pack. You could also download
other asset packs or create your own assets to
keep adding new content. Everything in this
project is structured in ways to be edited
in isolation. You can modify their
individual scenes and scripts if you want to. Every implementation is only a basic template
that you can build upon or recreate your own from scratch and
integrate into the game. Altering anything from how
the camera behaves to how the save files are managed
should be easy to change. Thanks to the
structure we've built, Go ahead and build
your game the way you want to make it your own. Then share it with
the class either by uploading the project or
sharing a link to your Ch page. Play the submitted projects of your colleagues and leave constructive feedback
on their work. Thank you for completing
the course and be sure to check out my other courses
for continued learning.