Transcripts
1. Introduction: Welcome to my
introductory course on three D Game
Development and Gada. In this course, we will
cover everything you need to know about using
the Gudo engine to build a third person
character controller from the very basics
of working in three D space up
to and including controlling a character and
camera in a three D scene. We will explore how
to use math, physics, and vectors to achieve
the third person gameplay you expect from
most modern three D games, with the left stick
moving and rotating the character and
the right stick rotating the camera around them. When you're done, you'll
have a good understanding of third person
character control and a fully functional
implementation that is highly customizable for
any three D project, whether it's a platform
or action or PG, or even a cozy farm sim. You can take the project
in any direction you wish. You'll also learn useful skills for working in the
Gada game engine, organizing and designing your projects to
be more scalable. You'll be learning
how to code in GD script with everything
explained in detail. Our scripts will
be written to be highly customizable
and reusable. All of the project files
will also be available on GT Hub if you need to
review the completed project. These videos were recorded
using Gada version 4.2. Assets downloaded from
HO made by Kusberg.
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 dot gD. 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 Dictionaries: 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 Debugging: Hello, friends.
Today we'll go over a few different strategies
that you can use to help you when you
inevitably get stuck. I've already
attached a script to the scene's root
node named debug. You've probably noticed the
autocomplete feature that is available if you specify
the type of your variables. This not only allows
you to see all of the properties and
functions of the class, but after selecting a function, you can also see the types of all the functions parameters so you can provide
matching arguments. A, when you know the types
of all your variables, this makes mismatch
errors clearly apparent, and you'll be warned of the error without
having to run it. Without strict types,
the engine doesn't know what is mismatched until
attempting to execute the line. If you want to browse through a built in class like color, we can easily access the
information by clicking on either of the buttons in the upper right
corner of the panel. Online Docs opens a tab in
your default web browser to the GADO API matching the version of the editor
that you're using. Here you'll find basic
information about 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. Basics: Hello, friends. Our objective for the first section
of this course is to create a three D
character that the player can control in third
person perspective. Our first lesson
will be setting up a basic three D scene
and a new project. Opening Gada for the first
time, there are no projects. Cadeau recommends exploring
official sample projects. Press cancel to ignore
this recommendation. We will be starting a new
project from scratch. Click on the new button
to create a new project. We will need to give
the new project a name. I'll name mine Gada
three D course. And select a folder on your
hard drive to store it. I recommend making a folder for Gada projects in documents. Then click the re
folder button to create a new folder at this location with the name of the project. You can change the renderer
settings for your project. Forward is best
for high fidelity, PC or console games. Or the mobile renderer
for mobile devices. I like to use the
compatibility renderer for most projects
because it is most compatible with web
builds that can be uploaded to sites like dot IO. Lastly, God supports integration with GT Tub version
control by default. Click on create and Edit to create the project and
open it for editing. The Gdo editor window is
divided into several tabs. In the center is a preview of what the current
scene looks like, as well as several tools
we can use to edit things. To the upper left
is the scene tab, which holds a list of everything
in the current scene. Below that is the
file system tab, which holds all of
the imported assets for our project like models, textures, sounds, and music. As well as all of the files, we will create using
the Gada editor, like scenes, scripts,
and resources. Currently, the only file
here is the Gada logo. To the right is
the inspector tab, where if we select
anything in the scene, we can see its properties
and edit them. All of these tabs
can be resized and rearranged as you see fit to best accommodate
your workflow. Our scene is a vast
empty three D space divided by three colored axes. The red x axis represents
right and left. The green y axis
represents up and down. The blue Z axis represents
forward and backward. Let's get started by clicking on the three D scene button
in the scene tab. This has created a root node for our scene in the scene tab, named Node three D, which is also the nodes type. We can rename this node
by double clicking on it or right clicking
and selecting rename. In many games, this
scene will be referred to as the main scene
or the game scene. You can also name it after the style of game play
that will be used, such as the platforming, exploration, combat,
or puzzle scene. I'll name mine game. This root node will form our scene tree as we
add branches to it. Either click on the plus
button or right click on the root node and select Add child node to add a
branch to the scene tree. There are many
different node types to choose from that serve
many different purposes. Node types follow a
hierarchy of inheritance. So all nodes share
the common type node. But then there are node
three D, Canvas item, node two D, control, each with many
different subtypes. If you know what
you're looking for, you can type the name of the
node into the search field. Let's add a mesh
instance three D node. You can see that a mesh
instance three D node is inheriting properties of a geometry instance
three D node, visual instance three D node, node three D, and node. Click the Create button to add the mesh instance three D
node to the scene tree. You can see that the node
is added to the scene tree, but it is indented to imply that it is a
child of the root node. Any node with children
can be collapsed or expanded to hide or
show its child nodes. This is supposed to
be a three D image, but nothing is displayed
in the preview window. That is because
this mesh instance doesn't have a mesh resource
telling it what to draw. With the mesh instance
three D node selected. Look in the inspector tab to view and edit the
nodes properties. The properties are divided into the same types we saw
when creating the node. So we can see which
properties are inherited from different types and which are specific to our mesh instance. We can see that the
mesh field is empty. Clicking on the drop down, there are several options, including some
basic shapes, box, capsule, cylinder, quad,
prism, sphere, and Torus. Let's make this shape a plane. Now we can see a three D
shape drawn in our preview. Clicking on the mesh
resource expands its properties so we can
view and edit them as well. Here we can set the
size of the plane in both the x and
y dimensions by either typing a number into the field or clicking and
dragging on the field label. Units are generally
considered to be meters or approximately three feet
in imperial measurements. But there is no requirement
to use this ratio. In a strategy game, a unit might be a
kilometer or mile, or if you're playing
as a tiny insect, then a unit could be a
centimeter or an inch. Now that we have
something in our scene to give us a point of reference, let's explore the
scene a little more. There's a grid of lighter
gray lines dividing the x z plane into units with every eighth
line being lighter. We can zoom in or out
using the mouse wheel. Zooming out, we can see that this pattern continues
as each eighth, larger subdivision
is also marked, 64 units apart, which
is eight squared. And again, every eight or 512th unit eight
the power of three. Zooming back in, the original grade becomes visible again. Holding down the mouse wheel and moving the mouse
allows us to rotate the world about the y axis and also tilt our
perspective up or down. Holding down the right
mouse button and moving the mouse allows us to
rotate our perspective, looking right, left, up or down. Still holding down the
right mouse button, we can use the WASD
keys to fly through our scene while also using the mouse to orient
our point of view. If it seems like you're
moving slowly or not at all, you're probably zoomed out. This allows us to
quickly and easily view our scenes from
any position or angle. The mesh only defines the
shape of a three D object, not its color lighting
or anything else. Those are all set by material. Expanding the drop
down labeled material, select new standard material
three D. Then click on the newly created
material resource to view and edit its properties. Materials have a
lot of properties to control how they
interact with light. Expand the albedo section. Here we can adjust
the color or also use a two D image file to apply
texture to the material. Try dragging the Gada logo
into the texture field. If there is both a
texture and a color, then each pixels color will be multiplied by the color
value to produce the result. In the upper right corner
is a series of buttons, one of which is a
clapper board with a play icon, run current scene. Clicking on this button
will prompt us to save the current scene if
we haven't done so yet. Otherwise, it will save
the scene automatically. This allows us to run a simulation of our
game starting from this current scene that we are editing. Nothing is displayed. We can close the window
to end the simulation. This is because there is
no camera in the scene. A camera is required to
view three D scenes. A camera is just
another node type. So we can add it
to the scene the same way we added
the mesh instance. We need to use a camera three
D to view three D scenes. The camera is added
at the scene origin, which is the center
of the floor, and is looking in the
negative Z direction. We can see which direction a camera is looking as
well as the width of its field of view based on the magenta gizmo
shaped like a pyramid. And the triangle shows us
which direction is up as well. With the camera three D nodes
selected in the scene tree, clicking on the prev toggle will show us what
the camera sees. Where the camera is sitting right now, it can't
see anything. Click the pre toggle
again to return. We need to move it
away from the floor and rotate it to
look at the floor. We can move the
camera easily along a single axis by clicking and dragging on any of
the three arrows, red for the x axis, green for y, and blue for Z. We can also move the
camera along a plane by clicking and dragging
any of the three squares. This will lock the
associated x y or z value while we move the position along
the other two axes. Move the camera up along the green axis and
forward along the Z axis. Depending on the
perspective of your editor, forward may seem like backwards. But for clarity, forward is used to refer to the
positive Z direction, which is the direction of
the blue arrow points. This can also be done
by locking the red axis and moving the camera along
the Y Z plane like so. Next, we need to rotate the camera to look
down at the floor. We can rotate about
a single axis by clicking and dragging any
of the three circles. Rotating about the green y axis will be like turning your
head to the left or right. Rotating about the
blue z axis will be like tilting your head to either side or even upside down. Looking down is a rotation about the x axis, which is red. Click the preview to see if your camera can see
the floor clearly. With the camera three
D nodes selected, look in the inspector in
the properties inherited from node three D.
Expanding transform, we can see that the position
and rotation values of the camera have been edited. All three D nodes will
inherit this property for controlling their position and
rotation in three D space. Here, we can manually
edit the values, or we can click and
drag on the labels to adjust them with the mouse
like we did with the plane. Scale is a multiplier of
a three D objects size. By default, the X Y and Z
values are all linked together, so editing one will
edit all three. Camras don't have size, so changing the value
doesn't affect the camera. But setting the scale
to a negative value will effectively
mirror its rotation. We can reset the scale
to one by clicking on the reset button and
ignore this property. Checking the preview, the
camera can now see the floor. But if we run the current
scene, the floor is black. This is because there is no
light source in our scene. In the editor, the
floor is illuminated because there is preview
some light and environment, which we can toggle on or
off using these buttons. Turning them off the
floor turns black, which is what we see
when we run the scene. So for the camera to be
able to see the floor, we need to add a light
source to the scene as well. Add a directional light
three D no to the scene. Like the camera, it is added at the scene origin facing the
negative said direction. A directional light
simulates sunlight, producing parallel rays of
light over the entire scene. Is position does not affect its behavior, only its rotation. We can move and position
it anywhere in the scene, preferably somewhere where we can see the white arrow gizmo. This arrow points in the
direction of the light rays. Rotate the light source, so the light is pointing
down towards the ground, illuminating it for the camera. Now if we run the scene, the camera can see the floor thanks to the light
reflecting off of it. Every runnable game needs to have these three
basic components, something to look at, a camera to see it with, and
a light source. If you need to practice with three D positions and rotations, try making an abstract
three dimensional art piece using mesh instances. Note that lights do not
cast shadows by default. To enable shadows, click
on the light source node, expand the shadow section
under light three D, and click on the enable toggle. You can also add additional
lights to the scene, adjust the energy or color to produce a wide
variety of lighting. Feel free to experiment
with different properties of materials and how they
interact with lighting as well. When you're done,
you should have a good understanding of the
relationship between meshes, materials, cameras,
and light sources. How they're used to
create a three D scene, how to move and rotate objects in three dimensional space, and how to navigate your scene. In the next lesson, we'll start receiving input from the player. I'll see you in the
next lesson. Okay.
15. Scripts: Hello, friends. In
the previous lesson, we set up a basic three
D scene with meshes, materials, a camera,
and a light source. In this lesson, we'll accept
input from the player. Click on project in the menu bar and open
the project settings. Switch to the input map tab at the top of the
project settings window. In order to achieve
our objective of controlling a
three D character, we need to accept inputs for the four
movement directions, left, right, forward
and backward. Type, move left into the
add new action field. Then click Add to
add the action. The action name is
a description of what the input is
used for in our game, conventionally
written in lower case separated by underscores. Actions are triggered by events, which we can add by clicking
on the plus button. Events can be keyboard keys, mouse clicks, or movement
or input from a joy pad. We can search for
the desired inputs by expanding their categories, filter the search results, or use the listening field to just press the
button we want. For move left, most
games use the A key or the left stick of a joy
pad tilted to the left. Adding multiple events will better accommodate
different styles of play. We also need to be
able to move right, forward and backward, adding
a new action for each. I'll use the D key or left
stick right for right. WK or left stick up for forward, and S key or left stick
down for backward. The dead zone value is
the minimum amount of pressure or tilt required
to trigger this action. Now the engine will listen for these input events and
trigger these actions. When you're done,
click the close button to close the project settings. Let's create a new
node that will react to the input
actions we created. This node doesn't need to represent any physical
object in our scene, so it can just be a basic
node, and name player. Anything in our
scenes can be given behaviors by attaching
scripts to them. Right click on the player
node, select Attached script. Or with the player
node selected, click on the attached script
button in the scene tab. The default language
for scripts in the Goda engine is GodaScript, which is very easy to learn and well optimized for
game development. Scripts inherit the behaviors of the node type that
they were attached to. In this case, just
a normal node. Templates are provided for different node types to
provide basic functionality. The template check
boxes on by default. Let's take a look at what
the default template for a node script has in it. We are creating a new script, not using one that is
built into the engine. We can give the script a name, which will be recommended
by the engine, matching the name of the node
it is being attached to. It is conventional
to use snake case when naming scripts
like the input actions. Lowercase separated
with underscores. Click the create button
to create the script, and the editor
will automatically switch to script
view to edit it. You can switch back to
three D view anytime by clicking on the
three D button at the top of the editor. Likewise, switch to the script view by clicking on script. The top line specifies
the node type that this script can be attached
to, which is node. The keyword extends,
allows this script to inherit the properties
and behaviors of a node, so we can use them
in the script. Functions are behaviors that the script will run on the
node, it is attached to. Our script template
starts off with two functions,
ready and process. Gray text, starting with
an ctathorp or comments. Text that exists only
for our benefit as the developer to explain the
script in plain language. The comments tell us that
the ready function is automatically called
when the node enters the scene tree
for the first time. This is not when we create
the node in the editor, but when we press the run
current scene button. The simulation will
build the scene tree, and when the player node is added to the scene
tree and is ready, then it will call this behavior. The process function
is called every frame, which with the default
project settings is 60 frames per second. This function has a
parameter named Delta, which is the amount
of time that has elapsed since the previous
frame in seconds. This variable is represented
as a floating point number, a number with decimal points. We can specify the type of
any variable with a colon, followed by the type name. Since Gudo script is loosely
typed, this is optional, but specifying a
type will improve the efficiency of the engine and reduce the frequency of bugs. So it's recommended to
always specify the type of your variables unless you
need to allow multiple types. Okay. We can use the print
command in our scripts to output basic text to the output console here at
the bottom of the window. The print command takes
a string as an argument, which is a sequence
of characters contained within
quotation marks. Let's print ready during the ready function and process during the process function. We can also use
the plus operator to append strings together, allowing us to append the value of Delta to the process string. But Delta is a float, not a string, which
is not allowed. We can cast the
type of Delta from a float to a string
using the command STR, passing Delta as the
argument in brackets. Run the current scene and
stop it after a brief moment. In the output console, we can see that ready
was printed once, followed by process 0.016, et cetera many times, which is the frame rate
of one 60th of a second. Sometimes the number
might be higher because that frame took slightly
longer to process. So we can see that
these behaviors are being called automatically
by the engine. Let's remove these
print statements and the entire ready function. Instead, we will want
to check if the player is giving any input during
the process function. To store this information, we need to declare a
variable at the top of the script using
the keyword VR. Let's call it move direction. Preceding the name
of the variable with an underscore
marks it as private, meaning that only this script needs to be concerned with it. The type of our variable
will be a vector two. A Vector two is a structure for holding two floats
named x and y. We've already been
working with Vector two when resizing
the plane mesh and vector three is when editing the position or rotation
of any three D node. During the process function, we can set the value of move direction to
the current state of the movement input
actions we created earlier using the assignment
operator or equal sign. Move direction
equals input vector. Input is a singleton class, a built in script that we
can reference by its type, since there will always
only be one instance of it. The period accesses
variables or functions, which in this case, are called methods because they
belong to a class. The method we are calling
is named get vector. Get vector accepts four
strings as arguments, which are the names
of the input actions that will be used to
create a vector two, which will then be
stored in the variable. A negative x value, which is left will be subtracted
from a positive x value, to calculate the value
of x in the vector two. Likewise, a negative
y value up will be subtracted from a
positive y value down to calculate
the value of y. This may seem reversed, but when looking at
the input mappings, the up direction is listed as the negative side of the axis
and down is the positive. Also, the camera looks in
the negative set direction. To move forward from the
camera's perspective actually means to move backwards
through three D space. This is consistent with
two dimensional games since the scene origin is at the top of the screen and positive values
move down, not up. For now, we can just print the value of our variable
to see what it is. Let's run the current
scene and test it out. The output console is
constantly printing zero, zero because no input
is being given. If we press any of the WASD keys or tilt the left
stick on our joypad, the value is being
printed will change. Left means the x value
is negative, positive. Down sets the y
value to positive, which is toward the camera and up negative away
from the camera. Closing the window, we can
see a warning message. The value of Delta is not being used by our
process function. We can add an underscore to
the front of Delta to mark it so the engine knows that it can be ignored since
we don't need it. We are now able to read player movement input from
either keyboard or JoyPad. In the next lesson, we'll use the player's input to move
an object through our scene. I'll see you in the next lesson.
16. Controls: Hello, friends. In
the previous lesson, we started receiving
input from the player. In this lesson, we will move a three D object through
our scene using that input. Let's start by removing all the meshes from our
scene except the floor. And adding a new node
to the game scene, a character body three D node. This node comes with some built in functionality
for the physics of moving a character but doesn't actually represent anything
the player can see. So we should add a child
node to the character body, a mesh instance three
D. Which we can populate with a
capsule mesh with the intention of replacing this with an actual
character later. The character body three D is at the scene origin on the floor, as is the mesh instance. If we consider the characters position to be
between their feet, then the capsule should be moved up in relation to
the character body. With the mesh instance
three D nodes selected, which should be childed
to the character body, move the capsule up along the y axis until it
is on the floor, which should be one unit. Selecting the character
body three D node, if we move this node, the mesh instance
will also move. Child nodes follow the position, rotation, and scale
of their parents. Okay. There is a warning in the scene tab stating that the character body needs
a collision shape. This is another child node. We need to add to the
character body three D node, a collision shape three D node. It is not assumed
by the Goto engine that collisions will happen
when meshes collide. Instead, we can add
an extra shape, usually one that is much
more simple to detect collisions in order to make the calculations more efficient. Much like the mesh instance three D node needs
a mesh resource, the collision shape three D node requires a shape resource. We'll use the same
capsule shape and move it up one unit to be in the
same position as the mesh. Okay. This is now a branch of nodes
that all relate to our character
specifically and have nothing to do with the
overall game scene. Right clicking on the
character body three D node, select safe branch as scene, and give this scene a name. You can name this
scene the name of the character or just leave
it as character for now. The child nodes in the
scene tree are now hidden, and the character node
has a clapper board icon. If we click on the
clapper board, the character scene is
opened up for editing. Okay. A new tab is opened in the editor
for our new scene, and we can switch between
the game scene or character scene by
clicking on the tabs. The character is now
a separate scene from the game scene that we
can edit in isolation. Any changes we make
to this scene will alter any instances of
it in other scenes. Just like our player node
has a player script, the character node also needs a character script to
tell it how to behave. Attaching a new script to the
root node of our character, this new script
will inherit from character body
three D. We can use the basic template
for this node type and click Create to
open the New Script. There are a few differences
to this template. The character has
a speed and jump velocity stored as constants. Constants are similar
to variables, but more efficient for
values that never change. A gravitational force is being read from the
project settings. Okay. Instead of the
process function, there is a physics
process function. By default, both the
process and physics process run 60 times per second. But in games with lots of
complex physics bodies, the physics process frame rate
can be reduced to improve efficiency without sacrificing graphical updates
or response time. Anything to do with velocity
or force calculations are done in the physics process function to keep them separate. During the physics
process function, if the character is
not on the floor, then gravity times
Delta is being subtracted from their
vertical velocity. If the player presses
the UI accept action, a built in input action
assigned to the space bar, then the character will jump. This template is also using similar code to read the movement input from
the arrow keys. The input is then converted into a vector three by
adding a zero for the y value and using the vertical y movement
for the zt axis, converting up and down
inputs on the keyboard of controller into forward
or backward movement. This is then multiplied
by the nodes basis, which is a structure of three vector threes pointing
right up and forward. These are the same three
colored arrows that are displayed in the editor when
we select any three D node. However, these arrows
are in global space. If we rotate the character body, the arrows remain the same. The nodes basis vectors are
displayed if we switch to local space by clicking on this togle or pressing
the T shortcut key. As the node rotates, its basis vectors or its local interpretations of right up and forward
will also rotate. These are the directional
vectors that are being used to determine the direction
the character will move when input is given. The direction is
then normalized. Meaning regardless
of any rotation or partial tilt of
the left stick, the length of the directional
vector will become one, resulting in the
character always moving at a constant speed. This is shorthand for if
the direction is not zero. Then move the character along the x and z axes by direction
multiplied by speed. If the direction is zero, then move their velocity toward
zero at a rate of speed. Finally, the move
in slide method is called to apply
their velocity and external forces for
the duration of Delta to figure out where the character
should be this frame. If we try to run this scene, nothing is drawn because this is the character scene which
has no camera or lighting. So switch to the game scene and run this swan
to test the game. The character immediately
falls through the floor. This is because the floor
doesn't have any collision. Let's add a static body
three D node to the scene. A body is a general term for any object which is affected
by physics or collisions. Static means that it is
unchanging, or in this context, that it will not move
either from physics like gravity or from collisions with other bodies
like our character. The floor mesh instance
should be a child of this static body node as it is the visual
component of it. Then we need to add a collision shape three D node to give the floor a shape with which it will collide
with other bodies. This shape will be a box. Expanding the box
shape resource, we can give it a length and
width matching the floor, -5 meters in both length and
width and a height of zero. We can rename the nodes to
reflect this new arrangement, so the static body three
D node is the floor, and the mesh instance three D is a child using
its default name. Now when we run
the current scene, the character's collision
shape will collide with the floor's collision shape,
stopping them from falling. The floor will not react to the collision because
it is static. Crossing the arrow
keys will move the character body three D node and pressing the
space bar will jump. Back in the character script, the comments tell us
that as good practice, we should replace the
built in UI actions with custom gameplay actions,
which we already have. So we can comment
these two lines, pressing Control
or command K. But the direction variable
is being declared here inside the physics
process function. This is called the
scope of the variable. It is declared inside
this function and therefore only exists
inside this function. Like we did with
the player script, we can declare this direction variable at the
top of the script outside the physics process to expand its scope to
the entire script. We'll set it to be private
by proceeding it with an underscore and set its
type to be vector three, a structure containing
three floats for XYZ. And also edit the name of the variable inside the
physics process to match. To move the character,
let's write a new function in our
script named move, which we'll accept a parameter called direction of
type vector three. This direction parameter is different from our
direction variable, since this one doesn't
have an underscore, and its purpose will be to store the value that was passed
into this move function, assigning it with the
assignment operator. The name of this function move does not have an underscore, marking it as
public, which means that it is meant to be
accessible to other scripts. Okay. Our player
script will call this method to
move the character based on the move direction
that was received. We can switch between
scripts by clicking on them in the list of scripts to
the left of the script view. Switching to the player script, we can add a new variable to hold the character of
the players controlling. This is a private variable
underscore character of type character body three D. Adding at export to the front of the
variable declaration, will allow it to be accessed by other parts of
the Gda editor, namely the inspector tab. In the game scene, if we
select the player node, the character
variable is listed in the inspector and we
can assign its value. Since we specify the type of the variable to be
character body three D, only nodes of that type or nodes that inherit from that
type can be assigned. Assign the character
node to be the value of the character variable in
the player nodes script, either by clicking on a
sign and selecting it from the list or clicking and dragging the
node into the field. Instead of printing the
value of move direction, we want to call character do move and pass the move
direction as an argument. But the type of move
direction is a vector two, and the type of the
parameter expected by the character do
move is a vector three. Even if these were
the same type, this would not be the correct direction
to move the player. Since the direction that
we expect the character to move is dependent on
the camera's perspective. Let's add another exported
variable for the camera. Then assign its value. We'll rename move direction
to input direction and create another variable for move
direction of type vector three. How do we convert the two dimensional
input direction into a three dimensional
move direction based on the camera's
perspective? We multiply the camera's
basis by the input direction, similar to the way it was
done in the template code, but we'll do it a
little bit better. First, take the camera's right
direction, it's red arrow, which we can access with
camera dot basis dot X and multiply it by
input direction dot x. If the player presses right, they will move along the
camera's right direction, and if they press left, then input direction
x is negative and right multiplied by negative
number will go left instead. Then add using the
plus equals operator, the cameras forward
direction, it's blue arrow, camera dot basis do multiplied
by input direction dot y, converting the vertical
input into movement along the cameras forward
and backward directions. But the camera is currently rotated since it's looking down. So if the player moves forward, they will be moving slightly
down into the ground, moving slower than they
would left or right, and moving backward, they
will actually be trying to move up into the air to
move closer to the camera. Multiplying the basis
vector by vector 3101, will remove any y component, flattening it onto
the x said plane. We then need to normalize
the flattened vector three, so it will always
have a length of one and not affect the
player's movement speed. This will remove any effect of x or z rotation on
these directions, but the camera's y rotation will still alter the
player controls. Now when we run
the current scene, the players script is checking
for input, every frame. If we give an
input, then it will check the camera's right
and forward directions, flatten them onto the ed plane, normalize them, and tell the character what direction
to move based on the input. Then the character's
physics process will move them in
that direction. Moving the character using the camera's flattened
basis vectors allows the player to
maintain control of the character from
any camera angle, and even while the camera is moving or also being
controlled by the player at the same time since
the movement direction is calculated
dynamically every frame. Separating the player inputs
from the character is also a more flexible structure since the character of the player is controlling can
easily be changed. The script telling the character where to move could easily be controlled with game logic
instead of the player input. We now have the player able to move a capsule
through our scene, and the next lesson we'll import a character model to
replace the capsule. I'll see you in the next lesson.
17. Import: Hello, friends. In
the previous lesson, we used to play
your input to move a capsule through
a three D scene. In this lesson, we'll replace the capsule with a
character model. Let's start by downloading
an asset pack from Soto. For this course, I'm
using the free version of Kits character Pack Adventurers. Clicking on download now, you'll be prompted to pay, but you can click on No Thanks. Just take me to the downloads. We optionally choose to
support the artist by giving any amount of money
you feel is appropriate. There are also paid versions of these asset packs, which
include more content. I recommend trying
them out for free and paying for the extra versions
if you like their work. Then click on download
to download the assets. While waiting for the
download to complete, let's go back to the
asset page and take a look at what is included
in this asset pack. There are four
characters all with a variety of weapons
and accessories, and each character
is animated with a wide variety of animations for us to use in a
consistent format. The models are
efficient for use in mobile games and are compatible
with the Cada engine. These assets are free for personal or commercial use under creative
common zero license. Be sure to check the
licensing for assets you download before using
them in your projects. In some cases, attribution
may be required, or use in commercial projects
may be forbidden entirely. When the download is complete, extract the contents
of the zipped folder somewhere on your hard drive. Then open the folder
to see what's in it. We want to use the
characters and the models are available
in two different formats. For this course, I'll
be using the GLTFFiles. Importing the assets
into Coda is as simple as clicking and dragging them into the editor window. Our resource folder
is starting to look a bit messy, so
let's organize it. Right click on the
root resource folder and create a new folder. Let's name this folder,
imported assets. Then create another folder inside that one
named characters. Group select and drag the imported character
assets into this new folder. Okay. We can also create a new folder in the root resource folder for holding scripts and another for scenes. The scripts have a cog icon
and the extension of GD. While scenes have the board icon and their extension is TCN. You can organize your
resources, however, makes sense to you and best
accommodates your workflow. Before we can work with
the imported assets, we need to edit some
import settings. Select any of the
character GLB files in the file system tab. Then switch the doc
that is currently displaying the scene
tab to the import tab. Here we can edit
how the God editor interprets the files and
generates the imported assets. Most of these settings are
fine for our purposes. First, let's add a root type of character body three
D. By default, the character will
have been imported as a node three D. But we want our character's root node
to be a character body three D to be compatible
with our character script. If we change any
of the settings, they will not be updated
unless we re import the asset. Next, click on the
Advanced button. The advanced import
settings window opens, and we can see a preview of what the imported asset
will look like. You can zoom in and out with
the mouse wheel or click and drag to rotate the
model to get a better view. To the left is the scene tree that will be generated
by this asset, including a skeleton, bones, meshes, and an animation player
with all the animations. Before we can work with
this character model, we need to allow certain
animations to loop. Let's start with
the idle animation. With the idle
animation selected, we can preview what it
will look like with the play button at the
bottom of the window. But it only plays
once and then stops, which is exactly the problem. On the right side, we can change the import settings
for the animation, changing loop mode to linear. Now when we hit play, when
the animation finishes, it will return to the
start and play again. Ping Pong will instead
play the animation backward to return to the start and forward again after that. Let's set it to
linear. Then repeat for any animations
in this list that contains the word idle,
blocking, or running. While not covered
in this course, you may also want to allow
looping for shooting, spinning, blocking,
and spell casting too. When you're done, click on
the re import button to re import the assets with the
updated animation settings. You will need to repeat
this process for the other characters to before
being able to use them. Switch to the character scene. Click and drag the
character asset onto the root node of
the character scene to add it to the scene tree. Let's turn on preview lighting so we can see our
imported character better and delete the
mesh instance node drawing the capsule mesh. The barbarian node
is just one node, not the big tree of nodes we
saw in the import settings, and it has the
clapper board icon, implying that this is an
instance of another scene. Like we created the character
scene from the game scene. We can do that in reverse. Right clicking on the Barbarian
and selecting make Local. This will break the
link this node has with the imported asset and make it a unique version that only
exists in this scene. And now we can see all
of its child nodes. The animations are contained within the animation
player node. Let's collapse the rig node
for now to hide most of it. Next, we want the Barbarian to be the root
node of the scene. We can right click on it again
and select make seen root to organize the
scene tree so that this becomes the new
scene root node. Next, let's click and
drag the collision shape three D node to re parent
it to the Barbarian node. Making this the collision
shape for the Barbarian and delete the original
character body three D node since it is now redundant. However, we still need a character script attached
to the root node. We can also add this by clicking and dragging
it onto the root node. Our scene tree still looks
the same as it was before. Only the mesh
instance three D node is now replaced with
the character rig, and there is an
animation player node. Our character appears
to be holding far too many items
in their hands. Expand the rig node
to view its children. We can hide any of the
items being held in the character's
hands by clicking on the icon beside them. We can also hide the
character's hat and p for any of the characters
other than the rogue. The rogue has an entirely
different model for when the hood is up since the
hair is also removed. Depending on the version of Coda or the assets you're using, there may be a red X icon showing that there is a
problem with this asset. Selecting it and looking
in the import settings, we can change the GLTF
naming version to do 4.1 or earlier and re import
the asset to fix this issue. When you're done, collapse
the rig once more. If we save the
character scene from the scene menu or pressing control S or command S. Then switch to
the game scene. We can see that the character
in our game scene has been updated running this scene, we can move the character
around just as before, but they are awkwardly
moving around in the TPs. We now have the character model imported into our project. In the next lesson, we'll
animate our character walking. I'll see you in the next lesson.
18. Walking: Hello, friends. In
the previous lesson, we imported character
models into Cada. In this lesson, we'll animate the character to walk
around our scene. Let's start by moving
the camera back a bit since the character is
larger than the capsule was. The character movements
are very rigid. The character is either moving at full speed or not at all. This doesn't feel very good and isn't how anything
actually moves. There should be
acceleration up to a maximum speed and
deceleration before stopping. Opening up the script,
we can see that the character speed is
declared as a constant. Let's instead declare some
exported private variables to define how our
character moves. Starting with their
walking speed measured in units per second, which is best
represented as a float. We can give variables
a default value using the assignment operator in the same line when they're
declared. I'll set it to one. We also want acceleration
and deceleration values measured in units
per second squared. Also float types. Consider how quickly
you would switch from standing idle to
walking at full speed. About half of a second
sounds reasonable, which is an acceleration of
double the walking speed. And also how quickly
you would come to a complete stop when
walking at full speed. This is usually faster than acceleration, so
I'll make it four. Selecting the
Barbarians root node, we can see the
exported variables available to be edited
in the inspector tab. Editing these numbers
will overwrite the default values
provided in the script. But pressing the reset button will return them to
their default values. This allows us to easily experiment with different
values and also provides the possibility of different characters having
different movement speeds. To make things easier, we can separate the
characters velocity on the x z plane from the
velocity along the y axis, allowing us to isolate
adjustments in movement velocity from things
like jumping and gravity. Let's declare another private
variable X Z velocity of type vector three. At the start of the
physics process, we can set the value of x velocity to be a
new vector three, copying the character's
velocity on the x and z axes, while setting the
y value to zero. We can then make our
adjustments to the characters X Z velocity here without worrying about the impact
of their y velocity. Then at the end of the physics process, before movement slide, we can set the
characters velocity to be the adjusted
values for X and Z, but the same value
as it was for y. This method we'll also
have additional benefits later. Okay. Next, we will need
to edit this block of code that moves
the character. The statement is dividing
this into two cases. If the player is providing a
movement direction or not. Instead of breaking the velocity down into x and z components, since we removed the y velocity, we can simply edit the x
z velocity directly here. If the player is providing
a movement direction, we should assign x z
velocity to move toward direction multiplied by walking speed at a rate of
acceleration times Delta. This will take whatever their
current velocity is along the X Z plane and move it toward their walking speed
in the direction that the player wants to go at
a rate of acceleration. Since Delta is the
amount of time that has passed since
the last frame, usually one 60th of a second. It will adjust the acceleration
to match the frame rate, resulting in the character accelerating and moving
at the specified speeds, regardless of what the
game's frame rate is set to. Likewise, when no
movement input is given, the character should
decelerate to a velocity of zero at a rate of deceleration
multiplied by Delta. We can delete the original speed constant now since it
is no longer used. Let's switch to the game scene and try moving the
character now. Now the character
accelertes up to a walking speed and
deceleratees more naturally. Back in the character scene, we can switch to three D view so we can
see the character. Select the animation
player node and the animation tab will open
at the bottom of the window. The animation
currently being viewed is one handed male attack chop, and the character model has assumed the first frame
of the animation. We can press the play
button at the top of the animation tab to
preview the animation. Expanding the animation tab and scrolling through the
animation tracks, we can see that each bone is keyframed to produce a
resulting animation. Use the Zoom slider to adjust the amount of
time displayed in the animation tab and you can see each individual keyframe. Using the drop down,
we can select any of the animations that were
imported, including idle. And previewing this animation, since we configured
the import settings, the animation will loop. So the animation player node can play any of
these animations. We just need to control
which animation is playing. You can do this
through a script, but the code will be
very long and have to accommodate too
many possibilities. The best way to control
which animation is playing is using
another node, we can add to our character
scene, an animation tree. The animation tree
needs to be told which animation player
it is controlling. So clicking on the assigned
field beside Anim player, we can select the animation
player node from the list. Next, we need to provide a
resource for this node just like we did with the mesh instances and
the collision shapes. This resource is called
a state machine. Creating the state
machine we'll open the animation tree tab at the
bottom of the editor window. We can pan the viewport of this window by dragging with
the mouse wheel pressed. The state machine will track what state the character
is in and play the correct animation
for that state or even blend multiple
animations together. We simply need to tell the state machine what
those states are and also how and when the character will
transition between states. For now, we will
only have one state, which will blend the idle and walking animations together. If the character starts
walking from idle, they shouldn't
immediately transition from idle to walking, nor should they immediately transition back when they stop. Not to mention that the
player will be able to tilt the left stick slightly to
move slower if they want to. We need the idle and
walking animations to blend together depending on the
character's movement speed. This can be achieved
with a blend space. Right clicking on empty space, add a one dimensional blend
space to the state machine, and name it locomotion. Then click on the
Connect Nodes tool. Click and drag from
start to locomotion. Making this the
default state where the character will start
when we run the game. Switch back to select
and move nodes. Then click on the pencil to edit the locomotion blend space. A one dimensional blend
space has one axis, which is displayed
horizontally ranging from negative one to positive
one named value. If we consider this to
be the characters speed, we can rename it to speed and set its minimum value to zero and maximum
value to one. One, in this case,
should be considered to be 100% of speed, not a speed of one
unit per second. We can add animations to the blend space with
the create points tool. Clicking over at zero. We can add the idle animation. One, the walking animation. Click set the blending
position within the space. Then click anywhere
along the axis to see how the animation
looks at that speed. You can also click and
drag to see how changing the speed over time blends
the animations together. Okay. Now, all we need to do is have
the character script, tell the animation tree
the character speed. Nodes can communicate
with each other easily by assigning
them to variables, and we can do it all in
one line of our script using at on ready at the
start of the declaration. Let's call this variable, underscore animation
of type animation tree and assign it of value. Using the dollar sign,
we can then specify a node path to access any
node in the scene tree, starting from this node that
the script is attached to. If the node we are trying to access is a child of this one, we simply specify
the node's name. Following a sequence of
nodes can be done by separating the node names
with a forward slash. If the name of the
node contains spaces, you can put the node
path in quotation marks. If you're ever unsure
about the node path, you can click on any node in the scene tree and
select Copy Node path. Then paste it as text. Our script now has a reference to the animation tree node. All we have to do is set the blend space position after our X Z velocity calculations. Using the variable,
we can access the set method to set
any property we want. The property we want to set
is defined as a string, representing a path
to the property, using the same format
as the node path. We can use the suggestions
to auto fill this parameter, looking for our locomotion
states bled position. Like with the node path, selecting the animation
tree in the scene tree, we can expand parameters, locomotion, and copy
the property path. Then paste it as text. Then specify the value we
want to set this property to, which will be a value
0-1 representing the percentage of
the character's maximum speed at which
they are moving. Since the D velocity
is a vector, we can use the length
method to know exactly how fast the character is
moving along the D plane. Then divide this by
their walking speed to convert it into a number 0-1. Let's see how this looks
when we run the game scene. Now the character blends between the idle and walking animation relative to their speed
along the x d plane, but they aren't rotating to match the direction
that they're moving. Try experimenting
with different values for the character's
walking speed, acceleration and deceleration
to see how they feel. In the next lesson, we'll
add rotations and jumping. I'll see you in the next lesson. Okay.
19. Rotation: Hello, friends. In
the previous lesson, we animated our
character's locomotion. In this lesson, we'll rotate the character model to look in the direction
they're walking. Starting in the
character script, if the player is providing
movement direction input, then we can simply
tell this node to look at its current position
plus the direction. This will rotate the character body three D node so that it's forward basis vector is pointing directly at
these coordinates. Since we have restricted
direction to the x said plane, this will result in only
ever rotating the character about the y axis to face the direction the
player wants to move. But the rotation will
be immediate and point the character in
the opposite direction. Since Gudo reverses the Z axis, we can correct this
by simply changing the plus direction
to minus direction. Also, if we have any
nodes childhood to the character in the game
scene like the camera, for example, then it will also be moved and rotated,
which isn't what we want. The solution isn't
going to be sufficient. Okay. In the character scene, we can see that all the
visual components of the character are children
of a node three D named rig. Selecting rig in the scene tree, we can edit its transform, clicking and dragging
the y rotation slider handle to rotate the character
model about the y axis. This way, the
character model can be rotated independently of
the character body three D, so it won't affect any other
children in the game scene. In the character script, much like we did with
the animation tree, we can use at on ready to store a reference to the rig node
in a private variable. Then when we add movement
velocity to the character, we can also tell the rig to
look at where it's going. The character now
faces the direction they're moving without
affecting other nodes. But the immediate rotation might not be what
we're looking for. So let's make the character rotate a little bit each frame. We can define a character's
rotation speed as an exported private variable
and give it a default value. Game engines typically calculate rotations in radians,
not degrees. If you're not familiar
with radians, a complete circle of 360
degrees is Pi times 2 radians. There's also another
mathematical constant named T, which is Pi times two. But if we consider the
use case for this value, we are only concerned
with how fast the character will rotate
up to 180 degrees. Otherwise, they would simply rotate in the
opposite direction. So we can define the exported variables value as
a multiple of Pi or how many half rotations the character can
make in 1 second. Which inverted will
tell us how long the character will take
to rotate halfway around. Alternatively, you can define your character's
rotation speed in degrees and use the degree to radiance function to convert
it when the game starts. I'll use the Pi
method and say that my character should be able to turn around in half of a second, making the rotation speed
Pi times two. Okay. In the physics process, if the character is
giving movement input, we first need to figure out what angle we are rotating two. We'll call this
the target angle. We can find our target
angle using trigonometry. If target angle is theta,
which in this case, represents the y rotation of an object facing
in the direction, we can find it using the
inverse tangent function. A t two. This takes
two float arguments, which are direction dot x
and direction dot z values. Now target angle
holds the value, we want to gradually
move our y rotation to. Next, we need the difference
between these two angles, expressed as a number between negative Pi and positive Pi. Telling us how far we need to rotate and in which direction. Subtracting our current
y rotation from target angle will provide the difference between
the two angles. To ensure that the
character will only ever rotate along
the shortest path, we then wrap this result
using the rap f function, making sure that the
result is always between the value of negative
Pi and positive Pi. We now have all
the magic numbers, we need to rotate the character,
but how do we use them. We can determine
the direction of the rotation using a
function called sine, passing the angle
difference as the argument. This returns negative one if the number is negative, zero, if it is zero, and positive one if it is
a positive number. If we multiply this by the
character is rotation speed, and Delta, then we
know how much they can rotate and in which
direction in a single frame. Adding this to their y rotation, we'll rotate the character, but we'll also overshoot
the target angle. So adding a clamp function, we can restrain the value
rotation speed times Delta to only be between zero and the absolute value of
the angle difference. The absolute value is
with the sine removed, so a negative number
will become positive. Then multiplied by the sine to ensure the character rotates
in the correct direction. This will stop the
character from overshooting the rotation and
stop at the target angle. To make this more efficient, since we are only
using the target angle once in the calculation
of the angle difference, we can copy and paste
its calculation into the same line and
have one less variable. And since this process is
running 60 times per second, it should not be
declaring any variables. So we'll move the variable declaration to the
top of the script. Lastly, we need to remove the look out line below since
it is no longer relevant. Now the character rotates gradually to face the
player's input direction. And we can alter how fast
they rotate in the inspector. Next, let's add a
run button that the player can hold
down to move faster. Open the project settings and switch to the input map tab. Add a run action. I'll use the shift key
or left face button. In the player script, we can react to the individual
button press events more efficiently than we did
with the movement inputs using a different function,
underscore input. This function is
only called during frames when there is an
input event happening. Instead of Delta, the parameter for input is an input event, which is a structure containing information about what the
event was that happened. Inside this function,
we can then check this structure to see if
the run button was pressed. If it was, we can tell
the character to run. Okay. Likewise, we can also check if the run
button was released. And if so, tell the
character to walk. In the character
script, we can add a running speed to go along
with the walking speed. Then add another non exported private variable called movement speed with a default
value of walking speed. All we have to do
in the walk and run functions is set the value of movement speed to be the walking speed or
running speed accordingly. Then use the movement speed
in our velocity calculations. However, when determining
the blend position for the character's
locomotion animations, we will use the running speed. Selecting the character's
animation tree, we can then edit the
locomotion blend space. Now, if we consider
a speed value of one to be the character
running at full speed. We can use the select
and move points tool to move our walk animation
somewhere closer to the middle of the axis and add another animation
on the far right for our running animation, either by switching
to the create points tool or right clicking. At any value of speed, the blend space will blend together the nearest
animation to the left and to the right
applying weights to the animation based
on their proximity. Back in the game scene,
before testing a running, we may want to give our
character some more room. So selecting the floor, we can edit the size of
the floor and all of its children quickly by
increasing its scale. Now when we run the game scene, the character can not only
walk around the scene, but also run around by
holding down the run button. Depending on the values used for acceleration
versus deceleration, it may seem like the
character slides around struggling to
redirect their momentum. If we take a look at
the physics process, the statement breaks
this down into whether the player is providing
movement input or not. We can break this down further
to also account for if the player wants to move in the same direction as
the character or not. To check if what direction
the player wants to go is similar to the direction the character is already moving. We can use some vector math. A dot product is a way of multiplying two vectors
together that will result in a number representing how similar or different
those vectors are. The dot product of two vectors, both pointing in the same
direction will be one, and opposite directions,
negative one. If the two vectors
form a right angle, they are perpendicular, their
dot product will be zero. So we can check
if the results of the dot product is
greater than or equal to zero to know if the character should be accelerating in
the desired direction. Since the player
wants to move in more or less a similar direction to where the character
is already moving, Otherwise, the dot
product is negative, which means that the
player is trying to move more or less in
the opposite direction. We should tell the character to decelerate, not accelerate. Now the character's
deceleration can be applied when the player
suddenly changes direction. We now have the
character rotating and running around our scene. In the next lesson, we'll
add jumping animations. I'll see you in the next lesson.
20. Jumping: Hello, friends. In
the previous lesson, we rotated the character to face the direction
they're moving. In this lesson, we'll
add jumping animations. Let's start by enabling shadows
from our scenes sunlight. Select the directional
light three D node, expand the shadow section, and turn on the enable toggle
if it isn't on already. This will give us a little
bit more perspective of how high the
character is jumping. In the character scene, if we select the animation
player node, the animation tab should open at the bottom
of the window. You can also open it manually by clicking on animation here. Looking through the
list of animations, we can see that there are several different
jumping animations. If we select any of these
animations, they will not play. This is because
the animation tree is in full control right now. Select the animation
tree and click the active toggle to disable it while we explore
the jump animations. Switching back to the
animation player, open the jump full short or
long animation and hit play. These animations
aren't very flexible. We want our jumps
to be more dynamic. Opening the jump
start animation, we can see that this
animation is what should happen when the player
presses the jump button. Zoom in so you can see
the animation key frames. Since there are 30
frames per second, they are spaced 0.033
3 seconds apart. We can set the snap to
this value to allow us to scrub to each individual
frame using the timeline. If we switch our preview
to orthogonal left or right view and zoom in
on the character's feet. Note that the character's
feet remain on the ground for a whole 0.3 seconds
of the animation. This is important, since
if we add velocity to the character at the start of the animation, it
will look wrong. This is then followed by
the jump idle animation, which should loop until the
character touches the ground. Which should then lead to
the jump and animation. Breaking these animations
down like this allows the jump to be of any duration and still look good
to the player. Also note that the final
frame of the jump start animation is identical to the first frame of the
jump idle animation. This is also true about
the final frame of the jump idle and the
first frame of jump land, but it's less importance
is the moment when the character lands
could happen at any time. Selecting the
animation tree node, we can turn it back on in the inspector and then take
a look at the state machine. We can add the three jump animations to the state machine. I'd like to put them above
the locomotion blend space, arranged in the same sequence left to right as
they will be used. Add a connection from
jump start to jump idle. Then look in the
inspector panel. These are the properties of the transition between states. We want these two
animations to play in direct sequence since the last and first
frame are identical. When the jump start
animation is complete, just immediately
switch to jump idle. Expanding the switch section, we can set the switch
mode to at end. The arrow icon in the state
machine will change to indicate that this
transition will happen at the end
of the animation. Adding the transition from
jump idle to jump land. We want this
transition to happen immediately when the
character touches the ground. Meaning we won't know
exactly what frame jump idle is in
when this happens. To avoid awkward twitching, we can cross fade the two
animations together for a brief window like
one tenth of a second, just long enough for the
change to not be immediate. As for when the switch happens, we want it to be immediate, but under a specific condition, when the character
touches the ground. Expanding the advanced section, we can write any condition or combination of
conditions we want in the expression text area to control when this
transition will happen. Like in the character script, we can use the character
body three D nodes built in method is on floor to check if the character
is touching the ground. When this becomes true, the transition will
start cross fading from jump Idle to jump land for
one tenth of a second, then playing the
remainder of jump land. However, to use this feature, we need to reselect the
animation tree node and change its advanced expression
base node property. This is the node that the advanced expression
will be called on, and we want to use
the character body three D node for this to check
the value of is on floor. Next, at a transition from
jump land to locomotion. We will want to allow
this animation to play in its entirety before transitioning
back to locomotion. So it's switch mode
should be at end. While the final frame of jump land is the same as
the first frame of idle. If the character lands
while in motion, then we don't want
to immediately switch from jump land
to walking or running. So we can add a cross sad to this transition as well to
blend it more smoothly. Adding a transition from
locomotion to jump, this will be triggered by the
player pressing a button. Expand the advanced
section and change the advanced mode
from auto to enabled. This turns the green arrow
gray in the state machine, since the transition is
no longer automatic. This will allow us
to manually trigger this transition through our
character script later. Since we don't know what the
locomotion blend space is doing at any random moment when the player presses
the jump button, adding a brief cross fade to this animation will help
smooth the transition too. Lastly, the jump idle and jump
land animations could also be useful for if
the player walks off of a ledge
instead of jumping. Adding a transition
directly from locomotion to jump idle
will accommodate this case. We can set the advanced
expression for this transition to be if the character is
not on the floor. And cross fading from locomotion to jump idle to smooth
the transition. But what if the conditions for transitioning from locomotion to jump start or jump idle are
both true at the same time. The character has
left the floor and the player pressed the
jump button this frame. If you want to control
which transition takes priority in this case, you can select each transition and set their priority setting. The tool tip tells us
that lower numbers are given priority when multiple
transitions are possible. I'll give priority
to jump start in this case by setting its
transition priority to zero. We can preview how these
animations, transitions, and crossphads will look by clicking on the play button on any state to force the state
machine into that state. Note that this character
is floating in empty space and is
therefore not on the floor. Our character script
is still using the basic template code to
make the character jump. We should update
this to match how we handle inputs for
walking and running. Open the project settings and switching to the input map tab, we can add an action for jump. I'll use the spacebar and bottom face button
on my controller as events for the jump action. Okay. Closing the project settings and opening the player script, we can add another condition to our statement to check if
the jump button was pressed. And if so, tell the
character to jump. Switching over to the
character script, we can add a public
jump function. Okay. Before we can write
the jump function, we need access to
the state machine. Adding another
variable declaration below the animation tree. Starting with add on ready, we'll get a reference to
the state machine playback. It's type is animation
state machine playback and we can access it from the animation tree
using square brackets. Inside the square brackets, we can specify a path to what we're trying to
access as a string. The thing we want to access
is parameters playback. Okay. Like before, if we select the
animation tree node, we can see the playback
resource listed here. Hovering over its name
gives us the property path, or we can right click and copy its path and
paste it as text. Now that we can access
the state machine when the character
is told to jump. If the character
is on the floor, then we can request that
the state machine travel to the Jump start state. Make sure that the name of
the state matches exactly. Then also copy the
line from below, which applies the
characters jump velocity, and remove these lines from the physics process altogether. If we test this out, the
character will jump, but the animation and
velocity are not in sync. Adding another separate
private function named apply jump velocity, we can set the
character's y velocity to the jump velocity, moving the character
body upward. And make sure the script is
saved before you proceed. Disable the animation
tree for a moment. Then in the jump
start animation, scrub to the frame when the character's feet
leave the ground. Clicking on plus add track. We can add our own tracks
to this animation. The track type we want to
add is a call method track. Next, select the node we
want to call a method on, which is the character
body three D node. Scroll down to the bottom to
find our newly added track, and right click on that track where the scrub
line intersects it. Then select Insert key. We can call any method from our character script or any of the inherited methods from the character body
three D node itself. The method we want to call is to apply the jump velocity
to our character. Remember to reactivate the
animation tree before testing. Now when the player
presses the jump button, the character will start the jump animation
and wait until their feet actually
leave the ground before moving upward. Okay. In some games, you may want the character to jump
more responsibly. In that case, it is important
to use animations where the character's feet
leave the ground at the start of the animation. We now have the characters
jumping and falling animated. In the next lesson
we'll improve how the jumping feels and how it
is controlled by the player. I'll see you in the next lesson.
21. Gravity: Hello, friends. In
the previous lesson, we added jumping animations
to our character. In this lesson, we'll improve the jumping and
falling mechanics. Testing of the jump,
there are a few problems. It's slow and floating. The player has just
as much control over the character in the air
as they do on the ground, and we can't change the
jump height in any way. If we look in the
character script, the jump velocity is
defined as a constant. We can also see
that the force of gravity is being retrieved
from the project settings. If we open the project settings, we can follow this
string to find the gravity settings under
Physics three D deft gravity. The direction of gravity
is also editable here. Let's increase the gravity so the character feels heavier. I'll multiply by two. Note that this change will
affect everything in the entire project that
is subject to gravity. If you only want to affect
the character's gravity, we can instead do that
in the character script. In the game scene, switch
to three D V. In my mind, it doesn't really make sense
to define a jump velocity. Instead, I like to think of how high I want the character
to be able to jump. We can quickly and easily create basic geometry in
our scene using a node type called a CS G or
constructive solid geometry. Let's add a CSG box. Move it to the side and rest
it cleanly on the floor. Add collision to the box by clicking on the collision
toggle in the inspector. We can resize the box
by entering values into the inspector or clicking and dragging the red
dots in the preview. Resize the box
until it represents the maximum height
you would like your character to be
able to jump onto. We can see exactly how tall the box is with its y
value in the inspector. But make sure that the bottom
of the box is on the floor. It's transform position y value should be half of its height. Duplicate this box either by right clicking on it and
selecting duplicate or using the shortcut
Control D or command D. Place the duplicate
next to the other box. Make this box taller than
the previous one and have it represent a height
that the character should not be able to jump onto. In the character
script, like we have defined a bunch of variables to customize how the
character moves, we can do the same thing with
how the character jumps. Let's export a private
variable named jump height as a float and give
it a default value somewhere in between
the heights of the two boxes we just made. This will allow the
character to jump onto the first box,
but not the second. Then add a private variable
not exported to hold the actual jump velocity that is required to
reach that jump height. We now have two completely
separate categories of variables in our script, one for controlling
how the carrier moves and another for controlling
how the carrier jumps. We can add another line
above these to further emphasize their distinction
at port category. Then provide a string
name for the category. I'll call these
locomotion variables and the ones below
jumping variables. This not only provides us more information
here in the script, but also separates
them in the inspector. I'll also move direction into the locomotion category and gravity into the
jumping category. I mark it as private
with an underscore. If you want your
character to experience a different force of gravity from other objects in your game, you can also export
another variable. Let's call it mass and give
it a default value of one. This will act as a multiplier to the gravity being added
to the character. So at 0.5, gravity would
be reduced by half, or with a mass of two,
gravity would double. In the ready function, we can calculate the jump
velocity using physics. The jump velocity required to reach the desired jump height is the square root of jump
height multiplied by gravity, also multiplied
by mass if you're using it and multiplied by two. Okay. Switch the jump velocity being added to the character's y velocity for this new variable, and we can now
delete the constant since it is no
longer being used. We also need to multiply graphty by mass in
the physics process. Let's run the game scene and
try jumping in our boxes. The character can
jump high enough to reach the first box,
but not the second. Try experimenting
with different values for jump height, gravity, or mass, and see how they change the feel of the
characters jumping. What if the player doesn't
want to jump at full height? Most games will have
the character to do a smaller jump
tapping the button, and a higher jump,
holding the button down. The character animation having
a delay before actually jumping can really be beneficial for implementing this feature. Let's start by not
defining a jump height, but both a max jump height
and a min jump height. Which means we will also need a minimum and maximum
jump velocity. And calculate both of these
values in the ready function. Okay. We then need to scale the amount of
velocity being added to the character based
on the amount of time the button
was held down. To measure time, there's a convenient node
type we can add to our character a timer node. Let's name this jumper. And grab a reference to the timer node during
the ready phase, the same way we did
with the other nodes. Since the name of the
node contains spaces, the path is enclosed
in quotation marks. Splitting the jump into
two server functions, let's call the first
one start jump. When the jump button is pressed,
we can start the timer. Then when the jump
button is released, we can set its paused
property to true. Which means that
when we start it, we will also have to set its
paused property to false. So we'll need to edit
the players script to call start jump
when the button is pressed and complete jump
when the button is released. In the inspector,
we can see that the timer is set to count
down from 1 second, which is longer than 0.3
seconds, so that's fine. But we need to check
the one shot toggle, so the timer will
only count down once and not reset itself
every time it completes. When the jump velocity is actually added
to the character, we can pause the timer if it
hasn't been paused already. In case the player is still
holding down the jump button. We can start by adding the
minimum jump velocity. Then also add the
difference between the maximum and
minimum velocity. Multiplied by one minus the
time remaining on the timer, divided by the maximum
amount of time possible, which we determined from the
animation is 0.3 seconds. We never want this to be
above 0.3 seconds so we can apply the min function
to cap it at 0.3. If the player
presses and releases the button within
a single frame, this will be near zero, and the result will be
minimum jump velocity. If the player holds
down the button, the holds 0.3 seconds, this will be zero point 3/0 0.3, which is one, and the result will be the maximum
jump velocity. An amount of time
in between will scale linearly between
the two results. Let's try it out by
running the game scene. Tapping the jump
button, the character jumps up to a height
of 1.5 units, and holding down the button,
they jump up to 2.5 units. Holding down the button for different durations produces
jumps of different heights. Next, let's add more
exported variables to our jumping category. Air control and air brakes. These are multipliers to acceleration and
deceleration respectively, which will reduce the amount of control the player has over the character in mid air without
eliminating it entirely. 0.5 is a good place to start
for their default value. You will need to test to determine how you want the character to
feel in your game. In the physics process,
the first block of code rotates the character rig to
face the desired direction. In most games, this
will happen regardless of whether the character is
on the ground or in the air. Next is adding the gravity, which only applies when the character is
not on the floor. This block is moving
the character, but it is doing it
in a way that only makes sense when the
character is on the floor. But applying it every frame, regardless of whether they are on the floor or in mid air. Then the locomotion animation blend position is being set, which again, only applies when the character
is on the floor. Applying D velocity and the move and slide method
are universally applicable. So we can separate the parts of the physics process
into general physics, ground physics, and air physics, writing a new function for
ground and air physics, both private and accepting
a flow parameter for Delta. We can then cut and
paste the gravity into the air physics function and the movement calculations into the ground
physics function. Then in the physics process, if the character is on the
floor, call ground physics. Otherwise, call air physics. Passing Delta as an
argument to both. Our physics process is now much smaller and
easier to understand, while the ground
and air physics are separated from each other and
easier to manage as well. So how do we apply air
control and air brakes? Copy and paste the x
velocity calculations from the ground physics
into the air physics. Then multiply anytime
the player is trying to control the character in
the air by air control. And anytime they
are not trying to control the character in
the air by air brakes. This is not the same
as acceleration and deceleration in this case, since when trying to
turn around in midair, the player is still trying
to control the character. Applying the jump velocity 0.3 seconds after the
jump animation starts, we'll produce a large amount of coyote time to the character. If you don't want this, you can require that the
character still be on the floor before adding the jump velocity
with an if statement. Also, the movement
speed initialization should be using add on ready. Currently, this is initializing movement speed to the default
value of walking speed. But if we were to overwrite the default value
of walking speed, it would not be assigned
to movement speed. Use geometry blocks to build a small obstacle course
for your character. How difficult is it to control
the characters jumping? Experiment with different values until you get the feel
you want for your game. We now have the
characters jumping and following mechanics,
feeling more realistic. And the next lesson, we'll
add camera controls. I'll see you in the next lesson.
22. Camera: Okay. Hello, friends.
In the previous lesson, we adjusted how forces affect
our character in midair. In this lesson, we'll add camera controls to our game scene. Like we've done in
previous lessons, it would be easy to simply child the camera to the player. The camera will follow
the player around, but then rotating the camera around the player
would be complicated. But if we just add another
layer of separation, a no child to the player with the camera
then a grandchild, this becomes much more simple. The camera tilts and
rotations that we expect from third person
controlled games can be achieved by only altering the rotation values of
this intermediary node, either about the x or y axes. Rotating about the y axis will rotate the camera
around the character, like we expect from tilting
the right stick horizontally. Rotating about the x axis will tilt the camera up or down. What we expect from tilting
the right stick vertically? Because the camera is
a child of this node, it will match its rotation and stay focused on the
character as well. We can adjust the
camera's focal point, moving it from the
character's feet up to their head by moving the
node up along the y axis. From here, we can also adjust the camera's horizontal
offset property to further offset the camera along the x axis to produce a
shoulder camera effect. We can even control how far away the camera is
from the character and control that dynamically
by changing the node type. Right click on the node and select change Type and search for spring
arm three D node. We can also edit the name of the node to reflect
its new type. With the camera as a
child of the spring arm, we need to reset its transform, so it has the same position and rotation of the spring arm. In the inspector, we can see the length of the spring
is set to 1 meter. The camera will always try
to reposition itself to be 1 meter away from this
node along its Z axis. Let's increase that distance to something higher
like 5 meters. Okay. This spring arm
node also has collision. If there's a collision
object between the origin of the spring
arm and the camera, then the margin is how far from the collision the
camera will be positioned. The camera will be positioned as close to the spring
arm's length as it can get before a collision happens, compressing the spring. If the obstruction is removed, the spring will decompress and push the the full distance away. This is done with a ray cast, checking a single point along
the spring arms set axis. But if we also specify a
shape for the spring arm, we can give the collision volume replacing the raycast
with a shape cast. Requiring there to
be more empty space before allowing the camera to move back to the full distance. If we make the shape a box, we can define its dimensions, and now the box will
be used to detect how far along the spring
arm the camera can go. To control the camera,
we need to add more input mappings to
the project settings. Open the project settings and switch to the input map tab. Add actions for left, right, up and down. I'll use the arrow
keys and stick tilt for the events that will trigger each of these actions. In the player script, we already have a reference
to the camera, but it isn't the camera we are
actually going to control. We need to get a reference
to the spring arm node. And set it in the inspector. In the process function, we can call a function we
haven't written yet. In a script we haven't
even created yet until the spring arm to
look in a direction. The direction to look we can get from input like we did here, input vector with left
as the negative x, right as the positive x. Look up as the negative y, and down as the positive y. Let's create the
spring arm script. It will inherit from
Spring arm three D. There is no template for
spring arm three D scripts, and the default no
template isn't relevant. Let's uncheck the
template toggle, allowing us to start
from a blank script. I'll just name this spring arm and save it in the
scripts folder. We already know that we need to write a public function n, which will accept a
direction parameter of type vector two from
the player script. Okay. We only need to then
rotate this spring arm about the x axis by direction and the y
axis by direction x. But like everything that happens during a process function, it's happening 60
frames per second, and we want it to work
independently of the frame rate, which means multiplying
it by Delta. But we don't have
access to Delta here, since this function doesn't
have it as a parameter. We can add it to the list of parameters and pass it
from the player script. Okay. Or we can simply call a built in method, get processed Delta
T to retrieve it. Now the spring arm holding the camera will rotate
with the right stick tilt. These rotations are happening at a speed of 1 radian/second. We can edit the speed of the rotations by exporting
a private variable. Let's call it rotation speed. Then multiply this by
direction and Delta. If you want these rotations
to have different speeds, you can export separate
variables for them. Okay. While the y rotation is typically allowed to
loop endlessly around, the x rotation
usually has limits. The controls will break if
we allow the camera to tilt all the way to looking
directly up or directly down. Since the cameras
axis flattened onto the x sad plane will
become zero or inversed. So we need to clamp the
spring arms rotation to be between a minimum
and maximum value. If the camera being flat with the horizon is a
rotation of 0 radians, looking straight up or
straight down is a rotation of Pi over 2 radians in either the positive
or negative direction. We only need to make sure
that the absolute value is less than Pi over two. Clapping the x rotation between
negative and positive Pi over three is a quick
and easy solution that still offers a wide range. We can also export these values and allow these
limits to be tweaked. Okay. You could go further and use
two fifths of Pi, or even use the
degrees to radiance function to convert 89
degrees to radians. So long as the camera can't go directly above or below
the character, Okay. In the player script,
we need to make some adjustments to our
character controls. Since the camera is now
child of the spring arm, the camera is no
longer rotating, at least not within local space. The spring arm is rotating and the camera remains
stationary relative to it. We can get the cameras
global basis vectors, which instead of being
relative to the parent node, will be relative to
the entire scene. Alternatively, we can use the basis vectors of
the spring arm itself. Okay. And in this case, we don't even need a reference
to the camera at all. We now have the camera easily
controlled by the player. In the next lesson,
we'll import and implement environmental
models into our game. I'll see you in the next lesson.
23. Dungeon: Hello, friends. In
the previous lesson, we added camera
controls to our game. In this lesson,
we'll build a room for our character to
run and jump around it. I've gone ahead and downloaded K Hits Dungeon remastered
Asset pack from HDO. Opening the assets folder. If we look in the G LTF folder. There are a large number
of models that we can easily import into
ado for our project. In Go, let's first make
a new folder inside the imported assets
folder named Dungeon. With this new folder selected, importing the assets will
place them in this folder. Just like with the characters, we will need to make
some adjustments to the import settings
of these assets. Let's start with
floor dirt Large. Double clicking on the asset
or selecting advanced from the import settings tab to open the advanced
import settings window. If we select the mesh
instance three D node, we can allow God to generate
a collision shape for this asset automatically by
checking the physics toggle. A wire mesh of the
collision shape is visible in the preview, and a new physics section is
added with several options. Since the floor is part
of the environment, we expect it to have collisions but not move as a result of those collisions or be affected by external forces like gravity. It's body type is static. There are a few different
options to choose from for generating
this collision shape, and we can see that
the current one was produced using
decomposed convex. This is fine for most
objects in our game, but if we tilt the preview, we can see that the
collision shape is noticeably far above the
floor in certain parts. This would result
in the character's feet not touching the floor, which wouldn't look very good. Selecting simple convex
from the drop down. This collision has
fewer polygons than before and
the same problem. The character's feet would not touch the floor in some places. Selecting tri mesh, the collision shape will match
the mesh instance exactly. Since these assets are low poly, this isn't really as much of a problem in terms
of performance as it would be if this mesh were made of thousands
of triangles. The character's collision shape will collide with
these rocks, however. Depending on how you
want your game to work, that might be a good or a bad
thing. Just be aware of it. If you want to ignore
the rocks entirely and just have a flat
box for collision, then select box for
the collision shape. The box collision shape
will not be automatically configured to match the
size or shape of the mesh. But we can edit
its dimensions and positioning here at the
bottom of the panel. I'll select primes
for this floor to demonstrate how the character's collision shape interacts
with the rocks. Once your floor tile has
the collision shape, you prefer, click import
to re import the asset. Next, let's take a look at an object we might want
to be more dynamic, like the Boxarg asset. Like we did with the floor tile, we can select the met instance three D node and add physics
by clicking on the toggle. Since we want this to not
only have collisions, but also move around
and be affected by external forces like gravity, its body type should be dynamic. Then select a collision
shape for it. I'll switch this
to simple convex. Some objects, you
might not want to bother with collisions
or physics at all, like banners or other
such decorative items. In those cases,
there's no need to bother editing the
import settings. Repeat this process for any assets you want to
have physics apply to. There are a lot of assets
in this asset pack. There's no need to do
them all right now. You can always edit
their import settings and add them to your
game later if you want. We want to build a room
with these assets, which we can do by
clicking and dragging any asset into the scene
tree to add to the scene. But manually positioning each individual
asset is tedious. Not to mention that we may want our game to have
different rooms. Like we have the
character defined as its own separate scene, we want our games levels to also be their own
separate scenes. That way, the game
scene can load whichever level the player
is currently playing in. Let's delete the
floor and CSG boxes. We can instead replace them with a new node three D to
represent a level of our game. Let's call it dungeon. Since the game scene is
meant to play any level and different
levels are probably going to have
different lighting, let's make the sun light a
child of the level node. Right clicking on
the Dungeon node, we can save it as its own scene. Named Dungeon saved
in the scenes folder. So all visual components of the game scene are actually contained within other scenes. The only things
in our game scene should be the player
input handler, a character, spring arm, and camera for the player
to control and the level. Then click on the clapper board to open the dungeon scene. Here we can edit our
dungeon separate from other stuff in the game scene like the camera or character. In the dungeon scene, we
can add a new node type, a grid map node,
which will allow us to quickly build
out the level using the assets we imported. But the Grid map requires a resource called
a mesh library, which we have to generate first
from the imported models. From the main menu,
select scene new scene to create a new blank scene and select three D scene
in the scene tab. This scene won't actually
represent anything in the game, but will act as a collection of three D models to
use in the grid map. Coking and dragging any
three D models you want in your grid map onto
the scenes root node, we add them to the collection. The models will all be
at the scene origin occupying the same
space, which is fine. We can filter our
resources to make the selection process
easier using this field. Filtering for GLTF will
show only the GLTF files. Group selecting the assets, they could be added to the
mesh library much faster. Once all of the models you
want to use are in the scene, save the scene not in
the scenes folder, but in the imported
assets Dungeon folder. And name it Dungeon
Mesh Library, with the extension T SCN. Next s scene from the main menu, Export as Mesh library. Let's save this in
the same folder, call it Dungeon Mesh Library. This time with the extension Mb. Back in the Dungeon scene, selecting the Grid Map node, we can assign our new mesh
library in the inspector. We can drag the resource from
the file system tap into the field or click on the
dropdown and select Quick load. This will present a
list of resources within the project of
a compatible type. Selecting our newly
created mesh library, we can see that the
meshes have been populated into the
selection grid. Selecting the floor tile asset. We can place a four tile anywhere in the grid
map by clicking. Positioning the tiles,
they will snap to grid coordinates
along the X D plane. Expanding the section
of the inspector tab, we can edit the cell sizes that the floor tiles
are snapping to. Since we know these
tiles are 4 meters wide, let's set the XD
cell size to four. Now, we can easily place a three by three grid of
floor tiles by clicking. And delete them by
right clicking. All of these four
tiles are on for zero. This is the cell
coordinate on the y axis. If we increase the floor, then the tiles will be
placed 2 meters above and likewise at floor negative
one, 2 meters below. And we can edit the
floor height difference with the cell size y value. If we want to add meshes
to the mesh library later, we can return to the
mesh library scene. This last wall asset
needs to be re imported with an earlier
G LTF naming version. We can add more meshes to
this scene. Then report it. Make sure the merge with
existing checkbox is checked to update the
existing mesh library to include the new meshes. Back in the dungeon scene, selecting the grid
map, the new meshes are added to the selection grid. Not all of the
meshes are 4 meters. Many are 2 meters or
even 1 meter or smaller. Changing the grid
dimensions to 1 meter, I can erase the current tiles since they are now overlapping. Instead, select
coordinates that are a few cells apart to
redraw the four tiles. But now a smaller mesh can be placed at coordinates
every 1 meter instead of every 4 meters. Okay. While deciding on the location to place the mesh
on the grid map, the WASD keys can also be
used to rotate the mesh. With a rotating
about the x axis, S y axis, and D Z axis. The Wk will reset back
to the default rotation. Create a basic dngon scen
from the assets and save it. If there are any
objects you want to place not on exact
grid coordinates, they can still be added
to the scene separate from the grid map and
positioned manually. Then return to the game scene. In the game scene, the dungeon floor is
above the character. So let's move the character
up above the floor. When we run the game scene, the character can collide
with the floor, walls, and any other objects
in the scene, which had physics generated
by the import settings. With these tools, you can build a wide variety of levels for your game
quickly and easily, adding, removing or
changing things as needed. We now have a fully functional third person
character controller, allowing the player to
control a fully animated character walking and jumping around in a three D world. Okay.
24. What's Next?: Welcome to my course
on the essentials of game development and Gado. This course is a continuation of introduction to three D game
development and Gado but can be followed and applied
to any project that contains a character the player can control to move through
multiple levels. You're welcome to join
our discord server to work on this course
alongside your peers. In this course, we will
cover essential elements that are common to almost
any game of any genre. Building a title scene, menus, smoothly transitioning
between scenes, moving a character between different levels of your
game, background music, settings, and data
persistence between scenes, levels, and play sessions. When you're done,
you'll have a good basic structure of
a game that you can further develop into something of your own
design of any genre. You'll also learn useful skills for working with the
Gada game engine, organizing and designing your projects to
be more scalable. You will be learning
how to code with GD script with everything
explained in detail. We will apply object
oriented design principles in our scripts, inheritance, encapsulation and abstraction,
to keep them organized, customizable, and reusable
for a project of any size. All of the project files will also be available on GitHub, if you need to review
the project as it was after completing
each lesson. These videos were recorded
using Gadot version 4.2 0.2. The project starts
with assets from Ka Kits Character and Dungeon remastered Packs
made by Kay Lauberg. In this course, I'll
also be adding assets from Basic Guy Bundle
made by Penzilla, and music in the
Barns Music pack made by Eric the Funny Baron. All are available to download
for free on H dot IO.