Transcripts
1. Intro: Welcome to my course on Dialogue and event Systems in Gado. This course is a continuation of game development essentials
in Gado but can be followed and applied to any project that involves a character that
the player can control to interact with NPCs or objects by pressing a button and
displaying dialogue. You're welcome to join
our discord server to work on this course
alongside your peers. In this course, we will
cover the creation of dialogue boxes, events,
and interactions. Then tie them together
with progress flags, camera controls, and
character animations. When you're done, you'll have a comprehensive dialogue
and events system for your game that can
be used to produce high quality cut scenes and complex branching
narratives that react to players choices and allow them to progress
through the game. 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, alongside other software
engineering design patterns like Singleton and
Composite pattern, to keep them organized, customizable, and reusable
for a project of any size. All of the project files are
also 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 K Kits Character and
Dungeon remastered packs made by K L auberg and basic
Guy bundle made by Penzla. All are available to
download for free on dot IO.
2. Dialog Box: Hello, friends. Before
we get started, you should have a game seen
in your project that contains a character that the player can control and a level they
can walk around in. You should have other
characters to act as NPCs or objects for the
character to interact with. Throughout this course, we
will add the ability for the player to interact with the environment by
pressing a button, either to initiate
a dialogue with another character or
interact with an object. These interactions will include the player progressing
through the game, making decisions, and creating basic cut scenes with custom camera behaviors and animations. You can use your own project to follow along with this
course or download a starter project
from my Github titled Godo Dialog Course Main Branch. Your project can be
two D or three D and almost any game
genre that involves the player interacting with
the environment or NBCs. Let's start by adding a
dialog box to the game scene. Depending on the
assets you're using, you may want to make
the root node of the dialog box a panel
or a panel container. But these assets are so large that I want
to scale them down, which will require
the panel node to not have any children, since the scale
would be inherited, and I don't want that. You may want the
dialog box to be visible over the FAD
for artistic reasons. It really depends on
how you want to use it. I'll have mine drawn first behind everything
else on the UI. With my dialogues root node, just a simple control
node that doesn't actually represent
anything drawn on screen. I'll anchor the dialog to
the bottom of the screen, covering the entire
width and set the top anchor to two thirds down from the top of the screen. The dialog box will cover the
bottom third of the screen, which with my window size
is around 240 pixels. Then I'll add a child node, a panel to draw the
actual box on screen, and set its anchors
to full wreck, so it matches its parent. Giving the dialogue
group node a new theme. We can add a panel to the theme and give it a new style box
texture to draw it. I'll use box orange rounded
as the texture from my dialog box with texture margins of 256
pixels on all sides. This image is huge
at 1522 pixels. I'm going to scale the
node down to half size. Then use grid snapping and set its dimensions to match
the dialog parent node. Most dialog boxes will
contain at least the name of the person who is speaking and a line of dialogue
arranged vertically. Let's add a vertical box
container and inside that, also add two labels. One for the speaker's name, and another for a
line of dialogue. But for the line of dialogue, you might want to use
a rich text label instead of a normal label. Rich text labels
allow the use of BB code to do all sorts of formatting tricks
to your dialogue text, such as bold, italics, or even colors and animations. You can express the
tone of the speaker, associate related themes
with unique colors, or highlight important keywords to help the player
remember them. I'll resize the
vertical box to fit inside the boundaries
of the dialogue box. I tend to use the American
spelling of dialogue when referring to a UI
interface displaying text. But I'll use the traditional
spelling of dialogue when referring to a conversation between characters in a scene. Let's fill these in with
some placeholder text so we can see how they look. Somebody is speaking, and
they're saying some oram Ipsum. Turning on fit
content will force the rich text label
node to expand to fit its contents like a
normal label node would. Let's set the default font
and font size for the theme, using the more legible
font as the default with a reasonable size that's
easy to read. I'll use 32. Adding rich text
label to the theme, we can set the default color for the dialogue line and not the speaker's name since they are different node types. I'll use black to stand out better against
the orange box. Then we can add the
label node to the theme and use a different font to
display the speaker's name. I'll also give the name labels a different
color and a shadow, so it sticks out even more. I'll use a fancier font for
the speaker's name label. Before writing any
dialogue for your game, it's a good idea to get
a rough estimate of how much text comfortably
fits on screen in your game. Make sure your dialogue box
is capable of displaying a reasonable amount of text while still being
easily legible. Using fake placeholder
texts like this, we can come up with a
worst case scenario of how much text we can display and use that as a hard limit when writing
dialogue for the game. There should also
be some indication to the player that the
dialogue has finished, and the game is waiting for
their input to continue. Let's add a button
to the dialog box. Since I want the
buttons texture to match the scaling
of the box texture, I'll have it as a child of the box and anchor it
to the bottom right. This is a unique button, so instead of setting its
style box in the theme, I'll use the theme
override section to add a new texture style
box that will only apply to this
specific button. I'll use the arrow icon. Use the pixel dimensions of the image to define
the button size, and position it
where it looks good. I'll create more style boxes for the other button states
using the same arrow icon. Then apply color modulation
for each other state. Faded gray for disabled, green and yellow
colors for the others. The theming for buttons is still available if
we want to use it later to add themed buttons to the dialog box without
affecting this button. Make sure you check
how your dialog box appears in the
context of the game and make any
necessary adjustments you need until you're
happy with how it looks. Once you're satisfied
with your dialog box, we can hide it by
default by clicking on the icon beside it
in the scene tree. Next, let's add a script to it and save it in the
UI scripts folder. Let's grab a reference to both the speaker label and
the dialog rich text label, assigning them to
private variables. Our script needs to be
able to display any line of dialogue spoken by
any speaker in our game, which means it will
need a public function accepting two parameters, who is speaking
and what they are saying. Both as strings. Both can be set by accessing
the text property, assigning it to the parameter
passed into the function. In older versions of Godot, the rich text label will have separate properties for
text and BB code text. Make sure you're using
the BB code text property if you want your
BB code to work. We can make this more functional by having the
speaker be optional, setting the visible property
of the speaker label to be dependent on whether or not the speaker's name
is an empty string. If we swap the order
of the parameters, we can also make the
parameter optional by assigning a default value
of an empty string. Optional parameters
must always be listed after mandatory ones
when defining a function. We should also set the visible property of the dialogue box. Let's do that in a separate
public function called open. And also a closed function
to set it to false. Grabbing a reference to the
continue button as well. After the text has
been displayed, we should tell the
continued button to grab focus so it can be accessed
with a controller. Let's connect the button
pressed signal to the script, and use it to close the
dialogue box for now. In the game manager script, we can grab a reference
to the dialogue box. When the game first starts, we can tell the
dialogue box to display a message. Hello World. Since the speaker's
name is optional, let's see how it
works without it. This time, let's add a
speaker name, ghost. The ghost will say, Boo, I'm a scary ghost, but it will have some
BB code applied to it, so the player will
get a better sense of how the line is
meant to be read. If your labels leave the confines of the area,
they will be cut off. But we can change
that behavior in the control properties
unchecking clip contents. There are a lot of
different BB code tags that provide a wide
variety of effects, and you can even
create your own using a custom script that inherits
from rich text effect. For a full list of BB code tags, refer to the Google Docs. We can now display
a dialogue box to give the player information. In the next lesson, we'll add a typing animation and display
sequences of dialogue. I'll see you in the next lesson.
3. Monologue: Hello, friends. In
the previous lesson, we added a dialogue
box to the game scene. In this lesson, we'll
animate the typing, so the text appears
gradually and allow the box to display
sequences of dialogue. For demonstration purposes, I've referred it back to using Guram Ipsum for this lesson and I'll make the
dialogue box visible. With the dialogue rich
text label node selected, expand the displayed
text section. We can see that the number
of visible characters is an easily editable property with a default value
of negative one. Negative one visible
characters is obviously impossible and is being
used to mean unlimited. But if we adjust this number by clicking and dragging
it slowly to the right, we can see the text disappear
and fill in gradually as the number of visible characters increases from zero to
the length of the string. In our dialogue script, when we set the text
being displayed, we can also set the visible
characters to zero. We then want to increase the
number of visible characters gradually over time until all of the characters
are visible. Since we don't know how
many characters there are, we can use a wile loop to keep repeating the same code as
long as the condition is met. The condition, in this case will be as long as the number of visible characters is less than the total number
of characters. Checking the documentation
for the rich text label node. We can see that
there is a function called get total
character count, which conveniently returns
the total number of characters excluding
BB code tags. While the number of
visible characters is less than the total
character count, we can add one to the
visible characters. But this loop and the
entire function are going to run in the
span of a single frame. The entire text will still
just be displayed instantly. We need to tell the loop to pause for a brief
time each iteration. We can tell the loop to await a signal being emitted
by the scene tree, a signal that is emitted
once every process frame, 60 times per second with the
default project settings. This loop will now
display the characters at a rate of 60 characters per second and will automatically adjust with the frame rate
setting of the project. But what if we don't want to
use the projects frame rate? L et's instead add a
typing speed variable to the settings resource and give it a default value of
90 characters per second. In the settings menu scene, we can add a label and a slider to allow the player to
change the typing speed. And give it a reasonable range. I'll set it to be
30-120 characters per second with a
step value of ten. Connect its value changed
signal to the script. And change the
settings resource to match and initialize the
value in the ready function. See the game development
essentials course in this series for more
information on game settings. Back in the dialog box script, we'll need a variable to hold how much time has
passed since the typing started and set it to zero
before the typing loop starts. Then in each loop iteration, we can add the
amount of time that has passed by accessing
the function, get process Delta T. Since we know how many
seconds have passed since the typing started and
how many characters we want typed per second, we only need to multiply
these two numbers together to get the number
of characters visible. Let's see how it looks. We can even change the typing
speed in the game settings. The debug log tells us that
we are narrowing precision, converting a float
to an integer. We could either make
the typing speed into a float or ignore this warning since we
know that the slider was configured to only
use integers anyway. So I'll add a warning ignore
tag before this line. Godo knows to ignore the
narrowing precision warning. This also happens in the settings menu script
for the same reason. Now the typing speed
is a bit faster, but some players may still want to skip the
typing animation. So let's change the behavior
of the continued button. If the player presses the continued button
and the number of visible characters is less than the total character count, we can instantly
set the number of visible characters equal to
the total character count. This will display the
entire text and force the wile loop to break since its condition
is no longer met. If the number of
visible characters is not less than the
total character count, that means that the typing
has already finished. In that case, we should
close the dialog box. For this to work
with a controller, the continue button will need to grab focus before
the typing starts. When the typing
animation starts and the player presses
the continue button, either it will skip
the typing animation or it will close the
dialogue box, not both. Skipping the typing
animation will still give the player the opportunity
to read it before moving on. But what if we want
a character to say more than one line of dialogue and keep the dialogue box open? For that, we can add
another public function for displaying dialogue. This time, accepting an array of strings as the
first argument. Let's start by just calling our other function to
display the first line, using Index zero on the array, passing it and the
speaker's name along to have it typed
out into the dialog box. In order to then continue onto the second line when the
continued button is pressed, we will need the script
to remember what the lines are and which line
is currently displayed. Since the scope of this array is only within the context
of this function call, it cannot be accessed
from another function. So declaring a new lines
variable, an array of strings, we can assign its value at
the top of the new function, and another variable
for remembering which line is currently
displayed as an integer. We can set it to zero at the top of the
function call as well, and use it to index the array. Then when the continued
button is pressed, we can add one to it, then check if it is still less than the size of the array. If it is, then there
is more dialogue, so we can call the
first function again. Changing this to be
the private variable, which we declared above
and used to store an array of strings passed into the public
multi line function. If you want the speaker's name to continue to be displayed, you'll need to store that
in a variable as well. And if not, then the
dialogue is finished, and we can hide
the dialogue box. But now the continued
button will not work as expected if we only display
a single line of dialogue. We will need to separate the public functionality of displaying one line of dialogue from the details of how it is accomplished by creating a
private method that does the. The public functions, either accepting one line
or multiple lines, can now both call
the private function to achieve their
goals internally. The first function can
store the single line into the array as its only contents and set the current
line to zero, and now it behaves exactly
as the multi line function. But whether it is
you or someone else writing the dialogue
scripts for your game, there now exists
multiple options for displaying a single line or
multiple lines of dialogue. Opening the dialogue box
and grabbing focus only needs to be done once for each set of dialogue
to be displayed. And the continued
button only needs to call the next line
function as well. I typically like to organize functions in my scripts
to have public at the top and private at the bottom and keep related
functions together. This follows two object oriented
principles, Abstraction, hiding the details of how things are done privately
inside the script, and encapsulation, only exposing publicly the functions you
want others to see and use. Let's try simulating a character speaking multiple
lines of dialogue. First, we'll declare
a variable to hold a sequence of dialogue
on a array of strings and pass that into the function call
as the first argument, which we will populate by
opening a square bracket. But since the sequence of multiple long strings will
take up quite a lot of space, I prefer to have each of
them on their own line, and it helps to use indentation
to keep them organized. Then the closing
square bracket can be used to clearly mark
where this array ends. Running the scene, we can see that the first
line is displayed. Clicking on the continued
button displays the next line, clicking on the continued button while the line is still typing, we'll skip the typing animation and display the complete line. When all four lines
have been displayed, clicking on the continued
button closes the dialogue box. But I don't want to have
to declare a variable, populate it, and call
a function every time. I would prefer to do it
all in one line of code. Following a similar format, we can replace the
variable declaration with the function call The parentheses add another layer
of indentation. Our array is declared
and populated within the function call along with specifying the
speaker's name. However, the array now doesn't
have a type specified, but since Godo Script is a loosely typed
language, this is fine. We can actually remove
the type requirements from our dialogue script
function parameters. But be careful to only pass valid arguments or
you will get errors. We can now display
longer sequences of dialogue with animated
typing and skipping. In the next lesson, we'll create custom event scripts that can display complex
sequences of dialogue. I'll see you in the next lesson.
4. Event Manager: Hello, friends. In
the previous lesson, we improved the dialogue
system with typing, skipping, and multiple lines. In this lesson, we'll create
an event manager to handle our games dialogue and synchronize
it with other systems. Starting in the game scene, let's add a new basic node, and name it event Manager. The game manager script can grab a reference to this node
using add on ready, along with the player node. Thinking of the scene tree like an organization, typically, if different branches want to request something
from one another, they need to send the request through a manager
above both of them. In this case, something in the level is going
straight to the top of the game scene
manager to request permission to use the event
manager to run an event. If anything in our game scene wants to trigger an
event to happen, they need only ask the game
manager to start an event, passing the event
as an argument, and we may also want to wrap up events with a
common function too. The game manager can
disable the player node, so the player can't control the character during an event. And when the event is over, we can restore the
player's control. Once all preparations
have been made, tell the event that
they are good to run, passing the event
manager reference. Can make this more
flexible by also adding an optional Boolean parameter to the function call with the
default value of true. Now it's possible to write some events that do not
remove player control. In the player script, let's add a public variable for enabling and disabling
player control. In the same lines where
we are checking if the game is paused
before processing input, we can add a condition using the or logic operator
represented by two pipes. If the game is paused or the
player has been disabled, we return and ignore the input. But if the character is moving when the player
node is disabled, they will continue running in the same direction until
control is re enabled. We can define how
a variable is set, as well as add automatic
functionality with a setter. Following a variable
declaration with a colon, we can write a block
of code for better restricting how this
variable is accessed. Creating a set function, accepting a new value
as a parameter, we can assign the new
value to our variable. But we also want to ensure that if the player node
is not enabled, that we tell the
character to stop moving. This behavior becomes automatic anytime the enabled
variable is set to false. Similar to a scene manager, the event manager's
job will be to facilitate communication between different
parts of the scene. But this script doesn't have the same responsibilities
as a scene manager, so it won't inherit
from that class. It can simply inherit from node. I will still save it in
the manager's folder, however, since it is still
considered a manager. At the top of the script, we will need references
to anything that might be commonly
involved in an event. The character, the dialogue box, and the FAD, all is
public variables. You may also want to
add extra functions to the event manager to bundle commonly used
behaviors together, build an event queue, or even nest events
inside other events. Now, any script in our game
scene will only need to reference the game manager in order to be able
to start an event, present dialogue,
fade in and out, and even control the character. L et's switch to the
level scene and set up a basic scenario where when
the player enters this room, they are presented
with a message. For that, we will need
an area three D node with a collision shape. I'll make it a box that covers the entire doorway
into the room, so there's no way
the player can enter the room without
colliding with this area. Keep in mind that these
types of proximity triggered events can be disruptive to some players and should
be used sparingly. Most players prefer to
enter into events that remove their control voluntarily
by pressing a button. It doesn't need to exist
on any collision layer, masking for collisions
with the player only. It only needs to
be monitoring for the player and not
monitorable by anything. We can attach a script And make a new folder restoring
all of our event scripts. T's name it enter room, connecting the body entered
signal to the script, We can write what
will happen when the player character
enters this area. We can find the game manager using dollar sign a notation, starting from the root node. We can tell the game
manager to start an event, passing itself as the argument as the event that
is to be started. You might want to do
this using ad on ready, but I would recommend doing it when the event is triggered. Since not all events
will be triggered every time a level is
loaded into your game, so there's no need to have them searching the scene
tree all at once, when it would be more efficient to do it only if they need it. Since we don't need
the body reference, let's prefix it
with an underscore to market for the engine. Before starting the event, we can also double
check to make sure that what was passed as an
argument is not null. And if it is not null, also make sure that it has
a method named run event. Reversing the logic, we
can check if this is null or if it doesn't have
the run event method. If either of these
conditions are true, we can return
ignoring the request. I prefer to use if
not event here, which means the same thing. Let's also print out
a warning message to the developer about this, telling them that they have made a mistake that they need to fix. Push warning will display a yellow warning message in the log without
disrupting the game. The event script can now define this run event function
as a series of instructions passed
through the event manager to any of the other scene
components as required, and everything can
be synchronized through the use of signals. To create our scenario, we only need to go through
the event manager to access the dialogue box and tell
it to display a message. Then tell the game manager
that the event is over. Adding a weight to the
front of any function, we can return a signal from it, so the event knows when
this task is complete. Switching over to the
dialogue box script, let's have the display
line function, return a signal for when
the dialogue is finished. And also do the same for
the multi line function. At the top of the script, we can give the
signal a definition, and emit it when closing
the dialogue box. The main objective of all
this infrastructure is to create scripts that read
somewhat like a screenplay, giving clear instructions
of who is saying, how they are acting,
where they are standing, what the camera is
looking at, et cetera. If the player enters this room, they are now presented
with a message. Control has been
temporarily taken away from the player while
the message is displayed. Upon pressing the
continued button, the dialogue box emits the signal that the
dialogue is complete, allowing the event
script to move forward, ending the event, Restoring
control back to the player. We now have an event
manager that disables player control while an event plays out involving dialogue. In the next lesson, we'll create an interactable component that the player can interact
with to trigger events. I'll see you in the next lesson.
5. Interact: Hello, friends. In
the previous lesson, we added an event manager
that can run an event script. In this lesson we'll add interactions for the
player to initiate. A more popular way to
initiate events is when the player presses a button to interact with
something or someone. Opening the project settings, switching to the input map tab. Let's add an interact button. I'll use E on keyboard
or Y on my Controller. Then in the player script, We can check if the interact
button was pressed, and if so, tell the character to interact with whatever or
whoever is in front of them. To figure out who or what
that would be, we can use a. In the character scene, let's add a child node to the
rig of type three D. We want the ray to point
in front of the character, a reasonable distance to be able to interact
with something, I'll set it to 2 meters. Since the rig is the thing
that's actually rotating, as a child, the raycast
will rotate with it. The collision mask determines what the ray can collide with. We want this ray to
find interactions. We should give interactions
their own collision layer. Opening up the
project settings in the general tab,
under layer names, three D physics layers, I'll set layer five to be
specifically for interactions. The ray cast will
collide with layer five, but also be obstructed
by Layer one, preventing the player
from interacting with something on the
other side of a wall. Expanding the collides
width section, we will be looking
for both areas, our interactions, and
bodies like terrain. Positioning the height
of the ray cast, it makes the most
logical sense to put it somewhere
around shoulder level, or at least somewhere
between chest and eye level. But we can also use this
as an opportunity to implement certain range
restrictions on our interactions. Let's position the CSG box in
front of the character and imagine that our
interactable object or NPC is on top of the box. How tall can the box
be before the person or thing on top of it is
no longer interactable. Since the ray would collide with the box and not the
thing on top of it. I think it's reasonable
to allow interacting with something that is 1 meter
higher than the character, but not 1.5 meters. So I'll position it
somewhere in between. 1.3 meter seems good, which is half the height of
the character's collider. In the character
script, we can grab a reference to the cast
node using add on ready. But note that doing
it this way will require every character
to have the cast node. By changing the dollar
sign annotation to a function, get node or null. We can pass the node path
as a string argument, which we can copy by right clicking on the node
and selecting copy path, or click and drag the node directly into the
quotation marks. Now for any character
without a cast node, this variable will
just be set to null instead of
generating an error. L et's add a function to the character script
named interact, which we called from
the player script when the interact
button was pressed. First, checking if
the cast node exists, we can then check if it is
colliding with anything. We can then check if
what the ray cast collided with has a
method called interact, and if so, call it. If the ray cast doesn't
exist, nothing will happen. If the ray cast isn't hitting anything,
nothing will happen. If the ray cast is hitting
terrain like a wall, the terrain will
not have a method named interact, so
nothing will happen. If the raycast is hitting
something on layer five, that has a method
called interact, it will call that method, triggering an interaction event. But thinking about all
the many different things in a game that the player
might interact with, an NPC and a door are both interactable by
pressing a button, but they don't share
much else in common. An NPC might want to inherit
the same properties and behaviors used by a playable character to
walk around and such. While a door needs properties
like whether or not it is locked and behaviors
like opening or closing. Using an inheritance structure. You could make all doors into NPCs that just
don't move or make all characters into doors with their otherwise door like behaviors and
properties ignored. Using the interactable
behavior as an abstract parent of both
doors and NPCs is better, but still implies that all NPCs and all doors
should be interactable, which is often not the
case in most games. You might want to
have an NPC class that inherits from character, but also inherits
from another class to gain additional functions
of being interactable. But Godot does not allow
multiple inheritance. It would be better to have
the interactable behavior of doors, NPCs, and anything else be independent of the nature of the thing
you're interacting with. We can instead apply the composite pattern to accomplish this
more efficiently. Creating a child node to
represent the interaction, we can then attach an interaction behavior
to anything we want. In our level, we have a
few different characters. Let's pick one and attach a
child node to it to represent the interaction component of this NPC as an
area three D node. This area will not be
monitoring for anything, but will be monitorable by the ray cast we
created earlier. Therefore, its collision
layer will be five, so it can be detected
by the ray cast and its mask can be zero since
it's not looking for anything. It will need a collision shape. I'll use a capsule and have
it larger than the character. The ray doesn't
need to be exactly pointing directly at them. As for the height
of the capsule, we can apply the same logic
as we did before in reverse. This time, imagine our character
is standing on the box. How tall can the box be
before our character is no longer allowed to interact with the NPC standing
on the floor? I'll set the same
restrictions and let the player interact with
something 1 meter below them, but not 1.5 meters. Knowing that the ray
is being cast 1.3 meters above the ground the player character
is standing on, then the interactions
collision shape be at that t plus 1 meter. Making it 2.6 meters
tall seems reasonable, which also happens to be the same height as the
character's collider. But note that even a tiny object like a key sitting on the floor, will need to have a collider, this tall to be
hit by the recast. We can attach a script
to this interaction. Let's call it mage dialog. And save it in the
event scripts folder. This script will
need two functions, interact and run event. The interact function will be much the same as the
previous lesson, asking the game manager to
run this script as an event. For the run event function, we can have the mage
say anything we want. And end the event after
they finish talking. Now let's duplicate
this interaction node and attach it to the door. Then also duplicate the
Mage dialogue script and call it cell door. Replacing the script attached
to this interaction, then edit it to say something
like the door is locked. In just a few simple steps, we are able to create any kind of interaction that player can initiate by pressing
the interact button with any object or NPC. Since I modified the scale of an ordinary wall to
create the door, the collision shape is
inheriting its scale property. But after centering
it within the door, it still does a good job
of encompassing the door. Let's try it out. Pressing
the interact button when we aren't facing
anything doesn't do. But if we walk up to the mage, we can press the interact
button to initiate a dialogue. Likewise, we can also walk up to the door
and interact with it. We now have a door, the
player can't yet open, and in NPC, the player can
talk to by pressing a button. In the next lesson, we'll add dialogue choices and
conversation trees to the dialogue system. I'll see you in the next lesson.
6. Choice: Hello, friends. In
the previous lesson, we created an
interaction component that can be attached to
anything in our game. In this lesson,
we'll add branching dialogue choices to
the dialog box system. First, we'll need to add more buttons to
the dialogue box. It's up to you how
and where you want to position your
dialogue option buttons. Whether they are in
the same dialogue box, a separate box, floating in the
center of the screen, accompanied by dialogue or not. For my game, I'll have my buttons inside
the dialogue box. Adding another
vertical box container to the dialogue box. L et's name it options, and it will contain three
buttons named option zero, option one and option two. I'll give each of them
some default text and set their text to
be aligned to the left. If I hide the speaker's name and restrict the dialogue to
a maximum of two lines, everything should
fit inside the box. I'll just anchor it to
the bottom right of the dialogue box and move it to a position
where it looks good. Holding down the alt or
options key while clicking and dragging will move only the selected node and its children. Editing the dialog box them, we can add for buttons. I'll have the default
font color black. Disabled, faded gray. Focused covered and
pressed all white, and add a black outline, so the white text
is still legible. In the Constance tab, I'll set the outline
to two points. Next, I'll replace all of the style boxes from the
buttons with empty style boxes, so they just display
as normal text. Selecting the Options
vertical box container, I'll use the theme
override section to add 12 pixels of separation between each of the option
buttons to space them out. Take some time to organize
your dialog box layout and use fake placeholder text to test the limits of how
much information can be displayed at once, including how many options you would like to
present to the player. Once you're happy with how
everything is displayed, remember to hide the dialog box. We'll need to alter
the script to be able to display
everything appropriately. L et's grab a reference
to the options as an array of its child nodes using the Get children method. And at a public function
called Display Options, accepting a line of
dialogue as a string and an array of strings for the options that the
player will choose from. Like the display line function, we'll assign the
array of lines to be just the single line
that was passed as a parameter and
setting it inside square brackets and set
the current line to zero. When displaying dialog options, I want the speaker's
name to be hidden, so I'll set it to
be an empty string. We can then tell the dialog
box to open it if it isn't already and start
typing up the next line. Where this differs
from displaying a simple line of
dialogue, however, is displaying the
option buttons, which I don't want to happen until after the line
has finished typing. For that, we'll need
another signal, emitted by the next
line function. L et's call it finished typing. And await this signal
after the next line call. We will also need to define this signal at the
top of the script. Once the typing has
been completed, the continued button
is no longer relevant. So we should set its visible property to falls to hide it. We can then use a four loop to iterate through the
array of option buttons. But since we need to
match the indices of the button array with the
indices of the string array, we can instead use a
different kind of four loop. Using the alias I, which in programming is universally
used for index, followed by the keyword, we can iterate through
the arrays size. The value of I will start
at zero and increase by one until it reaches the
size of the button array, in this case, three and stop. We can now use the value of i to check if it is smaller than the size of the
string array that was passed as a parameter
to this function. In case the dialogue has less than the maximum
number of options. If this is true, then we can set the text property
of the button at index to the string
value of the same index, and also set the
button to be visible. If the button index is larger
than the number of choices, then the button should
not be visible. Finally, we can tell
the top button to grab focus so it can be
accessed with a controller. To make this work
seamlessly with our other dialog
display functions, we'll need to toggle
the visibility of the continued button
and the option buttons when typing dialog lines. I'll also move the line telling the continued button to grab
focus to be here as well. With everything being
displayed correctly, there's just a matter of
telling the event script, which option the
player selected. The return type of this
function will be an integer. The index of the button
the player pressed, which matches the index of
the choice they selected. Each of our buttons, one
clicked, will call a function. Let's call it on option pressed, with an integer
parameter named index. This function, just like
pressing the continued button, can close the dialog
box and emit a signal. Let's call the signal selected, and pass the index along through the signal
as an argument. At the top of the script, we can give this
signal a definition, and this time, also give the signal an integer
argument named choice. The display options
function can now return a weight selected, which will cause
it to wait until the player presses
one of the buttons, then receive the
integer index of the button that was
pressed and return it. The last thing to do is connect the button pressed
signals to this function. Make sure your script has been saved so the engine can
find this new function. Selecting each of the buttons, connecting the pressed signal, select the dialog box
node from the list. Ignore the suggested
receiver method and click on the pick button. This presents a list
of functions in the dialog script
whose parameters match the signals arguments. Since the pressed signal
has no arguments, only functions with no
parameters are compatible. Click on the Compatible
methods only toggle to see a full list of functions and
select on options pressed. To fix the compatibility issue, we need to add our own arguments to the signal in the
advanced section. Using the drop down, we can select any built in
type for the argument. The one we want to
use is an integer. Adding an integer
argument to the signal, we can set its value as the
array index of the button. Option button zero is index
zero. Click and Connect. We can then repeat this process with the rest of
the option buttons, changing the argument value for each to match its
index in the array. The argument will now be passed to the on option
pressed function, emitted by the selected
signal and returned by the display options function back to the event
script, which called it. To test out the
dialogue options, let's modify the Maj
dialogue event script. Similar to displaying a
single line of dialogue. We can await em dialog
Display Options, providing a short
line of dialogue, followed by an array of options. This statement returns
an integer between zero and the number of options
in the array exclusively. Assigning the returned
integer to a variable, we can then branch our
dialogue based on its value. You can use an if
statement for this, but I find match statements much cleaner for this purpose. The keyword match functions similarly to switch
in other languages. In the block of code following
the match statement, we can provide an option
followed by a colon, then inside that block of code, proceed with what will
happen in that case. And provide different
cases for each of our dialogue options.
Let's try it out. I have the text typing speed set to the minimum to test
the continue button. If we talk to the
mage, they will say a line of dialogue,
which we can skip. After skipping, we are
presented with three options, which have grabbed focus, and the continue button
is no longer available. Selecting any of the options, the dialogue continues
based on the selection we. If we do it again, we can
select a different option. The mage says
something different. We can simplify this
script even more by replacing the
variable declaration with the match statement. We now have dialogue options for the player to choose from, leading to branching
dialogue trees. In the next lesson, we
can allow the player to progress through the
game during these events. I'll see you in the next lesson.
7. Flags: Hello, friends. In
the previous lesson, we allowed the player to
choose from a list of options and used their selection to
create a branching dialogue. In this lesson, we'll use progression markers to
change how events play out. Let's start by having an
NPC's dialogue change each time the player
interacts with them. In whatever script you're using
for your games save file, in this project, we're using a custom resource
named progress. We can add an exported variable for tracking how many times the player has interacted
with the maj as an integer, which will naturally
default to a value of zero. In my project,
actual file saving and loading has been removed. Keep in mind that
my demonstrations will always start with
the default values. For more information on saving progress information
and custom resources, see the previous course in this series, Game
Development essentials. In the Mage Dialogue script, we can start our run
event function by first matching the
value of this variable. Then based on the number of times the player has
spoken to the Mage, we can provide different
lines of dialogue. It would make sense
that if the player has never spoken to the Mage, that the first might start
with an introduction. We can then access
the same variable and increment its value by one. And the next time the player
speaks with the mage, they will say
something different and also increase
the value by one. Adding a default
option, in Gado script, this is identified with an
underscore instead of a value. We can provide a repeating
line of dialogue for after the player has exhausted
all the previous options. The third time,
speaking with the mage, and every time after that, we'll produce this
line of dialogue. Let's try it out. The first time we speak with the mage,
they introduce themselves. The second time they go on
to being a hospitable host. The third time and
every time after that, they will simply
tell us that they are too busy to talk right now. Alternatively, instead of
having repeated dialogue, we could this interaction after the dialogue
has been exhausted, so this NPC will no
longer be interactable. We can also create cycles
of repeating dialogue by resetting the value to a lower number after
the last option. So now the made will say
something different every time repeating the same three
lines in sequential order. Instead of creating a separate
variable for each MPC, you might want to consider
using an array of integers. We will need to initialize this array to contain
a zero for each MPC. Since I have three NPCs, I'll add three
zeros to the array. Then each MPC in your
game can be assigned a number starting from zero
used to index the array. We can make this easier
and less prone error using an enumeration,
a numbered list. I like to keep all of
my enumerations from my project in a single
script called enums. Starting with giving
it a class name Enums. It doesn't need
to inherit any of the properties of node since
it won't be attached to one. The only contents of
this script will be declaring enumerations to be
used throughout the project. Our first num will be a list
of all NPCs in the game, night, Mage, and Rogue. The convention for Enums is
to use upper snake case, sometimes referred to as
screaming snake case. The Mage dialogue
script can now access the array and index it
with the enumeration. F. These are common practices
for NPC dialogue in games, but they don't really allow the player to make
progress through the game. Let's use the door as an
example of progress and have the player able to open the door during an
interaction event, but only if they
know the password. Knowledge of the
password must be given to the player
character by the mage through a separate
dialogue event before they will be allowed
to use it to open the door. In the progress resource, we can add an exported
Boolean variable for whether or not the
player knows the password, which will naturally
default to false. Boolean variables
used to control game progression are commonly
referred to as flags. When speaking with the mage, the character will
have the option to ask to see the prisoner. Upon selecting this option, the major will tell the
player the password. At which point we can set the variable in the
progress resource to true, since the player character
now knows the password. Attaching a script to the door, We can add an exported
variable stating whether the door is currently open and add three public functions, checking if the door is open, opening the door, and
closing the door. Returning a Boolean. We'll just return the value of
the is open variable, so other scripts can easily get its value, but not set it. Applying encapsulation. I'll also declare
a variable to hold the y position of the door
when it is closed as a float. And set its value during
the ready function to be either its current y position or its current y position
plus 4 meters, based on the value of is open. Each of these functions
can set the is open variable then call a private function
to move the door. To open a door, I'll
move its y position down 4 meters and to close it, reset its position back
to the closed value. With a tween variable. After checking to
make sure the tween doesn't already exist and isn't currently running,
create a new tween. I want the door node to remain interactable while
the door is open. I will remain in
the same position, and I will instead twe
just the doors me. Opening this scene, ignoring the warning since I'm
not making any edits. We can see that the mesh node of this door is n Wall Half. I'll use that as the
node that is being tweed and tween its
position y value. Since the position
is the property, we can access the y property of the vector three
structure with a colon in the property path to move to the new position over a
duration of 1 second. Returning the tween
finished signal up through the function calls in case any other script
wants to await it. This is a little overkill
for this demonstration, but this script is now
flexible enough to work with both opening and closing similar doors
anywhere in my game. In the doors interaction
event script, we will need a reference
to the doors root node. For simplicity, I'll
just use get parent. We can first check if the
door is currently open. In this case, we can end the event to give control
back to the player, then tell the door to close and return to prevent any more of this script
from running. The player won't have
to wait while the door closes and can continue playing. If the door is
closed, then we can check if the player knows
the password or not. If the player doesn't
know the password yet. We can present some simple
dialogue about the door being sealed shut
and end the event. But if the player does
know the password, I'll have the barbarians say the password in
line of dialogue, end the event and tell
the door to open. I would like to
prevent the player from interacting
with the door while it is opening or closing in
case the matching the button. In both cases, I'll set the collision layer of the
interaction node to zero, preventing the interaction
rec from detecting it. Then add a weights
and set it back to its original value after the door has finished
opening or closing. We can see the value by covering the mouse
over the bit mask. Each layer is represented
by a single bit, either a zero or
a one in memory. And each bit is numbered
starting from zero. Layer five is bit number four. Its value represented
as an integer is then two to the power
of four, which is 16. So we can reactivate
this collider by setting its collision
layer back to 16. If using multiple
collision layers, adding their bit
values together will generate the integer
value of the bitmask. Any integer can be used as a
bitmask in this way and is an extremely efficient
way of storing up to 32 Boolean flags
inside a single integer. The door needs to
be set using at on ready. Let's try it out. When attempting to interact
with the door first, the door is sealed shut. But after speaking with the mage and gaining knowledge
of the password, interacting with the door again, the interaction plays out
differently and the door opens. Once the door is
completely open, we can interact with
it again to close it. We now have progress
flags that allow the player to make progress through the game using events. In the next lesson,
we'll change what the camera is looking
at during events. I'll see you in the next lesson.
8. Camera: Hello, friends. In
the previous lesson, we allowed the player to
progress through the game by setting and reading
flags during events. In this lesson, we'll add a cinematic camera to the
event management system. Let's start in the
game scene by adding a camera three D node
to the event manager. And set a reference to it in
the event manager script. Then in the level sn, using the maje dialog
event as an example, we can add another
camera three D node as a child to the
interaction event. Then move the camera around and use the camera preview
toggle to position or rotate the camera to
where we think it will look good as the perspective
for our dialog event. Once you're satisfied
with how it looks, change the type of the node to Marker three D and rename it to something
more appropriate. Retaining its
transformed properties of position and rotation
within the scene. We can now use this
marker to position the cinematic camera
during the dialogue event. Adding a script to
the cinematic camera, I'll put it in the
events folder. We will need a public function to move the camera to a marker, setting the camera's position and rotation to match that of the marker and making
this the current camera. But since positions
and rotations of children are relative
to their parents, we'll need to use
global position and global rotation to make sure we're getting
the correct values. Then in the Maj
dialogue event script, we can grab a reference to this marker node and tell the event manager to move the cinematic camera to it. When the event
ends, we will need the game manager to switch
back to the player's camera. In the game manager script, we'll need a reference to the player's main camera so
that when events are ended, we can make the player
camera the current camera before allowing the
player to start playing again. Let's try it out. When
speaking with the mage, the cinematic camera moves to
the marker and takes over. Then when the event ends, the player's camera
resumes control. We can also fade the transition between cameras quite easily. Adding a function to the
event manager script, let's call it fade to marker, accepting of Marker three D, and returning a signal. We can await fade to black, then send the marker onto
the cinematic camera. Then return fade to c. The game manager
can also fade in and out when switching
back to the player camera. But it would only make
sense to do this if the event was using
the cinematic camera. We can also make this behavior optional by adding an
optional Boolean parameter to the function and only use the fade if requested by
the event specifically. Adding true to the end
event function call, and awaiting Fay to marker
at the start of the script. The camera now fades
in and out as it switches perspective at the
start and end of the event. If your events have
more than one marker, you might want to sort
them under a parent node. Let's call it camera markers. And in the event script, grab a reference to it as
an array of its children. The event script can request to the camera to move to
camera marker zero, one, two, et c, more
like a film set. But what if we want
the camera to pan? Let's add some more functions to the camera script,
Pan direction, to move the camera using
a vector and pan to marker to gradually move the camera directly to
a specific location. Both will accept a duration of time for the pan and return a signal to tell the event when they are finished in case
we want to await it. To gradually change
something over time, we'll need a tween variable. Both of these functions can be simplified into one
private function, Let's just call it tween camera, accepting the
target destination, the time duration, and
returning the same signal. Since pan to marker already
knows its target destination, we simply pass that information along to the tween
camera function, accessing the global
position of the marker. And the Pan direction
function can calculate its target destination by adding the direction to its
current global position. After checking if the tween already exists and is running, if so, kill it, then
create a new one. Then we can tween the cameras
global position to be the target destination over
the requested duration. In the pan to marker function, it would also make
sense to match the markers rotation
as well as position, but maybe not every time. Let's add an optional Boolean parameter to allow
this behavior. We could duplicate the
tween camera function to add another with rotation, but I would prefer
to add the rotation in the Boolean parameter
to this function, giving the rotation a
default value of vector 30. If the event wants the camera to match the markers rotation, Then also tween the
camera's global rotation to match the target rotation
over the duration. By default, multiple tweens
will happen in sequence, but we want to sap
in simultaneously. We can add another function call here parallel to achieve this. If we change these
argument types from a marker three D to a
generic node three D, then any node three D
can be used as a marker, including the player's camera. They will also need
to set this as the active camera if
it isn't already. In the event manager script, we can also change this
Marker three D type to a node three D. Grabbing a reference to the player's camera node
will mark it as private, so other scripts won't see it. We can then add a
function that moves the cinematic camera to the
player camera's position. In the Mage dialogue script. Instead of using the Fade, let's try setting
the cinematic camera to the player
camera's position and panning the camera
to the marker during our event before the
first line of dialogue. Then pan the camera upward and fade back to the
player camera at the end. Let's try it out. This dialogue now
looks more like a cut scene with the
camera moving around. But we can take
this even further by creating camera tracks that will guide
the camera around the scene in a smooth
sweeping motion. Start by adding a
path three D node to the interaction event node. This node has one property, a curve three D resource. Clicking on it to expand it, it contains an array of points. Adding a point, each point contains a vector
three position. Starting with the first point at the original markers location, we can add more
points to the array. To move the camera
through the doorways, Then to the guards window
into the prison cell. The other point properties are
anchors that add weight to the calculation of points before and after them
along the curve. The first point only
has an out anchor, the last point only
has an in anchor, but every point in
between has both. Adjusting these anchors
will smooth out the curve following a
bezier curve formula. Now that we have a path we would like the camera to follow, we need to add another
node to the path. The path follow three D node. The second property of
the path follow node, the progress ratio will move it along the path
by a percentage. Adding a camera three D node to the path follow node and giving its original rotation
looking towards the mage. We can then click
the preview toggle to view from the
camera's perspective. Then adjust the progress value and watch the camera
sep through the scene. We can now delete the
camera three D node. To do this in our scripts, we only need to
grab a reference to the path follow node
in the event script. I'm going to break
the dialogue up into separate lines to
better synchronize with the camera movements. We can also use this as
a marker to position the camera if we want before
initiating the movement. We can then tell the
camera to follow a path, passing the path follow
node as an argument, along with the duration of time. Let's define this function
in the camera script. Accepting a path to follow and a duration of
time as parameters. The first thing we
will need to do is re parent the camera to
the path follow node, then reset its position
and rotation to match it. Doing all the usual tween things and returning the
signal when finished. We can then tween the
progress property of the follow node to reach a value of one over
the duration of time. And make this the
current camera. Finally, the game
manager can re parent the cinematic camera to the event manager while ending
the event if necessary. L et's try it out. After
mentioning the prisoner, the camera sweeps through the scene to look at
them through the window. If you want to restrict how the camera rotates while
following the path, it's easy to limit
it to either only rotating about the y axis
or not rotating at all. We now have a cinematic
camera that can take our simple dialogue events and
turn them into cut scenes. In the next lesson, we'll also
add character animations. I'll see you in the next lesson.
9. Animation: Hello, friends. In
the previous lesson, we added a cinematic camera
to the event system. And this lesson will animate the characters
during our events. Let's start in a
character scene. Selecting the
animation tree node, the animation tree panel opens at the bottom
of the editor, Displaying the
state machine that controls the characters
movement mechanics. For more information
on the basics of character animation
and state machines, see the introduction to three
D game development course. To prevent confusing tangled
webs of state transitions, we can keep different types of animations separated into
different state machines. Right clicking in empty space, let's add a new state
machine to this one. For lack of a better name, I'll call it miscellaneous. Using the Connect tool, add connections from locomotion to the new state
machine and back again. Using the select tool, select the transition from locomotion to the
new state machine. Expand the advance section and change the advance mode
from auto to enabled. This will allow us to trigger the animations through script instead of expecting
the animation tree to automatically
handle the transition, like what is happening with
the movement mechanics. Selecting the other transition, expand the switch section, and change its switch
mode to at end, allowing any of the
animations inside the new state machine to finish before returning
to locomotion. Selecting the new
state machine itself, change the state machine
type from root to grouped. The movement mechanics are currently the root
state machine, and this new state
machine will contain similar animations which will be grouped together to help
keep things organized. Then click on the Pencil icon to edit this new state machine. We can return to the root of
the state machine by using the bread crumb buttons at the top of the
animation tree panel. In this state
machine, we can add any animations that both start and end with
the idle pose, which are meant to be
played once and not loop. In this asset pack, the cheer, interact, and pick up animations
fit this description. For the state machine
to work as expected, the number of transitions
from start must match the number of transitions entering
the state machine. Likewise, the number
of transitions exiting the state machine must match the number of transitions
leading to the end state. Since there is only
one transition into and out of
the state machine, there can be only one transition connecting from start to end. We need an intermediate
state in between, the state which both
starts and ends all of these animations, the idle pose. Unfortunately, this asset pack does not contain an idle pose, but we can make one easily. Switch to the animation panel at the bottom of the editor. And select the idle
animation from the dropdown. Click on the animation
button beside the dropdown, then
select duplicate. Rename the new
animation idle pose. Change the duration
of the idle pose animation to zero and
turn off looping. Back in the animation
tree panel, inside the miscellaneous
state machine, we can add two copies of
the idle pose animation. L et's name them Idle Post
start and Idle Pose end. Then connect from start to idle pose and then to each
other state from there. And each of these to
the second idle pose, and finally, the end state. Selecting each of the transitions
from idle pose start, change their advanced
mode to enabled. And the transitions to idle
pose end can switch at end. Now the number of
transitions into and out of the state machine
match the number of transitions from
start and end to end. There is no possibility of ambiguity for the animation
tree to navigate it smoothly. We can preview how
these animations look by clicking on the
play button beside them. And we can add a
short duration of cross fade to the transitions in and out of the
state machine to better blend them with
the locomotion states. When you're satisfied
with the state machine, we can save it as a resource
within the project. After creating the idle pose animations for
the other characters, this state machine
can be loaded into their animation tree nodes since they all have
the same animations. In the character script, we can add a public function to trigger the
transition to any of these animations by simply passing the name of the
animation as a string. We just need to tell the state machine playback to travel to the animation by
adding the name of the grouped state machine which
contains it to the front, along with a separator. But we will need to manually restrict only allowing
this to happen from the locomotion state by first checking the state
machines current node. If the current node
is not locomotion, we should ignore this request. This will prevent starting these animations
while in mid jump. We also might not want
the character to be able to move while these
animations are playing. Let's add an optional
boolean parameter named locked with a
default value of true. And also declare a
private variable for the script indicating whether the character
concurrently move or not. Adding a setter to this variable
after setting its value, If the character can't move, we should also set the
value of input direction to zero to stop the character
if they are already moving. Then in any function
that moves or jumps, we can add a conditional
statement checking if the character can move
before allowing it to happen. When told to perform
an animation, if the animation locks movement, then we can set the
can move variable to false and set it back to true after the
animation finishes. But we can't just await the
animation finished signal, since it will be emitted when the current animation finishes, which is idle, not the
one we want to wait for. Placing the wait command inside a while loop and just
continuing the loop, we can await every
animation finished signal repeatedly until the specific
one we want is emitted. Holding control or command
and clicking on the signal. We can see that it passes an argument of the animation
name that just finished. O. We only need to compare this value
to the animation name, which when received, will
now break the loop of awaiting and set the can
move variable back to true. Lastly, to use this as part of the event
management system, we should have a
return a signal, just in case we
want to await it. Let's call this signal
animation finished. We can emit and
return the signal if the animation
was canceled and give it a Boolean
parameter for whether or not the animation
request was successful. Passing false is the argument. And at the end of the
function with true. Defining the signal and its parameter at the
top of the script. Since our event manager already has a reference
to the player character, telling them to perform these
animations is now trivial. In the cell door
interaction script, let's await and interact animation before any
dialogue happens. In the Maj dialogue script, we will need a reference
to the Maj character in order to be able to request
animations from them, which can easily be done using add on ready
and get parent, since this interaction note
is childhood to the mage. If you want to include
multiple characters, simply export a variable for each one that is included
in the dialogue. I'll have the mage cheer while talking by excluding
the await keyword, but pretend that the
animation is actually angry. Note that we can also
give the characters other commands
such as start jump or face direction.
Let's try it out. When speaking with the mage, they angrily flail about while shouting at the player
character in dialogue. If we interact with the door, the player character
will reach out and touch it before initiating
the rest of the event. We now have the ability to animate characters
during our events. In the next lesson, we'll move
the characters around to. I'll see you in the next lesson.
10. Direction: Hello, friends. In
the previous lesson, we added character animations
to the event system. In this lesson, we'll tell the characters to walk
and run around two. Let's set up a basic cut scene. When speaking with the mage, let's have them walk
back to the wall, grab something from the shelf, return to the player
and hand it to them. Like we did with the camera, we can add marker three D nodes that tell the character
where to move. Much like stage directions. We can add one as a
child of the mage to easily copy their exact
current position. Then duplicate it and move the new one to be over by
the shelves on the wall. Making sure to leave
enough room so the character's collider
doesn't collide with the wall before
reaching the destination. If you have multiple
markers in an event, it would help to have them
sorted under a parent node. Clicking and dragging
them to be re parented. But if the character
walks around, since all of these
nodes are children of the character node,
they will all move to. We can select the parent
nodes holding our markers, expand the transform section, and click the top level toggle, which will prevent the transform properties from being inherited. The markers will
remain stationary while the made walks around. Then in the event script, we can grab a reference to this node as an array
of its children. After a line of dialogue, we can tell the mage to walk
to character marker one, interact, then return to character marker
zero and interact again before displaying
another line of dialogue. In the character script, we can add the function that tells the player to move to a marker. Accepting a three D as a marker, and an allowable distance to the destination as a
float for parameters. This will allow the function to end when the character gets close enough to the marker without having to stand
directly on top of it. Let's give it a default
value of a quarter meter. Then return a signal
when it's done. Let's add a signal
named destination reached then emit it and return it at the
end of the function. Subtracting the character's
current global position from the destination will give
us a vector pointing to it, a vector with both a
direction and a distance. Our character controls assume
a vector length of one, as that is what is provided
by the input singleton. When using this direction
to move a character, we will have to
normalize it first, setting the vector's
length to one. Simply by setting the
direction variable, the physics process will
then move the character. If we wrap all of
this in a wile loop, we can keep running this
code while the character is farther from the destination
than the allowable distance. Awaiting the next physics frame This code will run alongside the physics process to move the character from where they
are to where they're going. When the destination is reached, we can set their
direction to vector 30 to stop their movement, but the character will still decelerate forward from here. If we want the character to more accurately
stop on the marker, they will need to start
decelerating before reaching it. So we can split this
into two phases before and after the point where the character starts
decelerating, called the stopping distance. We can calculate the
stopping distance by taking their current
x z velocity, averaging it with a
velocity of zero, which can be simplified
into dividing it by two, then also dividing it
by their deceleration. The stopping distance will be longer the faster the
character is moving, and also be dependent on
the rate of deceleration. Up until the character reaches
the stopping distance, they will accelerate up
to their maximum speed. When reaching the
stopping distance, they will start to
decelerate and come to a complete stop more or less directly at
their destination. If the allowable distance is achieved before the
stopping distance, then the character
will still stop. All of this can be done using a turn rey statement
in one line. As long as the player
character interacts with the mage from the
front, this works fine. But if the player
character is standing in the way of where the
mage wants to walk, this may cause the mage to be unable to reach
their destination. While navigation around a static environment
is fairly simple, dynamic path finding around moving obstacles is a
difficult problem to solve. Many older games often had characters walk through
each other or could get blocked for a limited
duration of time and then would snap to their
destination as a last resort. Neither of these look very good. Most modern games, including AA, will instead opt to have
their cut scenes operate in an entirely controlled
environment and eliminate any possibility
of path obstruction. To achieve this,
any cut scene that evolves characters
walking around will start by fading to black. Then every character in the
scene can be repositioned, if necessary before
starting the scene. Then when the scene
fades back in, every movement is
carefully choreographed. We can add another character
marker to specify where the player character should stand during this interaction. But with the top
level toggled on, the character markers appear to have moved to
the scene origin, but they still retain their
global positions as before. This does make adding
more markers difficult. Let's turn off the
top level toggle and edit the transform position
to force it to update, then reset it to zero. Now it appears in the
correct position. Let's add one more
and move it to where the barbarian
should be standing. Setting its rotation to
face toward the age. Then turn top level back on. In the character script,
we'll need a function that will instead of wocking
the character to a marker, simply set their position and possibly rotation
directly to it. Accepting a node three
D as a parameter and an optional parameter to match the y rotation with a
default value of true. We don't need to return a signal since this will happen
instantaneously. Setting the character's
global position to match and if matching
the rotation, also set the y rotation
of the rig to match that. When the cut scene starts, we first fade to black. Then position the
player character on character Marker two, matching its rotation as well, before fading back in and resuming the
rest of this event. Now, even if we deliberately
stand in the major's path, the cut scene will fade to black to correct this before playing the event in a more
controlled fashion to prevent potential problems. T L et's make the character walking
function even more flexible by adding the option
to have the character run. Adding a Boolean parameter named running with the
default value false. We can tell the character to run at the start of the function and walk at the end of the function if this parameter
is set to true. We can also add another
function which will allow the character to follow a path with multiple points
instead of just one. Starting with the same function definition as move to maker, let's rename it follow path. Change the first parameter
to path and remove the type. We'll just assume it's an array of node three D
without enforcing it. Then emit and return the
destination reached signal. Declaring a new variable for the current point in the
path we are heading towards, which will start at zero. As long as this number is less than the size of the array, we should await moving
to that marker. And we can pass along the same allowable distance
and running values. In the dungeon scene, I've gone ahead and
positioned a marker for the barbarian to walk to
after opening the door, and three markers which form a path for the
roge to run along. Since the door interaction
node doesn't move, there's no need to worry about
the markers moving either. The cell door interaction then needs a reference
to these markers, as well as the rogue character. While the door
opens, the barbarian can move out of the
way to marker zero. Then the rogue will show thanks while exiting out
of the prison cell. I'll add more allowable
distance leniency and have the character running. I'll let the player
have control back as soon as they're done
with the dialogue box, so they don't have to
sit idle while the rogue runs. Let's try it out. Interacting with
the cell, the door opens and the barbarian
walks off to the side. Then the rogue shots
thanks and escapes. We now have characters able to walk and run during events. And the next lesson will improve the modular design
of our events. I'll see you in the next lesson.
11. Modularity: Hello, friends. In
the previous lesson, we had characters walk and
run around during events. In this lesson,
we'll wrap up with some improvements to
the event scripts. Throughout this course, we have created a variety of
different events. Some triggered when the player
enters a specific area, others when pressing a button. Looking at these two scripts, we can start to see
the similarity of the run event function with
something like a screenplay, giving directions to characters and cameras on a film set. You can imagine in
making an entire game. There are going
to be hundreds or possibly thousands
of these scripts, possibly written by
multiple people. We want them to be as easy
to write and edit as they possibly can be to reduce the amount of time
required to produce them, as well as standardized
to reduce potential bugs. Let's start by adding a
class name declaration to the event manager script. Then in our event scripts, specifying the type
of the event manager, the engine will now produce
autocomplete suggestions when accessing variables or functions inside the event manager. A useful feature to reduce bugs in any script that uses it. Next, it would be
useful to separate how an event is triggered
from how it has run. Let's create a new script
inside the event folder. While Godot doesn't
actually have an implementation of
an abstract class, we can still pretend this
is an abstract class, one that is never meant to be instantiated in any
way, only inherited. Inherited from Aa three D, and give it a class
name interaction event. Moving the interact
method into this class, Any interaction event script in the game can
then inherit from interaction event to hide the interact function
through inheritance. We can also clean up the
collision layer code by calling inherited methods
with appropriate names. Like set interactable, passing true or false
as an argument. Then the interaction event class can hide the details of how this is achieved and standardize it for all interactable
events in the game. Setting the collision
layer to 16 if it is interactable or else zero
to become uninteractable. It would be a good
idea to further inherit from interaction
event to create a common class that
can be applied to every similar interaction
in your game, such as having one
script for all doors or one script for all
pickups, for example. The only differences between
their similar interactions being exported variables or
references to other nodes. Many events in games are only triggered under
certain conditions, and many become irrelevant
after they have happened once. Any of our interaction
event scripts can easily override the interact
method to add conditions, checking the player's progress
for flags if necessary, before calling the
superclass definition of interact to start the event. Okay. Setting the same
flag inside the event. We can also make this event uninteractable to prevent the same event from
happening twice. Another popular option is to use the ready method to check if
this events conditions are met and set it to be interactable or not when
first loaded into the game. This is often the case anytime triggering an event
requires zoning out and back into the
level to reset it before talking to an
NBC, for example. The conditions for the event are being checked only
when first loaded. Let's duplicate the
interaction event script and also create a
proximity event script. Changing the class
name to match. We can swap out the interact
method with on body entered, accepting the body as
a three D parameter. This collision will
trigger the event, but the body entered signal
isn't inherently connected. In the ready function,
we can manually connect the signal to
the callable method. Let's also set the collision
mask to detect the player, which is layer nine, bit eight, two to the power
of eight is 256. Now any event script can inherit
from proximity event and its triggering
conditions as well as collision masking are all
handled automatically. In this project, I have set all the characters to the
same collision layer, so any of them will
trigger proximity events. Opening the project settings, Under layer names,
three D physics. Let's add a separate
layer for NPCs. Then in each NPC scene, we can change their collision
layer from player to NPC and have them
both mask each other. Now only the player
character will be detected by proximity events. Let's also make
another type of event, one that is not triggered
by the player at all, but can be triggered
through other scripts. The game manager
will likely start an intro event the first
time the game is loaded. A level might also want to trigger events when
the player enters it, or events might want to
trigger other events. Creating a new script
in the events folder, let's call it scripted event. This time it will
inherit from node, since it doesn't
need any behaviors of the Aa three D node class. Since a scripted event
doesn't need to trigger, the only purpose of
the script will be to ensure that there is
a run event function, and also provide a signal that will be emitted when
the event is finished, which can be emitted
and returned. Any script which inherits
from this class can now return super dot run event
to automate this behavior. Adding a variable to
the player's safe file, let's call it intro
played Ablean, which will naturally
default to falls. Let's create an intro
event that will only be triggered once when the game is loaded for the
very first time. For simplicity, I'll
just have it fade in since the current
state of the fate is black when the
game first loads. Then say hello world and
set the flag to true. Then return Spot Run event, which emits and returns
the finished signal. And also tell the game
manager to end the event. Adding a new note
to the game scene to hold de scripted event. The game manager can grab a reference to it
using add on ready. In the ready function. We
can now check this flag. Then start the intro event and
await its finished signal. If the flag has been set to true, then it
will be skipped. Running the scene,
the intro event ps. If the player saves
and reloads the game, the intro scripted event will
not trigger a second time. I'll demonstrate by defaulting the variable in the
sa file to True. Let's add another node to
contain a secondary event, one which will be triggered
by a primary event. Adding a script to this node inheriting
from scripted event It will just say
secondary event, and return Super dot run event. Any event script in the
game can now await running this secondary event
and it will be executed before returning to
the event that called it. This can help divide
extra long events into more sizable chunks, split branching narratives
into separate scripts, or recycle the same event to be used in multiple contexts. We now have a very comprehensive dialogue and event
management system in place that can be used to create complex
branching narratives, compelling cut scenes and give the player choices of how they want to progress
through the game. The system is also
optimized to make producing the event
scripts fairly simple.
12. What's Next?: Welcome to my course on
inventory and shops in Gado. This course is a continuation of dialogue and events in Gado but can be followed and applied to any project that involves
picking up items, adding them to an
inventory, dropping, using or equipping items
from an inventory menu, and trading items with an MPC. You're welcome to join
our discord server to work on this course
alongside your peers. In this course, we will cover different methods of
picking up items, managing the players
inventory data, displaying their inventory
on the UI in a menu, allowing the player to interact with their inventory
in different ways, and trading items with NPCs. When you're done,
you'll have a simple inventory system for your game that is easily customizable in its presentation
and function. You'll also learn useful skills for working with the
Gada game engine, organizing and designing your projects to
be more scalable. You'll 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, abstraction, and polymorphism,
to keep them organized, customizable, and reusable
for a project of any size. All of the project files are
also available on GitHub, if you need to review
the project as it was after completing
each lesson. These videos were recorded
using Gdoersion 4.2 0.2. The project starts with
assets from K Kits, character and Dungeon remastered
packs made by K Lauberg. Basic Guy bundle
made by Penzilla, and icons made by Shakahi. All are available to
download for free on it. Sound effects made by
Valensbyer are also included and are available
on freesound.org.