Build a Metroidvania in Godot! | Thomas Yanuziello | Skillshare

Playback Speed


1.0x


  • 0.5x
  • 0.75x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

Build a Metroidvania in Godot!

teacher avatar Thomas Yanuziello, Indie Game Developer

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

    • 1.

      0-0 Trailer

      2:01

    • 2.

      0-1 Download

      2:34

    • 3.

      0-2 Editor

      4:42

    • 4.

      0-3 Scenes

      6:48

    • 5.

      0-4 Physics

      4:51

    • 6.

      0-5 Scripts

      5:43

    • 7.

      0-6 Game

      5:44

    • 8.

      1-1 CharacterBody

      5:15

    • 9.

      1-2 Input Map

      8:47

    • 10.

      1-3 Movement

      7:15

    • 11.

      1-4 Jumping

      7:23

    • 12.

      1-5 Falling

      7:25

    • 13.

      1-6 Running

      4:40

    • 14.

      1-7 Coyote

      8:41

    • 15.

      2-1 Sprite

      7:36

    • 16.

      2-2 Animation Player

      9:56

    • 17.

      2-3 Animation Tree

      10:02

    • 18.

      2-4 Sound Effects

      8:58

    • 19.

      2-5 Animated Sprite

      7:54

    • 20.

      2-6 Instantiate

      11:27

    • 21.

      2-7 Audio Bus

      6:13

    • 22.

      3-1 Tile Map

      8:05

    • 23.

      3-2 Terrain

      8:15

    • 24.

      3-3 Camera

      6:46

    • 25.

      3-4 Look Ahead

      9:24

    • 26.

      3-5 Look Up

      10:41

    • 27.

      3-6 Boundaries

      11:05

    • 28.

      3-7 Parallax

      10:06

    • 29.

      4-1 Contents

      6:58

    • 30.

      4-2 Rooms

      8:29

    • 31.

      4-3 Parallax

      10:06

    • 32.

      4-4 Transition

      7:38

    • 33.

      4-5 Viewport

      7:05

    • 34.

      4-6 Visibility

      7:49

    • 35.

      4-7 Map

      7:46

    • 36.

      5-1 HUD

      9:38

    • 37.

      5-2 Gauge

      10:27

    • 38.

      5-3 Icon

      8:00

    • 39.

      5-4 Counter

      8:40

    • 40.

      5-5 Hurt Box

      10:57

    • 41.

      5-6 Data

      6:10

    • 42.

      5-7 File

      8:27

    • 43.

      6-1 Double Jump

      10:37

    • 44.

      6-2 Wall Slide

      10:16

    • 45.

      6-3 Wall Jump

      10:28

    • 46.

      6-4 Dash

      10:45

    • 47.

      6-5 Cooldown

      11:34

    • 48.

      6-6 Hat

      13:32

    • 49.

      6-7 Projectile

      18:21

    • 50.

      7-1 Pace

      12:03

    • 51.

      7-2 Invisible Walls

      6:41

    • 52.

      7-3 Ledge Detection

      9:24

    • 53.

      7-4 Flying

      11:48

    • 54.

      7-5 Crawl

      14:44

    • 55.

      7-6 Sight

      12:48

    • 56.

      7-7 Aggression

      14:25

    • 57.

      8-1 Combo

      12:21

    • 58.

      8-2 Hit

      11:35

    • 59.

      8-3 Juice

      12:51

    • 60.

      8-4 Shake

      7:39

    • 61.

      8-5 Death

      13:28

    • 62.

      8-6 Boss 1

      15:37

    • 63.

      8-7 Boss 2

      10:13

    • 64.

      8-8 Boss 3

      13:48

    • 65.

      9-1 Save Point

      11:55

    • 66.

      9-2 Metroidvania

      10:45

    • 67.

      9-3 Shortcut

      13:19

    • 68.

      9-4 Map

      11:19

    • 69.

      9-5 Music

      8:14

    • 70.

      9-6 Final Boss

      9:54

    • 71.

      9-7 Final

      5:42

  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

6

Students

--

Projects

About This Class

Learn how to build a Metroidvania game in Godot from the ground up.  Give your players unlockable abilities that they can use to gradually access and explore more of your game's world, a map that shows their location, and save points to keep track of their progress.

In this course, you will learn how to:
Use the Godot game engine to build games
Write scripts in GDScript
Object Oriented Programming design principles
Break down a complex project into individual components

Building an entire metroidvania game by yourself can seem like a daunting task, but this course will teach you how to break it down into small pieces that are much easier to manage, while teaching you the design structure that puts all the pieces together.

This course is for students of all skill levels.
If you're a beginner, it is recommended that you follow along with the course exactly as it is shown.  Every line of code will be typed on screen and every step of the process demonstrated.
For intermediate students, you may want to try using other assets, or adapting some of the game's mechanics to put your own twist on what is provided.
Advanced students should be able to use their own assets to design, and build their own Metroidvania game using this project a template for overall structure and inspiration.

All of the assets used in the videos will be provided so you can follow along, but you're welcome to use other assets or make your own if you prefer.

Meet Your Teacher

Teacher Profile Image

Thomas Yanuziello

Indie Game Developer

Teacher

Related Skills

Development More Development
Level: All Levels

Class Ratings

Expectations Met?
    Exceeded!
  • 0%
  • Yes
  • 0%
  • Somewhat
  • 0%
  • Not really
  • 0%

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. 0-0 Trailer: Welcome to my course on how to build ametroivnia in the Gadot game engine. My name is Thomas Ionizlo and I'll be your instructor. I have a bachelor's degree in computer science, and I've been teaching game development across multiple platforms for over five years. I've included a complete set of assets, including sprites, sound effects, and music that you can use to follow along or you can use your own assets if you prefer. This course will help you become familiar with the GdoGame engine. It's very efficient workflow for building games and its native scripting language GD script. You'll learn how to build and edit scenes using Gado's various nodes and alter their behavior with scripts. While writing these scripts, you'll learn how to apply object oriented programming principles to keep your code concise, scalable, and reusable for other projects. Most importantly, you'll learn how to structure a complex project of this magnitude by breaking it down into its individual components. We'll start by building and animating a two D platforming character. Then build an environment for the character to explore and move the game's camera. Next, we'll allow the character to transition between different rooms and draw the world on a map to help the player explore your world. After saving and loading the player's progress, we'll draw a heads up display. Then add our unlockable abilities, including a double jump, a wall jump, a dash, and shooting a projectile. Finally, we'll add in some enemies for combat, a boss fight, and keep track of the player's progress through the game. Whether you're new to game development, new to the Gadot game engine or just new to Metroid venias, this course will teach you everything you need to know to build a complete game from the ground up. If you need any help, our discord server is full of other students and budding game developers who can answer any of your questions. Click the link in my profile to join. You can preview the first two sections of the course for free, and I have another course that covers the basics of GD Script programming completely for free, too. Once you're ready, we can get started learning how to build a metrovnia in cado. 2. 0-1 Download: Hello friends. In order to follow along with this course, you'll need the Godot engine. Open any web browser and head over to godot.org and download the latest version, which as of the recording of this video is 4.5 0.1. The engine is free to use, but you may optionally choose to donate to support the development if you want to. After the download is complete, you'll need to extract the files from the zipped folder, then store them in a safe location on your computer. To run the editor, all you have to do is run the larger file that doesn't end in console. There are no installers or launchers to worry about. The first time you open Godot, your project list will be empty, and it will ask you if you want to explore some of the example projects that are available. We're going to ignore this and create our first project, which opens a new window. Here we can decide where to save the project. The default location is in the documents folder, but it's a good idea to create a separate subfolder to hold your Godot projects. Next, we can give our project a name. Don't worry about this too much right now. You can always change it later. A new subfolder will also be created with the same name to hold all of the files related to this project. Three different renderers can be selected, each optimized for different purposes. I recommend starting all of your projects using the compatibility renderer to start since it is best for web Builds and only switch your renderer later if you need better support for better quality visual effects. Version Control with Gi Hub is automatically supported and I highly recommend learning how to use it. Creating this project, it will be opened automatically so we can edit it. I'm going to close the editor though and reopen it. Our project is now included in the project list, and we can open it either by double clicking on it or selecting it from the list and clicking the Edit button. We now have access to the Godot and we can get started editing our first project. In the next video, we'll explore the layout of the editor window. I'll see you in the next video. 3. 0-2 Editor: Hello, friends. Before we start editing our project, we'll need to familiarize ourselves with the layout of the Godot Editor window. The majority of the window is filled with a three D view of the current scene. This project will be in two D, not three D. We can switch the view between three D and two D using the buttons at the top of the window. In two D, we have the X axis drawn in red, the Y axis drawn in green, and a blue rectangle which shows the viewport, or what will actually be drawn on screen when running the game. Note that the viewport is below the X axis with Y equals zero representing the top of the screen. The Y axis is inverted, so positive values for Y will move down the Y axis, not up. To the upper left, we have a tab for the current scene's structure, which is currently empty and is asking us to create a root node for this scene. Since our game will be two D, let's make a two D scene. The scene tab now shows our scenes root node, a node two D. We'll explore more about scenes and nodes in the next video. This dock also contains the Import tab, which will allow us to change the import settings for imported files we will use in our project like graphics and audio. These files will be stored and organized within the project file system, which we can see in the lower left. Currently, our project has only one file in it, which is the Gadot icon. In the file system tab, we can keep our project files organized by creating folders and searching through them using the filter. Returning to the scene tab, if we have a note selected, then its properties will be presented in the Inspector tab on the right. We can expand categories of these properties and edit their values here. This dock also contains the node tab, which provides a list of signals this node can emit, as well as which groups it is a part of. The history tab contains a list of every change we have made within the current scene or for the entire project. You can see all of the changes we made, plus I also edited the Editor Fontie. The bottom dock is currently collapsed, but we can click on the output button to expand it. The output will also show us changes we make to the project and also display text output from our game, which is very useful for debugging. We can manually output messages here, but the editor will also output warnings and errors here too. The debugger has a variety of useful tools to help us figure out what is slowing down or crashing our game. There are also tabs specifically for audio, animation, shaders and more tabs will be added here as they are needed. We can collapse this dock by clicking the currently selected tab. By clicking on the three dots, these docks can be rearranged to fit your needs, however you think will work best to accommodate your workflow. The individual tabs can also be dragged between docs. When you find a layout that works well for you, you can save it from the editor menu. You can also change a variety of editor settings to personalize the editor to fit your needs. When we eventually have a game to actually run, we can do so from the buttons in the top right corner. Either playing the game from the beginning or only running the current scene, pausing or stopping the game. In the next video, we'll learn how to build a scene. I'll see you in the next video. 4. 0-3 Scenes: Hello, friends. Now that we're familiar with the layout of the editor, let's start editing our first scene. We'll start by clicking two D scene, making the root node of our scene a node two D. A node two D doesn't actually represent anything in our game. It is only a two dimensional transform. A transform is a combination of a position, rotation, and scale. Godot also includes a skew component in two D transforms. If we actually want to draw anything, we'll need to add another node to our scene. Either right clicking on the root node and selecting Add child or clicking on the Plus button, we can add a child node to our scene. There are a lot of different kinds of nodes that all serve different purposes. The most common way of drawing something in two D games is using a sprite. So we'll add a Sprite two D node to our scene. Node types inherit from other node types. So a sprite two D node is also a node two D and a canvas item, and a basic node, and has all of the same properties inherited from these types, plus some more of its own. The Sprite two D node is added as a child of the root node, indicated by the indentation. Scenes are organized into a hierarchy, starting with a singular root node then branching into any number of child nodes. These child nodes can also have their own child nodes forming the scene tree. We can collapse branches of the tree or the entire tree by clicking on the arrow beside them, and again to expand them. The sprite node still isn't drawing anything because it hasn't been told what to draw. Selecting it, so we can see its properties in the inspector, where it indeed has properties inherited from node, Canvas item, and node D. Under the Sprite two D section, it also has a texture property, which is currently empty. We can load a texture file or drag the GAD icon from the file system tab into the texture field to populate it. And now the Sprite two D node is drawing the GADO icon texture as a sprite in our game. Let's duplicate this Sprite two D node in the scene tree by right clicking and selecting duplicate or selecting the node and pressing Control or Command D. The duplicate has all of the same properties as the original node and is renamed by appending an increasing number to its name. Siblings in the scene tree are not allowed to have the same name. We can move either of our sprites around in the scene view by simply dragging them. This will automatically update their transformed two D position property in the inspector. We can also rotate the sprite, scale it, and skew it if we want to. Next, let's drag this Sprite Tu Di node onto the other one, assigning it to be a child node of the other Sprite Tu Di node, indicated by another level of indentation. Now, if we select the parent Sprite tu Di node and edit its transform properties, these changes are inherited by the child. The child's own transform properties are defined in relation to that of the parent. This hierarchical structure of scenes and inheritance of properties allows us to build complex scenes with multiple different components. We can add any number of different nodes to the scene of different types, such as an audio stream player two D node, and a GPU particle two D node. Now this two D scene can draw something, emit sounds, and particles, and all of these components will inherit to their transform position from the root node. To make things easier, the transform of a root node should always have all default values and have only child nodes with their transforms edited. We can rename nodes, which is particularly useful for the root node of a scene to describe what it is. Let's call it something like robot. Then we can save this scene as a scene file in our project, and it will automatically be named the same as the root node. Then create a new two D scene and imagine this to represent a room containing the robot. Our robot scene can be added to the scene as a single node. We can even have multiple robots if we want to. From the context of this larger scene, we can now edit the transform properties of a robot to move it around, rotate, scale, skew, et cetera. Let's rename this scene Room and save it. If we return to the robot scene and edit the robot's child nodes, these changes will be applied to all instances of the robot in other scenes. The room scene does not need to be concerned with how the robot is constructed, only where it is within the room. Constructing scenes from individual components allows us to keep the details of individual objects contained and hidden away from larger scopes. It also allows us to easily produce multiple copies of the same thing, but with only one source for that object that needs to be edited. Building an entire game will consist of building many smaller scenes that represent individual game objects, characters, and locations, then combining multiple of these scenes together to create a larger scene while the game is running. In the next video, we'll explore how the engine provides useful built in node types for simulating physics. I'll see you in the next video. 5. 0-4 Physics: Hello, friends. Now that we understand how scenes are constructed, we'll go over how game engines provide built in functionality to make game development much easier. We can get rid of the robots and delete that scene from the project. Let's start by adding a rigid body to our scene. In the context of physics, a body is any object which can collide with other objects and has mass. A rigid body represents a solid object. It will be affected by external forces like gravity, collide with other solid objects, but will not deform or break. It will maintain its shape. This rigid body needs a shape, which is another node which must be added as a direct child of the rigid body. This shape node doesn't know what shape it is until we give it a shape resource in the inspector. From the drop down, we can select a few different options. I'll make a rectangle shape. Now this rigid body has a shape, but nothing is actually being drawn. So we'll add a Sprite two D node to it and have it draw the Godot icon texture. The sprite and collision shape do not match. So selecting the collision shape node, we can adjust it to better fit the sprite by clicking and dragging the red dots or by editing its properties in the inspector. Holding down the Alt key with the rigid body node selected, let's position it to be somewhere around the top middle of the screen. Then press the run current scene button or press F six as a shortcut. The game engine automatically applies forces to the rigid body to simulate real world physics like gravity. The mass and gravity scale can be quickly adjusted in the inspector to tweak how the rigid body behaves. We can even give it a physics material to add friction or bounce, adjust it center of mass, and many other options. For now, let's have this simple box collide with a floor. Very sturdy objects which we do not want to move are usually constructed from a static body instead of a rigid body. A static body can still collide with other bodies, but it won't move as a result of that collision and won't be affected by forces like gravity. A static body also needs a collision shape. From the drop down, I'll select world boundary. Unlike other shapes, a world boundary extends into infinity. In two D, this is represented as a line. The arrow in the two D view points away from the boundary. By default, it will create a floor. Moving the position of the static body node down to the bottom of the Viewport will stop the rigid body from falling beyond this world boundary. Let's duplicate the rigid body and see how it behaves when colliding with another rigid body. We can click on a node and change its type. So let's change this rigid body into a static body. Then position and rotate it so that the rigid body will collide with it in midair. Remember to rename nodes after you change their type so you know what type they are. But you can also tell by the icon beside their name, too. If we were to manually program all of this ourselves, it would take a lot of work to draw these sprites, calculate how they are affected by forces, figure out when they collide with each other, and determine how that affects their behavior. Along with physics, game engines help us by automating a lot of the functionality common in most games to drastically speed up and simplify development. In the next video, we'll learn how to attach scripts to nodes to edit their behavior. I'll see you in the next video. 6. 0-5 Scripts: Hello, friends. While the game engine gives us a lot of built in functionality, we can also add our own by attaching scripts to nodes. Selecting any node in the scene tree, I'll use the static body that is a floating box. We can either right click and select attached script or press the attached script button. This opens a new window. The script will automatically inherit from the type of node it is being attached to, and we can start our script from a template if we want. We will also specify a name for the script, which should reflect its purpose. I'll name my script spin, as well as the folder it is saved in. Clicking the Create button will automatically switch the view to script view, so we can start editing the script. The template starts us off with two functions, ready and process. Any text following the Octothorp symbol is a comment. It is not used by the engine and only exists for our benefit to help us better understand the script. As these comments describe, the ready function will automatically be called when this node enters the scene tree for the first time, which is a little bit misleading. But for now, it will mean when the game first starts. Process, on the other hand, will be called every frame while the game is running, which with default project settings will be 60 times per second. A function declaration starts with the keyword funk, followed by the name of the function. These function names start with an underscore, which is the way in which Gudo marks functions as private, meaning they are only meant to be used within the context of this script. The function name is followed by parentheses, which will contain parameters for the function if there are any. The process function has one parameter, Delta, which is the number of seconds which have passed since the previous frame. So this will most often be one 60th, but will sometimes increase if processing a frame takes longer than expected. The parameters are followed by a hyphen greater than an arrow, then the return type of the function, which in both cases are void, which means they do not return any value. Declaring return types is optional, and these can be removed if you prefer. The contents of a function are indented one tab, no need for braces. Both of these functions contain only the keyword pass, which basically means do nothing. But we can replace this instruction with something different. To see how this works, let's change the body of the ready function to do something. The print function will output anything we want to the output panel when the game runs. We can call functions by specifying their name followed by parentheses. Then inside the parentheses, we can provide arguments matching the functions declared parameters. Inside quotation marks, we can write anything we want, such as ready to spin. Anything we put inside quotation marks is known as a string, short for string of characters. In the process function, let's do the same thing, but this time, print the value of Delta. Let's also do what this script was meant to do and modify the value of this nodes rotation, increasing it by Delta using the plus equals operator. The plus equals operator will take the value of Delta and add it to the value of rotation. Then also assign the result to rotation. Running the scene, we can see that the box is spinning. Stopping the game, we can see in the output that the ready message was printed first, only once, followed by many numbers, most of which are one 60th. How does this work? Both ready and process are functions of node, meaning every node in our scene tree already has these functions, but they aren't doing anything. Our script is overriding the definitions of these functions for one particular node that this script is attached to. We can see in our script when a function is overriding a function definition when it has a blue arrow in front of it. When we run the game, the engine will reconstruct the scene tree for this scene, starting from the root, adding one node at a time from top to bottom. The ready function for each node will be called after it is added to the scene tree, but also after all of its children have been added and their ready functions have been called first. This means parents can rely on their children being added to the scene tree before they are considered ready. After the entire scene tree is ready, then the process frames will start. Process will be called on every node in the scene tree from top to bottom in sequential order. Unlike ready, parents will not let their children process first. Now that we have a basic understanding of how Godot works, in the next video, we'll start setting up the project to make our game. I'll see you in the next video. 7. 0-6 Game: Hello, friends. Before we start our game project, we'll need to prepare a few things. First, we can either delete everything or start a new project so we have a clean slate. I'll remove everything except the floor collision. Then rename this scene and its root node to game. The game scene will be one centralized scene where all of the main gameplay takes place. The game root node will have two main child branches, a world environment node, and a canvas layer node. These represent a clear distinction between what exists in the game's world, like the level the player is currently playing in, all of the characters items, et cetera, and what exists outside of that world purely for the player's benefit, information displayed on a heads up display or user interface. The world Environment node will have several children. One or more nodes for the current levels that are loaded, for now we'll just represent that as a static body tutti. A character body tutti node is most often used for the player character and a camera Tutti node that usually follows the player. I'm going to reset the position of the floor collision back to the scene origin. With a camera node in the scene, the blue rectangle no longer represents what part of the world is drawn on screen, instead, the camera's view port is shown in Magenta, currently centered at the origin. So the floor will be at the center of this camera's view. The canvas layer will have children for things like displaying the player's information on screen, menus, et cetera. These will all be represented with control nodes. The Canvas and control nodes will still use the blue rectangle as the viewport for the sake of arranging their positions on screen. How this works while the player is playing the game is that the camera will view the world and draw what it sees, all of the children of the world Environment Node. After the camera has rendered a rectangular Toti image of the world, the Canvas layer will draw its children over top of that, creating the typical gameplay screen that we are used to seeing. These two branches also share another clear distinction. The world will need to be paused, but the canvas layer will not. So selecting the game node in the inspector under node, expanding process, will change its process mode to always. The world environment node will be set to pausble and the Canvas layer will stay as the default inherit. It will inherit the process mode of its parent, which is the game node, so it will also be always. Since the default process mode is inherit, all children of the world environment will inherit being pausible while children of the Canvas layer will inherit always processing. The first time we press the Run Project button in any new project, we will be prompted to set the main scene. We can select the current scene or select another one from the project. With the game scene currently open, we can set it as the main scene for now and change it later when we have a title scene. This can be changed in the project settings under the run settings. It's also a good idea to set your display settings before getting started. So select window. Here we can set the viewport width and height measured in pixels. I find 12 80 by 720 to be a good size for most games, and it's compatible with most platforms. Under stretch, changing the stretch mode from disabled to Canvas items will ensure that two D nodes will stretch or shrink to fit the game window. Since I'll be using pixel art, I'll change the scale mode from fractional to integer. So pixels will only double or half in size, avoiding mixed size pixels. Under rendering textures, I'll also change the default texture filter from linear to nearest. This will draw my pixel art assets exactly as they are, preventing them from being blurry. After closing the project settings, you may need to trigger an update by just zooming or panning the viewport to force the blue viewport rectangle or camera to resize themselves. Now we are ready to start breaking down the project into its individual components and tackle them one at a time. In the next section, we'll create a basic Tutti platforming character that the player can control. I'll see you in the next video. 8. 1-1 CharacterBody: Hello, friends. In this section, we're going to build a two D platforming character that the player can control. If your game scene doesn't already have a character body two Dnode, you should add one now. The character body two D isn't a body in the sense of a person, but a physics body, like the rigid body or static body. Unlike rigid bodies, a character body does not automatically have gravity applied to it but will react to collisions, unlike a static body. So the character body node also needs a collision shape as a child of it with a shape resource to tell it what shape it is. Most games use a capsule to represent a character, but rectangles and circles are also used. For now, I'll use a rectangle with dimensions of 128 by 128. We can always change or resize to collision shape after we have a drawing of the character to base it off of later. In addition to a collision shape, we'll also need a sprite node to draw the character. It is important not to use an Animated Sprite node as this is not its intended purpose. Animated Sprite nodes are only for drawing simple animated sprites. As soon as you have to synchronize any other nodes with your animations like sounds, particles or functions, you'll need more flexibility than the Animated Sprite node can provide. For now, I'll just use the Godot icon as the character's texture, which is 128 by 128 Pixel square matching the collision shape. Let's rename the character body Toti node to the name of the character. While we might not have any intention of having multiple copies of this character, it's still a good idea to compose its structure in a separate scene, isolated from the game scene. We can accomplish this by right clicking on the node, then selecting save branch as scene. Let's create a new subfolder for scenes, another inside that for characters, then save it as the name of this character. In the game scene, this character is now represented as only a single node with a clapperboard icon, telling us that it is a scene. All of its child nodes are hidden, now contained within the character scene. We can open the character scene by clicking on the clapperboard icon. In the character's scene, it is good practice to consider the origin of the character to be at the center point between their feet as if the X axis is the floor. This ensures that all characters will have their origin point in common regardless of their size. So let's move the Sprite and collision shape nodes up along the Y axis. In addition to the collision shape and Sprite nodes, our character will need an animation player to play the Sprite animations. Also an Animation Tree to control which animation is playing at any given time. An audio stream player two D node will allow us to play sound effects too. We may also want a GPU particle emitter to create particle effects for things like footsteps and jumps. Each of these nodes will be covered in their own video to explain them in more detail. For now, it is important to understand how a scene can represent anything in the game with its own scene having many child nodes that are used to build it. The root node of this scene will usually have a script attached to it. Depending on how you prefer to organize your files, you may want to keep this script in the same folder as the scene it is attached to or in a separate folder specifically for scripts. You should name your scripts after their intended purpose in as vague a name as possible. This script won't be specific for my kiddy character, but should be written such that it could apply to any character, at least for now. The purpose of the script will be to not only control the character body node itself, but also to conduct the operations of all of the child nodes to operate in harmony as needed to create our character. This script attached to the root node of our character scene is also accessible from the game scene and will serve two additional purposes. It will obfuscate the processing of the individual components of the character scene while also providing a clean and concise interface for interaction with other scripts and objects in the game scene. This will be explored in further detail throughout the rest of this section. Now that we have a basic character structure ready, we'll need to receive input from the player to control them. I'll see you in the next video. 9. 1-2 Input Map: Hello, friends. In this video, we're going to receive input from the player and send it to the character. Since I created the script for my character body Tu Di node from a template, it already has some code that will receive input and move the character left or right. If we run the game scene, pressing the left and right arrow keys will move the character, but it doesn't feel very good and isn't implemented very well. The comment provided even tells us that we should at least replace the UI left and right actions with our own custom gameplay actions. To do this, we'll open the project settings, then switch to the input Map tab. In the field that says add new action, we'll type the name of our action, starting with move left. This must not contain any spaces and is usually written in lower snake case, all lowercase separated with underscores. Press the Enter key or click the Add button to add the input action, then make another one for move right. And we'll also need one for Jump later. To the right of each of these actions, click on the plus button to add an event to this input action. This will be the button that is pressed to trigger it. The listen field will automatically detect input events, or you can search through the input events available. To move left, I'll use the left arrow key, moving right with the right arrow key, and jump will be the z key. We can add multiple different events to each action, meaning we can add controller support easily by adding extra events for the controller. I'll use directional pad left and right for moving the character, as well as left stick left and right, and also the bottom face button for jumping. When you're done, close the project settings. In this video, we're only going to change this one line where we are retrieving input, and more importantly, move it to a separate script. It's good practice for all of your scripts to have only a single purpose. This script will only be used to control a character, not to receive input from the player. Adding a new node to the scene tree, this one can just be a normal node since it won't be doing anything other than running our script. Let's name it Player. Then attach a new script to it. There are multiple ways to receive input. The most common is to override the input function. Function overrides must be an exact match to the function being overridden. So the name must have the exact same spelling, the same parameter types and the same return type. The input function has one parameter, an input event, which we can name whatever we want. But I prefer to use their default names, and it returns nothing. The input function will be called during any frame when any input action event happened, whether that is pressing a button, tilting an analog stick or moving the mouse. Inside this input function, we can access the input event by its name and access its contents with a period. We want to call a function of the input event called Is action pressed. When calling a function, the function name must be followed by parentheses. Inside the parentheses, we provide arguments to the function matching its parameters. In this case, a string, the name of the input action we are checking. So we'll write jump in quotation marks, being careful to match the exact case and spelling of the input action we created in the project settings. This function returns a boolean, either true if the button was pressed or false if it wasn't. By putting this function after the keyword if, then followed by a colon, we can create a new indentation level for our script. Anything we put inside here will only run if the result of the I statement is true, so only if the button was pressed. For now, we'll just print out the word jump. Running the game, if we press any of the input events we specified as the jump input action, we will see that jump is printed in the output panel. We don't need the ready function, so let's remove that. For things like movement, especially when using controllers with analog sticks, it can be useful to check for this input inside the process function. Just like the character script was doing, we'll need to use the input singleton. This singleton class has a function called get axis. The G axis function requires two parameters, a negative action and a positive action. Left is always considered the negative and right positive, matching the X axis in our two D world. So we'll write the names of our input actions we created in the project settings in quotation marks, move left and move right. This function will return a decimal number between negative one and positive one, representing how far the left stick is tilted in either direction, or simply negative one or positive one if using keys or buttons. Since a value of zero, meaning no input is being given is still a valid input, we will always want to send this information to the player every frame. So there's no need for an if statement here. Let's just print out the value being returned here so we can see it. Running the game, the number zero is being printed repeatedly. Pressing the left or right arrow keys, the direction pad keys on my controller or tilting the left analog stick changes the value between negative one and positive one. If you ever want more information on any of these classes or functions, you can access their documentation by holding the control or command key and clicking on them. Opening the character script from before, we have a variable named direction being declared inside the process function. Let's comment this out and move this variable into the global scope of our script by declaring it at the top of the script instead. Switching back to the player script where we received movement input to pass this information from one script to another, we'll need a reference to the other script. In the player script, we'll create a variable which holds a reference to a character body to dende. By adding an export tag to the front of the variable declaration, the variable will be accessible from the inspector, and we can assign its value as our character. Then instead of printing out the value of input dot get axis, we'll send it to the character by assigning it to the character's direction variable. When we run the game, the player node will be checking every frame for the state of our move left and move right inputs. Then assign that to the character's direction variable. The character scripts process function will check the direction variable, every frame and use that to move the character left and right. We now have our player giving input to move our character. I'll see you in the next video. 10. 1-3 Movement: Hello, friends. In this video, we're going to improve the character movement by applying physics. In the character script, we can see that the default template is checking if the value of direction is zero. Zero is treated as false and any other value is true. So if given any value of direction that is not zero, the character's X velocity is being set to direction multiplied by speed. Or if direction is zero, then it is moving the value of the character's X velocity toward zero by speed. The result of this is that the character's velocity is either zero or speed, except in the case of tilting the analog stick. This isn't very realistic and doesn't feel very good. These speed and jump velocity values are also defined as constants, which, unlike variables are never allowed to change. This is problematic, and we should change this if we ever want to have the characters move at different speeds in our game. We'll deal with jumping later. Another thing to note is that these values are measured in pixels, so they will vary based on the size of your character. For now, let's replace our speed constant with a move speed variable of type float. I'll set mine to 256 pixels per second. It's a good idea to separate variables and functions into what is meant to be public versus private by preceding private names with an underscore. Anything private is meant to only be accessed from within this script itself, unlike the direction variable, which is being accessed and changed from the player script, so it is public. In the physics process, we'll replace the speed constant with our move speed variable. And now everything works exactly as it did before, except that the speed is a bit slower. If we add an export tag to the front of this variable declaration, we'll be able to change the value of it in the inspector. This is a great way of testing and tweaking the value to get it feeling just right. Since we can run our game, see if it feels good, then change it and test again. Another benefit is if we have multiple characters using this same script, each can have their own value for this variable. But characters don't usually go from standing still to moving at their maximum speed instantaneously. Instead, they have some amount of acceleration applied over time before reaching their maximum speed. This can be accomplished by declaring another variable named acceleration, also afloat and will be measured in pixels per second squared. If it makes it easier, you can consider its value to be a multiple of your speed. If my acceleration is double my speed, then I'll reach maximum speed in half of a second. Or if it's half of my speed, then it will take 2 seconds to reach top speed. To apply acceleration in the physics process, all we have to do is move the value of our current X velocity toward speed multiplied by direction at a rate of acceleration. But this will happen all at once in a single frame. To adjust velocity over time, we have to do what the template is doing here where it applies gravity and multiply by Delta. Since Delta is the amount of seconds that have passed since the previous frame, usually one 60th of a second, this will cause the rate of acceleration to be applied over time rather than all at once. The move toward function takes what is in the first parameter, moves it toward the value of the second parameter by the amount of the third parameter, and returns the result. It doesn't modify the value of the first parameter, so we have to assign it to overwrite the previous value of our X velocity. The move and slide function is defined in the character body two D class and will perform all of the difficult calculations for us, considering the character's velocity, external forces like gravity, collisions with other physics bodies, and then decide where the character should be each frame. Running the game now, we have gradual acceleration being applied before we reach our maximum speed, and we can also adjust the value of our acceleration in the inspector to get it feeling the way we want. We can remove these comments. The same logic can be applied to deceleration by simply adding another variable. This value is usually much higher than acceleration. The code in physics process is already using move toward. We just have to replace speed with deceleration times Delta. However, there is another time when we may want to apply deceleration other than when the player isn't giving any input, which is when they want to turn around and go the other way. We can expand our I statement to consider this case. After checking if direction is not zero, if our X velocity is zero, the character is standing still, then we still want to accelerate. But also, if the sin of direction is the same as the sine of X velocity, then the player is trying to keep moving in the same direction they are already moving and we want to keep accelerating up to speed. The sine function will turn any number into negative one, zero or positive one, making comparing whether these two values are both positive or both negative simpler. We can combine conditions together with or. So if either of these values are true, then the whole statement is true. If both of these are false, then we want to turn around, which means applying deceleration instead of acceleration, resulting in a much quicker turnaround than it would by applying acceleration if the value of deceleration is higher. Testing this out, moving our character left and right should feel a lot more realistic now, and we can easily adjust the values for our speed, acceleration and deceleration in the inspector. I'll see you in the next video. 11. 1-4 Jumping: Hello, friends. In this video, we're going to improve how the jump is implemented. Before we get started with jumping, I'll move the camera up along the Y axis so the floor is at the bottom of the screen, not the middle. The template script for our character already has a jump button using UI except, which is the space bar, setting the character's Y velocity to jump velocity. But we already added our own jump button in a previous video. Unlike gravity or acceleration, which are multiplied by Delta and applied to the character over multiple frames, this jump velocity is applied to the character all at once immediately. Running the game, we can press the space bar to jump, but there are multiple problems. The jump is always the same height, and we have the character script checking for input. Let's first remove this from the physics process of the character and instead put it in a public function. We'll call it jump. Then in the player script, when pressing the jump button, instead of printing jump, we can tell the character to jump instead. Accessing the character's contents with a period followed by the name of the function, and the function has no parameters, so there's nothing in the parentheses. But now we are allowing the character to jump anytime the button is pressed, even if they aren't on the floor. We need to check if the character is on the floor first before allowing them to jump. This is on floor function is defined in the character body two D class. Now the character can only jump if they're on the floor, but the jump is always the same height. In most games, the character will jump higher holding down the button than if it is just pressed. Sometimes we can measure how long the jump button is held before releasing it, then pass that information through the jump function call to adjust the jump force. But especially in two D platforming games, we usually expect jumps to happen immediately when we press the button, which means we can't wait for the button to be held down or released. So instead, it is more common to assume that the jump will be the full jump height and then cut it short if the player releases the button early. The same way we reacted to a button being pressed in the player's script, we can react to it being released too. Using the keyword if, this will only be checked if the previous if conditions were false, removing the redundancy of checking if the button was both pressed and released at the same time. If the jump button is released, we can call a different function to cancel the jump cutting it short. Back in the character script, we need to declare the canceled jump function, requiring no parameters and returning no value. A simple way to do this would be to set the character's Y velocity to zero, but this can feel pretty unnatural coming to a complete stop mid air. So instead, I'll divide the Y velocity by two. But we only want this to happen if the character's velocity is currently less than zero, meaning they are moving up and only if their velocity is less than zero because they jumped. If there were any other reason why the character was moving up, but they did not jump, we wouldn't want to let them cancel a jump and reduce their velocity. We can control this by declaring a new variable. We'll call it Is jumping of type Boolean, which will naturally default to false. If we set this variable to true when the character jumps, then we can check if the character is jumping before allowing them to cancel the jump. In the case that they don't cancel the jump, we need to set the is jumping variable back to false automatically. So in the physics process, checking if the character is jumping and if their y velocity is greater than or equal to zero, meaning the character is falling or at least not moving up, then we can set is jumping back to false because it is too late for the character to release the button to cancel this jump. This works great, but now we might want to adjust how high the character can jump. The template script declares jump velocity as a constant. Just like we did with the movement speed, we can switch this to be a variable. But knowing how much velocity is required to reach a specific height isn't very intuitive, and from a design perspective, it's often better to be able to control exactly how high a character can jump. So let's declare another variable called jump height as an integer. This will be measured in pixels. I'll set mine to 256, which is twice the height of the character. Using the on ready tag, we can use some math to calculate the jump force required to reach this exact height. The on ready tag will run this line of the script when the node is ready before the ready function. I'm not a physicist, so I would not be able to explain why this equation works, but it does. Just take the square root of gravity multiplied by jump force times two. Then also multiply all of this by negative one since negative along the y axis is up. In order to do this, we need to know the force of gravity being applied to the character as a decimal number, afloat. So declaring another variable using the on ready tag, this one must be above the jump force variable, so it will be set first. We can get the force of gravity from the project settings. We just have to specify the path to the setting we want in a string. Finally, when the character jumps, we'll set their Y velocity to this calculated jump force. Now the player can control how high they want to jump. And we can easily edit a character's maximum jump height from the inspector. I'll see you in the next video. 12. 1-5 Falling: Hello, friends. In this video, we'll adjust how the character feels and controls in midair. Our character can jump higher now, but it feels very floaty, and the character can control their movement in the air just as easily as they could on the ground. You may want this for your game, but often the player has less control over the character while in the air. In the character script, we can remove these old constants since they're no longer being used anywhere. Since our script is doing multiple things, moving the character left and right and jumping, it would be a good idea to keep it organized. I'll keep my move speed, acceleration, deceleration and direction variables grouped together and do the same with variables related to jumping. We can even create categories for our variables using the export category tag and specify the name of the category as a string. Let's have a separate category for variables related to locomotion and another for jumping. This adds headers to the inspector, so our exported variables will be organized into these categories. Our jumping category has only one variable, but we'll add some more, starting with a gravity multiplier. This will be a float and I'll give it a default value of one. Then multiply our gravity by the gravity multiplier after retrieving it from the project settings. Since this number is used to calculate the jump force required to reach our desired jump height, the character's jump height will not change, and the force will automatically be increased to compensate. Opening the project settings, looking under Physics two D, default gravity is set to 980 pixels per second squared. We can modify the force of gravity here for the entire project and will affect all of our physics bodies. We may want some things to feel heavier or lighter than others, and an easy way to do this is to multiply their own individual gravity by a different number. Increasing our character's gravity multiplier will make their jump faster and make the character feel heavier. Lowering the gravity multiplier will make them feel more light and floaty. Next, we can reduce the amount of control the player has over the character using more multipliers, commonly referred to as air control and air brakes. Air control being used when the player is giving movement input in midair and air brakes when they are not giving input in mid air. I'll set both to be 0.5, meaning the character's acceleration and deceleration will be halved. Down in the physics process, we can better organize everything that is happening by dividing it into different functions, one which is called only when the character is on the floor and one when they are not. We'll pass along the value of Delta to both functions. Defining a separate ground physics function, we can now focus on only applying the appropriate physics for when the character is on the ground. Then create another function for air physics and use this function to control the character differently than when they are on the ground. Here is where we'll apply the force of gravity. Then also copy the controls for the character moving and not moving from the ground physics. Turning around is less important in midair since there is no ground to provide any friction, so it wouldn't make sense to turn around any faster. All we need to do is multiply the character's natural acceleration by the air control multiplier and their deceleration by the air brakes multiplier. Another method is that you could consider air control and air brakes to be alternative values that replace acceleration and deceleration rather than using them as multipliers. You may also want to consider adding a terminal velocity, depending on how much falling the character will be doing for long distances and whether the player needs to have better control in such situations. A terminal velocity is the maximum speed an object will be able to travel through the air, since the force of air resistance will eventually equal the force of gravity and cancel each other out. Since we don't really have a proper environment to test this with yet, all we can really do is set the character's jump height to something very high, maybe even zoom the camera out so we can see higher up. But we can see that the character will move very fast when following long distances, and it is very difficult for the player to control with any accuracy. So let's export another variable for our terminal velocity. In Earth's atmosphere, a skydiving human will reach terminal velocity at about 54 meters/second. If 100 pixels is 1 meter, then that's 5,400 pixels per second. To apply this limit in the air physics function, we can simply use the Min function to return either the character's current Y velocity or the terminal velocity, whichever is lower. Then assign the returned value to the character's Y velocity. Now we can easily limit how fast the character is allowed to fall in the inspector, giving the player more control when they are following long distances. I'll set mine to 4,096 for now. I'll see you in the next video. 13. 1-6 Running: Hello, friends. In this video, we'll add a run button that the player can hold down to change their movement speed. First, I'll reset the camera's Zoom and position it where it was before. The character's movement speed when playing with the keyboard is always the same, but we can adjust the movement speed by only partially tilting the analog stick. In some games, this is enough to allow the player to either walk or run. But in others, there's a designated run button for the player to hold down to move faster. Opening the project settings, we can add a Run button to our input Map. I'll use the shift key or the right face button on my controller. In the player script, we'll use the same functions as we did with the jump to check for the run button being pressed or released. When the run button is pressed, we can tell the character to run, and when released, tell them to walk. While we're here, we can deal with this warning that the process function is not actually using the value of Delta. In order to override an existing function, the parameters must match exactly, so we can't remove Delta entirely. Instead, the Godot engine would prefer that we mark this parameter as unused by preceding its name with an underscore. Alternatively, we can open the warnings panel and click on Ignore to tell the engine to ignore this warning. Now the character script will need these public functions, run and walk. We'll start by adding two more exported variables in the locomotion category, a walk speed and a run speed. Then the move speed can have its tag changed from export to on ready and its value assigned to walk speed by default. Giving the walk and run functions a declaration, accepting no parameters, and returning no values, we only need to set the value of move speed to run speed or walk speed. Then the physics process will handle the rest by using the current value of move speed. That's all it takes to change our character's movement speed while holding a button. We could easily reverse this to have the character run by default, but hold down a button for walk by just changing the default value of move speed to run speed and having the player script call the opposite functions. Since this lesson was so short, I'll also prepare for the next video by adding a block for the character to jump off of using a static body node. A I'll see you in the next video. And 14. 1-7 Coyote: Hello, friends. In this video, we're going to help the player out by adding accessibility features that will make our character feel more forgiving to control. I'll start by resetting the character's jump height back to a reasonable number. In most two D platforming games, the game will allow the player to press the jump button for a short time after their character has walked off of a ledge and also when they are falling and very nearly landing. In both of these cases, the is on floor function will return false, but it would make the game feel better to the player if they are still allowed to perform a jump. To add these features, let's start by changing the return type of the jump function from nothing to a boolean. So we will return true if the jump was successful or false if it wasn't. That way, the player script can react to the jump button being pressed too early and retry the jump after the character lands on the floor. Switching to the player script, instead of just calling the character dot jump function, we can react to its return value by putting it inside of an if statement. If jump returns true, we don't need to do anything, but if it returns false, we can assume that the player wants to jump when it becomes possible if that happens relatively soon. So if not jump, if jump returned false, then we will buffer this jump input for a brief window of time. The easiest way to keep track of time is using a timer node. We can add this as a child of the player node, or since the player node doesn't have a specific type that we're using it for, we can change its type to be the timer itself. Looking at its properties in the inspector, we can set how long the timer is, which will be the window of time during which the input will be buffered and set its one shot property to true. This means that once started, the timer will only count down to 01 time. The default behavior would be to count down to zero repeatedly. Next, let's declare a new variable. We'll name it buffered input of type callable. Callable is a class that holds a function call complete with arguments. So if our jump fails, we want to set our buffered input to be character dot jump without any parentheses, since we are not calling the function now, but storing it in a variable. And also start the timer. Since changing the type of this node to a timer, we'll have to change the type of node that this script extends at the top of the script to be able to access functions of the timer class. Then in the process function, if this timer is running, if it is not stopped, then call the buffered input. If the buffered input returns true, then it succeeded, and we can stop the time. If the buffered input returned false, then it can't happen yet this frame, and we'll want to try again next frame until the timer runs out or it succeeds. For testing purposes, let's also use some print statements to print out when the jump is buffered, when the buffered input fails, and when it succeeds. Running the game, if we press the jump button early, the jump input is buffered. It fails a few times then succeeds when the character hits the ground. If we press the jump button when the character is way up in the air, then the input is buffered, but it fails until the timer runs out and never succeeds. Once we're satisfied with the performance of our input buffering, we can remove or comment out these print statements. Next, we want to allow the character to jump if they have recently walked off of a ledge, commonly referred to as coyote time, referring to Wiley coyote. In the character scene, we can add a timer node and rename it coyote. Like the input buffer timer, it will be a one shot timer, and we can adjust the duration of the timer. With the character scene open in the character script, we can grab a reference to any child node by holding down the control or command key, then clicking and dragging the node into the script. This automatically creates a variable with the same name and uses the on ready tag to set its value using the node path notation starting with $1 sign. Using this dollar sign node path notation requires that this node must exist with the exact node path relative to the node that this script is attached to. Otherwise, it will cause an error. However, this coyote time is a feature that is generally only applicable to the player character. We may not want to have this node in every character scene. So we can use the function get node or null passing the same node path as a string argument. This function will either return a reference to the node following this node path or null if it doesn't exist without causing any errors. In the jump function, our check to see if a jump is possible will now either be if the character is on the floor or if the character has a coyote timer and the coyote timer is not stopped. In logic operations, and will be evaluated before or unless you include brackets. In regards to the A statement, the left side will be checked before the right side. So if the coyote timer does not exist, the A statement will be considered false. So the right side of whether or not the coyote timer is running will not be checked. Now all we have to do is start the coyote timer. To do this, we need to know when the character walks off of a ledge. I'll consider this to be included in the jumping category, and we'll declare two new variables. Is on floor to store the returned value of the I on floor function for this frame. This will allow us to perform this check once per frame and store the results so we can use it multiple times, saving the need to call the function multiple times per frame and was on floor, which will hold the value of is on floor from the previous frame. During the physics process, we'll set the value of was on floor to is on floor, then is on floor to the returned value from the function is on floor. Any other calls to the is on floor function can use our variable instead to be more efficient. Then if the character was on the floor in the previous frame and is not on the floor of this frame and their y velocity is greater than or equal to zero, meaning they did not jump, the only logical conclusion is that this character has walked off of a ledge or the floor they were standing on has disappeared, and this is the exact moment when the coyote timer should start. For testing purposes, let's add print statements so we know when the coyote timer starts and when a coyote jump is successfully performed. This will require a redundant if statement, but we'll remove it later. Okay. Running the game to test it out. We can walk off of a ledge and see that the coyote timer starts. If we press the jump button while the coyote timer is running, then the character is still allowed to perform a jump. We now have a solid Toti platform and character controller. In the next section, we'll draw and animate the character. I'll see you in the next section. I 15. 2-1 Sprite: Hello, friends. In this section, we're going to take our simple Gadot icon and polish it into a fully animated character. For that, we're going to need assets, images and audio files. The ones I'm using will be included if you want to use them, but I recommend intermediate students to use your own or other free assets. Before we start importing assets, it's a good idea to create a separate folder to hold them. We can also create different subfolders for different types of assets like sprite sheets and sound effects. Click on the folder you want your imported assets to be in, then simply click and drag the assets into the Gadot window. The importing process will happen automatically, and they will be sorted into the focused folder. If they're not in the correct folder, you can always drag them into the file system tab to reorganize them too. But this will trigger a reimport, so it's better to import them into the correct folder to begin with, especially for larger files. Open your character scene if it isn't open already. Using these assets to draw the character might be as simple as replacing the Gadot icon with your imported image file if your assets have only one sprite per file. The assets I'm using are organized into a sprite sheet, so there are multiple sprites in a single texture, which isn't what I want to draw. Using the drop down, we can select other types of textures, and the one for using a sprite sheet is called an atlas texture. Clicking on the atlas texture to expand it, we can edit it right here from the inspector. Assigning the sprite sheet as the atlas. We can then edit the region of the atlas, which will be drawn by the atlas texture. When selecting a region of the sprite sheet to use, we can use the auto slice feature to select a single sprite or pixel snapping to click and drag a specific region. Since these sprites are all evenly sized and spaced, I'll use this 32 by 36 pixel region. When changing the sprite, the width and height will not change. Only the X and Y position will increment by uniform values. This makes it much easier to animate the character. Before committing to using a set of assets to animate a character, it might be worthwhile to edit your sprite sheet to have this even spacing to save your time and effort. I'll move the sprite down so it rests on the X axis. Its Y position is now half of the sprite region's height. Now that we have an accurate visual representation of our character, we can better fit the collision shape. We can reorder the child nodes to draw the collision shape over the character sprite to make things easier. I'll use a capsule shape and make it large enough to include the head but not the ears and the body, but not necessarily the arms, tail or weapon. This will be used to collide with the environment, so anything outside the collision shape will overlap with the walls, ceiling or ground. Make sure that your collision shape and sprite remain centered about the Y axis and sit comfortably on the X axis as if it was the floor. If your character's size changes significantly, you will probably want to change the values of their variables for things like movement speed and jump height. We can also type mathematical equations into these fields, and the engine will determine the result for us. But I'll reorder the collision shape to be behind the sprite again. With the character centered along the Y axis, in order to make the character face left or right, all we need to do is change the value of the flip horizontal variable in the Sprite two D node. Switching over to the character script, we'll grab a reference to the Sprite two D node and store it in a variable by holding down the control or command key and dragging it into the script. Then we'll declare a public function named face left, accepting no parameters and returning no value. While the current intention is to only use this function privately within this script, you can probably imagine that telling a character to face a direction could be useful in a public context. To face left, all we need to do is set the value of the sprites flip H property to true. We could also write another function named face right, but the function would only be setting the value of this variable back to false. Sometimes it's easier to write our functions to be more flexible. Instead of having both a face left and face right function, we can give the face left function a parameter that lets it do both. I'll just name it left and give it a default value of true. Giving a parameter a default value makes it optional. So this function named face left, can be called without providing any arguments, and we will assume that they want to face left. We can then set the value of flip H to the value of left. To make the character face right, all we have to do is call face left but provide false as an argument rather than calling a completely different function. In the physics process, if a character's direction is negative, then they want to face left. And if it's positive, then they want to face not left, which is right. If direction is zero, then they will remain facing the same direction they were in the previous frame. To get a better view of my small character, I'll zoom the camera in by a factor of four and reposition it so the floor is at the bottom of the screen. O. When we hit play, the character is being drawn and their sprite flipped to face the correct direction, but they still aren't being animated. I would like my character to be faster, so I'm going to increase their values in the character scene. I'll see you in the next video. 16. 2-2 Animation Player: Hello, friends. In this video, we're going to add animations to our character. If your character doesn't have an animation player node, you should add one now. With the animation player node selected, the animation tab should expand automatically. If it isn't open, you can open it by clicking on the Animation button. The animation panel is currently empty as there are no animations yet. Clicking on the Animation button, we will select New to create a new animation and name it idle. With an animation selected, there is now a timeline measured in seconds. If you prefer, you can change the seconds to frames per second, and the timeline will be displayed in frames instead. The frames are very close together, so I'll zoom in to display frames further apart from each other. We can change the frame rate too. I'll set mine to 12 frames per second. It also helps to apply snapping to the timeline cursor with this toggle. Now when we scrub through the animation timeline, it is automatically snapped to the nearest frame. From this panel, we can add tracks to the animation by clicking on the plus button. The most common being a property track, which allows us to edit any property of any node in the scene tree over time. The node we want to animate is the sprite Ti node, specifically, it's texture property. This adds a track to the animation timeline, and on this track, we can add keyframes to edit the property to a new value at that time. Right clicking on the track, we can select Insert key to add a keyframe to this track. By default, the value of this keyframe will be its current value, which is the same texture we set it to in the previous video. This is good enough for single sprite files, since we can just change the texture here. But since I'm using a sprite sheet, I don't actually want to change the texture. I just want to change the region of the texture that is being used. There are a couple of ways to do this. One is to click on the property track and edit its property name, adding a colon and the word region after texture. This tells the animation that I want to edit the region property of the texture, not the texture itself. I'll reset the animation back to being empty for now. Make sure to have the animation player node selected in the scene tree, then select the Sprite two D node. Expanding the atlas texture property, we can see its properties in a nested panel. Hovering the mouse over the region's property name, the pop up will show us the name of the property. This is helpful for ensuring you have the exact case and spelling when trying to access it through code or through the animation track name like we just did. But the second method of adding property tracks to an animation, which is much easier is to use the key button to the right of the property. This button will only be here if the animation player node was previously selected in the scene tree. An animation is currently open, and then another node in the scene tree is selected. Clicking this key button will add a keyframe to the current animation at the timeline cursor's position. The first time we add a keyframe for a property which does not currently have a track in the animation, Godot will ask us if we want to make a track for it. And also, if we want to add this track to the reset animation, we will say yes to both. This adds the track to the animation, plus it also adds the first keyframe at the same time. Clicking on the idle animation name to expand the drop down, it also created the reset animation, which is how GoDO will store the default values of the properties. This helps prevent problems with running animations in the editor and permanently changing the values of properties unintentionally. Godot will reset all properties back to the default values specified in this reset animation. To make this smoother, try to always add property tracks to animations when they are in their default state. Now we are ready to start the actual animation process. You should either have a texture track for changing individual sprites or a texture region track for changing sprites within a sprite sheet. Your assets may also use different sprite sheets for different animations. If this is the case, you will also need a track for both the atlas texture and the texture region. All we have to do is scrub forward in the timeline one frame, then edit the value of either the texture or the texture region and click the key button to add another keyframe and repeat. Since I know that all of my sprites are the same size and evenly spaced, I can do this even easier by simply inserting new keyframes than manually editing the X property, increasing it by 32 each frame. If we test this animation, the engine will assume that we want to tween the values of these properties between keyframes. Since the frame rate of the overall game is 60 frames per second, but this animation is 12 frames per second. The engine creates four additional frames between each keyframe to create a smoother transition. Obviously, this isn't what we want with a sprite animation, so we'll need to switch the update mode of this track from continuous to discrete. Now the property will only change when the animation hits a keyframe and it will change the value immediately. The animation is too long, so we'll need to set the number of frames of the animation so it ends after the last keyframe. This animation will also need to loop, so click the animation looping button so that the looping icon is blue. If you click it once more, it will be set to Ping Pong, which is a different kind of looping. For character animations, we generally want forward looping. It's a good idea to also set the reset animation tracks to discrete in case you decide to use it to reset a character through script. This idle animation is now complete, and we can continue to make the rest of the animations we need for our platform and character. Clicking on the animation button, we can add a new animation and start from scratch, or we can duplicate this idle animation and use it as a template. Since I know that my sprites are evenly spaced, I only need to edit the Y properties of my keyframes to switch from idle to walk frames. We can copy and paste keys to make things easier. A line connecting two keys means that the values aren't changing between them. When making the running animation, I'll simply double the frame rate. We can click and drag keys to move them along the timeline. I'll also need to make jumping and falling animations. Depending on the assets you're using, you may need to make different animations. For example, you might need a jump start, jump idle, fall idle or landing animations. Make sure you know which animations need to loop and which don't. Now the character has all the animations, but they're stuck in the idle animation. So we'll need to tell the character when to switch to other animations. I'll see you in the next video. 17. 2-3 Animation Tree: Hello, friends. In this video, we'll control which animation is playing in the animation player using an animation tree. If your character scene doesn't have an animation tree node, you should add one now. The purpose of the animation tree is to control the animation player, deciding which animation should play at any given moment. So with the animation tree node selected in the scene tree looking in the inspector, we'll set its animation player property to our animation player node. The tree root property is a resource that will be used to determine which animation will be played by the animation player node. Clicking on the drop down, we have a few different options for this, most of which are more applicable when using three D or bone animations, which often blend multiple animations together. Since this is a simple two D sprite, we'll use a state machine. Clicking on our new state machine we'll open the animation tree panel at the bottom of the editor window with a state machine open for editing. Our state machine currently has a start node and an end node. These are not the same as the nodes in the scene tree, but are nodes in the context of a node graph. We can click in empty space to add more nodes. Let's add the idle animation as a node. We can click and drag these nodes around to organize them however we want. Their positions inside this space are irrelevant. Changing our edit mode from select and move nodes to connect nodes, we can click and drag from one node to another, creating an arrow which connects them in one direction. This is a transition from the start node to the idle node. So when the state machine starts, it will immediately transition to the idle node, which tells the animation player to play the idle animation. Switching edit mode back to select and move nodes, let's add the walk and run animations, too. And switching back to connect nodes once more, create a transition from idle to walk and walk to run. When this state machine starts, it will immediately transition all the way from the start node to the run node. So we want to add conditions to these transitions to restrict when this transition is allowed to happen. Selecting the transition from idle to walk, expand the advance section. Under the advance expression is a text area. Here we can specify any logical expression we want to control when this transition is allowed to occur, as long as the result will be a boolean, true or false. In this case, we want to play the walk animation if the character's X velocity is anything other than zero. So this will accommodate walking both left and right. This expression will be checked on the node which we specify in the properties of the animation tree node, the advance expression base node. Setting this to be the root node of the scene, the character body two D node, we can have the animation state machine react automatically to the character's velocity to change the animation. Returning to the state machine, transitioning from walk to run, we will want to check if the absolute value of the character's X velocity is greater than the character's walk speed. You may want to specify a different threshold, such as requiring the absolute value of X velocity to be equal to run speed or greater than the average of walk and run speed, et cetera. Now we need to add a transition back to idle from walk. When creating transition loops between two or more nodes in a state machine like this, the conditions of all transitions must not all be true at the same time. This would make it impossible for the state machine to decide which state should be currently active, as it will just cycle between the states indefinitely. Since the condition for transitioning from idle to walk is for the X velocity to be anything other than zero, then the transition from walk to idle should be if the X velocity is equal to zero. These two conditions can't be true at the same time, so the state machine can't get stuck looping between these two states. Applying the same logic to transition from run to walk, the conditions for this transition must be logically incompatible with the transition from walk to run. So if the absolute value of the character's X velocity is less than or equal to the character's walking speed, this will never be true at the same time as greater than. When we run the game, the state machine will start from the start node and move along transitions between different states, each state representing one or more animations. In some cases, the state machine may transition to the end node. In which case it will stop working. When animating our character, we never want this state machine to reach the end node, so there's no transition leading to it. This works to animate the character idling, walking and running left or right, but we also need to add animations for jumping. If we were to add the jump animation here, we would need to add transitions from all of our existing states to the jump animation, since the character can jump from any of these states. This will get tangled and messy and difficult to manage, especially if your character has multiple different jump animations. What we can do instead is create a nested state machine. We'll start by saving our current state machine as a project resource. Call it locomotion. Then replace this state machine with a new one. In our new blank state machine, right, click in empty space and select Load. Find the Locomotion state machine and add it to the new state machine as a single node, and rename it so you know it is the Locomotion state machine. Then add a transition from start to locomotion. The entire Locomotion state machine now exists as a single node which we can open by clicking on its Edit button with a pencil icon. We still have the idle walk and run animations transitioning as before, but now it is nested within a larger state machine. We can return to the other state machine by clicking on the bread crumbs. In this state machine, we'll add any animations related to jumping and connect them with transitions. This is what an infinite loop in a state machine looks like. We want the character to switch from any locomotion state to jump if their Y velocity is less than zero, meaning they are moving up. If their Y velocity is greater than or equal to zero, they have reached the apex of their jump, and I'll switch to the fall animation. Then transition from fall back to locomotion if the character is on the floor. We can call the function or use our private variable that we created earlier. We may want to skip jumping and go straight to falling if the character walks off of a ledge. So let's add that as another transition under the condition that the character is no longer on the floor. This creates a conflict in our state machine when the character is in a locomotion state and jumps. Their velocity is both less than zero and they are not on the floor. So which transition should the state machine use? In cases like this, we can give our transitions priority over others by lowering their priority property. If the character jumps, their Y velocity is less than zero, and they are not on the floor. But the transition to jump is given priority. So that is the transition which will be used. If the character walks off of a ledge, their velocity is greater than zero, and they are not on the floor, so there is no conflict. Our two D platforming character is now animated, but we can make it feel a lot better by adding more polish. I'll see you in the next video. 18. 2-4 Sound Effects: Hello, friends. In this video, we're going to add sound effects to our animated character. For that, we'll need to import some sound files into our project, sorted into an appropriate folder. I'll include some sound effects you can use to follow along with this lesson, but I recommend intermediate students use their own or free assets. First, I'll get rid of the static body node that is this white rectangle since I'm not using it anymore. Start by adding an audio stream Player two D node to your character scene if you don't have one already. The audio stream player two D node differs from an audio stream player node in that the volume of the sound effect will vary based on its proximity to the listener. The default listener being the camera. We can populate the stream property of our audio stream player with a sound effect and click on the playing toggle to hear it. If we take a look at our character's animations, particularly the jump animation, We can add an audio playback track to the animation and select the audio stream player to denode to be the player for this track. We can then keyframe sound effects to play as part of this animation, but this comes with a lot of limitations. If the sound effect is longer than the animation, it will be cut short. If the animation is looping, it will be repeated, and with sound effects, the player will hear many times while playing the game like this one. It's a good idea to add some variety and randomization to it so the player doesn't get tired of hearing it. So let's remove this track from the animation and try a different method. Since we know the exact moment when the character jumps from the script, it would be easier to play the sound that way. Let's rename the audio stream player to DnodeT jump and grab a reference to store it in a private variable. When the character jumps, all we have to do is tell it to play. Keeping the sound effect out of the animation, it will play in its entirety and only once. We can duplicate this audio stream player two D node to add more sound effects, too, like one for a landing sound. Then in the script, we can play it the same way as the jump sound, but we don't have a convenient function for when the character lands on the floor yet. In the physics process, we have an if statement checking if the character has walked off of a ledge. Reversing this logic, we can check if the character has landed this frame, if they were not on the floor last frame and are on the floor this frame. We'll then call a new private function. Let's call it on landed. For now, this function only needs to play the landing sound effect. These sound effects will play when the character jumps and lands, but they will always be the same, which will get annoying to the player after some time. We can improve this by slightly randomizing the pitch scale of the sound every time it is played. So let's attach a script to our audio stream player two dende and name it something like sound effect randomizer without using a template and save it in the appropriate folder. This script will need a public function telling it to play a randomized sound effect, accepting no parameters and returning no value. The end result of this will be to call the play function defined in the audio stream player to denode class. But before playing the sound effect, we want to edit the value of the pitch scale property. Using the built in function rand F range, we can generate a random float between a minimum and maximum value that we specify. With one being the default pitch scale, I'll randomize it to be between 0.5 and 1.5, which should produce a wide range of similar sounds. We can attach this same script to the other audio stream player to Dnode by clicking and dragging from the file system tab onto the node in the scene tree. Back in the character script, instead of calling the Audio stream Player to D classes play function, we'll call our play random function instead. This might be sufficient to reduce the monotony of our jumping and landing sounds, but footsteps will be heard constantly while playing the game. So you may want to have multiple different sound effects for something like that and have the audiostream player pick one at random. Let's duplicate an audioStream player two D node to make another one for footstep sounds, also with the same randomizer script still attached. In the script, we'll export a variable to hold all of the different footstep sound effects as an array of audio streams. This can quickly be populated by group selecting all of the footstep sounds in the assets folder and dragging them onto the exported array in the inspector. Then when playing a randomized sound effect, we'll check if the size of this array is not zero. If the array isn't empty, that means we should set the value of stream to be a random element of this array. Accessing a member of the array with an integer index number as a random integer between zero and the size of the array minus one. This will simply pick one random sound from our array of sounds. In the case of our other randomized sounds, since they aren't specifying any value for the array, its size is zero, and it will not try to randomize the sound effect, only adjust its pitch scale. Now we just need to have the sound effect triggered by the walk and run animations. Switching from script view to two D view, first select the Animation Tree node, then the Animation Player node to temporarily preview the animations without the Animation Tree interfering. With either the walk or run animations open, we can add a call method track to the animation and call a method on the footsteps audio stream player to denode. Inserting a key in the animation on this track, we'll call the play random method. A method in this context is synonymous with a function. We can scrub through the animation to find the frames when the character's feet actually make contact with the floor and set the key frames to be on these exact frames. Then repeat this process with the other animation. Now we have a bunch of randomized footsteps, jumping and landing sound effects, but we may also want dust clouds. I'll see you in the next video. 19. 2-5 Animated Sprite: Hello, friends. In this video, we're going to create dust particles for our platforming character. For that, we'll need to import more sprite sheets. Starting in the character scene, we'll add an Animated Sprite to de node and name it something like Jump dust. The Animated Sprite to dende differs from a regular Sprite to de node in that it doesn't have a single texture property. It has a Sprite frames resource. Clicking on the dropdown, we can create a new Sprite frames resource for this Animated Sprite two D node. Then click on the resource to open it. This will open a new tab in the bottom panel labeled Sprite frames. In this window, we can create one or more Sprite frame animations that this node will be able to play. These animations can only change the Sprite itself. They can't be synchronized with audio, function calls or other properties the way an Animation Player node can. Depending on the assets you're using, you can either add sprites from individual files or from a single Sprite sheet. I'm using Sprite Sheets, so I'll click on the ad frames from Sprite Sheet button. Browse through the file system to find your desired assets. If using a sprite sheet, you'll be prompted to select which frames of the sprite sheet you want to use. Unlike an atlas texture, these sprite frames must be evenly spaced. Select the number of frames in each row and column of the sprite sheet, and they will be sliced for you. You can then select each of the frames you want to use by clicking on them. The number in the corner of each sprite frame is the order in which it will be added to the animation. If they are not in the correct order, you can deselect and reselect the Sprite frames or import them out of order and reorder them later. Just like with the character animations being one frame longer than the last frame, it's a good idea for sprite animations like this to have an empty frame at the end. So the last frame will stay drawn on screen for the full duration of a frame before the animation is considered finished. We can set the frame rate of the animation. I'll use 12 frames per second and whether or not this animation should loop, which I will turn off. There is also an option to autoplay this animation. Each Animated Sprite Toti node can have only one autoplay animation, and the node will play this animation when the node is added to the scene tree. When a dust cloud is created by the player jumping, we wanted to play this animation. So let's make this animation autoplay. We can preview what the animation will look like by clicking on the play button. To see the animation better, we can hide the character sprite and the collision shape temporarily by clicking on the eye icon beside them in the scene tree. Just like the character, this animation needs to rest cleanly on the floor of the X axis. So in the offset section in the inspector, we can set its Y offset to be half of its vertical size. With the assets I'm using, that's 16 pixels up. This keeps the nodes origins at 00 while still drawing the Sprite offset. If the color of the sprites isn't quite right or you want to adjust their transparency, you can do so from the canvas item section of the nodes properties in the inspector. The modulate property is a color with a default value of white, which has values of one red, one green, one blue, and one Alpha. Each pixel in the sprite will have its own color multiplied by this modulate color before being drawn. So we can adjust its red, green, blue, or Alpha values. I'll give my dust cloud half transparency by setting its modulate Alpha to 0.5. We can reorder the sprites by clicking and dragging them using the arrow buttons, delete frames, add empty frames, or import more frames if we need to. Once you're happy with how the animation looks, remember to unhide the character sprite and collusion shape. I'll turn on looping for demonstration purposes. When we run the game, the dust follows the character around since it is a child of it. But what we typically want for something like this is for the dust cloud to spawn in a specific location and not move. This dust cloud will need to be a sibling of the character, not a child. Right clicking on the Animated Sprite two D node, we can save it as its own scene. Put it in a new folder for dust clouds. Delete it from the character scene. Then open it up so we can edit it in isolation. Let's turn off the looping from this animation. We still want it to autoplay when the dust cloud is created, but we'll deal with that in the next video. For now, we have two more dust clouds to create. One for when the character lands and another for when they walk or run. So we can duplicate our existing jump dust scene to make things easier. It won't make a difference, but I like to make sure the name of the root node of my scenes matches the scene's file name. Then just change the sprites out for the correct ones in each case. A Make sure that all are properly offset to rest cleanly on the floor of the X axis. We now have three different dust clouds we want to use with our character. We just have to instantiate them. I'll see you in the next video. And 20. 2-6 Instantiate: Hello, friends. In this video, we'll instantiate the dust clouds in reaction to the movements of our animated character. Starting from the game scene, we want our dust clouds to exist in the world as a sibling of our character, not a child. Let's attach a new script to the world Environment Node. I'll name it world and put it in the scripts folder. This script's main purpose for now will be to spawn new scenes as its children. So we'll write a new public function named spawn, which accepts a packed scene as the first parameter. I'll just call it something vague like SN and also a position where it should be spawned, which is a vector two. Then this function will return the node it creates in case we want to use it. We can turn a packed scene into a node by calling its Instantiate function. We will need to store this node in a variable to be able to use it. I'll name it instance. Creating a node does not add it to the scene tree. We have to do that, too. But to add any node to the scene tree, the node will need a parent. This world environment node will be the parent, so we'll call it add child function, giving it the instance as an argument. This will add the node to the scene tree, but its position will be at the scene's origin, coordinates 00. So we want to set the position of the newly instantiated node to be the spawn position. However, this assumes that the newly instantiated node has a position property, which isn't true of all nodes. While this wouldn't cause an error yet, it might in the future, if we use the spawn function to create different types of nodes. So it would be a good idea to check if this instance is a canvas item before trying to access its position property. We can make the spawn position parameter optional by giving it a default value of vector two dot zero. Finally, we can return the instance node. Now we need to call this function as a reaction to the character performing certain actions. The easiest way to do this is using signals. In the character script, we can declare signals at the top of the script for each of the three times we want to spawn a dust cloud when the character has stepped, jumped, or landed. Signals are conventionally named in past tense, so as to say that the event in question has happened. Just like functions, signals can have parameters. In order to spawn the dust clouds, we'll need to know the character's position and which direction they are facing, indicating whether the sprite should be flipped. In the jump function, we can emit the jumped signal, passing the character's current position and whether or not the characters sprite is flipped horizontally as arguments matching the signal's parameters. The same can be done when the character lands. For the footsteps, we can declare a new private function. Let's name it on step and emit the signal. If you want, you can also tell the footstep sound to play inside this function instead of including it in the animations as a separate track. But in order to do that, I'll need a reference to the footstep audio player to denote at the top of my script. Selecting the Animation Player node and the walk animation. We can add a call method track to the animation, calling a method on the scenes root node. Then insert keyframes to call the on step function during the frames when the character's feet make contact with the ground. If triggering the sound effects through the script, we no longer need to do this from the animation and can delete this track. Then I'll also need to repeat this process for my run animation. Switching to the game scene, we can connect signals to scripts to react to them automatically. Selecting the character, switching the inspector tab to the node tab, we can see our list of signals. Either double clicking on a signal or right clicking and choosing Connect, a new window is open. From this window, we can select the node we want to connect the signal to. In this case, the world environment node. Then either enter the name of a function to call as a reaction to the signal or picking one from a list. I'll manually enter the name of a new function and name it spawn Jump dust. Clicking on Connect will close the window and display the World Environment Node script with this function added automatically. This function also has a green icon indicating that it is being called by a signal connection, and the character node in the scene tree has a signal icon to show us that it is emitting a signal with a connection. This signal connection is also listed in the node tab. We can export variables to hold our three different dust cloud scenes, which are of type packed scene. Then populate each of these variables with the appropriate scenes from the file system tab. For now, let's just call the spawn function passing Jump dust as an argument and also the character's current position. Godot doesn't like it when we name our function parameters the same as a node's already existing properties. But we can rename this parameter to something more specific like character position. This will instantiate a jump dust cloud automatically anytime the character jumps. The Animated Sprite two D node will auto play the Sprite animation we created, but we don't want this dust cloud node to continue to exist after the animation finishes. After the character has been jumping many times, the scene tree would have lots of Animated Sprite two D nodes that aren't doing anything. So we will need to clean them up. The way to remove nodes from the scene tree is to call their free function. But we don't want to do that until after their animation has finished, which is a signal of the Animated Sprite two D node. Since we know the node type of our dust cloud scenes is an Animated Sprite two D, we can store them in a private variable of that type and name it dust. We can connect signals through code just like we did through the inspector and connect its animation finished signal to its free function. Remember that when referencing a function rather than calling a function, we do not use parentheses. We can also flip the dust cloud horizontally if the character's sprite was flipped when this signal was omitted. Repeating this process for the other two signals, we can change the type of dust cloud that is spawned. A running the game, we will return to the Editor window and click on the remote button in the scene tab. We can see the scene tree of the game that is currently running. Expanding the tree so we can see the children of the World Environment Node. We can see that the Animated Sprite two D nodes are being created and destroyed as we jump, land, and walk around. The remote scene tree doesn't update every frame, so some dust clouds may be created and destroyed without being displayed in the remote scene tree. I'll see you in the next video. A 21. 2-7 Audio Bus: Hello, friends. In this video, we'll add a separate sound effect for the character's voice. I'll need to import some more audio files for the character's efforts. Voice sound effects differ from other sound effects in that we only ever want to play one at a time, since our voice boxes can only produce one sound at a time. So we'll only need to have one audio stream player TD node for all vocalizations for a character. In the character scene, let's duplicate any of the existing audio stream player TD nodes to create a new one for the character's voice. You may also want to position this node somewhere around the character's neck or head instead of at their feet. Although this won't make much of a noticeable difference on my tiny character. The sound effect randomizer is no longer applicable to this node, so we can remove the script by clicking on the detached script button. We'll replace this with a new script. Let's call it voice. In this script, we can export any number of standard voice sound effects, commonly referred to as efforts or exertions. Let's export a variable for Jump effort sound as an audio stream, which we can populate in the inspector. We can preview what this will sound like by clicking on the resource. Then we'll add a function which will play a jump effort sound. However, this function will not need to do anything if the voice audio stream player to denote is currently busy playing any other sounds. Only if not playing, then we will set the stream to Jump effort and play it. This will prevent any new requests to play another effort sound from interrupting the previous one. We can also use the same techniques as we did with our sound effects here, like randomizing the pitch scale or having an array of audio streams and choosing one at random each time. And unlike regular sound effects, which are always expected to be played every time, efforts aren't always necessary. So we can also add in a random chance that the effort doesn't play, even if it is possible. Using the rand F function, this will return a random number 0-1. We can check if this is less than a percentage chance, let's say, 25%, expressed as 0.25. Appending this to the conditional statement with an and operator, this character's voice must not be busy playing any other sound and also must pass a random 25% chance before being allowed to play this effort. All we have to do to trigger this is connect the character's jumped signal to the voices play Jump effort function. But the character's jumped signal provides two arguments and the voices play jump effort function doesn't accept any parameters, so they aren't considered compatible. If we click on the compatible methods only toggle, all functions will be shown even if they don't match, and we can select the play Jump effort function. To prevent this from causing an error, we'll expand the advanced section by clicking on the Advanced Toggle. Here we can bind new arguments or unbind the existing arguments. Simply unbind the two existing arguments from the character's jumped signal, and now the signal is compatible with the function call. Testing it out, we will hear the effort sound randomly played with a 25% chance, and it will never interrupt itself. B. Another thing we can do to separate voices from other sound effects is to give them a separate Audio Bus on the Audio Bus layout. Click on the Audio button at the bottom of the editor to open the Audio Bus layout, which currently has only a single bus, the Master Bus. But if we click on Add Bus, we can add more buses to the Audio Bus layout, one for sound effects, and one for voices. It's also a good idea to have another one for music. This allows us to sort our audio into different categories and easily control the entire category all at once across the entire game. We can adjust the volume of all sound effects, voices, and music independently, and master volume will affect all of them. There are also many different audio effects which can be added to each bus if we want to. In our character scene, we have multiple different audio stream player two D nodes, and these each need to be told which Audio Bus they belong to, with the default being Master Bus. Each of the sound effects can be told to play on the sound effects Bus, while the voice audioStream player two D node can be told to play on the voice Bus. Now we can adjust the volume of our sound effects independently from voices. M. M. We now have a very polished two D platforming character. Next, we'll start building environments to move around in. I'll see you in the next section. 22. 3-1 Tile Map: Hello, friends. In this section, we'll build an environment for our character to move and jump around in. To do that, I'll need to import assets for my terrain, sorted into a new folder for terrain sprite assets. Double clicking on the asset, we can preview it in the inspector. We can see that this asset is a sprite sheet just like the character. Except instead of animation frames, this sprite sheet contains different sprites to draw terrain based on its surrounding tiles. Starting in the game scene, we'll make the current level static body to denode its own scene. I'll make a new folder for room scenes. And depending on the size of your game, you may also want to sort rooms into regions. So I'll make another folder for rooms in the forest region and save this room as something like start room. Then open this new room scene. I no longer want the static body two D node to be the root node, since this room will contain more than just a floor. So I'll add a regular node two D to this scene as a child. Then right clicking on this node, we can select make scene root to reorganize the scene tree so that this node is now the root node. Now the floor is a child of the root node, and we could add more static bodies to this node for the walls, ceiling, platforms, and obstacles to build our room. I'll rename the root node to be the same as the scene name. When using tiles from a sprite sheet that are equally sized and spaced, we can use another node that Gadot provides called the tile map layer to draw them easier. Adding a tile map layer node to the scene opens the tile map panel at the bottom of the editor, but we can't do anything here yet. First, we need to take a look at the tile map layer nodes properties in the inspector. The tile map layer node can't draw anything until it has a tile set. Clicking on the drop down, we'll create a new tile set. Then click on the tile set to expand its properties. The first option is the shape of the tiles in this tile set. The tiles I'm using are square, but there are also options for isometric, half offset square, and hexagon shaped tiles. These are usually used for top down perspectives, and this game is a side scroller, so I'll be using square shaped tiles. Next, we need to set the size of the tiles. My imported sprite sheet has the tile size in its name, 32 by 32 pixels. We will ignore the rest of the settings for now and switch the bottom panel of the editor from Tilemap to tile set. Now we can add tiles to the tile set we just created. Either clicking on the Plus button at the bottom, then navigating through the file system to find your assets or clicking and dragging the assets from the filesystem tab into the tile sources area. This will give us a prompt for the engine to automatically divide the sprite sheet into tiles for us. If we select yes, and if we set the tile size correctly, then the tiles in the sprite sheet will be automatically sliced to that size for us and added to the tile set. Clicking on the eraser button, we can click on tiles to remove them from the tile set. Clicking and dragging will erase all of the tiles we drag over. Holding control or command, then clicking and dragging, we can remove all tiles in the selected area. Toggling the eraser back off, we can use the same controls to add tiles to the tile set. If you want to create tiles that are oversized, you can do so by holding down the Shift key, then click and drag over multiple tiles. This will treat multiple tiles as if they're always meant to be drawn as one larger group of tiles. But this sprite sheet doesn't have any oversized tiles, so I'll erase this and add each of these tiles to the tile set individually. Now that our tile set contains tiles, our tile map layer can use this tile set to draw the tiles. So we'll switch from the tile set panel to the tile map panel. With the paint tool selected, we can select a tile source from the tile set, then a tile from the tile source. In the two D scene view, the editor provides orange guidelines of how the tiles will be laid out and we can now paint this tile wherever we want by clicking. If you want to change the offset of the tiles, you can adjust the tilemap layer nodes transform position. Like in the tile set, we can use the erase toggle to erase tiles instead of painting them. The picker tool can be used to select a tile from the T D view rather than the tile set panel. We can draw tiles flipped horizontally, vertically or both and rotate them clockwise or counterclockwise as needed. We can also group select multiple tiles and paint them all at once. Toggling place random tile, this will instead pick from the selected tiles at random. Increasing scattering will leave some tiles unpainted while dragging around. You can draw lines too. With multiple tiles selected, they will be repeated along the line. And the rectangle tool can fill an entire area of the tile map layer very quickly. The bucket tool can be used to fill an empty enclosed area of the tile map layer. The random and scattering options also apply to the line wrecked and bucket tools. The bucket tool also has a contiguous toggle, which when turned off can be used to effect all matching tiles across the entire tile map layer. With the tile map layer node drawing our terrain, we no longer need the static body tutti node and can delete it from the scene. Back in the game scene, we can see our start room. The node is not renamed. It still says current level. If we run the game, the character doesn't collide with the terrain and just falls through it. We now have a tile map layer using a tile set to draw tiles, but we need to do more work to configure our tile set. I'll see you in the next video. 23. 3-2 Terrain: Hello, friends. In this video, we'll use more tile set options to make painting tiles onto a tile map layer quicker and easier. Starting in the room scene, select the tile map layer node, then expand the tile set in the inspector. Expanding the physics layer section, it is currently empty, so we'll add a new physics layer. We don't need to edit any of the physics layer properties. It just needs to exist. We'll go over collision layers and collision masking later in the course. But if you wanted to, you could also add a physics material to adjust its friction, roughness, bounciness or absorbency. I don't want a physics material, so I'll clear it from the drop down menu. With the physics layer added to our tile set, we can open the tile set tab at the bottom of the editor to apply this physics layer to our tiles. Switch from atlas setup to paint properties by clicking on this button. Here we can paint a lot of different properties onto our tiles, but the one we want to paint right now is physics layer zero. Under painting, the entire tile should be filled in with four white nodes at all corners by default. If it isn't, click on the three dots and select reset to default shape or press the Fkey. We can now paint this physics collision shape onto our tiles. All of these tiles are designed to be the same square shape. But if you're using different assets, you may need to adjust the collision shape for different tiles. We can click and drag these white points to adjust the collision shape, delete them or add more as needed to create any shape we want. If you need a better view to be more precise, you can expand this editor to fill the entire panel. There are also grid snapping options available. Setting the grid snapping to the pixel size will allow you to create pixel perfect collision if you want. A after painting collision shapes onto the tiles, running the game again, we'll have the character colliding with our tiles. Now we have collision, but drawing terrain one tile at a time is very tedious. We can use tools built into the editor to make it easier. Expanding the terrain section, we can add a terrain set to this tile set. The terrain set needs a terrain added to it also. So we'll create an element here, too. We can name the terrain whatever we want. I'll name mine forest and give it a color. This color won't be drawn in the game only in the editor as a way of setting it up. It helps to choose a color which will contrast with the colors used in the sprite sheet so it stands out. I'll use pink to contrast against my green and brown terrain tiles. Next, open the tile set panel at the bottom of the editor. Terrains are a way of drawing tiles that are designed to connect to each other like this sprite sheet is. Switch to paint properties, and the property we want to paint is the terrain property. We can then select our terrain set we just created terrain set zero and the terrain in that terrain set, the forest terrain. To paint the terrain property, first, we have to select each of the tiles which are part of the terrain so they are no longer dark. In this sprite sheet, that is all of them. When mousing over a tile that has been added to this terrain, the tile is divided into nine squares. Middle square needs to be filled in to draw this tile as part of our terrain. The other squares tell the editor to draw this tile if there is another terrain tile adjacent to this one in that direction. Starting with this tile, this is meant to be drawn if it has no other terrain tiles adjacent to it. So only the middle square is painted. The tile over to the far left is meant to be drawn if there is another terrain tile to the right of it, so it will have the right square painted. The opposite tile to the right follows the same logic to the left, and the middle tile is drawn if there is adjacent terrain both to the left and the right. The vertical tiles are the same, just up and down. The large block in the top left corner combines these two, adds corners, and also has the solid tile in the middle with all squares painted. This will be sufficient to draw convex shaped blocks of terrain. Opening the tile map panel, switch from tiles to terrains, then select the terrain we just created. With the paint tool selected, we can paint terrain tiles freely onto the tile map layer, same as before. But now, if our terrain tiles are adjacent to other terrain tiles and form rectangular shapes, then they will automatically update to the correct tile from the terrain. Erasing tiles will also update surrounding tiles. This also works with the line wreck and bucket tools. If we want to create concave corners, we will need to return to the terrain painting. Adding each of the remaining tiles to the terrain, we'll fill in any squares which match whether there should be another terrain tile adjacent to this one in that direction. If you're following along with these assets and are unsure of what pattern should be drawn on each tile, you can pause the video and copy mine. Some tile assets will provide a guide or reference to help you with the adjacency rules for tiles, or you can just experiment and see what looks good. Try drawing some terrain in a variety of different shapes to see if every possible combination is accounted for and drawing appropriately. The tiles at the bottom are also meant to be drawn with adjacent terrain in all directions. Every tile in a tile map is usually designed to have a unique pattern. But if you have multiple tiles with the same pattern, the terrain tool will pick one at random with the same pattern when drawing. We can adjust the probability of this randomness by painting another property from the drop down list probability. Simply input a probability. I'll use 0.01, which is 1% and paint this onto the tiles at the bottom. Anytime the terrain tool draws a tile with adjacency in all directions, it will have a 1% chance of drawing each of these tiles at the bottom or use the blank tile by default. When painting terrain from the tile map, if the random distribution of these tiles doesn't look very good, you can easily keep drawing over it. The terrain editor will recalculate the random distribution each time. We can now easily draw rooms for our game using a tile map layer node, but the camera isn't moving, so we can't move our character very far. I'll see you in the next video. 24. 3-3 Camera: Hello, friends. In this video, we'll have the camera follow the character so they can move further around the environment. In the game scene, we have the character and camera two D nodes as siblings. Obviously, if we assign the camera as a child of the character, then it will follow the player around. However, this isn't a very good way of doing it because most games will have transitions, cutscenes, or other areas of the game that adjust to the camera's view independently of the player character. In some games, the player has a choice of which character they want to play, and that character must be loaded, so it can't be determined in the game scene that the camera must follow this character. It's better to leave the camera as a sibling and have the camera follow the player character through a script instead. Let's attach a script to our camera to denode. We could call it camera or name it after the behavior, follow player or combine the two, camera follow. We could export the player character as a variable like we did with the input handler, but let's instead assume that this camera will be told to follow more than one character throughout the game and instead define a public function named follow, which accepts a subject as a parameter. Theoretically, this subject could be anything as long as it exists in the two D world and has a position. So we'll give it a type of node two D rather than the more specific type of character body two D. That way, this camera could be told to follow a rigid body or anything else we want. This function doesn't need to return any value. All it really needs to do is store this subject into a private variable. Then overriding the process function, if this camera has a subject, then it will set its position to be the same as the subject's global position. Since the subject may be part of a larger scene, we don't want to use its local position relative to its parent, but rather its global position within the scope of the entire game scene. We aren't using the Delta parameter, so I'll proceed it with an underscore. Now we need another script to call this function from a node which has access to both the camera and our character, which will be the game node. Attaching a script to the game node, we'll name this the game manager, and I'll put it in a new scripts folder for manager scripts. It will be the responsibility of the game manager to manage when the player is playing the game or in a cut scene or transitioning between scenes. For now, when the game manager is ready overriding the ready function, we'll tell the camera to follow the player and activate the input handler. To do that, we'll need to store each of these nodes in variables. Since the hierarchy of the game scene is quite complex compared to other scenes, we may need to rearrange nodes, changing their parentage in the process. When that happens, the node path in our script will no longer be valid causing errors. We can avoid this by right clicking on nodes in our scene tree and selecting access as unique name. This adds a percent sign to the right of the node in the scene tree. This allows us to replace the dollar sign annotated node path with the percent sign annotated unique name. If the node has a unique name already assigned, it will be automatically applied when holding control or command and dragging it into the script. Now that we have all of these node references, when the game manager is ready, we can tell the camera to follow the player character and enable the input handler. We haven't written an enable function for the input handler yet, so let's switch over to that script and write it. Creating an is enabled private variable as a boolean, which will default to false, and a public enable function we can set is enabled to true. Then before we handle any inputs from the player, we'll check if the input handler is enabled and return if it isn't will exit out of the function if is enabled is false, preventing any of the remaining code from running. We'll also need a disable function that sets the is enabled variable back to false. This may seem excessive, since we could just make the is enabled variable public. But using this encapsulated structure, we can do other logic inside of these functions. Such as when the input handler is disabled, tell the character to stop moving by setting direction to zero and also tell them to walk. When enabling the input handler, since movement is being checked in process, it will already be detected automatically. But if the player started holding the run button before the input handler was enabled, the input will have been ignored, but the player will still expect their character to run. We can check the status of the run button when enabling the input handler and tell the character to run accordingly. If using the input function for movement, we could check the input singleton for the status of the move left, right axis, and tell the character to move here too. Returning to the game scene, since the character's position is also at the origin, this is what the camera will draw when the game starts. This isn't a very good framing of the character or the room. Usually the player needs to see more of what's above them or what's ahead of them. We can give the camera a position offset to move it up along the y axis and to the right along the X axis to achieve this. I'll put it 64 pixels up and to the right and also change my Zoom to two. So when the camera's position is set to match the character's position, every frame, it will be offset by this amount after that, giving the player a view slightly up into the right of the character. We now have our camera following the character around, but the camera only looks to the right. I'll see you in the next video. 25. 3-4 Look Ahead: Hello, friends. And this video will allow the camera to look ahead of the character either to the left or right. Starting in the character script, we can make things easier for ourselves by providing class names for some of our scripts using the keyword class name at the top of the script. Let's name this class something more generic like character. This will allow Godot to auto complete the names of variables and functions when accessing members of this class in other scripts. Just like how we used signals to react to the character's actions to produce dust clouds, we will need to make another signal for when the character changes the direction they are facing. Declaring a signal named changed direction, we'll need to pass which direction the character is now facing as a parameter. Adding another variable to the locomotion section, we'll need to know which direction the character is currently facing to know if they have changed their facing direction. Since a Boolean variable naturally defaults to false, I'll name the variable is facing left, which when false implies that they are facing right. In the face left function, we can emit our new signal and pass the value of direction as the argument. Oh down in the physics process, when telling the character to face the direction, this will be happening every time the player gives input every frame. We don't want to emit this signal every frame, only on frames when they change direction. So we'll add the condition that the character is facing left and the direction is greater than zero, or the character is not facing left and the direction is less than zero. Now these functions will only be called during the frame when the character actually changes their facing direction, which will emit the signal for us to react to it. Switching over to the camera script, we'll need a function matching the signature of the signal we just created to connect it to. Let's call it on subject changed direction, since the character will be the subject, accepting the direction the character is facing as a float and returning no value. What we want to do is have the X offset of the camera to match the sign of direction. So we'll need to store the value of offset in another variable unaltered. We'll name it default offset and set it to the value of offset using an ready tag. Now we can safely edit the value of offset without forgetting what the original value was. H. When the subject changes direction, we'll set the value of offset to be the default offset multiplied by the sign of direction. So if direction is positive, then the camera's offset will be positive too, and likewise for negative. To connect this signal when telling the camera to follow a subject, if the subject is not null and the subject is a character, the class we just named, then we can safely access the character's changed direction signal and connect it to our function. A Godot will not recognize new class names until after you've saved your scripts. But if we call this follow function on the same subject more than once for any reason, this will cause an error since the signal is already connected. First, let's check if the subject the camera is being told to follow is the same as the subject they're already following. In this case, we don't have to do anything and can just return it to this function. Now we know that the subject is something other than the current subject. Let's check if the current subject is not null and if it is a character. If both of these are true, then we should also check if the character's changed direction signal has been connected to the on subject changed direction function. And if this is true, we'll disconnect the signal, since the camera is no longer following the subject and doesn't need to react to it. Running the game, this will position the camera ahead of the character, but it will happen instantaneously, which is quite jarring for the player. What would be better is to adjust the value of offset gradually over time, which is something we can do easily using a tween, short for in between. So we'll declare a new variable, call it something like look ahead tween. A then when the character changes direction, assigning the value of this variable to create tween. Create tween is a function of the node class, so any node can use it. When the tween is created, we'll access its tween property function. This accepts an object as the first argument, which is this node itself. We can specify this with the keyword self. Then the property we want to tween as a string, which is the offset property, specifically the X component, which we can specify with the colon. The next argument is the final value, which is the default X offset multiplied by the sign of direction we calculated earlier. And finally, the duration of tween in seconds. For now, I'll set it to 1 second. Since this tween will take 1 second, but it's very possible for the character to change directions multiple times during that 1 second, this would create multiple tweens all trying to edit the value of offset at the same time. So before creating the tween, we should check if it already exists first, and if it does kill it. This way, no more than one of this tween can exist at a time and be trying to edit the X offset. Running this now, the camera's X offset is tweening to look ahead of the character over 1 second, but it's still a little jarring. We can adjust how this tween happens by exporting some more variables. First, let's export a duration for this tween as a float and give it a default value of 1 second. Then replace the one in our function with this variable. Next, we'll add a transition type and an easing type. Together, these three variables will allow us to create a wide variety of different tweens by adjusting how fast the value changes at the beginning and the end of the tween. Since create tween returns a tween, we can access its set trans function and pass our transition type as an argument in the same line. The set trans function also returns the tween, so we can continue this line and call the set Es function immediately after that, too. Passing our Es variable as an argument. All of this will return the tween with the transition and ease type set and assign the result to the variable. From the game scene, selecting the camera tutti node, taking a look at its properties in the inspector, we can edit the values of these variables with the transition and easing types providing convenient drop down lists. We can also make the duration of the tween shorter or longer. I'll have mine use a quad in out tween over 2 seconds. Try experimenting with the different options to get a feel for how they work. The transition type determines what mathematical formula is being used to alter the behavior, while the easing type decides how it will affect the first half or second half of the tween. We now have the camera gently panning left or right to show what's ahead of the direction the character is facing, but sometimes the player may want to look up and down, too. I'll see you in the next video. 26. 3-5 Look Up: Hello, friends. In this video, we'll allow the player to tell the character to look up and down. First, we'll need to add more input events to the input map in the project settings to look up and down. I'll use up and down arrow keys. The up and down directional pad buttons, and the right stick tilted up and down. Then in the player script, unlike the movement direction, which is only being used by the character, the look direction will be used by both the character and the camera, which means we will need a reference to the camera. It's worthwhile to store the look direction in a variable, so we only need to calculate it once per frame. Then in the process function, get its value from the input singleton, the same way we did with the movement direction using the get axis function, changing the names of the input actions. Remember that since up is negative, it will be the first argument. We can then send this look direction to both the character and the camera. For the camera, I'll use a function call. But for the character, I'll disset the value of a public variable. To make the name of the direction variable of the character class a little more clear, it might be smart to rename it to move direction. Since this function doesn't exist in the camera script, I'll comment it out for now to avoid errors until I'm ready to write it. Over in the character script, we can open the replace dialog with Control or Command R and replace direction with move direction so it is more clear what it will be used for. Then add a new public variable for the look direction. The L direction variable doesn't need to be used anywhere in the script. It will only be read by the Animation Tree. From the characters scene, selecting the Animation Player, I'll need to create LU and L down animations. I'll duplicate my idle animation to make things easier. Then just edit the Y values of my key frames to draw the character looking up or down accordingly. Then in the Animation Tree state machine editing the Locomotion state machine, I can add the look up and look down animations. Transitioning to them based on the value of look direction. And making sure their X velocity remains zero, otherwise, they should transition to walk instead. And back to idle in the case that look direction is equal to zero. Or if they have started walking, This will have the character animating to look up or down as we press the up and down directions, but the camera isn't moving yet. In the game scene, try altering the camera's Y offset and decide how far you would like the camera to move when the character looks up or down. I'll use 128 pixels and reset back to the default of negative 64. Returning to the player script, I'll uncomment the camera function now, then switch to the camera script to declare this function. I will accept a direction as a float and return no value. The body of this function will be almost identical to the on subject changed direction function. All we have to do is replace all the instances of a head with up down. Then declare these as new variables. We'll also need another exported variable for the max look distance we decided on earlier, which we will use instead of the default offset, and offset X will be offset Y. However, when we let go of the up or down arrows, we want the camera to return to the default offset value, not zero. So this will require a little bit of extra logic. Another way of adding simple if statements to your scripts is to use a ternary operation, which in GDScript uses the same if keywords as a normal if statement, starting with the default case, which is the value we've already calculated, but only if direction is not zero, followed by else. So if direction is zero, then we want to use our default Y offset instead. This works, but in most games that have this mechanic, the player has to hold down the button for some time before the camera moves. We can achieve this by adding a timer node to the player node. Let's name it. Look hold. We can adjust the wait time from the inspector, but 1 second is fine, and the timer will need to be a one shot. Connecting this timer's timeout signal to the player script, This is when the players script will tell the camera to start looking toward the direction. At the top of the script, we'll need to grab a reference to the old timer node, and we'll also need a boolean so we know if the player is currently looking or not. In the process function, after getting the value of look direction from the input Singleton, if the player is already looking, then also if look direction is zero, we'll reset the camera by calling its look function and passing zero as the argument. Then set is looking to false. The reason why I didn't use an and statement is so that I can use an else if here and now assume that is looking is false. Then also check if look direction is not zero. In this scenario, where the player isn't looking yet, but they are giving look direction input, then if the hold timer is stopped, we'll need to start it. Adding one more else if the player isn't looking and isn't giving any look direction input, then also if the look hold timer is not stopped, that means we need to stop it and cancel the look. Adding print statements for all of these, we can confirm that each is called only once when it is appropriate. We'll also need to set the is looking variable to true when the look hold timer times out and the camera starts to tween. Running the game, tapping the up or down arrows without holding, the hold timer is started but stopped and the camera doesn't move, but the character is still animating looking up and down. Holding down the up or down keys, the timer times out after 1 second and tells the camera to look up or down. Releasing the up or down keys, the camera is reset back to its default offset position. I'll comment out to these print statements and adjust my tween settings in the inspector to quad out over half of a second. The player can now hold down the up and down directions to look up and down, but we may want to restrict how far the camera can move to avoid showing content outside of the current room. I'll see you in the next video. 27. 3-6 Boundaries: Hello, friends. In this video, we'll restrict the camera to stay within boundaries of the environment. Starting in the room scene, we can change the root node to be an area two D node rather than just a normal two D node. This is similar to a physics body in that it has a collision shape, but it doesn't have any mass or react to forces. Instead, you can think of it like a motion or heat detector. It represents a region of space and will emit a signal when physics bodies enter or exit it. This will be useful later for detecting when the player character enters the room. But for now, all we need to know is what shape it is. Adding a collision shape to denote as a child, we can give it a shape resource. We'll use a rectangle shape. We can then move and resize the shape to create room boundaries. A attaching a script to the root node of the scene, we'll name it Room and sort it into the scripts folder. We'll need a reference to the collision shape to dende. I'll rename it to shape node. Then we need functions which can be used to retrieve the upper left corner and bottom right corner of this rectangle so they will return vector two coordinates. We can access the collision shape two denodes shape property by its name shape. Then we also want to call a function on the shape resource, Get wrecked. The G wrecked function returns a wreck two, which if we open the documentation for this class, we can see it has a position and an end. The position is the top left corner and the end is the bottom right corner. But these are relative to the nodes position. We will need these coordinates in global space, so we'll add them to the nodes global position. The global position of a node will be relative to the game's origin, not the parent nodes position. We can then return these values. Now, all we have to do is tell the camera to stay within these limits. From the game manager scene, for now, we'll just grab a reference to the start room and store it in a variable named current room. When the game scene is ready, we can get the top left corner and the bottom right corner from the current room, and we'll pass them to the camera by calling a function which sets its boundaries. Switching over to the camera script, we'll declare this function accepting the two vector twos retrieved from the current room and returning no value. In this function, we'll figure out how close the camera can get to the top left and bottom right corners of this room without seeing anything beyond those points. To do that, we'll need to create these variables as vector twos, and it will help to have a boolean for whether or not the camera is currently bound. We'll also need to know the size of the Viewport, which we can retrieve automatically with the function Get Viewport, then access its size property and store it in a variable using the on ready tag. The Get Viewport function is declared in the node class and returns a vector two, matching the size of the window we set in the project settings. This Viewport size isn't necessarily the same as the amount of pixels which will be drawn by the camera, since the camera can zoom in or out. We'll need to divide the Viewport size by the camera's Zoom to get the real pixel size of the area the camera can see. But a vector can't be divided by another vector. We have to use a float. I'll use just the X Zoom and assume that the cameras Zoom will always be uniform. But this still isn't good enough because the camera's position is at the center of its view. The closest position that the camera can position itself to the top left or bottom right corner of the room without drawing beyond these points will actually be half of the size. Dividing this value by two, I'll rename the variable to reflect its new value. When setting the boundaries, the top left most position will be the top left corner of the room plus half the size of the camera's view. While the bottom right most position will be the bottom right corner of the room minus half of the size of the camera's view, and we can set the is bound value to true. H. In the process function, after telling the camera to match the subject's position, we'll then apply these boundary restrictions using the Clamp F function. The clamp F function takes a float parameter like the camera's position, then a minimum value and a maximum value, then returns a value which will stay within those limits. We'll then overwrite the camera's position with the clamped result and repeat this for the Y. But since our camera also has an offset, its position isn't an accurate enough representation of the center of its Viewport. We are also editing the value of offset at unpredictable times. So it's not something we can factor into the top left and bottom right calculations, since the value will likely change often. So we'll need to factor the offset into the clamping functions by subtracting it from the top left and bottom right every time. If you want to have your camera zooming in and out during gameplay while still respecting these boundaries, you would also need to factor these into the calculations inside the process function as well. This works, but the camera tweens seem to struggle against the boundary limits. The tween moves the camera's offset slightly outside the boundaries. The frame gets drawn, then the next process loop pushes the camera's position away from the boundary to compensate for the new offset. This happens repeatedly until the tween stops. This is because the tweens are run after the process at the end of the frame just before drawing everything on screen. To fix this, we can change our tweens from using tween property to tween method instead. By calling a method, we can apply the boundary limits at the same time as we are changing the value of offset. The arguments for tween method, instead of a reference to an object, it needs a callable method. We'll say set Y offset. Followed by from the starting value, which is the current value of the camera's Y offset, then the remaining arguments are the same. Two is the final value at the end of the tween and the duration of the tween. We can do the same thing with the look ahead tween two. We just have to write this set X offset function, accepting a single parameter which matches the type of the from and two arguments. After changing the value of X offset, we can enforce the boundary limit on the camera's position to compensate immediately and do the same for the y offset. Running this now, the offset tweens will not peak beyond the room's boundaries. We now have the camera limited to only show what's in the current room, but the environment looks very flat. I'll see you in the next video. And 28. 3-7 Parallax: Hello, friends. In this video, we'll fix the parallax scrolling of the foreground and background with a custom script. Starting in the second room scene, I'll copy all of my modulation colors from the Parallax Tut nodes to their child nodes. Then I can unparent the sprites and delete the Parallax Tut nodes while still maintaining their color modulation. I'll then attach a new script to any of these sprites, name it Parallax, and put it in the scripts folder. This same script can be attached to all of the sprites I want to Parallax. To create a custom behavior, it helps to take some time to think about what it is you're trying to achieve, which node references and other information will be required. I have a number of different sprites. I want to move at different amounts relative to the position of the camera, and I know that the camera is currently bound within the limits of this room. I'll temporarily add a camera two D node to the scene and zoom it in by a factor of two to pretend that this is the game scene. If the camera is exactly in the center of this room, I wanted to draw everything exactly as it is. But when the camera moves away from the center, I'll move all of the parallax sprites by a relative amount with the background elements moving less than the camera and the foreground elements moving in the opposite direction. To do this, each sprite will need to know the position of the camera relative to the center of the room, as well as its own original position. In the Parallax script, I'll declare an exported variable to hold the factor by which the sprite moves relative to the camera and name it scroll scale as a vector two. This will work similar to the scroll scale variable in the Parallax two D node. When this room is loaded into the scene tree, we can use the on ready tag to get its position and store it in a variable as the original position. Getting a reference to the current camera is easy thanks to the node G Viewport function. Returning the Viewport that this node exists within, which also has the Get camera to D function to get the camera. We can store this in a variable as well, using the on ready tag. Overriding the process function, we don't need Delta and can mark it as unused. I'll just set the position of the sprite to be its original position plus the distance of the camera from the midpoint of the room multiplied by the scroll scale. Now the camera just needs a public function which can tell us its position relative to the current room's midpoint. Switching over to the camera script, I'll write this function, offset from midpoint, which will be the camera's position plus its offset minus the midpoint of the current room. Declaring this as a new variable, I can calculate it as the average of the top left and bottom right corner of the room when accepting them from the set bounds function. We could modify this function to accept the midpoint and size of the rectangle rather than its top left and bottom right corners, but it won't make any difference in efficiency anyway, so I won't bother. Now we can set the scroll scale for each of the parallax sprites to give them each a different distance from the camera. I'll still only use horizontal parallax, giving my hills a scroll scale of one third along the X axis, and the hills further back two thirds. For the foreground, they will need to move in the opposite direction as the camera, they'll have a scroll scale of negative one third and negative two thirds. This will be sufficient for creating the illusion of depth for our rooms. Running the game, we can move over to the second room and see that the parallax effect looks identical to the start room, despite being performed by a very simple script. Since the foreground sprites are moving in the opposite direction of the camera, they will need to either repeat and wrap their position to create an infinite loop, or we can just make the sprite larger than the room. Making my shrubs 1.5 times longer than the room and the grass double the length of the room will leave enough extra on either side so the edges never reach the camera's view. A it would be possible to use the Parallax two D node to draw the foreground and background using separate Viewport and draw them on different canvas layers or try to always draw the current room at the game scene's origin. These solutions would lead to other complications and limitations down the line when it comes to drawing the game's map, for example. I find writing custom solutions like this to sometimes be easier than using the provided nodes. The solution also keeps everything for a room in one place, which makes it simpler for designing and building of the rooms in a more intuitive way. I no longer need this camera Tuti node, so I'll delete it. Removing the clouds from the Parallax touti node, we can alter the Parallax script, also auto scroll the clouds too. Expanding the region wrect, all we need to do to move the clouds to the right is to decrease the exposition value over time. Attaching the Parallax script to this node, we can first set its scroll scale to one so it matches the camera's movement. This will make the clouds appear an infinite distance away compared to the Parallax hills. Exporting another vector two for the auto scroll speed, we'll need to use Delta in the process function now and can remove the underscore. Accessing the region rect property and its position, starting with the X, we'll subtract the autoscrollX value multiplied by Delta. So this will make the autoscroll speed pixels per second. Then assign the result to the region recs X position. But if we were to run this for a very long time, this number would approach the limits of a floating point number and may cause problems. The X position value matches the size, the result is actually the same. It would be better to reset the X position back to zero when this happens, so we can loop this around for an infinite amount of time. Using the AP F function, we can provide a minimum and maximum value to limit it to only be between zero and the rec size. This function will return a result which must be between these two values, but unlike clamping, the remainder will still be included in the result. And we can do the same thing if we want to use vertical auto scrolling to. I'll give the clouds a large auto scroll along the X for demonstration. And when we run the game, the clouds are auto scrolling to the right, looping infinitely. Mom. Mom. I'll reset the auto scroll value back to a reasonable amount. We now have the foreground and background parallax scrolling for both rooms. But the transitions between rooms is very sudden. I'll see you in the next video. 29. 4-1 Contents: Hello, friends. In this section, we'll allow the player to move between multiple rooms in our game. Opening the start room scene, we can see that the visual components of this room go outside the camera boundaries. If we were in a room adjacent to this one, we wouldn't want to see these sprites drawing when they are meant to be in a different room. Plus, if we were to imagine our entire game filled with many different rooms, it would be a huge waste of resources to have all of their visual components all loaded when we are only viewing one at a time. So we'll add another node two D to the scene. Name it contents. Then reparent all of these other nodes to the contents node, except for the collision shape. Right clicking on the contents node, we'll save it as its own scene. I'll make a new folder in the forest rooms folder for contents. Then name the scene Start Room contents. Then delete the contents from this scene. When the player character enters this room, all we have to do is load its contents and add it to the scene. Then delete the contents when they leave. We can detect when the player character enters the room by connecting its body entered signal, since the character body Tutti node is a physics body. Connecting this signal to the room script, it will automatically add a function for us on body entered, accepting the body as a parameter. We don't need the body, so we can proceed it with an underscore to mark it as unused. At the top of the script, it will be helpful to give this script a class name. Let's name it Room. Then export the contents seen as a packed scene. And populate this variable by clicking and dragging the content scene from the file system tab into the inspector. We'll also declare a contents variable as a node two D. Declaring a new function named load contents. I contents is null, then we'll assign it the value of the contents seen instantiated. Then add the contents node to the scene as a child. When the player character enters this room, we'll call the load contents function. Let's temporarily print out the value of body so we can see what it is. We'll also need another function to unload the room's contents when they're no longer needed, although we won't be able to test this out until the next video. First, checking if there are any contents to be unloaded. If there is contents, call its Qu free function and set the variable to null. Freeing a node from the scene tree doesn't erase the contents of the variable automatically. So if we were to leave a room, unload its contents, then re enter that same room, this if statement, checking if the value of contents is null, would say, yes, there actually is something in this contents variable. However, the node which was stored here was previously freed from the scene tree, so it will cause an error. So setting the variable to null after removing it from the scene tree fixes this issue. Running the game, our character is inside the start room's collision area, so it triggers the loading of the start room's contents. However, we don't want any other physics bodies triggering the signal and accidentally loading room's contents. We need to make sure that the player character will be the only body which can make this happen. Switching to the player character scene, we can select the character body Tutti node and expand the collision section in the properties inherited from collision Object. Here we have a collision layer and a collision mask, each with 32 numbered buttons. These are bit masks, which are just integer numbers used in a particular way. Since integer numbers are stored as 32 binary zeros and ones, we can use each individual bit as a layer. When it comes time to check for collisions, applying simple logic operations between one object's collision mask with the other object's collision layer, we can determine if they should collide or not. Only if the first object's mask has a one in the same bit position as the second object's layer will trigger a collision. Otherwise, it will just pass through as if it wasn't there. Changing the character's collision layer to any other number, it will now exist on its own unique collision layer separate from everything else in the game. Since the numbers are in groups of eight, I like to use the first eight layers for environmental collision layers, the next eight for the player character, the next eight for enemies, et cetera. The character body is still masking layer one, so it will still collide with the terrain during its physics process when we call move and Slide. We can name these layers in the project settings under layer names Physics two D. I'll name layer one as terrain and layer nine as player. These names will be displayed when hovering the mouse over the layer buttons in the inspector, along with their bit and integer value. In the start room scene, we can change the collision mask, so this area two dende will only mask the player character collision layer, only the player character will trigger collisions with this area. Running the game again, nothing will change, but we can be sure that no other bodies will be loading room's contents. We now have our room dynamically loading its contents when the player character enters it, but we still only have one room. I'll see you in the next video. 30. 4-2 Rooms: Hello, friends. In this video, we'll add a second room for the character to move to. We'll begin by duplicating the start room scene. I'll name this 1 second room. Then also duplicate the start room's contents scene and open it up so I can edit it, then remove the trees and canopy so it will be visually distinct from the start room. We can also edit the terrain and boundaries if we want to make the two rooms even more distinct. Opening the second room scene, I'll rename the root node of the scene to match its file name. Don't forget to assign the second rooms content scene in the second room space scene so it will load the correct contents when entered. In the game scene, since my world will be divided into regions, I'll add a node two D and name it forest. Then make both of my rooms children of the forest region. Save the forest region as its own scene in the rooms folder and open the forest region scene to edit it. In the forest region scene, we can now easily position these two rooms next to each other. We want to position the rooms beside each other so that we can switch rooms when the player leaves one room and enters another. Toggling on grid snapping, then expanding the snapping options and selecting configure Snap. We can set the grid step to the tile size of our terrain tiles -32 pixels. This makes it very simple to position the rooms exactly aligned next to each other. The area Tutti nodes body entered signal will be emitted as soon as the character Tutti's collision shape overlaps with the room's collision shape. To keep things simple, we'll need to leave a bit of space between rooms large enough that the character body can't overlap with more than one room at a time. Otherwise, we would need to add extra logic for when the character enters a room without fully leaving the previous one. Their character body overlaps with the areas of both rooms at the same time, but then turns around and goes back to the first room. It's much easier if the rooms are spaced out. When the character enters a new room, we know that they have already exited the previous room. This will also allow the character to move completely outside the camera boundaries before triggering the transition into the new room. Since my character's collision shape is only 12 pixels wide and the gap between the rooms is 32 pixels wide, I know for certain that the character will fit between these two rooms without touching both at the same time. Having this gap means that all of our rooms will also need to extend their floor collision far enough outside the camera boundaries of the room to prevent the character from falling before they reach the adjoining room. I'll zoom out the camera a bit and comment out the line of code which binds the camera to the current room's boundaries. Running the game, we can see that the start room's contents are loaded immediately because the character was already inside this room. Moving over to where the second room will be, its contents are loaded as soon as the player character crosses the threshold. But we also want to unload the contents of the previous room we just left. It will be the responsibility of the game manager to handle transitions, since it will involve many components of the game scene working together. We'll need to send a signal from the room which was entered to the game manager, which aren't even in the same scene in our project. We'll also have a lot of different rooms in our game and it would be a pain to have to manually connect this signal for every single room one at a time. So it would be easier to use our scripts to automate it. From the room script, we'll add a new signal at the top of the script which we can emit to say that the player character has entered this room with the room which was entered as a parameter. Then when the body entered signal is received, we'll emit this signal instead of loading the contents and pass this room itself as the argument. When this room is ready, we can access the Scene trees root by starting a doll or sign annotated node path with a forward slash, then the word root and another forward slash. From the scene Tree root, the first node we want to access is named game as it is named in the game scene. This gives us access to the game manager script with a period. Then we can reference a function we haven't written yet. Let's call it on player entered Room. Then we can write this function in the game manager script with a matching room parameter. I'll name it room Entered to be more clear in this context. In order to unload the previous room's contents, we'll need to know what the previous room was, so we'll need a variable to hold the room that the player character is currently in. I'll replace this current level with current room and leave it empty. I no longer needs the on ready tag. The first thing we should do is check if the room being entered is the same as the room the player character is currently in. In which case, we don't really need to do anything and can just return out of this function. Now we can assume that the current room and the room entered are different rooms. When first starting the game, there is no current room. We'll first check if current room is not null. If there is a current room, we should tell it to unload its contents using the function we wrote in the previous video. Then assigning current room to be the room entered, we can now tell this current room to load its contents and finally bind the camera to the current room's boundaries. But I'll comment this out so we can see what happens with it zoomed out. Now when we run the game, entering the second room sends a signal to the game manager to unload the contents of the start room before loading the contents of the second room. We now have multiple rooms in our game, but the foreground and background parallax scrolling do not work for rooms which are not centered about the scene origin. I'll see you in the next video. 31. 4-3 Parallax: Hello, friends. In this video, we'll fix the parallax scrolling of the foreground and background with a custom script. Starting in the second room scene, I'll copy all of my modulation colors from the Parallax Tut nodes to their child nodes. Then I can unparent the sprites and delete the Parallax Tut nodes while still maintaining their color modulation. I'll then attach a new script to any of these sprites, name it Parallax, and put it in the scripts folder. This same script can be attached to all of the sprites I want to Parallax. To create a custom behavior, it helps to take some time to think about what it is you're trying to achieve, which node references and other information will be required. I have a number of different sprites. I want to move at different amounts relative to the position of the camera, and I know that the camera is currently bound within the limits of this room. I'll temporarily add a camera two D node to the scene and zoom it in by a factor of two to pretend that this is the game scene. If the camera is exactly in the center of this room, I wanted to draw everything exactly as it is. But when the camera moves away from the center, I'll move all of the parallax sprites by a relative amount with the background elements moving less than the camera and the foreground elements moving in the opposite direction. To do this, each sprite will need to know the position of the camera relative to the center of the room, as well as its own original position. In the Parallax script, I'll declare an exported variable to hold the factor by which the sprite moves relative to the camera and name it scroll scale as a vector two. This will work similar to the scroll scale variable in the Parallax two D node. When this room is loaded into the scene tree, we can use the on ready tag to get its position and store it in a variable as the original position. Getting a reference to the current camera is easy thanks to the node G Viewport function. Returning the Viewport that this node exists within, which also has the Get camera to D function to get the camera. We can store this in a variable as well, using the on ready tag. Overriding the process function, we don't need Delta and can mark it as unused. I'll just set the position of the sprite to be its original position plus the distance of the camera from the midpoint of the room multiplied by the scroll scale. Now the camera just needs a public function which can tell us its position relative to the current room's midpoint. Switching over to the camera script, I'll write this function, offset from midpoint, which will be the camera's position plus its offset minus the midpoint of the current room. Declaring this as a new variable, I can calculate it as the average of the top left and bottom right corner of the room when accepting them from the set bounds function. We could modify this function to accept the midpoint and size of the rectangle rather than its top left and bottom right corners, but it won't make any difference in efficiency anyway, so I won't bother. Now we can set the scroll scale for each of the parallax sprites to give them each a different distance from the camera. I'll still only use horizontal parallax, giving my hills a scroll scale of one third along the X axis, and the hills further back two thirds. For the foreground, they will need to move in the opposite direction as the camera, they'll have a scroll scale of negative one third and negative two thirds. This will be sufficient for creating the illusion of depth for our rooms. Running the game, we can move over to the second room and see that the parallax effect looks identical to the start room, despite being performed by a very simple script. Since the foreground sprites are moving in the opposite direction of the camera, they will need to either repeat and wrap their position to create an infinite loop, or we can just make the sprite larger than the room. Making my shrubs 1.5 times longer than the room and the grass double the length of the room will leave enough extra on either side so the edges never reach the camera's view. A it would be possible to use the Parallax two D node to draw the foreground and background using separate Viewport and draw them on different canvas layers or try to always draw the current room at the game scene's origin. These solutions would lead to other complications and limitations down the line when it comes to drawing the game's map, for example. I find writing custom solutions like this to sometimes be easier than using the provided nodes. The solution also keeps everything for a room in one place, which makes it simpler for designing and building of the rooms in a more intuitive way. I no longer need this camera Tuti node, so I'll delete it. Removing the clouds from the Parallax touti node, we can alter the Parallax script, also auto scroll the clouds too. Expanding the region wrect, all we need to do to move the clouds to the right is to decrease the exposition value over time. Attaching the Parallax script to this node, we can first set its scroll scale to one so it matches the camera's movement. This will make the clouds appear an infinite distance away compared to the Parallax hills. Exporting another vector two for the auto scroll speed, we'll need to use Delta in the process function now and can remove the underscore. Accessing the region rect property and its position, starting with the X, we'll subtract the autoscrollX value multiplied by Delta. So this will make the autoscroll speed pixels per second. Then assign the result to the region recs X position. But if we were to run this for a very long time, this number would approach the limits of a floating point number and may cause problems. The X position value matches the size, the result is actually the same. It would be better to reset the X position back to zero when this happens, so we can loop this around for an infinite amount of time. Using the AP F function, we can provide a minimum and maximum value to limit it to only be between zero and the rec size. This function will return a result which must be between these two values, but unlike clamping, the remainder will still be included in the result. And we can do the same thing if we want to use vertical auto scrolling to. I'll give the clouds a large auto scroll along the X for demonstration. And when we run the game, the clouds are auto scrolling to the right, looping infinitely. Mom. Mom. I'll reset the auto scroll value back to a reasonable amount. We now have the foreground and background parallax scrolling for both rooms. But the transitions between rooms is very sudden. I'll see you in the next video. 32. 4-4 Transition: Hello, friends. In this video, we'll smooth the transition between rooms with a simple fade to black. In the game scene, most games will start with either a loading screen or completely black, then fade in when the game starts. Adding a node to the canvas layer, we can use a color wrecked node to draw simple colors easily. Expanding the layout section, we'll change the anchors preset to full wreck. This will tell the color wrecked to fill the entire canvas. It's currently white, but black is more common, so we can change the color to black at the top. When the game starts, all we need to do is gradually change this color from black to clear, revealing the camera's viewport behind it. With our small simple rooms, they load instantaneously. The loading and unloading of room contents doesn't currently cause any lag. But if we imagine that our rooms will consist of many more sprites and especially sound effects and background music being loaded, this will cause the room loading to take longer than a single frame. We can use the same effect backwards to fade to black, do our room unloading and loading, then fade back in when it's all done. This project won't get so big that we need an actual loading screen. Let's rename this node fade and give it a unique name. We will need to make sure this node remains at the bottom of the scene tree to be drawn over everything else or increase its Z index. Having this big black rectangle in the game scene can get in the way of working on other things. We might want to have this node hidden by default. Attaching a script to this node, I'll name it fade and save it in the scripts folder. Overriding the ready function, the first thing I want to do is set its visible property to true, so it covers everything in black. We will need two public functions to adjust the color to either black or clear. To adjust the value of a property over time, the easiest way is to use a tween. So we'll declare one as a variable. While I don't expect to allow fading more than once in rapid succession, it's still a good idea to check if the tween already exists and kill it just in case. Then create a new tween. No need for fancy transition or easing types here, a linear tween will work just fine. And will be tween this node itself, its color property and changing its color to be transparent over a duration of time. For now, I'll put 1 second. There is a transparent color defined in the class, but if we hover the mouse over it, we can see that it has red, green, and blue values of one, meaning it is transparent white, not transparent black. This won't create the effect I want, so I'll define my own transparent color as a constant and name it clear. Using all uppercase is conventional when naming constants. This will be a color with zero red, green, blue and Alpha values. We can also define a constant for the duration of the tween and replace the 1 second with this constant. This makes it a little easier to change the duration without having to look through the script and without exporting a variable for something you want to keep consistent for your entire project. For the feed to black function, we could copy and paste all of this code, only changing the final value argument of the tween property function call, but it would be better to avoid redundancy. Et's instead make this its own private function named tween color, accepting the final color as a parameter, then pass this to the tween property function call as the argument. Then both the fate to black and two clear functions can call this function, passing different arguments. In the game manager script, we'll grab a reference to the fade node at the top. When the player character enters a room after loading the room's contents, we can tell the fade to tween its color to clear. When the game first starts, there won't be a current room to unload, so this will fade in right away. After entering the second room, we'll want to fade to black before unloading the current room's contents, then proceed with loading the second room and fading back in. But this function will not wait for the tween to finish before unloading the contents of the room. It will do it immediately. To pause this function while the tween fades to black, we can use the keyword await. Await will be followed with a signal. The signal we want to await is the tweens finished signal in the fade script. So we can return this signal from our tween function. Changing the return type of our function from void to signal. Passing the same return signal along again through the fade to black and to clear functions. Back in the game manager script, the game manager can now access this signal anytime it calls these functions. We can await fade to black or await fade to clear, so this function will pause and wait for the tween to finish before proceeding. We generally don't want the player to have control over the character while this is happening. So we can call player dot Disable at the top of the function before starting the transition and re enable the player node at the end after the transition is complete. I forgot to update this direction variable to move direction when I renamed it. While these rooms are quite small and should be unloading and loading quite quickly, we still might be able to see an instance where the previous room unloads and because there is no terrain for the character to stand on, they fall through the floor before the next room's contents load to provide a new floor. So we should also disable and enable the character at the same time as we do the player node. Another way we can enable and disable nodes is by changing their process mode property. Setting it to disabled when the transition starts, then inherit when the transition is complete. Now the player can't control the character, and the character won't be affected by gravity while transitioning between rooms. Next, we'll work on drawing our rooms on a map. I'll see you in the next video. 33. 4-5 Viewport: Hello, friends. In this video, we'll start working on drawing our rooms on a map. While it is possible to draw the map separately from the game's world, it's much more of a hassle to maintain consistency between the world and the map if you have to update both separately every time you change something. Positioning icons on the map, especially icons that can move like the player character, can become an even harder task. A much easier method of creating a map is to simply have a second camera viewing the world from a Zoomed out perspective, but draw the world and its contents in a simpler way. So let's add another camera to our game scene and rename this one to be Map camera. By default, only the first camera will be used, and it will draw the game's main Viewport. To draw the map, the map camera will need to draw a different Viewport, so we'll add one as a node in the scene tree, which is a sub Viewport node. Rename it to Map Viewport and give it a unique name. In some of our scripts, we have already used the G Viewport function to get the game's main Viewport, which is being used to draw the game's two D world. The two D world in this context is not the same as the world Environment node. It contains all of the TD nodes currently in the centre, including the game manager. If we child the map camera to the Map Viewport, this camera will now draw the Map Viewport instead of the game's main Viewport, but it doesn't have anywhere on the screen to draw it. And so we'll need to add a texture wrecked node to the canvas layer to give the camera a place to draw the Viewport. Let's name this to be Map Viewport texture. I'll remove all of these dummy control nodes and make sure that the texture wrecked node is drawn before the fade node by putting it higher in the scene hierarchy. Clicking on the texture dropdown, we can select Viewport texture then select the map Viewport to be drawn as the texture for this texture wret. The texture rect will automatically be resized to match the size of the Viewport. It's currently just drawing the default gray background color because the map camera and the Viewport can't see anything. By default, every Viewport will have its own world. You can think of these worlds like parallel worlds or planes of existence. They can occupy the same space at the same time but exist independently from one another. But we want this Viewport to draw the same world as our main Viewport. In the game manager script, we'll grab a reference to the Map Viewport node. Then in the ready function, set the world two D property of the Viewport. We can access the main Viewport by calling the game manager nodes get Viewport function, then access its world two D property. Now, both the main Viewport and the Map Viewport will draw the same world. We generally want our maps to view a lot more of the world, so I'll set it to one eighth Zoom. If we want to change the dimensions of the map texture, we can set the pixel dimensions of the Viewport by selecting it in the scene tree. I'll make it half the dimensions of my game 640 by 360 pixels. Rather than drawing this gray color when there's nothing, I would rather the background of my Map Viewport be transparent. I'll toggle on transparent background. However, this makes the map difficult to read, I would like to have this map texture contained within a panel to make it appear separate from the main game. Adding a panel container to the Canvas layer, I'll reparent the map texture to be a child of the panel container node, then rename the panel container to Map and give it a unique name. With the default theme, the panel container will draw a transparent black color and since it is a container, it will automatically resize itself to contain its children. This makes it clear that the map texture is being drawn on the canvas, not as part of the world, and we can adjust how the panel container looks later through the theme editor. I'll use the panel containers anchors to anchor it to the center of the screen. We generally want the map camera to follow the player character around like the main camera does, but without any of the tweening or other features. Unlike the main camera, the map camera will never need to do anything else, so it is safe to always have it match the player character's position exactly. There's no way to have the map camera childed to the player character, since it must remain a child of the map Viewport to draw the map. We could write another script to move this camera, but there's a node we can use to make things easier, the remote transform two D. With the remote transform two D as a child of the player character, we can set its remote path to the map camera. Now the camera's transform properties, position rotation and scale will automatically update to be the same as the player character. We can toggle off the rotation and scale by expanding the update section. Even if we were editing the player character's rotation or scale, we wouldn't want the map to copy these properties. Mm. We now have our Games world being drawn on the Canvas, but we want to draw a simplified version of the world rather than an exact copy. I'll see you in the next video. 34. 4-6 Visibility: Hello, friends. In this video, we'll have the map camera draw a simplified version of the world. Starting in the start room, let's temporarily add in the contents of the room. I don't want to draw all of these sprites on the map, just an outline of the room's shape and terrain. We can use any two D node to draw this depending on the style we want. I'll use a Tile Map layer node. Give it a new tile set with tiles that are 32 by 32 pixels. I'll import a new sprite, which is just a simple 32 by 32 pixel white square. Then add this to the tile set. Drawing this white square on my Tile Map layer, I'll draw an outline of the room using the terrain as the floor rather than the camera boundary and leaving doorways to connect to other rooms. We want the main camera to draw only the contents, while the map camera will draw this Tile Map layer note instead. While both are looking at the same room, they will see two different versions of it. Now that I have the shape of my room, I'll delete the contents from the scene. Since the Tile Map layer node will always be viewed by the map camera, it will always remain loaded rather than being loaded and unloaded like the contents. Expanding the visibility section, I can use the modulate color property to change my white squares into colored ones if I want to. Since this is part of the forest region, I'll draw its map in green. To have the map camera draw this but not the main camera, I'll change its visibility layer 1-2. The default visibility layer one will be everything that is drawn by the main game camera, while layer two will be everything I want to draw on the map. Just like with the collision layers, we can name these visibility layers in the project settings under layer names two D render. I'll name layer one world and Layer two Map. These visibility layers are dependent on the visibility layers of their parents. If the Viewport can't see the start room area node, then it won't bother to check if it can see the Tile Map layer node child. The start room parent node will need to have its visibility layer set to both one and two, since it will have children that can be drawn by both viewports. Proceeding up through the hierarchy, the forest region needs to have its visibility set to use both visibility layers as does the game scenes root node. The world environment Node doesn't have a visibility layer property. Now selecting the Map Viewport, we can set its Canvas call mask to only view layer two and not layer one. This works similar to the collision mask property for collision objects. Only if the bit for the mask is set to one, will it be able to see anything whose layer has the same bit set to one? But we can't access the main Viewport from the editor to change its Canvas call mask to only view layer one and not layer two. By default, it will be masking every layer. So we'll need to edit it through script. In the game manager scripts ready function, we can access the main Viewport with the G Viewport function, then set its Canvas call mask property. Hovering over layer one, we can see its integer value is also one. We can just set the Canvas call masks value to one. Now it will only view visibility layer one and not layer two or any other layer for that matter. Copying the Tile Map layer node in the start room, I'll paste it into the second room to also draw this room on the map. I'll instantiate the room's contents, then edit the tiles to more accurately reflect the shape of this room and delete the contents when I'm done. Remember to set the visibility layer of the parent nodes to exist on both visibility layers in order for the children to be seen by their respective viewports. Now, both rooms are clearly visible on the map with very little effort required as we add more rooms, only needing to add the map for each room in its scene on a separate visibility layer. We can easily add other things to the Map two by adding more sprites to them and putting them on visibility Layer two, like a player character map icon. Opening up the player characters scene, let's add a Sprite to denode and name it Map icon. I'll populate the texture with an atlas texture, use the same sprite sheet, and select a region to use as the map icon. I'll use the character's head somewhere in the sprite sheet where the tail isn't in the way. After populating the texture property, we'll set its visibility layer to two and not one. Then the root node of this scene will need to have both layer one and two. I'll adjust to the offset of the sprite, so it sits on the floor like the character does. Knowing that the camera's Zoom is one eighth, I'll scale this icon up eight times making it appear very large in the scene. Since I don't want this icon covering up my character anytime I work in the scene, I'd rather have it hidden and this will be true of everything in my game that I want to attach a map icon to. I'll add a script to this node, name it Map icon. And I'll just have it set the visibility property to true on ready. Now, this very large map icon doesn't need to be drawn in the editor while I'm working on my character, but it will be drawn when I run the game. We now have our map drawing outlines of rooms and the player character with an icon, but we may want to have the map toggle on and off with a button. I'll see you in the next video. 35. 4-7 Map: Hello, friends. In this video, we'll allow the player to open and close the map with a button. Starting in the game scene, I'll have the map hidden by default by toggling the visibility of the container node holding it. Then attach a new script to the container. While I'm currently using a panel container for this, I won't need to access any of the features of the panel container in this script and can inherit from the more generic type of control. If I want to use a different type of control node in the future, I won't need to modify my script. This script will need public functions to open the map and close the map. To keep things simple, I'll just set the visibility of the node to true or false accordingly. There are also show and hide functions defined in the Canvas item class which do the same thing. While this may seem pointless to have a function that just calls another function, keeping your implementations encapsulated in public functions like this means we can easily update this in the future. We can add extra logic in these functions to make things happen when the map is opened or closed, or we could use a tween to move the map on an off screen or having it grow and shrink rather than just toggling its visibility. If I want to make these changes, I only need to edit this script, rather than also needing to edit the player script or any other scripts that are calling those functions. Since I've decided to use a toggle button to toggle the map on and off, instead of calling open or close functions, I would rather have a single toggle function that does both. Adding a private variable for whether or not the map is currently open, I'll have the toggle function close the map if it is open or open it if it is closed. Then the open and closed functions can set the value of I open accordingly. Opening the project settings, the input Map tab, we need to add a new input action for opening and closing the map. I'll use the backspace key and the select button on my controller. Most games with this kind of game map will allow the player to pan the camera around too. I already have look up and look down inputs. I'll just add look left and look right. Using the left and right arrow keys or the right stick tilted left or right on my controller. In the player script, we can export a variable to hold a reference to the map node and populate it in the inspector. Then in the input function, tell the map to toggle on or off if the map button was pressed. Running the game, the map starts hidden, but pressing the map button, displays the map and pressing it again, closes it. To pan the maps camera, we'll just need to adjust its offset value. So we'll give the map script a reference to the map camera. Adding a public function named pan, accepting a vector two argument for the direction to pan the camera. We'll add to the camera's offset this direction multiplied by a Pan speed, and we can access Delta even though we aren't in a process function by calling the node classes Get Process Delta t function. Exporting this pan speed variable, I'll set mine to 1024. This may seem fast, but remember that the map camera is zoomed out very far, so it will appear to move at one eighth of the speed. In order for the player script to control this camera when the map is open, it will need to be able to know if the map is open, rather than making the is open variable public, allowing any other script to modify its value, we can write a function which returns the value of the variable. When the map is closed, I'll reset the offset of the camera back to zero, so it will once again be centered on the player character. Switching over to the player script, we'll add the controls for panning the map in the process function. But only if the map is open. If the map is open, we'll tell the map to pan. Just like we used G axis to get the state of two opposite directions as a float, we can use G vector to get a vector two from four directions. We just have to give it the names of the four input actions matching the X and Y axis. Look left, look right, look up and look down. The last argument is a dead zone, which is the minimum length of the vector that will be used. If the vector is too short, we can ignore it, which is useful for analog sticks to ignore drift. While this won't cause any problems on a controller since I'm using the right analog stick, my PC controls are using the arrow keys for both movement and looking. So if the map is open, I'll put the rest of the looking and movement controls in an block to skip over them. If you want to allow the player character to still move and look up or down while the map is open, you'll need to change the input actions so they don't overlap. To prevent the character from continuing to move after opening the map, I'll set their move direction to zero when the map is opened. The player can now press a button to access a world map, displaying our two rooms and the player's current position, and we can pan the map camera around if we want to. In the next section, we'll start keeping track of the player's stats and draw them on the heads up display. I'll see you in the next section. 36. 5-1 HUD: Hello, friends. In this video, we'll start displaying information about the player character on the heads up display. Since starting this course, Godot 4.6 was released. Updating the project to Godot 4.6 did not cause any problems, so I'll continue the lessons in the updated version. I don't recommend updating to a newer version of any game engine while you're working on a project unless you have version control to revert back if you need to. I'll need to import some UI sprites into my project to draw the character stats on the screen. In the game scene, we have a canvas layer which is set to layer one in the inspector. The main Viewport which draws the two D world of the game is considered layer zero. So everything in the two D world of the game will be drawn on layer zero regardless of their z index. Canvas layer, layer one, will draw over all of that to draw things like menus and our characters heads up display like their health and magic. The nodes in the Canvas layer are usually Goy control nodes, which all have green icons to set them apart from two D nodes which are blue. Let's start by adding a control node as a child of the Canvas layer, rename it to HUD, reorder this to be at the top of the Canvas layers children and make the map a child of the HUD. We can then add as many icons, gauges, and text as we need to this parent node to create our heads up display. It helps to have a basic control node act as the parent of your entire HUD for several reasons. Some of the properties of the parent HUD node will be inherited by all of the child nodes. Since the canvas layer node isn't a control node, it doesn't have the same properties, so can't provide this benefit. If you want to give all of your HUD elements a consistent look and feel, you can add a theme to the parent node and it will be applied to all of the child nodes automatically. It also keeps the fade transition at the bottom of the scene tree naturally, since you'll only be adding children above it, so you won't need to worry about rearranging the scene tree every time. Unlike two D nodes, which use their transform position property to define where they are relative to the scene's origin, control nodes use both a position and a size to define their location within a defined space. Usually the game's window, which is displayed as this blue rectangle. We can see that the HUD node has a size of 40 by 40 pixels and is at position 00, which is the top left corner of the window. While we can just move and resize the control node editing these values, these changes will not scale well or work effectively if we were to change the window size in the project settings. So control nodes have these green anchors we can use to set the position and size of each control node relative to their parent with the canvas layer representing the entire window. We want the Hud to cover the entire window so we can set its anchor preset to full wreck. This puts the anchors at each of the four corners of the window, then automatically updates the size and position values to match. If we make the map visible, its position hasn't been updated, so I'll reset its anchors to center. Now we can see that since it is anchored to the center of its parent, moving or resizing the HUD node will move the map to remain centered. To draw the character's health and magic, I'll add a new control node to hold these elements grouped together and name it vitals. I'll leave this anchored to the top left but reset its size to zero. Next, I'll add a texture wrecked node to draw a simple texture, a profile of the character, and populate its texture property by dragging the asset from the file system tab. Adding another texture wrecked node, I'll name this one health icon and populate its texture with an atlas texture, selecting a sprite from my sprite sheet. I'll need to have multiple of these to represent my character's total health. So I'll duplicate it a bunch of times and position them roughly in sequence. It would be a good idea to keep these icons grouped together under a single parent node. So I'll add another control node named Health Counter and reparent all of the health icons to be a child of this node. Manually positioning each of these icons is kind of a pain. So Godot provides control nodes called containers to do it for us. Since I want these icons to be arranged horizontally, I'll change the type of the parent node to be a horizontal box container. This arranges all of the child nodes automatically. They're being stretched to fill the vertical height of the horizontal box container, which has the default size of 40 pixels. Resetting this value will force it to collapse to the height of the largest child. I would like them to be closer together, so under the theme overrides section constants, I'll set the separation to be negative eight pixels. Adding more icons to this counter is as simple as duplicating the child node. It's a good idea to lay out your HUD elements with their maximum limits in mind. So I'll have nine health icons, which is the maximum amount I plan to include in my game. Selecting any of the child nodes, their position values can no longer be edited manually, since the container node is now in charge of controlling their position. So keep this limitation in mind. If you wanted to animate the positions of these textures individually, you wouldn't be able to do so while they're inside a container parent. Next, I'll add another texture wrecked node to draw the character's magic gauge. Then populate its texture property with the asset from the file system tab. While this texture isn't functional as an actual gauge, it helps to draw everything you want to include on your hood to get an idea of how you want it laid out before getting into implementation. Like the health icons, I want to see how this magic gauge will fit into my layout when it is at its maximum limit. Stretching this image to make the gauge longer stretches the individual pixels of the image, which looks bad. We'll deal with this in the next video, but for now, we just need an idea of how large or small the magic gauge can be. You may want to take some time getting the layout of your hut elements looking the way you want. This layout looks good. But when I run my game, it is very small and doesn't match the pixel density of the rest of the game. Since I'm using a camera zoom of two, all of my pixels are doubled in size. So I'll set the scale of my vitals node to two, doubling the sizes of all of its children. This is easier to read and matches the aesthetic of my game better. Changing the anchor presets of the vitals parent node, we can easily change it to define its position relative to any corner of the screen or the center of any edge. Or the center of the screen like the map. You can then adjust the positions of the child nodes relative to the anchored parent, and they will retain their relative positions even if you decide to change the resolution of your game. I'll keep mine in the upper left corner and give it 20 pixels of padding from the edge of the window. We now have some textures drawn on the heads up display, but they aren't actually doing anything yet. I'll see you in the next video. 37. 5-2 Gauge: Hello, friends. In this video, we'll implement the magic gauge by using style boxes. Let's start by changing the type of the magic gauge node. There is a control node that does exactly what we need a progress bar. Changing the type removes the texture. The progress bar is drawn as a semi transparent gray rectangle, and it has a percentage displayed in text. I don't want this percentage label, so I'll disable it in the inspector. The indeterminate toggle is not applicable to a gauge, but is meant for progress bars which do not actually measure any progress. After setting the minimum and maximum values for our magic gauge, we can set the value to any number in between to see the progress bar fill up. The step value is the smallest the value can increment, forcing it to snap to the nearest step. The other properties inherited from range aren't really appropriate for a gauge and can be ignored. We can also change the direction in which the progress bar will be filled if we want to. I'll leave mine as begin to end. How the progress bar looks is determined by the theme. Since we don't have a theme yet, it is using the default theme provided by Godot. If we only want to change the theme for this node by itself, we can expand the theme overrides section. Since I'm not using the percentage text display, the colors, constants, fonts and font sizes are all irrelevant. But you can use them to alter how the font is written on your progress bar if you want to. Under the styles section is where we can find the style boxes for how the progress bars background and fill are drawn. Clicking on the drop down, we can select style box empty, which will just draw nothing. Style box line to draw only a simple line, style box flat to draw a simple rectangle or style box texture to use an imported texture asset. Clicking on the style box texture to expand its properties, we can populate the texture property with an asset from the file system tab. If we change the size of the progress bar, the texture will be stretched to fill the space, which doesn't look very good. Style boxes have more properties to alter how the image will be drawn when stretched. The texture margins will turn the texture into a nine slice, slicing the image twice horizontally and twice vertically, so it is divided into nine segments. The corners will not be stretched. The edges will be stretched in only one direction to connect the corners, and the middle will be stretched in both directions to fill the remainder of the space. Or we can toggle off drawing the center of the texture and draw only the borders. If you don't want the individual segments being stretched, the axis stretch properties will give the option to use tiling instead, or the tile fit option will use a combination of tiling and stretching to best fit the allotted space. Expand margins will draw the texture beyond the rectangle defined by the nodes position and size. A sub region can be used to draw a region of a sprite sheet. Modulate works just like it does with the Canvas item visibility, multiplying the color of each pixel by this color. Content margins are for style boxes used to draw container nodes, so they won't be applicable here. Adding another style box texture for the progress bars fill and populating it with the appropriate texture. If you do not want to enter the texture region values manually, the sub region editor can also be used to edit the texture margins visually, too. Using these assets, the magic gauge isn't meant to be stretched vertically. I'll set its height to be the same as the assets at 16 pixels. But using the style boxes, the gauge can be stretched as much as needed horizontally, and it will be drawn without stretching the pixels. The value can also be edited and the fill texture will also stretch as much as needed without the ends of the bar being stretched. If we wanted to have these style boxes applied to more than just one progress bar, we can make this automatic by editing the theme rather than using theme overrides. Selecting the HUD node then expanding the theme section, we can create a new theme from the drop down. Clicking on the theme resource will open the theme panel at the bottom of the editor window. Here we can see most of the control nodes Godot has to offer and how they are currently being drawn by the theme, including a progress bar here. Clicking on the Plus button, we can add Progress bar to this theme and override its theme properties the same way we did from the inspector for the progress bar node. All of the same theme properties are here for colors, constants, fonts, font sizes, and styles. If we click on the plus buttons beside background and fill, we can add style boxes to this theme for the progress bar. Since we already created these style boxes, we can select the progress bar node. Then click and drag the style box resources from the inspector into the theme editor. The progress bar in the theme preview is updated to use the style boxes. Now every progress bar which inherits from the HUD node will inherit this theme and use these style boxes to draw the progress bar automatically. This gives us the option of having all control nodes of the same type drawn the same way or using theme overrides to draw individual control nodes differently. We can remove the theme overrides from the progress bar node since these style boxes are now being inherited from the HUDs theme. If you want to use this theme for your entire project without needing to copy it into every scene, you can save it as a project resource. Then in the project settings, under GUI theme, we can set a custom theme by selecting the saved theme resource. This will require restarting the editor to take effect. Now, every progress bar in every scene of the entire project will be drawn the same way. But let's imagine you want to have multiple magic gauges that use these style boxes and multiple other progress bars that use other style boxes. Rather than having multiple themes, we can use theme type variations. Clicking on the Plus button, we can type a custom name to add to the theme like Magic gauge. Then click on the Tools button and give it a base type of progress bar either by typing it in or selecting it from the list. Now the other tabs contain the properties inherited from Progress Bar, and we can change the values of the background and fill style boxes. I'll copy the style boxes from my magic gauge node, then click on the chain link icon to unlink them from where they were copied. Switching to the progress bar theme properties, I'll remove the style boxes, reverting everything back to the default theme. Then selecting the magic gauge node in the scene tree, expanding the theme section in the inspector. I can select magic gauge in the type variation to tell them to use the theme properties for the magic gauge. Now my game can have multiple magic gauges if I need without having to set up the styles for each of them individually. I only need to tell the theme to use the magic gauge theme properties, and I can still have other progress bars in my game that are not magic gauges, which can be drawn using different styles too. We now have a simple magic gauge, but we still need to implement the health counter. I'll see you in the next video. 38. 5-3 Icon: Hello, friends. In this video, we'll create a health icon that has animations for when the character recovers or loses health. We currently have the health icons displayed as texture wrecked nodes organized horizontally by the horizontal box container node. If you wanted to animate these as sprites, you might think to change them into Animated Sprite two D nodes. But mixing control nodes with two D nodes causes problems. I'll quickly set up the Animation Sprite frames resource so I can demonstrate. Two D nodes will not behave like control nodes. They won't anchor their positions relative to their parents or be organized by container nodes or automatically resize to fit the Window resolution. Rather than changing the type of node we are using, we will need to animate this texture wret node as it is. If we expand the texture property dropdown, we can see that there is an animated texture option. This animated texture resource can only hold a single animation, already, it doesn't really fit our needs in this case. If we wanted to use this to animate the icon, we would need to set each individual frame of the animation as a separate atlas texture with a duration in seconds through this clunky interface. And after all of that, we would need to use a custom script to control which frames of the animation are being used anyway. If we wanted to have the same texture, play multiple animations. So it would be better to leave this texture property as an atlas texture. While we could write a custom script to change the value of the region property of this atlas texture, the amount of time and effort it would take wouldn't really be worth it when the Animation Player node already exists. So let's just add an Animation Player node as a child of this health icon. Before we do any animations, it would be a good idea to save this icon as its own scene first. This will encapsulate the Animation Player node, since it isn't really relevant to the context of the game's HUD. In this scene, first, I'll set the icons region to what I want to use as its default state, which will be empty. Then create a new animation for when this icon fills up, setting the frame rate to 12 frames per second, adjusting the Zoom and turning on frame snapping. I'll then key frame this to the start of the animation and add this to the reset track. Even though I don't want to use this as the first frame of the animation, it's now the default state being used by the reset animation. Next, I'll overwrite the first key of my fill animation with the actual first frame. Then continue with the rest of the frames filling up the icon. I'll set the duration of the animation to eight frames. Remember to change the update mode to discrete when using frame animations like this. That looks good, so I'll duplicate it and make another animation for when the character takes damage, which will empty the icon. Since I know that my sprite sheets sprites are all evenly spaced, I can simply edit the keyed region values by multiples of 32 pixels to get the frames that I want. This animation is shorter at only six frames. It's important that these animations end with the frame that will be persistent after they finish. Once this empty animation plays, the icon will remain empty until the fill animation plays. Likewise, after the fill animation plays, the icon will stay filled until the empty animation plays. Attaching a script to the health icon's root node and save it in a new folder for UI scripts. We'll assume this script can be used for any animated icon in case we want to use it for something other than health. This script only needs to get a reference to the Animation Player node when it is ready. Then provide public functions to play each of the animations when needed. Back in the game scene, we can delete all of the other health icons. I'll rename this first one to Health icon one, then duplicate it eight times to recreate the rest of them with the Animation Player and script attached. If we want to test this out, we can use the player script to tell the health icon to fill or empty with a keypress. When pressing the Run button, I'll use the dollar sign annotated node path to tell this first health icon to fill. When I release the Run button, I'll do the same thing to tell it to empty. Since this is only for testing purposes, I don't need to care how inefficient this code is. Running the game, pressing the Run button, fills all of the icons, even though I only told the first one to fill. This is because Godot naturally tries to be as resource efficient as possible and has created only one atlas texture resource to be used for all of the icons. So when we animate one icon, all of the icons are using the same texture, so all of them are animated. In the health icon scene, we can override this behavior by expanding the atlas texture resource in the inspector. Under the resource section, toggle on the local to scene option. This will tell Godot that every instance of the scene will need its own unique copy of this atlas texture resource with its own region property. This sprite sheet will still be shared by all of the icons, but which region of the sprite sheet they draw will be unique. Running the game again. This time only the first icon fills when I press the run button and empties when I release the button. With the test successful, we can remove or comment out the extra lines of code. We now have animated health icons, but the health counter isn't actually counting our character's health. I'll see you in the next video. 39. 5-4 Counter: Hello, friends. In this video, we'll write a script that manages the animated icons to reflect a value and a maximum value. We have our animated icons here, but we need to attach a script to the parent node that will control how and when they animate to reflect the counting of the player character's health. I'll name this icon counter and put it in the UI scripts folder. There isn't anything in this script specifically using the features of the horizontal box container, so I'll extend the more generic type of control to allow changing the type of this node later if I want to. The script will need two integers, one for the character's maximum health and another for their current health. Since I want this script to be useful for counting anything, not just health, I'll use the generic term value. For now, we can set the default value of maximum health to be equal to the number of children this node has using the on ready tag. Next, we'll need two public functions that will set both of these values, accepting an integer parameter and overriding the old value with the new one. When we have our player character taking damage and recovering health, we'll be able to call these public functions to communicate the health value changes to the player. It will be the responsibility of this script to control how many icons are displayed and which ones are filled or emptied based on the values of these integers and how they change. We'll start with setting the value of their current health. The first thing we should do is validate that the number we're being given to set the value to is a valid number. We can't display the character having less than zero health, nor anything greater than the number of available icons, their maximum health. So we'll first set the value to the return of clamp integer between zero and max health. If we try to call this function with a negative number, it will set health to zero instead. And likewise, anything above the maximum will be capped at the maximum. Next, if we're setting the value to the same number that it already is, we shouldn't have to do anything, so we could just return out of this function. Once we've established that we are changing the value being displayed by this icon counter to a new valid number, the process will differ if the number is increasing or decreasing. If the new value is greater than the old value, we'll tell some of the icons to fill. But which icons? The icons are conveniently drawn by the horizontal box container in the same order as their child index in the scene tree. Using a simple four loop, using I as the index, we can count through the range of value to new value. Range returns an array containing the sequence of integers counting up from the first number to the second, but excludes the second number. So if the old value was one and the new value is four, this will give us the numbers one, two, and three. Since the child index of the first child is zero, not one, this lines up perfectly with the indices of the icons that need to be filled. So we'll get the child node at Index I and tell it to fill. The process for decreasing the value is very similar. Only since we know that the new value is less than the old value, we need to provide an extra argument to the range function, negative one, which will tell it to count down. If the old value was four and the new value is one, range will return 43 and two. These are not the same as the child indices of the icons, but are one greater. So we can fix this by subtracting one from I in the get child function and tell these icons to empty. Only after all of this is done, do we set the old value to the new value? We can test this out without adding any extra scripts or input mappings. All we have to do is add an input function to the script. First, we'll check if the input event was an input event key and if the event was a key being pressed. Then use a match statement on the event's key coode. If the key that was pressed was any of the number keys, we can set the icon counter's value to that number. Declaring a number variable as an integer, we can calculate the number that was pressed by simply subtracting key zero from the events keycode. This works because key coodes are an enumeration, a simple numbered list. Whatever the keycode is for key zero, the keycode for key seven is going to be the key coode for key zero plus seven. We can print out a statement saying something like set current health two plus the number converted to a string. Then call the set value function passing the number. If we run the game, pressing any number key will set the icon counter to that number and animate the icons according to if they are being filled or emptied. And we can do the same thing for setting the maximum value too. While we could have the maximum number of icons in the scene already and just hide the ones we aren't using, it would be more scalable to instantiate new icons as we need them. Since this doesn't happen very often, it won't affect performance and will accommodate adding as many more as we need to if we decide we want to hire maximum later. So we'll export a variable to hold the icon scene as a packed scene. Copying the contents of the set value function. After changing every instance of value to max value, there's only a few more changes that need to be made. First, we can't validate the maximum value based on itself. So we'll need to set some limit to how high the maximum value can possibly be. You may want to export this cap rather than hard coding a value. Instead of filling icons, we'll be instantiating new icons and adding them as children. And instead of emptying icons, we'll be removing them from the scene tree by calling free. To test this out, I'll use the same number key input system, but require that I hold down the up arrow to set the maximum value. So if input dot action pressed, look up, then I'll print out Max Health and set that value instead and put the current health part in the else block. Running the game, I can change the maximum health value by holding down the up arrow and pressing any of the number keys and releasing the up arrow, I can still change the current value health, too. Remember to comment out the input function after testing. We now have an animated icon counter that can keep track of the player characters maximum and current health values. But neither the health icon counter nor the magic gauge are actually connected to the player character. I'll see you in the next video. 40. 5-5 Hurt Box: Hello, friends. In this video, we'll give the character some health to connect to the health icon counter. We could add health variables to the character script and track it that way. This would allow every character in our game to have health without any extra work. But if we consider that things that are not characters may also need to have health, like breakable walls, for example, this becomes less beneficial, especially since our character is a character body two D and the breakable Wall would be a static body two D. And maybe we have another rigid body to D that also needs health. We would need to have the same variables and functions in multiple scripts for each of these cases. We could rewrite our character script to inherit from physics body two D, everything in our game that needs health can also use this script. But there's a lot of stuff in this script which would not be applicable to these other objects. It would be wasteful to have all of these variables and functions attached to a breakable wall which would never need to use them and likewise, have the variables and functions meant for a breakable wall attached to our character. In cases like this, you may want to use the composite pattern rather than object oriented inheritance to reuse your scripts across your game project. By creating a health component, it can exist independent of what it is attached to. Characters can have a health component and breakable walls can have a health component too. In the character scene, we'll add a new child, an Area two D node, and name it Hurt Box. Then add a collision shape TD node as a child of the Hurt Box to define its shape. This will be the area on air character where if they get hit, they will take damage. You may not always want this to be the same as their character body's collision shape. Also, you'll generally always want the character body's collision to remain active, while the Hurt Box will often be toggled off to prevent the character from taking damage while dodging or during cut scenes, for example. So it helps to give your Hurt Box a separate collision area. I'll make mine also a capsule, but slightly smaller than the character body and set its debug color to green so I can tell them apart more easily. This area doesn't need to be responsible for monitoring anything. It only needs to be monitorable so hit boxes can detect it. Expanding the collision section under collision Object two D, we can set this to be on layer ten and remove its mask entirely. In the project settings, I'll give two D Physics layer ten the name of player Hurt Box. Now, anything in the game which is both monitoring and masking layer ten will be able to hurt the player character. Attaching a script to this node, I'll name it Hurt Box. This script will need the same maximum health and current health values as the icon counter has. Since I'll be indicating the health value with icons, these will need to be integers. If you're using a gauge for your character's health, you have the option of using floats instead since a gauge can indicate decimal values. For now, I'll just set the maximum health to five and use the on ready tag to set the current health to Max Health. Unlike the icon counter script, the main way in which a Hurt Box will be modifying the value of current health will be by receiving damage from hit box or recovering health. So these will be our public functions, both of which will accept an amount of health to lose or gain as an integer. When taking damage, we'll simply subtract amount from the character's current health. We can then use the max function to either return this value or zero, whichever is greater, and set that as the new value of current health. This will prevent their health from ever going below zero. We can make the recover function multi use by giving the parameter a default value. If we call this recover function with no arguments, we'll assume that to mean that they should recover all of their health up to their maximum setting current health to max health. If there was an argument given to the recover function, then add that amount to the character's current health and use the MIN function to either return this result or Max health, whichever is lesser. So current health will never exceed maximum health. Both of these cases, the value of current health is being changed and we need this change to be reflected by the icon counter on the HUD. But the Hurt Box is in the character scene and the icon counter is in the game scene. In most cases, whenever you need to communicate information between nodes and different scenes, the best way will be to use signals and form their connection through their closest common ancestor. In the game scene, the closest common ancestor between the character and the health counter is the game scenes root node, the game manager. Let's give the health counter a unique name. Then grab a reference to it in the game manager script. We could try telling the health counter to monitor the character's Hurt Box. We can use the Get node function to retrieve the character's Hurt Box. But then our icon counter script will be relying on certain functions or variables existing in the entity it is monitoring. So it would essentially only be useful as a health counter, which isn't reusable as a script for counting other things. Reversing this logic, it would be better to tell the Hurt Box to be monitored by that icon counter. Then call a function we haven't written yet like set counter and pass it the health counter as an argument. Keep in mind that this get node function will fail if you change the name of your Hurt Box node or rearrange the structure of your scene tree so it is no longer a direct child. I prefer this dependency over writing a new function in the character script to retrieve the Hurt Box node since that script is already quite long. Back in the Hurt Box script, we'll write this new function, accepting an icon counter as a parameter of type control, since we didn't give it a specific class name. Now we have the Hurt Box and the health counter in the same script, so we can easily communicate between them. More importantly, the icon counter script can still be used for anything we want, and any Hurt Box in our game which don't need to be monitored by a counter, can just ignore this one function. We could call the icon counters functions directly, but that would result in every Hurt Box in the game constantly checking if it has an icon counter monitoring it before calling these functions. It would be better to use signals to communicate these changes, one for when the max health changes and another for when the current health changes. Both will need integer parameters for their new values. We may also want another for when the health hits zero, which won't require any parameters. Now when their current health changes as a result of either taking damage or recovering health, we can emit this signal passing their new current health as the argument. If current health is zero after taking damage, we should emit the dyed signal two. Now when we set a counter to monitor this Hurt Box, we can simply connect these signals to the icon counters set value functions, since the parameters passed by the signals match the icon counters functions parameters in both quantity and type. It would also be a good idea to emit these signals immediately so the icon counter can set its values to match the Hurt Box. To test this out, we can write a simple input function in this script. If I press up, the character will recover one health. If I press down, the character will take one damage, and if I jump, the character will recover full health. Running the game, pressing down causes the character to take one damage with each button press. Taking damage at zero health is ignored. Pressing up recovers one health, and recovering health at Max is ignored. Going back down to a lower value first, I'll then jump and fully recover all health. Remember to remove or comment out this input function after testing. We now have our character able to take damage and recover health, but their health isn't being saved. I'll see you in the next video. O 41. 5-6 Data: Hello, friends. In this video, we'll create a custom resource that can keep track of the player's stats and progress through the game between play sessions. If we want the player to be able to earn Max health upgrades, then keep those upgrades the next time they play. The game will need a way of knowing what they have and have not found in the game. To do that, we can create our own resources. We've already been using several resources in this project, like the character Sprites Atlas Texture resource or the Animation Tree state machine resource. And the theme. These resources are all types defined by the Godot game engine to provide specific functions for the nodes that use them. But we can create our own to suit our own specific needs too. Let's create a new folder inside the folder named resources. We can then create a new script in this folder named data rather than inheriting from node, this script will inherit from resource. Resources are not represented as nodes in the scene tree. The first thing we want to do when creating a custom resource is make sure we give it a class name. Otherwise, we won't be able to create instances of it. Since the purpose of this resource is to keep track of the player's data, we can include as many variables as we need for everything we want saved between play sessions. For now, I'll need variables for the characters Max health and current health as integers. I'll also add their maximum magic and current magic as floats. All of these variables will be publicly accessible to other scripts. We can also include strings for things like their name or booleans to keep track of something being turned on or off. Arrays can be used to store groups of related information, like an array of booleans to know which upgrades have been found by the player and which have not. Dictionaries are very useful for storing entire classes of information, since we can use attribute names as the keys in the dictionary with their corresponding values. Beyond the basic variable types, we can also include other classes built into Godot like vectors for storing two D coordinates, colors, or even other custom classes that we create. Any variable you want saved between play sessions will need to have the export tag. Other variables which do not have the export tag may still be useful if, for example, you do not want to save the character's current health, only their Max health and start the player off with full health every time they load their game. These non exported variables will still remain if your game changes scenes during the play session, however. If you were to transition out of the game scene for any reason, maybe to play a cut scene, then return to the game scene once more, these variables will retain their value. While we can write functions in this script as well, it's not usually necessary. If you want to perform some automated initialization of your resource, we can override the init function. This innit function will be called automatically by Godot anytime we create a new instance of this resource through a script. So we can give the variables some default values, set the lengths of arrays, or add some keys to our dictionaries. Be sure to save this script with the class name defined before you try to use it in any way. So Godot will recognize this class name. We will need another script to access our custom resource, which we will cover in the next video. For now, we can test our custom resource by creating one in the file system tab. We clicking, select Create new resource, then type the class name or select it from the list. Then give it a name. Creating this resource we'll open it automatically in the inspector, where we can see and edit all of our exported variables. Creating a resource in this way does not use the nit function, but will use values provided for variables assigned in their declarations. If you select something else and want to return to editing this resource, you can double click on it in the file system tab. I'll quickly test out creating a data resource in the game manager script. We can create an instance of our resource by first declaring a variable with the class name as the type. Then assign the value of this variable to be the class name followed by dot Nu. We can then access any of the variables we declared in this class, both exported and not exported, overwrite their values and use them as needed while playing the game. Remember to remove or comment out this test code and delete the test data resource file. We now have a data resource that includes our characters Max Health and current health, but it isn't being saved yet. I'll see you in the next video. 42. 5-7 File: Hello, friends. In this video, we'll save and load the player's data using our new custom resource. To do this, we'll need another script that manages access to the resource we created in the previous video. This script will need to be accessible from anywhere in the project. The character needs to know their stats. Rooms will need to know if the player has opened a door, found an upgrade or defeated a boss. Even the user interface and the map will need access to the save data. We can make some nodes easily accessible like this by making them global. Let's create a new folder inside the scripts folder named Globals. Then create a new script in this folder named file. The purpose of this script will be to provide quick and easy access to the players save data to every other script we write across the entire project, as well as saving it on the player's system and loading it too. We'll need a variable of the type of our custom resource. I'll name it data with a lowercase D. We'll then need three public functions for managing this data, starting a new game, saving the game, and loading the game. Starting a new game is simple. We just need to set the value of data to be our class name followed by dot neu. This will create a new instance of the resource and initialize. When we're ready to save our game, we'll need to know where we want to save it too. For this, I'll declare a string constant, since this is something that I won't ever change, the path to the location where I want to save the player's data file. Godot provides a simple shorthand that works on any platform. Windows, Mac, Linux, web, and consoles can all use this. User Colon forwards forwardslash. Do will automatically replace this with the appropriate path for the platform as a location which is specific to both the user of the system and the game. Declaring a variable for the filename also as a string, I'll name mine save followed by dot TRS short for text resource. Now to save the game, we can access a Singleton class of the Godot, Resource saver, then called the save function. Passing the resource we want to save and the location we want to save it to by appending the path and filename. If we wanted to have multiple save slots, all we have to do is change the file name for each slot. Loading our game is similar to saving. All we have to do is use Godot loader Singleton. The load function only requires the location of the resource we are loading. And it returns the resource. So we'll assign it to our data variable. Since our game doesn't have a title scene, I'll just automatically assume that if a save file exists, I'll want to load it, or if it doesn't start a new game. So in the REDI function, I'll use the resource loader to check if there is a data file to load, and if so, load it. Otherwise, start a new game. Even with a title scene, it's helpful to be able to start your game directly from the game scene without having to go through the title screen every time while you're developing and testing. We have our data resource here. We can create it, save it, and load it. Now all we have to do is make this script easy to access from anywhere in our project. Opening the project settings, switch to the Globals tab. Click on the folder button to open a browser and navigate to the file script we just created. Then click Open. We can provide a different name for this global node if we want or use the one provided. Then click on add to add the script to the list of global nodes. Now, any script we write can access this file script with just its name. Much in the same way we have been using the input, resource saver and resource loader singletons. To test this out, I'll set the character's max health to match what is in the Safle rather than setting it at the top of the script. So I'll add an initialized function to this script after accepting a new Max value parameter, setting the old max value to the new max value. Then the current value to the maximum value, I'll emit both changed signals. Now the game manager after getting the character's Hurt Box can initialize it with the maximum health value from the player's data file. I'll also add an input function, which when pressing the up or down arrow keys will increase or decrease the character's maximum health in the data file and re initialize the Hurt Box to match. Then save the game by calling file dot save game. I'll need to reverse the order of these operations to display the correct values. Running the game, the character starts with five Max Health, but I can change this value by pressing the up or down arrow keys. I'll then close the game and run it again to see that their Max health has been saved and loaded. To find the data file we saved under the project menu, select Open user data folder. This will open a file browser window, and we can see our save dottRs file. Right click on this file and open it using any text editor. We can see that only the maximum health was saved. Godot keeps track of which variables are used by the game and will only save the variables we actually use and modify. So keep this in mind. We can actually edit this file here, and the game will actually load it with the edited values as long as the formatting matches. This is great for us while we are building and testing our game. But maybe you don't want to let your players easily edit their safe file when you're ready to publish. In the file script, simply change the TRS extension in the file name to dot RES. Instead of a text resource, this will now be saved as a binary resource, which can't be opened and edited in a text editor. Running the game again, I'll open the save dots file I just created. This is now impossible for me to edit. We now have the ability to save and load data about the player's progress through our game. In the next section, we'll add our unlockable abilities. I'll see you in the next section. 43. 6-1 Double Jump: Hello, friends. In this video, we'll give our character the ability to double jump. While many characters in our game may need the ability to walk, run, jump, and fall, the unlockable abilities are usually exclusive to the player character. In the character script, rather than adding more variables and functions to this script, which is already quite long, we can create a new script that will only be used by the player character. Make sure your character script has a class name and is saved before proceeding. From the character scene, right click on the character's root node and select attach script. Rather than inheriting from CharacterBody two D, we can inherit from the class name of our original script character. Then give this new script a name and save it in the appropriate folder. Since this new script inherits from character, it has access to all of the same variables and functions that we created for that class, and all of the values for the exported variables and signal connections are also copied over to this new script. But we can now add more that are only useful for the player character. Following this pattern of inheritance, character is called the superclass and our player character is a subclass. First, we'll need a variable that tells us whether or not this character has unlocked the ability to double jump. And in order to limit the character to only double jumping once, we'll need another variable to know if their double jump is ready. It might also help to have a signal to emit when we successfully perform a double jump to draw a particle effect if you want, just like we did with the other jump. Since double jumping typically uses the same button as normal jumping, we don't need to do any extra work with the input map in the project settings or edit the player script to call any new functions. We can simply override the Jump function declaration. When overriding a function declaration, the function signature must remain the same. The name of the function, its parameters, quantity, order, and types, as well as the return type. Since the original jump function in the character script accepted no parameters and return a Boolean, this jump override must also have no parameters and return a Boolean. We still want all of the original functionality of the jump to happen. So this character will be able to jump if the right conditions are met. We can access the superclass functions using the keyword super then use a period, the function name, and parentheses just like we would calling a function any other way. Since the superclass jump function returns a boolean, we can also return the result to satisfy the requirement of returning a boolean. When the player presses the jump button, the player script will call this player character jump function, which will then call the more generic character jump function. This structure allows us to intercept the process by adding extra code above the return line, allowing us to check if this player character can double jump in response to the request to jump first, then only proceed with the default jumping behavior after. So if this player character can double jump and the double jump is ready and the character is not on the floor, then we should allow them to perform a double jump and return true. If any of these conditions are false, then we need to proceed with checking if this character can jump under normal conditions using the character superclass jump function. We can pretty much just copy the lines of code that implement a jump when performing a double jump, but emit a different signal. And we'll need to set the double jump is ready variable to falls to prevent the player character from performing more than one double jump. In most games with a double jump, the ability to perform the double jump is restored after landing on the ground. Since the character class already has a function which will be called when the character lands, we can also override that. Anytime this character lands, if their double jump is not ready, we can set the variable back to true to make it ready. Then call the superclass function. In this case, the order of whether we want to run the superclass function before or after will not matter. To test this out, I'll add a print statement when the character double jumps successfully. And another when the double jump is restored. Then set the default value of C double jump to true. Running the game, we can press the jump button twice to double jump. Pressing it three or more times does not produce more jumps, and the ability to double jump is restored when landing. Now all we have to do is lock and unlock this ability to double jump. So I'll set the default value of C double jump back to false. Over in the player save data, I'll remove all of these unnecessary variables. We'll need variables to tell us which abilities the player has unlocked, which we can store in an array of booleans. Arrays are indexed using integers, we'll need each of our abilities to be assigned a number in sequence, starting from zero counting up to act as their indices to this array. If you don't want to memorize a bunch of integers, we can create an enumeration to give each integer a name that is easier for us to use. I like to put these enumerations in a separate script named enums. While Godot forces us to inherit from an existing class when creating the script, we can remove this line from our script after it is created. Then give this script a class name so we can reference it in other scripts. I'll typically use the same script to hold every enumeration for the entire project. We can declare an enumeration using the keyword num, followed by braces. Inside the braces, we can write the names of each of our abilities in a comma separated list. I've already created a double jump as my first unlockable ability, and I'll create a Wall Jump, dash, a hat, and shooting a projectile. Enumerations are conventionally named in Pascal case while the enumeration entries are in upper snake case. The enumeration will assign the value of the first entry as zero, then one, two, and so on, meaning we can use these as indices to our array quite easily. These don't need to be in the order in which they will be unlocked by the player. The order is arbitrary and only serves to assign each ability a unique number. When initializing the player's save data, we can resize the array to match the size of the enumeration, automatically giving us the correct number of booleans and defaulting all of them to false. Remember that variables must have the export tag to be saved. Back in the player script, we can reference the abilities unlocked array and store it in a variable of the same name using the on ready tag. Note that this does not just make a copy of the array in its current state. It creates a reference to the array, so any changes will affect both. This means that in order to unlock an ability for the character to use, we only need to set its value in the save data resource. We can now replace the C double jump variable with indexing the abilities unlocked array with Enums dot abilities dot Double Jump. This abilities unlocked array can now be used throughout this entire script for every ability. In the game manager script, I'll use the input function to test out unlocking the double jump ability when pressing the up arrow key. Setting file dot data dot Abilities unlocked, index dot enums dot abilities dot Double Jump to true. We can then save the game to make this permanent. Running the game, my character cannot double jump. Back in the Godot Editor, I'll switch to the remote tab to view the scene tree of the game that is currently running. Here I can see the file global node, which has a data resource. Inside this data resource, the abilities unlocked array has five Booleans, which are all false. The player character node also has a reference to the same array. Returning to the simulation, pressing up unlocks the double jump ability. In the remote scene tree, I can see that the boolean in the array was set to true in both the data resource and the character. Closing the game and running it again. The double jump is already unlocked since it was saved. We can remove the print statements now that the double jump is confirmed to be working. We now have an unlockable double jump ability, but we have several more unlockable abilities to add. I'll see you in the next video. 44. 6-2 Wall Slide: Hello, friends. In this video, we'll allow the character to slide down a wall, adding friction to slow their descent. First, we have to decide what this ability will do and when we want it to happen. When implementing a new feature like this, it's a good idea to set up tests first that you want to be able to pass. I'll start in the room contents scene and hide the trees so I can see better. To create a wall sliding mechanic, I'll create two walls in my room. I'll consider my Wall Slide mechanic to be complete when the character can jump, then slide down either wall at a reduced falling speed. I would also like to play a different animation while this is happening. The Wall Slide should end when the character is no longer in contact with the wall if they touch the floor or we press the movement direction to move the character away from the wall. So implementing this will be altering the character's aerial physics. In the player character script, we'll override the character's air physics process. Then call the superclass's Air Physics process from the character script. Here we can separate our code into three separate blocks. When the character is performing a Wall Slide, checking when they can start performing a Wall Slide, and any other time we call the superclass Airphysics process. We'll need to know when the character is performing a Wall Slide, so we should declare this as a Boolean variable. It would also be helpful to export a float to use as a multiplier to the character's gravity while performing a Wall Slide. I'll use 10%. Returning to the air physics process, we can now branch based on whether or not the is wall sliding variable is true or false. If the character is already wall sliding, we can either continue the wall slide or end it depending on if other conditions are met. If the character is not wall sliding, then we need to check if they can wall slide, and if the conditions are met to start wall sliding, then do so. And finally, if all of this is false, then we should proceed with the superclass air physics as the default. Writing your algorithm out in pseudocode comments like this makes it much easier to fill in after and rearrange if needed. It also makes the end result already commented. Let's begin with starting a Wall Slide by checking if the Wall Jump ability has been unlocked. Then also add the condition that this character must be on a wall. The character body class provides this function which acts the same as is on floor. I would also like to add more conditions that their Y velocity must be greater than or equal to zero. I do not want the character to start wall sliding if they are moving up, and the player must be pressing the move direction input toward the wall. The character body two denode also gives us another function. Get Wall normal. This returns a vector pointing away from the wall. So if we use the X portion of this vector, this will always be negative one if the wall is on the character's right or positive one if the wall is on the character's left. Comparing this to the character's move direction, I'll multiply either side by negative one and require that the results be equal. This will not allow move direction to be zero. There are several conditions in this I statement, and they do not fit inside my script window. If you want to write your if statement conditions across multiple lines, you can do so by wrapping them in brackets. This can make your code easier to read and debug if you prefer this format. While my walls are perfectly vertical, you may need to use the sine of the walls normal X if your walls are angled. If all of this is true, I'll allow the character to start a wall slide by setting I wall sliding to true and capping their Y velocity at zero using the min function. This will create a brief reset of falling velocity to make the wall slide feel slower. I'll also print out a statement for testing purposes. Now we'll assume that the character is already performing a wall slide, and we want to override their gravity to slow their descent. Simply adding to the character's Y velocity, gravity times Delta times the Wall Slide gravity multiplier, which I have set to 10% of normal gravity. Thinking about this from a physics perspective, this is the equivalent of saying that there is a coefficient of friction of 0.9 between the character and the wall. And last, we can think about when to end the Wall Slide. If the character is no longer on the wall or if the player presses the opposite move direction away from the wall. So if not is on wall or if the sign of move direction is the same as the wall normals X, then I'll set Is wall sliding to false and print out a statement that the Wall Slide has ended. It would be simpler to put the wall slide continuing as the else block to this I statement, so I'll cut and paste it here. There is one more condition that needs to end a wall slide when the character touches the floor. Since we already have a function which is automatically called when the character lands, I'll simply add another I statement here that ends the Wall Slide. If the character is wall sliding, set I wall sliding to false, then print out another statement. It helps if these two statements are different so we can see when each is actually happening during our tests. This will work for keyboard, but not for a controller since the move direction can be a decimal value. So I'll need to apply the sign function to both of these checks to work with the controller input. The last thing I need to do is add a Wall Slide animation to the character's animation player. So I'll duplicate their walk animation. Change the Y position of the region of the spreadsheet that is being used and delete the footsteps track. Adding this animation to the state machine resource. I'll allow the character to transition to this state from either jump or fall if I Wall sliding is set true. Then transition out of a wall slide into falling if I wall sliding is set to falls. Or to locomotion if they touch the floor. In the game manager script, I'll change unlocking the double jump to unlocking the Wall Jump. This should be sufficient to pass my test, so I'll run the game. The character can still jump normally, and jumping against the wall doesn't perform a wall slide since that ability is still locked. But pressing up to unlock the Wall Jump, now if I jump while moving toward the wall, they slide down it and stop when they touch the floor. Trying this on the other wall, the wall slide ends when they reach the bottom of the wall. And another try against the first wall, the wall slide doesn't start until the character starts falling, and I can end the wall slide early by moving away from the wall. The character's normal jumping behavior is not affected by performing any of these actions. With all of the tests passed, remember to comment out the print statements. We now have our character able to perform a Wall Slide, but we still have to implement the Wall Jump. I'll see you in the next video. 45. 6-3 Wall Jump: Hello, friends. In this video, we'll allow the character to jump away from the wall while performing a wall slide. In the character script, we've already overridden the jump function to do a double jump. When we want the character to wall jump, all of these conditions on the double jump may also be true. So we'll need to consider the Wall Jump as a possibility first and rule it out before checking for the double jump since the conditions for the Wall Jump are more specific. We'll only need to check if the Wall Jump ability has been unlocked and whether the character is currently performing a Wall Slide. If these conditions are not met, then we can check if the character can double jump instead. However, this will not allow the character to perform a wall jump until the Wall Slide has started, which is waiting until the character has reached the apex of their jump. If you want to allow wall jumping before starting the Wall Slide when the character's Y velocity is negative, then we can also add more conditions. Either the characters performing a wall slide or they are not on the floor and are on a wall, and the move direction is toward the wall. With the order of operations for logic, we need to put the or in brackets so it will be checked before the first and. We can use multiple levels of brackets and indentation to visually lay out the order of operations of complex logic like this to make it easier to understand if we need to. Now that we are ready to perform a wall jump, we can do all of the same things like we would with any other jump. Then return true, so the input handler knows that this jump button press was successful. As for setting the character body's velocity, we want it to not only go up but also away from the wall. So rather than just setting the character's Y velocity, we'll set the whole velocity vector, creating a new vector too. For the X, we can use the walls normal vector dot X, and for Y, we can use one. This gives us a vector pointing up and away from the wall, so we have the correct direction. Multiplying this by the jump force, we will get a vector with a magnitude greater than our normal jump since the original vector already had a length longer than one. We can normalize this direction vector if we want to, but the way it works right now will actually produce a better result, in my opinion, with the full height of the jump, plus the extra force being applied away from the wall. But since jump force is a negative number, this will invert the Wall normal to point toward the wall rather than away from it. So I'll add a negative to the front of wallnormalt X to make it a double negative, which becomes positive. To animate the Wall Jump, the character's state machine will need a transition from Wall Slide to jump under the condition that the character is jumping. This conflicts with the transition to falling since the character is no longer wall sliding at the same time. So the transition to jumping will need a lower priority value to be considered first. Instead of double jump is ready, we'll need to set Is wall sliding to falls when performing the Wall Jump. If we run the game, we can already perform the wall jump since it was unlocked in the previous video. Jumping against the wall, we can jump off of it. The amount of lateral velocity away from the wall is equal to the vertical force being applied. But since gravity is pulling the character downward constantly, it's a lot less noticeable than the force away from the wall, which is only being reduced by either the character's air control or air brakes. I would prefer if my character had much less lateral force applied and could return to the same wall after performing a wall jump and continue climbing upward. This will require multiple different alterations of the physics in order to work properly, both reducing the amount of lateral force being applied to the Wall Jump away from the wall, as well as giving the character more air control in order to return to the wall at a higher altitude. First, let's deal with the lateral force. Rather than hard coding a value, it's always better to use exported variables so they can be twisted and tweaked. I'll export a vector two named Wall Jump force multiplier and give it a default value of negative one positive one. We can then multiply the resulting velocity vector by this new Wall Jump force multiplier. This negative one will remove the need to have the negative applied to the wall normal vector, which I find more intuitive since I think of negative as away from the wall. When multiplying two vectors, the result will be the product of both Xs and the product of both Ys. With these values, this will work the same as it currently does. But since the value is exported, I can change it in my character scene to negative 0.5, reducing the X velocity by half. If you want to give yourself reminders of how your variables work, adding an extra number sign in front of a comment above the variable declaration will create a tool tip that will be displayed when you hover the mouse over the variable in the inspector. Running the game again, my character has the full upward force of the jump, but only half of the jump force is applied in the X direction. But I still can't get back to the wall at a higher point to climb any higher. For that, I'll need to give my character much more air control, but I don't want the character to have more air control at any other time. So I'll need to override the air control value specifically only when performing a Wall Jump, then set it back to its normal value. In the variables exported for wall jumping, I'll export a new float for Wall Jump air control and set it to something very high, like four. This will make the character accelerate four times faster than they would on the ground and eight times faster than they would during a normal jump with my air control defaulting to 0.5. I'll also store this in another variable using the ready tag. So when I want to set it back to its normal value, I'll know what that is. Since I don't know whether the player will be jumping away from the wall or back toward it or anything else that they want to do, the only reliable way to set air control back to its default setting would be to use a timer. Adding a timer node to the scene, I'll name it Wall Jump, give it a unique name, then get a reference to it with the on ready tag two. I'll name this Wall Jump air control override to be a little more clear in this context. Now when we perform the Wall Jump, we can change the value of air control to Wall Jump air control and start the Wall Jump air control override timer. Selecting the timer node, we can set its one shot property to true and give it a short amount of wait time. I'll use half of a second. Then connect the timer's timeout signal to the script. When the timer times out, we can set the character's air control back to its default value. Testing this out, it's closer, but I still can't get up the wall. So I'll need to adjust my Wall Jump force multiplier and Wall Jump air control values until it works. Running the game again, now when I perform a Wall Jump, my character has enough acceleration to return to the same wall at a higher point, allowing me to climb up the wall. We now have our character able to wall jump to scale up vertical surfaces. Next, we'll add a dashing ability complete with invincibility frames. I'll see you in the next video. 46. 6-4 Dash: Hello, friends. In this video, we'll give the character an unlockable dash ability. Starting in the project settings, the input Map tab will need a button that can be used for dashing. I'll use the D key or the right shoulder button on my controller. Then in the player script in the input function, if the dash button was pressed, try to dash, and if unsuccessful, buffer the dash input to dry again next frame, just like we did with the jump. The dash will need a direction, which we can get using the input Singleton, get axis, move left, move right. To provide arguments to a callable, we use the bind function, then pass the arguments to that function instead. So it would be better to only call the input get axis function once and store the result. Since this will only happen during frames when the dash button is pressed, it's not so bad to declare a variable here. When the buffered input is called, it will pass the bound arguments. If the dash was originally pressed with the left stick tilted, it will buffer with that same directional input. Now we can write this dash function in our player character script, accepting the direction to dash as a parameter and returning a boolean, returning false by default. Before we write this function, we'll need some variables. The distance we want the character to dash, I'll set mine to 128 pixels. The amount of force required to reach that distance calculated using the on ready tag, we can use the same formula as the jump height calculation, the square root of distance multiplied by the negative force, which in this case is deceleration times two. I would also like to suspend gravity while the dash is happening. So I'll also store the character's default gravity in a variable using the on ready tag. Now for the dash function, if the dash ability has been unlocked, then we want the character to face the direction of the dash, which we can do by setting move direction to that value. Then set the character's velocity to be a new vector two with the dash direction multiplied by dash force as the X and zero for the Y. Since the left analog stick can be tilted, this would mean that the player can dash slower or faster based on how far they tilt the analog stick on the controller. I don't want this to happen, so I'll use the sine function to remove tilt from the equation. We can suspend gravity while the dash is happening, setting the value of gravity to zero, then return true because the dash was successful and doesn't need to be buffered. As for the direction to dash, there are several cases to consider, not just if the player is giving directional input, but also if they are not and also if the character is currently performing a wall slide. Since that is the most specific case, I'll check that one first. If the character is wall sliding, I'll overwrite the value of direction to be sign of wallnrmaltX. If they are not wall sliding and the player is giving directional input when they press to the dash button, then I'll use the sin of direction as the direction, removing any analog tilt from the input so I can remove this from the line below. If the player is not giving any directional input when they press the dash button, I'll assume they want to dash forward. I'll use a ternary if to set the direction to be negative one if the character is facing left or positive one if they aren't restore the gravity back to normal, we'll need another function to call at the end of the dash. This doesn't need any parameters or return a value. We'll just set the character's gravity back to its default. In the character scene, the dash will need an animation in the Animation Player node. Setting the frames per second and zooming in, I'll add my key frames to animate the dash. We'll need to add a call method track and a keyframe on this track to call the end dash function at the end of the animation. Or whatever frame you want to restore the character's gravity. Using this procedure, if it is possible to interrupt this animation for any reason, we will still need to call the end dash function to restore the character's gravity, unlike the timer we used for restoring the character's error control. So keep this in mind and make a comment to remind yourself of this later. Adding this animation to the state machine, it should be possible to perform a dash from any of our existing movement states. Therefore, it would be better to put this animation on a new layer. Saving the current state machine as a project resource, I'll name it movement and put it in the character scenes folder. Then create a new state machine resource for the Animation Tree. Adding the movement state machine as a node in this new state machine, I'll rename it to movement and create a transition from start to movement, so it is the default. Adding a new dash animation to this state machine, we can add a transition from movement to dash and back to movement. In order to play an animation one time, the transition leading to it should be set to enabled, and the transition leading out of the animation should have its switch mode set to at end and leaving the advanced mode as automatic. We can test this animation by clicking on the play button in the state. With each click, it plays once in its entirety and returns back to the movement state when it is finished. Since the transition leading to the animation is not automatic, it will be up to us to trigger this animation manually through our script. Declaring a new variable, this will be used for multiple different abilities, so I'll put it at the top and just name it animation for short. The type of this variable will be an animation node state machine playback. This is what Godot uses to navigate the state machine and play animations. Selecting the node in the scene tree, we can see this playback resource under parameters playback. We can retrieve this resource by clicking and dragging the Animation Tree node to get its node path. Then use square brackets to get a resource from this node. Clicking and dragging this resource from the inspector into the square brackets gives us the path to the resource. If we expand movement and locomotion, you can see that each layer of the state machine has its own playback resource. While we currently only need this one for manually triggering the dash, we will be adding more abilities soon. This path will also change as we add more layers to the state machine, so it will need to be updated. To trigger the dash animation, when successfully performing a dash, we can tell this playback to travel to the dash state by specifying its string name as an argument. This line of code is the equivalent of pressing the play button in the Animation Tree when we previewed the animation. I also want to remove the player's ability to control the character while the dash is happening. So overriding the physics process, we can quickly interrupt it and check which state the animation state machine is currently in using the function G current node. This returns the string name we wrote on the node in the Animation Tree, which we can compare to see if it is movement or not. If it is anything other than movement, I'll set move direction to zero before proceeding with the normal superclass physics process. This will prevent the character from moving or facing a different direction while performing any of the abilities on the top state machine layer. In the game manager script, I'll change the up arrow key to unlock the dashability. Running the game, pressing the dash button does nothing. After pressing the up arrow key to unlock the dashability, we can dash, causing the character to dash forward. Pressing a direction will cause the character to dash in that direction. And if we dash while wall sliding, the character will dash away from the wall. We now have our player character able to dash around the environment, but we will need to add a cool down timer to prevent the player from using it too often. I'll see you in the next video. 47. 6-5 Cooldown: Hello, friends. In this video, we'll add invincibility frames and a cool down to our dash ability. Before we start adding invincibility frames to the dash, it would be helpful to have a hit box in our scene so we can test out dashing through it. In the start room contents scene, I'll add an area to denode and rename it to hit Box. Then add a collision shape as a child. Give it a rectangle shape as a resource and set its debug color to red. You can imagine that this will represent an enemy that damages the player character on contact. I'll make it one tile thick, 32 pixels, and two tiles high, 64 pixels. Then move it over to the left. I'll consider my test passed if the player character can dash through this area without being harmed. In order to have this collision area deal damage to the player, it will need to be monitoring. It does not need to be monitorable, although leaving this box checked won't make any difference. It does not need to exist on any layer, but must be masking the player Hurt Box layer. If it helps you to understand collisions better, it is fine to consider this hit box to exist on an enemy hit box layer or an environmental hazard layer. The only requirements are that it must be monitoring and masking the player Hurt Box. Attaching a script to this node, we'll connect its on area entered signal to the script. After checking if the area has a take damage method, we'll call this method and pass one as the amount of damage. This will make sure that the area entering this one has the Hurt Box script we created earlier, which has the take damage method, accepting the amount of damage as a parameter. If another area triggers the signal without a Hurt Box script, it would cause an error since there wouldn't be a take damage method to call. To be able to see our collision shapes while testing the game, we can open the debug menu and select visible collision shapes. If we run the game, we can see the hit box, and the player character takes one damage every time they enter this area. There is an error message because the room's contents are being loaded in response to our character entering a collision area, and Godot doesn't like adding another collision area during a collision event. We'll ignore this for now since we're just performing a test, and this isn't going to be part of our final game. In the character scene, select the Animation Player node and the dash animation. To add invincibility frames to our dash animation, all we have to do is keyframe the Hurt Box's monitorable property. We can set the exact frame when the Hurt Box turns off and when it turns back on. If you want, you can also keyframe the visibility of your Hurt Box to match its monitorable property so you can see it turn off and back on again while playing the game. This makes it easier to know when your character is actually invincible. It might also help to toggle off the visibility of the character body's collision shape to better see the Hurt Box. Running the game, we can now dash through the hit box without taking any damage. Walking into the area still inflicts damage as does ending the dash inside the area. Now let's add a Cooldown to the dash. We'll start by adding a timer node to the character scene. I'll name this timer dash Cooldown and set it's one shot property to true. I'll leave its weight time as 1 second. This will be the cool down time of the ability. Considering this is the second ability with an associated timer node, and we may want to add more in the future, you may want to consider using a normal node as a collapsible folder to contain them. But it will help to give it a unique name in case we want to restructure the scene tree. We don't need to connect the signal to the script to use this timer node as a cool down. In the variable declarations for the Dashability, I'll use the on ready tag to get a reference to this new timer node. All we have to do is start the timer either when starting or ending or dash. It's up to your preference which, but if you want to start the cool down at the end of the dash, another condition will be required to prevent the player from dashing twice in a row. I'll start the Cooldown timer after the dash ends. Then return to the I statement where we check if this character can start dashing. We can check if the current node of the animation state machine is movement. This will also have added benefits later, since we won't allow the character to dash while performing other similar action abilities. To enforce the Cooldown timer, we only need to check if this timer is stopped to know if we should be able to dash again. If the dash animation is already playing or the Cooldown timer is not stopped, that means the dash will fail. Running the game now, pressing the dash button allows the character to dash only once, then forces a 1 second cool down before they are able to dash again. This works, but it isn't being communicated to the player in any way. Usually games like this will have a diagetic indicator, something that exists in the game, not on the HUD to tell the player when the cool down is finished. So let's add an Animated Sprite two D node to the character scene and name it Dash Cooldown effect. Creating a Sprite frames resource for this node, we can import a Sprite sheet. I'll just put this in the dust folder for now rather than create a new asset folder for one Sprite sheet. And add the Sprite frames from the Sprite sheet. I'll turn off looping, set the frames per second to 12, and also add a blank frame at the end of the animation. This isn't centered on the character, so I'll move it up 12 pixels. This animation is longer than 1 second, but the 1 second mark is when I want the cool down to be considered finished. I'll give this node a unique name. Back in the script, all we have to do is grab a reference to this node. An tell it to play when the dash ends. But since my animation is longer than the Dash Cooldown timer, I'll actually need to stop the animation before playing it in case it is still playing. This will reset the animation back to the start. A Now, when we run the game and use the dash ability, the animation plays, but it isn't following the character. This is because a normal node does not have a transform. Therefore, it doesn't have a position. This breaks the line of inheritance from the character's root node. So the Animated Sprite two D node is now starting its own ancestry for two D transforms, starting at position 00. We can change the type of the abilities folder to no two D so that it will provide a line of succession through the scene tree, and the Animated Sprite will follow the root node. It is now clear to the player that their dash ability is on Cooldown and exactly when they can dash again without having to add any extra information on the HUD. We now have our dash ability able to dash through hit boxes without getting hit and cooling down before being able to be used again. Next, we'll add a passive ability which changes the character's appearance. I'll see you in the next video. 48. 6-6 Hat: Hello, friends. In this video, we'll implement a passive ability that alters the character's appearance. Starting in the room contents scene, I'll duplicate the collision area from before for this test. This time, the collision area will specifically represent intense heat from inside a volcano. I'll change the color to orange so I know which is which. I want the player to be unable to pass through this area until they have unlocked a magical hat that allows them to proceed unharmed. But wearing the hat will still allow them to take damage from normal hit boxes. So rather than have this masking the normal player Hurt Box layer, I'll use a different layer. I'll use layer 14 as a layer dedicated to this environmental heat effect. This will be easy for me to remember since it is directly below ten. In the project settings, I'll name this layer heat. Then in the character scene, I'll have the Hurt Box also exist on this layer. Now this will work the same way as the collision area before, dealing one damage to the player every time they enter this area. Only it is checking for collisions on layer 14 rather than layer ten. So all we have to do to make the player character immune to the heat damage while wearing the hat is remove layer 14 from their Hurt Box collision layer property. To alter the character's appearance, I'll layer additional sprites over the character. I'll import new sprite sheets for three different upgrades, a gem, a cloak, and a hat. Then in the character scene, I'll add an extra Sprite Tu Di node for each of them. All with atlas textures just like the body sprite. Then assign the sprite sheets as the atlas property for each node. The way these assets were drawn, I'll need to layer them specifically in the order of the cat, then the cloak, then the hat, and the jam on top. I'll start each off with the first sprite at position 00 with a size of 32 by 36 pixels and give them the same offset to move them up by 18 pixels. These assets are drawn to animate synchronously with the normal character sprite. So all of the region values will be the same for every layer at the same time. All I have to do is add each sprite's atlas texture region to the animation. Then copy and paste the keys from the body Sprite track to the other tracks. And change their update mode to discrete. Now the animation also includes all of the sprite layers in perfect synchronization. I'll need to do this for all of my animations, but it won't take that much effort. In order to flip each of these sprites, we could set all of their flip H properties every time they change direction. But there's an easier way using the scene tree's inheritance. If we make all of the other sprites a child of the body sprite, they will inherit their transform from it. And we can flip a sprite using its transform by setting its X scale to negative one. Note that this trick should not be used for anything involving physics or collisions. It works fine here because we are only drawing sprites. In the face left function in our character script, rather than setting flip H, we can set scale dot X to negative one, if left, otherwise, positive one. This will now synchronize the flipping of all of the sprites. With all of the setup of the sprites complete, I'll set the visibility of the other sprites off by default, then turn them on as abilities are unlocked. In the player character script, we'll need a function to manage which Sprite layers are visible based on which abilities the player has unlocked. I'll name it Update Sprite visibility. Then give each of the sprites a unique name and set their visibility to match the abilities unlocked value for the matching ability. The double jump will add the cloak. The hat is obvious. And the ability to shoot projectiles will add the gem. In the game manager script, when we first start playing the game, we can call this function to update the characters Sprite visibility right away in case we have loaded a safe ale with abilities already unlocked. While playing the game, we will want to also call this function anytime the player unlocks a new ability. So we'll add a signal to this script for when an ability has been unlocked, with the abilities integer number as a parameter. Then write a public function to unlock a new ability, accepting the same ability number and omitting the signal. We can then connect this signal to the player character. However, we did not write the update Sprite visibility function to accept any parameters. So when we connect the signal, we should unbind one argument to avoid causing any errors. This will work to change the character's appearance, but they will still be taking heat damage while wearing the hat. When updating the character's sprite visibility in the specific case of adorning the hat, I'll also remove the collision layer for heat damage to make them immune to it. To remove a single specific layer from a bit mask, first, hover your mouse cursor over the layer and take note of its value. Layer 14 has a value of 8,192, which is two to the power of 13 or two to the power of 14 minus one. The collision layer is actually an integer, which in binary is a sequence of zeros and ones. Currently, it is all zeros, except that the tenth and 14th bits are one. All we need to do is change the 14th bit to zero to remove it from that layer. We'll start by grabbing a reference to the het box. Since this function is not being called very often, it's fine to use a node path. To remove the collision layer, we could subtract its value 8,192. However, this depends on the collision layer currently existing on layer 14. If we were to call this function for any reason when layer 14 is already turned off, simple subtraction would not work. Using the bit operator and on our Hurt box's collision layer, if we can provide a sequence of all ones with only the 14th bit as zero, the result will be setting the 14th bit 20 regardless of its current value. To get that number, we can reverse the bits of 8,192, the layers value. This is done with the bitwise naught operator, which in GDScript is a tilday. This is a very mathematically efficient way of removing collision layers. If you want to add a collision layer, setting its bit to one, all you have to do is use the or operator instead of and and remove the knot. But we aren't interested in doing that here. In the game manager script, I'll update my input function to unlock each new ability in sequence when pressing the up arrow for my test. Declaring a new variable here, so it is bundled with my testing code. I'll remember to delete this along with the input function when I no longer need it for testing. I'll name it next ability as an integer which naturally defaults to zero. Each time I press the up arrow, I'll unlock the next ability and print out the name of that ability. We can use the keys function on an enumeration to get an array of strings that are the same as the ones we wrote as the enumerations entries. So indexing the keys array with the ability number gets the name of that ability. I'll then increment next ability, but keep it clamped to be between zero and the size of the abilities enumeration minus one to prevent any errors. I'll delete the current safe file so that my abilities will start locked and turn on visible collision shapes. Running the game, we start off with no abilities unlocked and the default character Sprite. I'll open the remote scene tree and select the characters Hurt Box to verify that they are on layer ten and 14. Pressing the up arrow once, the double jump ability is now unlocked, so the character is wearing a cloak. Pressing the up arrow again, the wall jump is unlocked, but the character's appearance remains unchanged since that ability was not associated with a cosmetic change. Touching either collision area will cause the character to take damage. Pressing up once more, now the dash is unlocked, too, and still the character looks the same. Up again to unlock the magical hat, and we can see that the character's Hurt Box is no longer on layer 14, but still on layer ten. Touching the normal hit box causes damage, but entering the new volcanic heat hit box does not, meaning the character is immune to it while wearing the hat. Pressing up one last time, we unlock the shoot ability and add the gem, but we haven't implemented that yet. We now have our character changing outfits to match their progress through the game. The last ability we will add is shooting a projectile. I'll see you in the next video. 49. 6-7 Projectile: Hello, friends. In this video, we'll add a projectile spell that the player can shoot by spending their magic points. First, we'll need to give this character magic points to spend. In the player character script, I'll add a new export category for casting spells. Then two variables for their maximum magic and their current magic both as floats. We'll also need signals to emit anytime these values change so we can update the HUD. Overriding the ready function, we can set the maximum magic to the value from the player save data. Then I'll just start the player off with full magic, setting the current value to the max value and emit the changed signals to update the HUD. From the game scene, we can easily connect the player character signals to the magic gauge since they exist in the same scene. The magic gauge doesn't have a script attached, but it doesn't need one. Since the progress bar already provides the functions we need, we just have to toggle off script methods only. The range class gives us the set max function accepting a float parameter. As well as the set value function, also accepting a float parameter. Now the magic gauge will automatically update with the player character's magic variables. In the project settings, the input Map tab will need a button that can be pressed by the player to cast a spell. I'll use the C key or the top Face button on my controller. Then in the players script, we can copy the same format as the jump to try to cast a spell and if unsuccessful, buffer the input to try again next frame. Back in the player character script, we can write the cast function, accepting no parameters and returning a boolean. All we really need to do here is check if the ability has been unlocked and if the character is not busy performing any other actions, if the animation's current node is movement. Then tell the animation to travel to the cast animation and return true if the input was successful or false if it wasn't. In case we don't want the casting of our spell to be instantaneous, everything else will need to happen in a separate function. We'll name this one shoot projectile. It doesn't need any arguments or to return any value, and we don't have any of the information to write this function yet, but we'll need this function declared and saved before we can include it in the casting animation. Switching to the character scene, we'll start by adding an Animated Sprite to dende to use as the casting effect. This will need a Sprite frames resource. Before importing my sprite sheets for spell casting, I'll make a new folder for magic sprites and move the dash Cooldown effect into this folder. Then import sprites for casting, the projectile and its impact sprites. Adding the casting animation sprites to the Sprite frames resource. Y I'll set it to 12 frames per second, not looping. And I'll add a blank frame at the end of the animation, then move it up 18 pixels with the Y offset. This looks good, but if we til this to the character Sprite, it will also flip to face the same direction as the character. Selecting the Animation Player node, we can duplicate any of the existing animations to create a casting animation. After removing unnecessary tracks and updating all of the Sprite region keyframes, We'll add call method tracks to both the character's root node and the casting effect Animated Sprite no two D. For the casting effect, all we have to do is tell it to play. For the character, we'll add a keyframe for shooting the projectile. These keyframes can easily be adjusted to happen anytime during the casting animation. I'll start the cast effect on the second frame and fire the projectile on the third frame. The rest of the animation will act as recovery. In the Animation Tree, we only need to repeat the same process we used with the dash. Transitioning to cast is enabled, and we're turning back to movement at the end of the animation. Next, let's assemble the actual projectile. Start by adding an area two D note to the character scene. Then give it a collision shape and a shape resource. I'll use a circle but reduce its radius to eight pixels, and I'll change its debug color to something I haven't used yet, like purple. Then add an Animated Sprite to denode and give it a Sprite frames resource. First, we'll add the sprite frames for the projectile as we want it to start and use that as the default Sprite animation. This will be looped and I'll set my frame rate to 12 frames per second. Then we'll click on the add Animation button to create a second Sprite animation and name this one something like Impact. Now we can add more Sprite frames for an impact animation that will be played by the same projectile when it collides with something. You may also want to add an empty frame at the end of this animation to allow the last frame to draw for its full duration. Remember to set the default animation to autoplay, so we will play this animation when the projectile spawned automatically. Still within the character scene, reposition the projectile to where you want it to start in relation to the character. You can make this easier by selecting the projectile and the scene tree and holding Alt while you move it. Mine is 12 pixels to the right and up from the character's origin. Next, add a marker two D node as a child of the projectile. This will ensure that its position matches the projectile exactly. The marker to D node is exactly the same as a node two D, except that it draws a little gizmo in the editor so we can see exactly where it is. This gives us the position where we want to spawn the projectile into our scene when casting the spell. So we can name the Marker Tutti node accordingly. Then reparent the Marker Tutti node to the character Sprite. Since it is a child of the character Sprite, it will flip its exposition when the character changes their facing direction automatically. Save the projectile branch as its own scene and save it in a new scenes folder for projectiles. Then delete it from the character scene. We now have a projectile and we know where we want to spawn it, so we'll return to the player character script. We can export the projectile as a packed scene and populate it in the inspector. We can also grab a reference to the spawn marker. It would be better if it had a unique name. And we'll need to specify a magic cost to firing this projectile. I'll set mine to 16 magic points. Before allowing the player to start casting the spell, we should also check if they have sufficient magic points by checking if their current magic is greater than or equal to the magic cost. Then when the cast animation calls the shoot projectile function, since there is a very brief window of time between when these two functions are called, it's not a bad idea to also still verify that the character has sufficient magic remaining to cast the spell again here. So checking if current magic is less than the cost, returning out of this function will not fire the projectile. Knowing that this is not the case, we can subtract the cost from our current magic and emit the current magic changed signal to update the HUD. Then we'll create a new projectile by instantiating the packed scene. We don't want this to follow the character around as a child rather as a sibling within the world, so it can move on its own. So we'll get the parent first before adding this projectile as a child. Then set its position within the world to be the projectile spawn marker's global position. We have to use global position here because the marker's local position is in relation to the character's position, but the projectile is being added to the world node, not the character. So we want the position of the marker in relation to the world. And finally, we'll tell this projectile which direction it is firing. We can pass an argument as Vector two dot left if the character is facing left or right, otherwise. Now we'll need to write this function for our projectile, opening the projectile scene, then adding a script to its root node, we'll name it Projectile and save it in the scripts folder. It will help to give this script a class name so we can reference it later. This projectile will need some exported variables, like a speed for how fast it moves and the amount of damage it does when it hits something. I'll name this default damage so it can be overridden and give it a value of one. We'll also need a reference to the Animated Sprite Tu Di node to tell it which animation to play. It needs to know which direction it's being fired and how much damage it will actually deal, if not the default, and whether or not it's currently moving. When told to fire, we can accept the direction as a vector two and the amount of damage as an integer with a default value of zero, making it an optional parameter. We'll set the direction to the direction parameter and flip the sprite if direction dot X is anything less than zero, meaning left. Then set damage to damage if any value of damage was provided, otherwise, use the default damage for damage, and finally set is moving to true. Overriding the process function. If this projectile is moving, then we can set its position to be plus equals direction times speed times Delta. This projectile will need to collide with both bodies and areas. So we'll need to connect both on area entered and on body entered signals to the script. The bodies this projectile will be colliding with will be the terrain, while the areas will be Hurt boxes. Both of these will do much of the same things. So we'll call a separate private function from both and name it on impact. When the projectile impacts something, we'll have it stop moving, then disable any more collisions by setting its collision mask to zero. This turns off every layer of the collision mask. Then play the impact animation and connect the animation finished signal to the quarter function to automatically remove the node from the scene. If the projectile hit a Hurt box, we can tell that Hurt Box to take the damage amount before calling on impact. We aren't using the body parameter, so we can mark it as unused with an underscore. If the projectile doesn't hit anything, we still need to clean it up. A simple way is to have the room automatically destroy it when it leaves the room it was spawned in. From the room script, rather than connecting this signal through the inspector every time for every room, it would be easier to do it through scripting. So we'll write an on area exited function, accepting an area as a no two D and returning no value. If the area which exited this room was a projectile, we'll queue it to be freed from the scene tree. Then connect the area exited signal in the ready function. We can also do this to automatically connect the body entered signals, too. Just remember that if any of your room scenes already have these signals connected, you should disconnect them to avoid errors. But as we create hundreds of rooms, making these signal connections automatically scripted will save us way more time and effort. In order for the room's collider to detect a projectile exiting it, it will need to mask the collision layer that the projectile itself is on. Running the game, I already start with every ability unlocked from the previous video and my magic gauge is full. I'll open the remote scene tree so I can see my projectile spawn and get cleaned up. Returning to the game, pressing the shoot button cast a spell, which fires the projectile. The projectile collides with the wall and explodes on impact. My magic gauge is reduced by the cost of the spell. Aiming in the other direction, the projectile has nothing to hit, and so it will leave the current room. But rather than continuing to move off into infinity, it will be destroyed once it leaves the boundaries of this room. We now have all of our unlockable abilities in the game, and we're ready to start adding enemies. I'll see you in the next section. 50. 7-1 Pace: Hello, friends. In this section, we'll add some enemies to our game. I'll start by making a new folder to hold my enemy sprites and import a sprite sheet from my first enemy, the blind mouse. We'll need a new scenes folder for enemies, too. We can make things easier if we duplicate our existing player character to use as a template to make our enemy. Then move it into the enemy's folder. Opening the new scene, I'll rename the root node to match the scene name. Then go through the scene hierarchy and decide which nodes are needed for the enemy and which are not. We'll still need a collision shape to collide with the terrain, a Hurt box to take damage, and a sprite to draw the character. But the children of the Sprite are not necessary, since this enemy doesn't have any other sprites or the ability to shoot projectiles. We'll keep the Animation Player, tree, and sound effect nodes, although you may want to change the sounds. The Coyote timer will not be necessary. Whether or not you want your enemies to be drawn on the map is up to your preference, but you'll obviously need to change the texture if you decide to keep the map icon. And we can also delete all of the ability nodes. The root node has the player character script attached. We'll need to replace it with the normal character script, since this is not the player character and they do not have any of the player character abilities. Selecting the Sprite Toti node, we can replace the atlas textures atlas property with a Sprite sheet for our enemy. Since the sprite sheet is different from the one for my player character, I'll need to go through each of my animations in my Animation Player node and check that they have the correct number of frames and the correct region values keyed. This enemy has fewer animations than the player character, so I can delete everything other than idle and walk. The idle animation works fine, since the keyed region values match exactly, but the walk animation doesn't match, so I'll need to fix it to match the region values for the sprite sheet. I'll quickly update to the keyed Y region values to get this animation working. We'll ignore the hit and dead animations for now since we can't test those yet. The Animation Tree will need a much simpler state machine for this enemy, not the same one that the player character is using. So we'll make a new state machine resource. Then add the idle and walk animations transitioning from start to idle by default to walk if X velocity is not zero, then back to idle if X velocity is zero. That's all this enemy needs to do for now. Taking a look at the character script for a moment, the script already has everything we need to make a character pace back and forth. Although since I don't want my enemies to have coyote time, I'll need to either move any code related to it to only be in the player character script, or I can simply make sure that none of the Coyote time related code runs if there's no Coyote time or node. Since I've already used the get node or null function to check for the Coyote timer node, this variable will be null if no such node exists. The only time we are using the Coyote timer node is in the physics process, and we can just check if there is a Coyote timer node before attempting to call it start function to avoid any errors. This same process could also be used if you don't want to include all of the exact same nodes in your various character and enemy scenes for things like sounds or particle effects. Let's switch to any of our existing room content scenes and add this new enemy to the room. My plan for this enemy is to have them pace back and forth. When they hit a wall, I want them to turn around, wait for a second, then walk in the opposite direction. So I'll edit my terrain to create a simple platform with walls on either side. So this enemy will pace back and forth in here. I'll also add another platform without walls, and the enemy should walk off of the ledge in this case. You may want to write another script like the player character script inheriting from character, which also includes this behavior. But it's better to think of this behavior as an alternative to the player node in the game scene, through which the player can control the player character. Instead, we write a script which controls this enemy's behavior. So we'll create a new node as a child of the enemy. I'll name this node pace. This way, the same behavior script can be used for multiple different enemies, and also the same type of enemy could have multiple different behavior scripts. Including this node in the enemy's own scene would make it automatically applied to all enemies of the same type. While putting it in the room contents scene will allow for the same enemies to potentially have different behaviors. We'll attach a script to this node and put it in a new scripts folder for enemies. In order to tell an enemy what to do, we'll need a reference to the enemy itself stored in a variable. We can call the function G parent to get the direct parent of this node and assign it to the variable using the ready tag. Note that this will only work if the behavior node is a direct child of the enemy. We should also export a variable to say which direction this enemy will go first. So I'll name it direction and give it a default value of one, meaning to the right. Overriding the process function, all we have to do is set the enemy's move direction to this direction to get them started walking. When the enemy is on a wall, we can tell the character to face left and set their move direction to zero, so they stop. To wait for a short time, we need a timer. Since this node doesn't have a type, we can use this node as the timer itself by changing its type. The type that this script extends will also need to change to timer in order to access any of the timer nodes, properties or functions. We don't need to connect the timeout signal, since all we really need to know is whether or not the timer is stopped. If the timer is stopped, we'll set the enemy's move direction to direction. Otherwise, we can set it to zero because this enemy is currently waiting. When the character hits a wall, we'll set the value of direction to be the sine of the walls X normal and start the timer. To make the character face right when they hit a wall to their left, we'll need to also make this dependent on the sine of the walls X normal. We also only want to stop the character and turn them around if their current pacing direction is not the same as the walls normal. We are calling this get Wall normal function several times, so it would be more efficient to store it in a local variable. Let's do some light refactoring here. In the room contents scene, we can adjust the amount of wait time and set the one shot property to true. I'll duplicate the pace node and attach it to the other enemy. Then give my two enemies different values for their initial pacing direction. Now when we run the game, with the help of our behavior script, the enemies pace, one to the left and the other to the right. The second enemy walks off of the ledge, but the first hits the wall, turns around and waits for a second before turning around and walking the other direction. And they both repeat this behavior. But you may have noticed that their animations don't always match their velocity. Both characters are actually animating in synchronization. This is because just like the health icons, they are sharing a resource. In the enemies scene, we'll need to select the Sprite two D node and make the atlas texture resource local to this scene. Now every instance of this enemy will have its own atlas texture and we'll be able to animate independently of each other. However, if any of your animations are changing the texture property of the Sprite two D node, this may break the animations which are referencing it. I'll delete this track from my reset animation. We also don't need the signal connection, which was copied from the player character scene, which is used to play Jump efforts. Running the game again, now we can see that they are able to play their idle and walking animations correctly. We now have an enemy which can pace back and forth, turning around at walls, but we also need to make sure they can't leave the room. I'll see you in the next video. Oh 51. 7-2 Invisible Walls: Hello, friends. In this video, we'll stop our enemies from leaving the room they were spawned in. To do that, we'll need our enemies to have collisions with invisible walls that don't affect the player character. We have the player character's body on collision layer nine, masking the terrain on layer one. In our enemies scene, we'll set the body collision layer to something different like layer 17. They still need to mask the terrain layer, but we'll also have them mask a second layer that we'll use for invisible walls. I'll use layer five. Opening the project settings, layer names Physics two D. I'll name layer 17 to be enemy and layer five to be invisible walls. In my room scene, I'll copy the Tile Map layer node, which is drawing the map, since this already has the boundaries of the room. Then paste it into the contents scene and rename it to Invisible walls. With the Tile Map layer note selected, clicking on the Tile set resource, we'll need to add a physics layer to this tile set, existing on layer five for invisible walls. From the Tile set panel, we can paint this physics layer onto the tile. Switching to the Tile Map panel with the paint tool and the tile selected, we can close this room's doorways, completely sealing the room on all sides with a physics layer only being masked by enemies and not the player character. We don't want these invisible walls being drawn on the map, so they shouldn't be on visibility layer two, and we want to be able to see them while we're testing. Let's put them on visibility layer one. I'll edit the terrain and move my enemies so they can freely walk across the entire room. Now, when we run the game, the enemies walk towards the edge of the room but are turned around by the walls. While our player character can walk right through them. I would prefer if these walls were outside the visible area of the room, so I'll move them each out one more tile. All we have to do to make the walls completely invisible to the player is toggle off their visible property, either in the scene tree or in the inspector. Now we need to make this enemy actually threatening to the player, which we've already done before. All we have to do is add an area to D node, name it hit Box. Give it a collision shape to D node as a child and give the collision shape Tut node a shape resource. Set the hit box to mask the player's Hurt Box. And it must be monitoring. Then attach the Hit box script and connect the on area entered signal. A now when we run the game, making physical contact with an enemy causes the player character to take damage. We'll deal with making the combat more engaging in the next section. For now, let's deal with these error messages. These error messages happen when we load a room's contents. This is because we are loading the room's contents in response to a collision event when the player character's body enters a room. And Godot doesn't like us adding more collision areas or objects while it is already busy resolving collisions for this current frame. So if we follow the advice being given by the error messages and use called deferred, this will tell the game engine to load the room's contents after the collision resolution for the current frame is complete. Godot cycle starts with input handling, followed by physics, which includes collisions, then processes and ends with deferred calls. So we can use this to perform any action we want as the last thing before the end of any frame. Running the game again, there are no more error messages. Our enemy will now stay in this room and can hurt the player character. But we need to add more enemy variety to our game. I'll see you in the next video. 52. 7-3 Ledge Detection: Hello, friends. In this video, we'll add another enemy which can detect wedges and turn around instead of walking off of them. We'll start by duplicating our existing enemy. Opening the enemy's scene, we'll change the root node name to match the enemy. Import their sprite sheet and set it as the atlas in the inspector. All of the sprites for the Sprite sheet are the same as the first enemy, so there's nothing to fix in terms of animations. In my room contents scene, I'll remove the two enemies and add in this new enemy then edit the terrain so they are in a raised terrace with a ledge on either side of different heights. I want this enemy to stop at the higher ledge, wait for a moment, then turn around and walk in the other direction. But then they should walk off of the shorter ledge. In regards to walls, they should act the same way as the previous enemy. Since this behavior is so similar, it would be good to use the same script. So I'll add a timer note as a child of the enemy. Rename it, attach the pace script to it, and set its one shot property to true. So this enemy should behave exactly like the blind mouse. Now we just need to figure out when the enemy is walking towards a ledge, which we can do using a new type of node, a cast two D node. We'll name this node edge detector. Then move it a short distance to the character's right and up. The ray has a collision mask which is already set to layer one for terrain. Expanding the collide with section, our terrain is a physics body, so the ray will collide with it. If using a ray cast node to detect areas, you'll need to check on areas here before it will work. The ray cast is represented in the editor as an arrow, but will be invisible while the game is running, just like our collision area nodes. It fires a ray starting from its position in the direction of the arrow and reports whether or not it hits something. In this case, whether or not it hits terrain. So if it hits terrain, the enemy will continue walking forward, and if it doesn't the character needs to stop. The distance to the right of the character will determine how far ahead they will look for the ledge. The distance up from the ground won't have much effect as long as it's above the ground and below the height of the character. The target position should point down in order to detect the ground, but how far it shoots the ray will affect what it considers to be a ledge and what it doesn't. I'll set mine to be 64 pixels down or two tiles. This means that if the enemy encounters a ledge that is only one tile high, it will still walk off. But if it's two or more tiles high, the ray will not hit terrain, and therefore, the enemy will stop. Unlike collision areas, however, the ray cast node does not emit any signals we can react to. So we'll have to attach our own script to this node. I'll put it in the enemy scripts folder. We can start by declaring our own signal, which we will want to admit when a ledge is detected and pass along the direction of the ledge in relation to the enemy as an integer. It's not quite as simple as whether or not the ray is hitting something, though. Similar to how we made the Coyote timer only start on the exact frame when the character walks off of a ledge, we only want to detect a ledge once during the exact frame when the ray stops colliding with terrain. So we'll need a Boolean variable for whether or not the ray hit terrain last frame and another four if it hit terrain this frame. During the physics process, we'll set was colliding to the previous value of is colliding, then I colliding to the current value returned by the ray cast. If the ray was colliding last frame but is not colliding this frame, this is the exact moment when we have detected a ledge and we will emit our signal. The direction of the ledge can just be the sign of the position of the node. Let's connect our ledge detected signal to the pace script and call a function named wait and turnaround. We can copy these three lines from above, which tell the enemy to stop, changes the pacing direction and starts the timer. Then replace them with a call to our function. But we'll need to pass the opposite of the walls normal X as the direction argument to match what is being passed by the edge detector. The ledge detector sends the direction towards the ledge. So here we need to give the direction towards the wall. The new pacing direction is therefore the opposite of the direction parameter. This will work as long as the enemy is moving to the right, but we will need to reposition the cast node to the enemy's left when they turn around. Fortunately, we have already emitted a signal every time a character changes direction. So all we have to do is connect this signal to the edge detector to react to it. Let's rename it on enemy changed direction. Since this gives us their new direction, we'll just set the nodes position to be the absolute value of its position multiplied by negative one if direction is less than zero or positive one otherwise. Tracing this changed direction signal omission back to the character script. I originally wrote this by passing the move direction variable as the signal parameter. However, this does not work in the context that we are using this enemy character, since the value of move direction will not necessarily match the direction we are telling them to face. So rather than passing move direction, it is more accurate to pass negative one if telling the character to face left or positive one for right. Now, the ray cast dende will always be in front of the character, whether they're facing left or right. Let's turn on visible collision shapes so we can see the ray cast while the game is running and hit play. The ray cast is drawn in red when it is colliding with something. Our enemy approaches the first ledge. The ray cast turns blue to indicate that it isn't hitting anything, and the enemy stops for a short time, then turns around and goes the other way. When approaching the shorter ledge, the ray cast is long enough that it still hits the terrain, so the enemy walks off and continues in the same direction. They eventually hit the invisible wall on the far edges of the room and turn around as the previous enemy does. We now have a second enemy type which behaves differently from the first, but we need much more variety to make our game fun. I'll see you in the next video. 53. 7-4 Flying: Hello, friends. In this video, we'll add a flying enemy which bounces around a room. Let's start by importing a new sprite sheet for the flying enemy. Then duplicating one of the existing enemy scenes. After opening the new enemy scene, we'll rename the root node to match and swap out the atlas with the proper sprite sheet. These sprites are 32 pixels square, unlike the other enemies so far. So I'll adjust the Y position of the Sprite now to lower it slightly. This enemy doesn't have separate idle and walking animations, so I'll remove the walk animation and edit the idle animation to have the correct number of frames. Then edit the keyed values for the idle animation to be the correct pixel height. Now the idle animation looks correct. Turning on the visibility of the character body's collision shape, I'll rotate it 90 degrees so the capsule is horizontal, which is a closer match to the shape of this enemy and adjust its radius and height as needed. Then do the same thing with the Hurt Box. And the contact damage hit box, making sure that it fits the size and shape of the enemy, so any damage inflicted on the player will feel fair. As for the Animation Tree state machine, since there's only one animation right now, it only needs to have an idle state transitioning from start to idle. In regards to the script attached to this enemy's root node, we don't need this enemy to walk or jump, but we still need this enemy to be able to face left or right, have acceleration and deceleration, be affected by gravity and terminal velocity, et cetera. So there's enough benefit to still use the same character script here rather than starting from scratch. But there's enough difference in the behavior of an enemy that can fly that we should write a new subclass which inherits from character. So let's create a new script named flying Enemy, inheriting from character, and save it in the enemy scripts folder. Our flying enemy will need a direction to fly. Unlike walking or running, which is represented as a float, since it only needs to worry about the X axis, a flying direction can be any direction in to D space, so we'll store it in a vector two. We'll also need a boolean variable to know whether or not this enemy is currently flying. We can then override the physics process to include a new check to see if the enemy is flying, and if so, call a flying physics function. If the enemy is not flying, then we can call the super class definition of the physics process as normal. You may also want to export a flying speed as a float if you want to have an enemy that has different walking and flying speeds. I'll give it a default value of 100 pixels per second. I'll hold Control or command and click on this function call to the superclass physics process to go directly to its declaration so we can take a look at what it contains. Note that the move and slide function call is inside the superclass definition of our physics process. This function must be called to apply the character body to Dnodes velocity and check for collisions. We also still want our flying enemy to face the direction they're flying. So we'll need to do that in the flying physics function. I'll copy this block of code and return to the flying enemy script and paste it into my flying physics. Rather than checking the value of move direction, we'll need to check the value of fly direction dot X. We'll also need to still call the move and slide function if the enemy is flying. We can then apply similar logic we used for air control on our non flying characters to make our flying enemy fly. If flying direction has a value, we will move the enemy's velocity toward fly direction multiplied by fly speed at a rate of acceleration multiplied by air control multiplied by Delta. Or if no flying direction is given, we can move their velocity toward vector two dot zero at a rate of deceleration multiplied by airbrakes multiplied by Delta. O. When we were editing only Velocity dot X, which is a float, a primitive data type, we had to call Godot move toward function in order to edit it. But since a vector two is a class, it can have its own functions. Holding control or command and clicking on the class name brings us to the documentation for that class where we can view all of its properties and functions if we need to. So here we have the option of calling velocity dot move toward, which differs from Godot function move toward in that the first parameter is already known as the vector itself. You may also want to export different values for flying acceleration, deceleration, air control, and air brakes for your flying enemies if you want them to use different values while they are not flying. We'll also need a public function that tells this enemy to fly, accepting a vector two parameter for the direction to fly. And another for when to stop flying. This will control the is flying variable and set the fly direction at the same time. Remember to attach this flying enemy script to the root node of the enemy in its own scene. In the room contents scene, just like our existing enemies have a separate node telling them how to behave, we will do the same thing with our new flying enemy. I'll delete this enemy from the scene and add two of these new flying enemies. Then I'll edit the terrain to enclose one of them inside of a box of terrain so we can see how it behaves a little easier. I want these bats to fly in a diagonal direction and bounce off of any wall, ceiling, or floor they come in contact with. So attaching a basic node to both of them, I'll name it ricochet. Then attach a script to this node and save it in the enemy scripts folder. This script will need an exported variable for the initial fly direction. I'll use vector 21, which is down into the right. We'll also need a reference to the enemy whose behavior we are controlling set to this nodes, parent using the on ready tag. Overriding the ready function, it would be a good idea to normalize the fly direction. Normalizing a vector will make sure that it has a length of exactly one. Since a vector with a value of 11 is longer than one, this would result in the bat flying faster than its flying speed. But if we normalize the vector, we are only using the vector's direction, not its length. Overriding the process function, we can tell the enemy to fly in the desired fly direction. But before we do that, we'll need to check if this enemy is currently in contact with any wall, ceiling or floor. We can then set the fly direction dot X or dot and Y to the appropriate value in any of these cases. For the ceiling, the value will be the absolute value of fly direction dot Y, making it always positive, which is down. And the floor is the opposite, multiplying the absolute value by negative one, which is up. For walls, we can simply multiply the absolute value of fly direction dot X by the sign of the wall normal dot X, which points away from the wall. I'll attach the ricochet script to the other enemy, too, then hit play. When we run the game, we can see that the bat bounces around, ricocheting off of every wall, ceiling, and floor it touches, creating an interesting obstacle for the player to avoid. We now have a flying enemy to fight, but we should at least have one more enemy type for our players to engage with. I'll see you in the next video. And 54. 7-5 Crawl: Hello, friends. In this video, we'll make an enemy that can crawl along any surface, including walls and ceilings. Let's start by importing a sprite sheet for this enemy. Then duplicate the flying enemy. After opening the new enemy scene, I'll rename the root node. Then swap out the atlas of the Sprite two D nodes atlas texture for the appropriate Sprite sheet. In the enemy's Animation Player, I'll update the idle animation to have the correct number of frames that are provided by the Sprite sheet. Updating the keyed region values as needed. Since we want this enemy to crawl on not just the floor but also the walls and ceiling, it would be helpful if we can rotate the enemy. With the enemy's origin at its feet, the way we've made every other enemy, rotating them will pivot everything about this point. To make things a little easier, it would be better to make the origin point of crawling enemies be in their center to make these rotations look smoother. So I'll center the sprite and all of the collision shapes to be centered on the origin point. Now when we rotate this enemy, it will remain in the same relative position. I'll quickly adjust the Hurt Box and hit box to better fit the shape of this enemy. For the character body's collision shape, however, I'll use a circle rather than a capsule. Since this enemy can rotate, the collision shape being a circle means that rotations will not result in any changes in what this enemy is colliding with. If you're using strict 90 degree rotations, a square collision shape would also work, but would require some extra steps when it comes to convex corners. So I'll stick to using a circle. In the room contents scene, I'll remove the bats and replace them with two snails. The goal is to have these snails crawl around both the inside and outside surface of this box. Just like we created a new script inheriting from character for flying enemies, we'll write another for crawling enemies and save it in the enemy scripts folder. Then replace the script attached to the enemy with this new script. I'll slow down my snail by setting its walk speed to only 32 pixels per second. First, we need to know the direction towards the surface that this enemy is currently crawling on as a vector two. Then also the forward crawling direction as another vector two. We'll also need to handle our own collisions here rather than relying on the character body's move and slide function to do all of the physics for us. So we can store collision data in another variable of type kinematic collision two D. Overriding the physics process, we'll check if this enemy is currently stuck to a surface, and if so, call the physics process for crawling. If they are not stuck to a surface, we can call the superclass physics process. This works because a vector two with its default values of zero, zero will be treated by the if statement as false and any other value as true. If they aren't stuck to a surface, we'll also want to check if this enemy can stick to a surface. Calling a slightly different function than move and slide, we'll call move and collide. This function accepts the motion of the character as the first parameter, which is their velocity multiplied by Delta. The function returns a kinematic collision two D, which we can assign to our variable. Much the same way our vector two can be used in an if statement, so can this collision data. If no collision occurred, it will be considered as false. And if a collision did occur, it will be considered true. If this enemy did collide with something, then we'll assume they want to stick to it and set the value of stuck to surface. We can access the collisions normal by calling the get normal function. This is a vector pointing away from the surface that the character collided with at the point of collision. Since we want the value of this variable to be toward the surface, not away from it, we can simply rotate it by 180 degrees calling the rotated function. But rotations in Godot don't use degrees. They use radians, and 180 degrees is the same as Pi radians. Oh If the enemy didn't collide with any surface, then we will call the superclass physics process. But the superclass physics process is also applying the character's velocity when it calls move and slide. Following this code, the character's velocity is being applied twice every frame as long as they are not touching any surface. We can fix this by passing a second optional parameter to the move and collide function, which is a Boolean named test. If true, this function will only check for collisions but will not actually move the character. Now that our enemy will automatically stick to any surface, we can get into the physics of crawling along that surface. First, we'll apply gravity toward the surface that the enemy is stuck to, since we didn't do it for the test for collisions earlier, and this will also help keep the enemy on the surface they are crawling along. Calling move and collide, passing the direction toward the surface, multiplied by gravity, multiplied by Delta, our collision data will now hold the collision with the surface. We can then branch based on whether or not a collision happened. In case the angle of the surface changed, we can update the direction toward the crawling surface, then calculate the forward direction, which is just the direction toward the surface rotated 90 degrees. If Pi is 180 degrees, then Pi divided by two is 90 degrees. However, the direction of rotation will depend on whether the enemy is facing left or right. If they are facing left, then we need to rotate clockwise. If they are facing right, we rotate counterclockwise to get their forward facing direction as a vector two. And if these values have changed since the previous frame, we'll also need to update the rotation of the enemy to draw them the right way. Since the enemy is facing to the right in their scene with a rotation of zero, all we have to do is use the forward direction then call the angle function. The angle function returns the angle between any vector two and the positive X axis. But since we're using negative X scaling to flip the character when they face left, we'll also have to correct for this in the rotation, rotating another 180 degrees or Pi radians if the enemy is facing left. Now that we have the enemy stuck to the surface, and we know which direction they want to crawl, then if the enemy is being told to move, we can finally use the move and collide function again to move them in their forward direction, multiplied by movement speed, multiplied by Delta. If there is any collision as a result of this movement, that means they have collided with a concave corner. All we have to do here is recalculate the direction toward the new surface. The next physics frame will handle the rest. If there was no collision, then the enemy is smoothly crawling along a flat or convex surface. If there was no move direction given, they will remain still. Going all the way back to the first if statement, if the gravity toward the surface did not result in a collision, that means the enemy has somehow lost contact with the surface they were crawling on. So we should call a function which will reset them back to a normal orientation. I'll name it unstick and make it public so other scripts can call it. This function will reset the direction to the surface back to vector 20 and the rotation to zero, returning them to a state of behavior that is just like any other enemy. There's still one thing missing from our crawling physics, though, which is telling the enemy to face the direction they are moving, since that was being done in the superclass physics process. Rather than copying and pasting the same code here, let's move this into a private function in the character class. Then we can call this function in both this character script, and also the crawling enemy script. Rather than creating a separate node and script to just tell this enemy to crawl, I'll export an integer variable for the Krall direction. Then override the process function and do it here. For testing purposes, I'll also override the input function to have the down arrow force them to unstick from whatever surface they're crawling on. When we run the game, the snail crawls around this box, turning at both the concave and convex corners to climb on the walls and again to crawl on the ceiling. Pressing the down arrow makes them unstick from the surface and fall to gravity as any other enemy would. We now have our final enemy type, one that can crawl on walls and ceilings. But our enemies should be more aggressive towards the player character. I'll see you in the next video. 55. 7-6 Sight: Hello, friends. In this video, we'll give some of our enemies the ability to perceive and react to the presence of the player character. In the room contents scene, I'll remove the snails and the box of terrain. Replace it with a block on two sides, then put a mouse note inside that will pace back and forth between them. I'll add the timer node, attach the pace script and set its one shot property to true. In order for this enemy to act aggressively toward the player character, they need a way to detect them. We'll need to use multiple tools to achieve this. First, we'll add an area two D node to our enemy. And a collision shape node for the area. This area will represent the entire range of the enemy's field of view. You may want to use a square rotated 45 degrees a capsule or a circle. If you use a collision polygon two D node instead of a collision shape two D node, you can actually create a custom polygon rather than using preset shapes. Simply click in the editor to draw each vertex of the shape you want. This allows us to make a triangle or a fan, which is a better representation of a field of view. Expanding the polygon property in the inspector, we can see the positions of each vertex, edit and rearrange them if necessary. Whatever shape you choose to use, make sure your shape has only convex angles. If your polygon is concave, Godot will divide it into multiple smaller convex shapes and double the cost of collision calculations as a result. If you do decide to use a normal symmetrical collision shape, however, flipping the enemy left and right is much easier, since you can just use the same method we used with the edge detector cast, setting its position property anytime the enemy changes direction. With the collision polygon two D node, however, the process is a little more complicated. We can't just move its position, and we shouldn't change its scale property either. If we open the documentation for this node, it specifically warns against this stating that it will likely not behave as expected. If we do decide to use these collision polygons for non symmetrical shapes that we need to flip, we'll have to manually edit the positions of each of the vertices in our script. The field of view will not be sufficient on its own, though, since there is nothing stopping it from going through walls. If we want the enemy to detect the player with an unobstructed line of sight, we'll need another node to check that a ray cast two D node. You'll want to position this up near the enemy's eyes where they would actually be observing from, but keep it centered horizontally. I'll rename this to line of sight. The line of sight will be colliding with both the terrain and the player character's body, both of which are bodies. This will allow the terrain to block their line of sight, even if the player character is within their field of view. With the field of view selected, we'll only need this to be monitoring, masking the player's body. I'll remove the shape node and just use the polygon as my collision area. Then give both it and the cast unique names. Attaching a script to the field of view area to dende we'll save it in the enemy scripts folder. Let's grab references to the shape or polygon node and the line of sight node using the ready tag. What I would like from this setup is for the enemy to react to the player character after they have both entered the field of view and the enemy has a direct line of sight to the player. If the player character both exits the field of view and the line of sight is broken, then I would like the enemy to return to their normal behavior. Consider how we want this node to communicate with other nodes through signals. We'll need this node to report two things when the enemy has acquired a target and when they have lost their target. These will be the exact moments when we want to change their behavior from their default pacing back and forth and instead tell them to start acting more aggressively or vice versa. We can connect the body entered and body exited signals from the field of view to the script. And this quite easily tells us when the player character enters and exits the field of view. We'll need to store this in a variable named target. Since the parameter is a no two D, we'll also set the variable type as a no two D to keep things simple. We also need a boolean variable for whether or not the enemy is currently targeting, whether the target is currently within the field of view and whether the enemy currently has line of sight to the target. When a body enters the field of view, then set is in field of view to true and set target to be body. When the body leaves the field of view, we can set it back to false. Remember that we are only masking the player character, so we already know that this body is the player character. Overriding the physics process, if there is no target yet, we can simply return since there's nothing to do. Now we can assume that the player character has entered the field of view at least once. Next, we'll point the line of sight at the target. We can create a vector pointing from A to B by subtracting A from B. So the target position of the raycast will be the target's global position minus our global position. Then the value of is in line of sight is going to be true if what the collider is hitting is the same as the target. If the ray cast hits terrain or doesn't hit anything at all, this will be false. Note that when the physics process happens, collisions have already been calculated by Godot. So when we point the line of sight at the player character in the line above, the collision data that results will not be available until next frame. What we are checking here are the collision results from the previous frame. If for some reason you absolutely need a cast to update immediately, you can call its force RycastUdate function. But with the game running at 60 frames per second, the delay will not be noticeable and this will perform just fine as it is. Now all we have to do is emit our signals at the correct times. If the enemy is not currently targeting, but the target is in their field of view, and they have line of sight to the target, then the target has been acquired. So we'll set is targeting to true and emit the target acquired signal, passing the target as an argument. And in the complete opposite scenario, if they are currently targeting, but the target is not in their field of view and they do not have line of sight, then we have lost the target. In this case, it would also be a good idea to set the value of target back to Null, which will stop the physics process from checking anything further until they re enter the field of view. Let's also print out when these events happen so we can test them out. The last thing we need to do is flip our collision polygon when the enemy changes direction, which we can do by connecting the changed direction signal to our script. If you're using a symmetrical shape, all you need to do is reposition it to the other side of the character. But I'm using a polygon, which means I'll need to edit the individual vertices. I'll start by getting the existing array from the polygon and storing it in a variable. The type is a packed vector two array. I can then iterate through each of the vertices and use the same strategy to flip their exposition to the other side of the character if they are facing left. The array of vertices provided by the polygon here is just a copy, not the actual array itself. So when we edit its values, it doesn't actually affect the collision polygon. To make the changes actually work, we need to overwrite the existing array with the edited copy. Let's turn on visible collision shapes from the debug menu and try it out. We can see our enemy pacing back and forth between the two walls and the collision polygon flips when they turn around. Entering the field of view, the ray cast node starts tracking our position, but the enemy has not seen yet since their line of sight is blocked by the terrain. If we jump up, the line of sight is no longer obstructed and we get the target acquired message output. Leaving the enemy's field of view and also their line of sight, the target lost message pops up and the cast is no longer tracking our position. We now have our enemy able to detect the player character's presence, but they still need to react to it. I'll see you in the next video. 56. 7-7 Aggression: Hello, friends. In this video, we'll have the enemy chase and attack the player. Starting in the enemy's scene, we'll need to add an attack for this enemy. Duplicating the idle animation, I'll quickly keyframe the proper sprites for the enemy's attack animation. This attack animation doesn't really communicate how much of an area the attack is going to hit. It only shows the enemy's movement itself. We'll need a separate sprite for that. I'll make a new Sprite folder for effect sprites and import a sprite sheet for this enemy's attack effect. We'll add an Animated Sprite to denode as a child of the enemy's Sprite, so it will automatically flip horizontally when the enemy turns around. Then create a new Sprite frames resource and add the sprites from the Sprite sheet. As always, I'll add a blank sprite to the end of the animation, set its frame rate to 12 frames per second and not looping. I'll put this animation on its first sprite, which accurately shows the size and shape of the attack. Then position it in front of the enemy. This makes it much easier to add a hit box to this attack. So let's duplicate the existing hit box. I'll rename the original hit box to contact damage and leave this new one as hit box. Now we can make this hit box as collision shape match the attack effect. Unlike the Animated Sprite, we shouldn't use the negative scaling trick to flip our hit box. So we'll need to connect the enemy's changed direction signal to the hit boox script and manually reposition it. As we've done before, we'll just grab a reference to the collision shape. And set its position to its absolute value multiplied by negative one if direction is less than zero or positive one otherwise. In this enemy's default state, it will not be attacking, so this hit box will not be active. So let's turn off its monitoring property and visibility. Then switch back to the attack animation where we can key frame these properties to turn them on during the attack animation and back off again. This will also add both of them to the reset track with their default values. And we can also tell the attack effect to play at the same time. In the room contents scene, we currently have this enemy's normal behavior pacing back and forth and a way for the enemy to see the player character. When this node emits the target acquired signal, we will want to change the enemy's behavior from pacing to be more aggressive. And when they lose track of their target, they should resume their original pacing. But we may not necessarily want this to apply to all enemies or other characters that are using the pace script. So we should create a separate node for aggressive behavior. I'll use another timer node and name it aggression. Then attach a script to this node and save it in the enemy scripts folder. Just like with the pace script, we'll need a reference to the enemy that this script is controlling using the ready tag. We'll also need to store a reference to the target in a variable that this enemy is acting aggressive towards. And we'll also need to know the direction towards the target as a vector two, as well as the distance to the target as a float. Knowing these values will allow the enemy to approach their target to get into attack range before attacking. But we'll also need to specify this enemy's attack range as an exported variable, which would be a float. If you want, you can also specify a minimum attack range too. If we change the type of the attack range to a vector two, we can use the X value as the minimum range and the Y as the maximum. I'll set its default value to be at least 32 pixels away from the player and up to a maximum of 64 pixels. So if the enemy is more than 64 pixels away from the player character, I want the enemy to run toward them. Or if the enemy is less than 32 pixels away from the player character, I want the enemy to run away from them. But within that range, the enemy will attack. Overriding the process function, we can ignore Delta, and if there is no target for this enemy, we can return. This shouldn't happen, but it's never a bad idea to have a failsafe. We can calculate the direction to the target by subtracting our enemy's position from the target's position and then get the distance by taking that same vector and calculating its length. If the distance to the target is greater than our maximum attack range, then this enemy should move toward the target setting its move direction to the sign of direction to target dot X. If the distance to the target is less than our minimum attack range, then this enemy should move away from the target, doing the exact opposite. If both of these conditions were false, then the target is within attack range. So the enemy should prepare to attack. I'll make sure the enemy stops moving by setting its move direction to zero and also face towards the target just in case they aren't already. I don't want the enemy to attack right away, since my attack animation is very short, it isn't telegraphed. So I'll start the timer here and wait for it to time out before attacking. The one shot property of this timer should be set to true to only count down once. The wait time property will be the time that the enemy remains within the attack range before attacking. Connecting the timeout signal to the script, here is where I'll tell my enemy to attack. It's also a good idea to face toward the target again in case something has changed since the timer was started. I only want to start the timer if it is currently stopped. I'll also stop the timer anytime the target is outside of attack range. This should be sufficient for our behavior. Now we just need to switch back and forth between our two behavior scripts at the appropriate times. We don't want our aggression script active when the enemy first spawns, so we'll set its process mode to disabled. Selecting the field of view in the scene tree, we'll connect the target acquired signal to the aggression script, and we can rename this function if we want to. To keep things consistent between my different behavior scripts, I'll name it zoom. This function will set the value of target, change the process mode to inherit, and also tell the enemy to run, which will make them start acting aggressively towards the target. But we also need to pause the pacing script. So we'll also connect the same signal to the pace node. And this time call a function named pause, but this signal provides an argument, and we don't need it in order to pause this behavior. We can also imagine a scenario where we would want to pause this behavior under other circumstances and wouldn't want to provide an arbitrary and unnecessary argument to call the pause function. So we will unbind the argument provided by the signal, so the connected function has no arguments. Here, all we need to do is set the process mode to disabled. When the enemy sees a target, they will pause their pacing behavior and resume their aggressive behavior. So when they lose their target, we can do the opposite, connecting the signal to the scripts and calling the opposite functions. Setting their process mode to inherit to activate them or disabled to deactivate them. When activating the pacing behavior, we will also want to tell the enemy to walk. Now in the character script, we'll need to trigger the attack animation when we tell the enemy to attack. To do that, we'll need a reference to the Animation Tree state machine playback. But this will cause an error in the player character script if we have another variable of the same name, since the player character inherits from character. So there would be an animation variable inherited from character, but also another one declared in player character, which is not allowed, but we're using it for the same purpose, so we can actually just remove this one from the player character script. Back in the character script, when told to attack, we just need to tell this playback to travel to the attack animation. In the Animation Tree, we'll add the attack animation and transition to it from Idle with Advance Enabled and return to idle at the end of the animation. If you don't want the enemy to be able to move or change direction while attacking, then we can add quick checks to see if the animation playback's current node is attack. And either set move direction to zero or return out of the function entirely. Now, when we run the game, once the enemy has us in both their field of view and their line of sight, their behavior switches from pacing to being more aggressive. They will chase after us and attack after a brief pause. We now have a much more aggressive enemy for our player character to contend with, but our player character can't really fight back. I'll see you in the next section. 57. 8-1 Combo: Hello, friends. In this video, we'll give the player character attacks of their own so they can fight back against our enemies. We've already given the mouse Knight an attack, and the process will be very similar. However, our player character has two different attacks. We can copy some of the nodes from the mouse night scene to save some time, the hit box and the Animated Sprite two D node. Switching over to the character scene, we can paste these nodes into the scene, then reorganize them as needed. The Animated Sprite two D node can be sorted under the character's Sprite, so it will automatically flip to match which direction the character is facing. I'll rename it to attack effect. I know which specific animated sprite this is. The hit box, I'll generally keep sorted next to the Hurt Box as a sibling. For the player characters hit box to deal damage to the enemy's Hurt Box, we'll need to make sure that the collision layers and masks are set accordingly. Since my player character is on collision layer nine and their Hurt Box is on collision layer ten, I'll then have my enemies on collision layer 17 and their Hurt boxes will be on collision layer 18. Opening the project settings, I'll add the enemy Hurt Box to my two D physics layer names as layer 18. Then remember to edit all of my existing enemy scenes to put their Hurt boxes on this layer too. The player characters hit box can then be set to mask layer 18 in order to cause damage to them. In order to flip the hit box, we'll need to reconnect the signal in this scene, since that signal connection will not have copied over from the other scene. But the function we wrote before in the Hit box script that we want to connect this signal to is named on enemy changed direction. This won't matter, but if you want to be consistent with how you name things in your game, you may want to change this function name to on character changed direction. This will add another function to the script, and we can easily just move the code to this new function and delete the old one, since it will just do the same thing. However, we now have to go into the enemies scene and edit their signal connection to connect to the new function. The earlier in your project you can identify these problems, the less time and effort you will need to spend on refactoring later. Back in the player character scene, we'll select the Animation Player node from the scene hierarchy and add a new animation. We'll name this attack one since there are two different attack animations and we'll need to differentiate them. After adding the character's Sprite frames. I'll copy the keyframes to the other tracks for the character's clothing. We can also turn on the hit box's monitoring property. And the visibility, too, if you like to see it turning on and off in debug mode. Taking a look at the attack effect, this doesn't match the attack animation. So I'll import the proper effect sprites for this attack. Then add a new animation to the Sprite frames resource. I'll name it attack one, turn off looping, set the frame rate, and add an empty frame at the end. Now the attack animation can tell the attack effect, Animated Sprite two D node to play the attack one effect. The keyframed function call is just play, which has been working fine as long as our Animated Sprite two D node only has one animation to play. But now we have two different animations, and we need to specify which one we want to play. Clicking on the keyframe, we can edit it in the inspector. Expanding the arguments section, we can see that the play function actually has three arguments, all of which are optional. We are only concerned with the first argument, the name of the animation, which we can type into the text field. If you're curious, the other arguments are for adjusting the animation speed or playing the animation backwards. The spelling must match the name of the animation exactly. I'll rename the default animation to attack two to match what I put in the animation. Duplicating the attack one animation, we can also make an attack two animation. After changing the character's sprite frames, I'll copy the keyframes to the other tracks. Then change the argument being passed to the attack effect to play attack two instead of attack one. I selecting the Animation Tree node, we can add the attack one and attack two animations. Then add a transition from movement to attack one, then from attack one to attack two. Both of these will have their advanced mode set to enabled, meaning we will trigger these transitions from our script. Adding transitions, leaving either of the attack animations returning back to movement, these will have their switch modes set to at end. The transition from attack one to attack two will also have its switch mode set to at end. This will allow us to transition to attack one if the attack button is pressed, and then from attack one to attack two if the attack button was pressed a second time during the first attack animation. To add an attack button to our game, we'll need to go into the project settings, the input Map tab and add a new input event for attacking. I'll use the X key on the keyboard and the left face button on my controller, which is also X. Then in the player script, we can add another I block for attacks, buffering the input the same way we have with every other action. Since the button to trigger the first attack and the second attack is the same, the players script should only be concerned with telling the character to attack, not which specific attack to perform. In the player character script, we can add this attack function, returning a boolean just like any other buffered input. This won't be locked and doesn't have any cost, unlike the cast function. If the character's animation playback is currently in the movement state, then I'll allow them to attack. If successful, then we can tell the animation playback to travel to the attack one animation and return true or return false if the character was busy doing something else. There's an error because the attack function is overriding the attack function of the character class, but the signature does not match. This means that either the parameters or the return type are different, which is not allowed. Holding control or command and clicking on the function brings us to the superclass definition of this function where we can see that it returns void, not a boolean. In order to use this inheritance structure with our enemies and our player character, while still allowing buffered inputs, we'll need to have the superclass attack function also return a boolean. We'll just return true to satisfy this requirement, and returning to the player character script, the error is solved. But what about the second attack? We can add an else if block and check if the animation playback is currently in the attack one animation. Then tell it to travel to the attack two animation instead. Let's turn on visible collision shapes in the Debug menu, then hit Play and try it out. Pressing the attack button, our character plays the attack One Animation. The hit box is activated, and the effect animation plays. Pressing the attack button again during the first attack also plays the second attack. Switching back to the editor, we can open the remote tree to see our current game running. Exploring the scene tree until we find the mouse night, we can select its Hurt Box and view its properties in the inspector, including their current health. Back in the game, we can attack our enemy and watch as each attack hits them for one health each time until they reach zero. Come on. We now have our character able to attack enemies, but our characters and enemies don't react to being hit. I'll see you in the next video. 58. 8-2 Hit: Hello, friends. In this video, we'll make our characters react to being hit by attacks. Starting in the player character scene, selecting the Animation Player node, we'll need to add a new animation for being hit. To make things easier, I'll duplicate the dash animation to create the hit animation. After updating the Sprite keyframes, I'll copy and paste them into the other tracks to also animate the character's clothes. Because I duplicated the dash animation, this hit animation now also includes invincibility frames. This is very helpful for preventing characters from taking damage from multiple hit boxes in rapid succession, which often feels unfair to the player and also causes the character to repeatedly take damage from persistent hit boxes, like environmental hazards, for example, because a new collision will be triggered when the Hurt Box is reactivated at the end of the animation, if the Hurt Box is still overlapping with the hit box. We also have the method track calling the end dash function, which if you recall, from the way it was implemented in the earlier section, must be called if the dash was interrupted. It would be a good idea to move this function call to the start of the hit animation. Let's take a look at this function in the player character script. Depending on which frames you turn the Hurt Box on or off, it may be possible for the character to get hit while the dash animation is playing and therefore interrupt the dash animation. Meaning that the end dash function would never be called, the character's gravity will never be reset, and the dash Cooldown never started. Obviously, this is problematic, but we can easily fix it. All we really need to do is track when the character is dashing with a Boolean variable. When the dash starts, we'll set it to true. Then when the end dash function is called, we'll check if the character is dashing first and if so, do all of the same things. Then set is dashing to false after. Now we can call this function anytime we need to regardless of whether or not the character is dashing. If the character isn't dashing, the function just won't do anything. Selecting the Animation Tree node and taking a look at the state machine, I would like the hit animation to be able to interrupt any of these existing action animations, as well as any jumping or movement animations, too. Therefore, it would be a good idea to use another state machine. So I'll save this state machine like I did with the previous ones. I'll name it actions and save it in my character's folder along with the others. Then replace this state machine with a new one and load the actions state machine as a node. Okay. You can then add the hit animation to this state machine. Adding a transition from start to actions makes it the default state. Then a transition from actions to hit with its advanced mode set to enabled, and another from hit back to actions with its switch mode set to at end. This allows us to trigger the animation from any existing state, play at once, then return back to our other states. In the character script, since I want this to apply to all characters, not just my player character, we'll need to make a new variable to hold the playback for this new state machine. We can duplicate the existing variable and rename it to something like Hurt animations. The path to this playback property is parameters Playback, since it is the root of the Animation Tree. If we select the Animation Tree in the scene tree and expand its parameters section, we can see the playbacks for each of our state machines. So the action state machine playback property path has changed, and we need to update it. To better clarify the use of this playback in relation to the Hurt animations, I'll name this playback action animations. Then use the replace feature to change all instances of this variable with its new name. Since the player character script inherits this variable, I'll also need to replace all instances of the old name with the new name in that script, as well. This will work for the player character, but in order for it to work for the enemies, we will also need to update their Animation Tree state machines to match this new format. I'll only update my mouse night since that enemy is in my starting room, and the process will be the same for every enemy. I'll make the hit animation in the Animation Player node. Then save the Animation Tree state machine as a project resource. Replace it with a new state machine. Add the old state machine as a node, add the hit animation, add the transitions with the same advance and switch modes. Rename it actions to match what the script will be looking for. Now this enemy has the same animation structure as the player character, so the scripts will work the same, having two separate playbacks for playing the hit animation and another for actions. Once the state machine is set up, the saved state machine can be deleted. This will make the state machine localized to the scene rather than a shared resource between multiple scenes. Now that we have access to our state machine playback, we need to trigger the animation. We'll start in the Hurt Box script. We have a signal that is emitted when the character's health changes, which we are using to update the HUD element. But this signal is also emitted when we initialize the character's health at the start, as well as anytime the character would recover health. So it's not the best signal to use for receiving damage. We'll declare a new signal for that. If you want to have the character react to different amounts or types of damage, you may want to give this signal parameters. I'll add a parameter for the direction that the damage is coming from, as a factor two. But we can't get that information from here. We only have access to the Hurt Box and not the hit box that caused the damage. So switching over to the Hit box script, here we have access to both the hit box and the Hurt Box. So we'll add this as another argument passed through the take damage function. We can calculate the direction from A to B by subtracting A from B, then normalize the result. Normalizing we'll ignore the distance between A and B by setting the length of the vector to one. Back in the Hurt Box script, we'll add the direction parameter. When our Herd box receives damage, we'll emit the signal and pass along the direction with it. Our character script can now react to this signal. I'll rename the connected function to on damage received for simplicity. Okay. We'll first tell the Hurt animation playback to travel to the hit state. Then also add a force to the character body in the direction of the damage. Since the direction is normalized, it will have a length of one pixel. We can multiply this by any number we want to alter how impactful the knockbck force will feel. You may even want to export this as a new variable so it can be tweaked for each individual character. We'll also need to make this signal connection in all of our character scenes. Et's try it out. My character can now attack the enemy and the enemy reacts to being hit by both playing and animation and a knock back force is also applied. The same also happens to my character when the enemy attacks me. We now have our characters reacting to receiving damage, but our combat doesn't feel very juicy. I'll see you in the next video. 59. 8-3 Juice: Hello, friends. In this video, we'll add some particle effects to our combat to make it feel more impactful. Starting in our player character scene, we'll add a new type of node to our scene, a particle emitter node. Godot has two different types, CPU and GPU. The CPU particle node will render particles the same way we are drawing our characters sprites using the device's central processor, which is less efficient but more compatible with older devices. While a GPU particle node will delegate drawing these particles entirely to the device's graphics card, which is more efficient but only on modern devices. The GPU particle node can handle much higher numbers of particles without sacrificing performance. I would only recommend using CPU particles if you're specifically targeting phone or tablet for your game. Otherwise, you should use GPU particles. Adding a GPU Particles two D node to our character scene, I'll just name it particles, move it up to be in the center of the character and take a look at its properties in the inspector. Before a particle emitter will draw anything, it needs a process material. This is a resource that tells the emitter how to spawn particles and how the particles should behave after they've spawned. There are two different options for our process material, a particle process material or a Shadum material. The particle process material will be created as a project resource that we can edit right here in the inspector, while a shader material will allow us to write a Shader script to do the same thing. Shader scripts are not written in GDScript like all of our scripts so far. They are written in GD shader, which will not be covered in this course. So we'll use a particle process material resource instead. Click on the resource to view and edit its properties. Now that our particle emitter node has a process material, it spawns individual white pixels from its origin point that fall due to gravity. To make the particles easier to see, I'll hide my character sprite. Before we get into each individual property, it helps to turn off gravity first, which is under the acceleration section. And since these particles are going to be used for impact during combat, I'll set their explosiveness to one. I have a sprite sheet I would like to use for my particles, so I'll import that into my effects folder, then assign it as my particle emitters texture property. But this just draws the entire sprite sheet for every particle. To animate a particle, we have to give it a custom material under the properties inherited from Canvas item. We'll make a new Canvas item material then turn on its particles animation property. Now we can change the number of animation frames horizontally and vertically in the sprite sheet. The particles are now drawing only the first frame. Expanding the animation section of the particle process material, we can set the animation speed for the particles. I want my particles to have a consistent speed, so I'll set both the minimum and maximum to one. A curve can also be used here to alter the randomization of each particle's animation speed. And the same values can also be provided to give each particle its own animation offset. Expanding the span section, we can edit the position where the particles will spawn, their rotation angle and their initial velocity. We can change the emission shape from a single point to be a variety of other shapes and change the size of the shape if we want to. Point emission shapes are usually the most appropriate for combat particles. Angle allows randomization of the rotation of the particles. To make it completely random, just set max to 360. Since I'm using pixel art, I do not want my particles rotated. The initial velocity will make our particles feel more explosive. The amount of force will be randomized for each particle between min and max, making the particles feel less uniform. The spread will create a fan shape. Maxing it out at 180 degrees, the direction will be completely random. The animated velocity section contains variables you can use to apply a variety of different constant velocities to your particles over their lifetime. Angular will make them rotate. Directional will continue to move them in whatever direction they're already moving. Orbital will make them orbit around the spawn point. Radio will move them away from the spawn point. And the velocity limit uses a curve to limit the velocity of each particle. The accelerations section contains similar variables that you can use to affect each particle's velocity gradually over its lifetime, which will make them feel more realistic. Gravity is a constant force in one direction for all particles. Linear is the direction that the particle is already moving. Radial is away from the spawn point. And tangential is similar to orbital. Damping is a useful force that will cause particles to decelerate over their lifetime, which is useful for explosive or impact particles like we're currently making. Particle attractors are not available for two D, so this setting won't do anything. Expanding the display section, we can change the way each particle is drawn. Scale will make the particles randomize their own size. If we add a scale curve, this will change how each particle changes its scale over its lifetime. We'll go over the curve editor soon. Scale over velocity will instead make the scale of particles relative to their velocity rather than their lifetime. This is particularly useful for making dust clouds that dissipate. Color curves has multiple different properties, starting with a base color for all particles. Color ramp will allow us to make a gradient with each particle progressing from the color to the left to the color on the right over its lifetime. This color is multiplied by the base color to produce the result. Color initial ramp will randomize the starting color of each particle across the gradient. The emission curve can be used to make three D particles emit different amounts of light over their lifetime, which isn't used in two D. The Alpha curve is useful for only editing the Alpha of your particles over their lifetime. Let's create a new curve texture resource here. This curve texture resource requires another curve resource of its own. Clicking on this opens a curve editor we can easily use to create a wide variety of effects. The X axis of this graph is time with zero being when the particle is spawned and one being the end of the lifetime of the particle. No 1 second. The Y axis is the value we are editing, which is the particle colors Alpha, one being opaque and zero being invisible. We can edit the minimum and maximum values for our axis, but we don't need to do it in this context. Simply clicking on the graph will add a point that we can drag around as needed. I want my particles to start opaque and fade to invisible over their lifetime. This is great for making particles fade away instead of just disappearing. Tangents are automatically applied to the points to create a curve. Selecting any point on the graph, we can edit the tangents to create any kind of curve that we need. We can also manually edit the points and tangents by expanding the points section at the bottom. Hue variation can be used to randomize the hue of your particle colors, which makes them look less uniform. The rest of the properties aren't very applicable to our tot project, but I'll give you a brief summary of what they're for. Turbulence can be applied to simulate how air currents would affect your particles. You can edit the noise pattern used to calculate the turbulence when it is turned on. Particles can be stopped or killed by collisions. Collisions here are not the same as collisions we have been using so far because the particles are being handled by the graphics card. They can only collide with light occluders, not physics objects. And if your particles scale, then their collision radius can also be scaled with them. And a sub emitter can be used to create effects like fireworks, spawning additional particle omissions. To make my particles match my game's other frame rates, I'll set the particles lifetime to be eight frames divided by 12 frames per second. Now that the particle emitter is set up, we should set its one shot property to true. Now it will only emit particles in one single burst, which we can trigger through our script. I'll turn my character sprite back on, then open my character script. Giving the particles node a unique name, we can store it in a variable with the on ready tag. Then when the character takes damage, we can tell the snow to emit particles by setting its emitting property to true. Note that this will not work if used in rapid succession, since the particle emitter will already be emitting particles. So just setting emitting to true will not spawn more particles. For times like this, you'll want to either spawn a new GPU particles node for each hit or call the restart function instead. Restart will force the particle emitter to stop its current emission and start over with a new set of particles. Remember to copy this node into your enemy scenes as needed. Et's try it out. Attacking the enemy, they now emit particles when they get hit. And the player character also emits particles when they receive damage. We now have particle effects triggered by our attacks, but there's more we can do to make combat feel even more crunchy. I'll see you in the next video. 60. 8-4 Shake: Hello, friends. In this video, we'll add a camera shake that can make combat feel even more impactful. Starting in the camera script, we'll need a variable that we can use to shake it, which will be a vector two. While there are a variety of different ways we can create a camera shake effect, I think it makes sense to use a tween, since it is a property change that happens over a specific time. So let's declare a new tween variable named shake tween. Then also declare a new function that will be publicly accessible that can shake the camera. First, checking if the camera is already shaking by checking if the shake tween already exists and if so, kill it. We can then create a new tween. There's no real need for special transition or easing types for this. We don't want to tween a property evenly from one value to another, but rather just call a method multiple times over a short duration. We'll need to declare the function we'll be calling, but I'll name it set shake as a private function. Then we need to provide an argument to pass to the function from the beginning to the end of the tween. This will be the intensity of the shaking effect measured in pixels. We can accept this as a parameter of the shake function so that any script which wishes to trigger a camera shake can specify the intensity. Then over the duration of the camera shake gradually reduce the intensity down to zero. You may optionally want to instead make this another parameter if you want to allow camera shakes that have a consistent or increasing intensity. The final argument is the duration of between, which we can also make into another parameter. Next, we'll write the set shake function, which accepts the intensity as a parameter. This function will be called every frame for the duration of the shake with the intensity gradually changing from the value provided in the original function call to zero. So all we have to do is set the value of shake to a new vector two. If we start with vector two dot right, then rotate it to a random direction. A random rotation angle can be a random float between zero and tau. We can then multiply the results by intensity. If for any reason your shake tween does not conclude with setting the value of shake to zero, then you'll need to do that at the end of the tween. We can connect the shake tweens finished signal to the set shake function and bind zero as the intensity. This will ensure that the value of shake anytime after the camera has finished shaking will be reset back to zero. To make the shake vector actually move the camera, all we have to do is add it to the camera's position in our process function. A in our games scene, we can now connect any signals we want to trigger a camera shake to our camera's shake function. Like, for example, when the player character attacks an enemy or when they receive damage. In the player character script, we can add signals for these events. Then back in the game scene, we can connect these signals to the camera's shake function. In order to add arguments to a signal connection, we need to expand the advanced section. We can then select float from the list of argument types since the intensity parameter is a float in our script. Then click on the Add button. The shake also needs a duration, which is also a float type. So we can click the Add button again to add another float. The values we provide for Argument one will change the intensity of the shake measured in pixels, and Argument two will be the duration of the shake in seconds. I'll have my player characters attack cause a camera shake with an intensity of four pixels over a quarter of a second. But when they receive damage, I'll shake the camera with an intensity of eight pixels over half of a second. Switching over to the player character scene, we need to emit these signals at the appropriate times, starting with our Hurt boxes damage received signal, which is already connected to a function named on damage received in the character script. If we override this function in the player character script, we can call the superclass function, then emit the signal. Next, we can select the hit box and add another signal connection to the on area entered signal and have it call a new function. Let's name this on attack landed. This doesn't need to be concerned with the other area node, so we can use the advanced section to unbind this argument from the signal connection. Since we unbound the area argument, the function has no parameters. All this function needs to do is emit the signal to cause the camera shake. No let's try it out. If I attack the mouse Knight, this triggers the hit boxes are entered signal, which will bubble up through the player character to the game scene and trigger a camera shake. Letting the mouse Knight hit the player character triggers the Hurt box's damaged received signal, which causes a more intense camera shake. We now have our camera shaking in response to the player character attacking enemies and receiving damage, but our characters don't die when they run out of health. I'll see you in the next video. I 61. 8-5 Death: Hello, friends. In this video, we'll have our characters die when they run out of health. Starting in the mouse night scene, selecting the Animation Player node, I'll quickly make a new dead animation by duplicating the hit animation. After changing the sprite to the correct frame in the Sprite sheet, it's a good idea to turn off the Hurt Box so they can no longer receive damage and not turn it back on again. The same can be done for all of the other area to Di nodes, turning off contact damage and the hit box two, so the dead enemy can no longer deal damage to the player character. Selecting the Animation Tree node, we can add this dead animation to the state machine and automatically transition to it if this character is dead. Unlike other animations, there's no need for a transition out of the dead state. Now we just need to add this Is dead variable to the character script. I'll put it at the top of the script as a boolean. And also declare a died signal two, just in case we ever want anything to react to certain characters dying. Then write a public function named die that will set the value of this variable to true and emit the signal. Anywhere in this script where a character could perform any action that shouldn't be allowed after they die, we can just check the value of the is dead variable first. So if the character is told to face a direction, but they're dead, they should ignore that request. The same way we ignore it if they are attacking. In a similar fashion, if this character is told to jump, but they are dead, they should not jump. Same with attacking. And in the physics process, I'll also set their move direction to zero if they are dead, so they will never move while dead, but will be affected by physics. In the Hurt Box script, we already created the dyed signal and emitted it when the character runs out of health when we wrote the script in the earlier section. But in order to play a death animation and not a hit animation when the character receives damage and dies as a result, we need to make sure we only emit the dyed signal at that time. So we'll move the damage received signal into an se block, giving the dyed signal priority. All we have to do is connect the Hurt Box's dyed signal to the enemy's die function to trigger the animation. Either in the enemy's scene or in the room contents scene, wherever you have your enemy's AI nodes, you may also want to connect the enemy's did signal to the free function of these nodes. Alternatively, you can do this in the scripts themselves. Overriding the ready function, enemy died can be connected to ure. This will save you from having to connect the signal manually hundreds of times across the entire project. Let's try it out. If I attack the mouse Knight five times, its health reaches zero, and it dies, which plays the death animation, and it stops all actions. I can also touch it without taking any damage. Now for the player character, I don't actually have death animation sprites for this character. What I will do instead is reuse the hit animation, duplicating it to create a death animation, since I know that when this character dies, the game scene will fade out and reset anyway. Like the enemy, it's a good idea to also turn off all hurt boxes and hit boxes. The only real change I will make here is to have the animation loop. In the Animation Tree, I'll add the dead animation, the same as before, with an automatic transition if the character is dead. But this time, also add a transition out of the dead state under the condition that they are no longer dead, since the player character will be revived after they die. After connecting the Hurt Box's died signal to the character script, I'll switch over to the player character script. We'll need to write a revive function that can be used to bring the player character back to life, setting the I dead variable back to false. We can also tell the Hurt Box to recover full health at the same time. To add more to the player character's death animation, I'll override the die function in the player character script. I'll start by calling the superclass function, then do some other things specific to the player character. Reusing the particle effects node, I'll set its one shot property to false, its explosiveness to zero. And it's emitting property to true, turning the instantaneous burst of particles into a steady stream. On top of that, I'll also tween the character's modulation color to have an increased intensity. After creating a new tween, I'll tween this node's modulation color to a new color with RGB values of 255 each. Then in the revive function, I'll set all of these back to their normal values. A in the game scene, the camera can also react to the player's death with another even more intense camera shake. The game manager will also need to react to the player character's death in order to reset everything. So we can connect the player character's dD signal to the game manager script. Let's name this function on Player DD. When the player dies, first, we can disable the input handler so they can no longer control the character. I'll wait 1 second first to let my character's death animation play, which can be done by telling the scene tree to create a timer. We can get the scene tree anytime using the get tree function and call the create timer function, which accepts an argument for the length of the timer. This returns the timer already started so we can await its timeout signal. This will pause the execution of this function at this line for exactly 1 second. After fading to black, we'll need to reposition the player character to where we want them to respond. Since we don't have proper systems in place to handle this yet, I'll just put them at the game scene's origin and leave a comment that this needs to be implemented later. We can now revive the player character, fade back in, and re enable the input handler so the player can continue playing. If you want to display some text, you can add a label node to the Canvas layer. I'll name Mind game over and give it a unique name. Then populate it with some text, center it. Give it a larger font size. And anchor it to the center of the screen. Before fading to black, I'll also create a tween that will fade the label's Alpha up to one. I'll modulate its Alpha to zero, so it is invisible by default. Without any await, this will happen at the same time as the screen is fading to black. The label will need to be drawn in front of the fade in order to be visible. After repositioning the player character, I'll then wait another 1 second before fading at the text. But I'll make it quicker than it faded in. After reviving the player character, I'll display a different message as the game fades back in. I'll duplicate the existing game over message, name this one revive, and change the text. Before the fade to clear, I'll use another tween to make this text fade in. Wait another 1 second, then fade the text back out. Let's try it out. I'll let the Mouse night kill my player character. We now have our characters dying and the game manager reacting to the player character's death, but our game doesn't have a boss in it yet. I'll see you in the next video. 62. 8-6 Boss 1: Hello, friends. In this video, we'll build a boss for our game that's more complex than our other enemies. I'll start by creating a new asset folder to hold the assets for this boss. Then import the sprites. I'll then create a new scene in my enemy scenes folder named Mac Mouse with a root node type of CharacterBody two D. This boss is comprised of multiple different sprite components that I'll need to assemble and layer one over another. First, I'll add a normal no two D that I can use to flip all of the boss' visual components when they turn around and name it flip. Then add multiple sprites for the various components. I'll need a sprite for the armor plating, which will be an atlas texture. And after populating the texture, I'll select the appropriate region from the sprite sheet. This will be offset up by 48 pixels. Duplicating this sprite node, I'll rename this one to be gears and change its type to Animated Sprite two D. Giving the node a Sprite frames resource, I'll add the gear sprites to the animation. Then change its animation speed to 12 frames per second. This should be drawn behind the armor plating, so I'll reorder the nodes. Duplicating the armor plating sprite node again, I'll make another sprite for the background behind the gears. After making the atlas texture unique to this node, I can then select a different region to draw and reorder this node to be drawn behind the others. This background is also meant to clip the gears so that anything outside the body will not be drawn. We can do this by putting the gears as a child of the background node. Then selecting the background under the visibility section, change clip children from disabled to clip and draw. Now the gears only draw inside the body. There's also a wind up key which is animated. So I'll duplicate my gears node to make another Animated Sprite tout node and name it key. After making the Sprite frames resource unique to this node, I'll replace the gear Sprite frames with the key sprite frames. I'll then reposition the key to be at the appropriate position at the back of the Mc mouse's body. I'll need more Animated Sprite two D nodes for the wheels, so I'll duplicate the key to make an animated wheel. After making the Sprite frames resource unique, I'll change out the key Sprite frames for the wheel sprite frames. Then put the wheel at a good position and offset it up to be on the ground. O. To draw the wheels behind the body, I'll reorder them to be above the body in the scene tree. I'll duplicate the front wheel to make a second wheel and reposition it. The last visual component of this boss is a barrel that extends from its mouth, which will just be an ordinary sprite. After assigning the sprite texture, I'll move it to be around the Mc mouse's mouth and layer it behind the body sprites. I When this barrel fires projectiles, I want it to be protruding out from the Mc mouse's mouth, so it will have an offset of negative 16 pixels. I'll add an area Tutti node at this position, which will be the projectile. Then give it an Animated Sprite Tutti node with Sprite frames resource and add the two fireball sprite frames. After setting the frame rate, I'll also have this auto play by default. This will need a collision shape, which will just be a circle. In order to work as a projectile, it will need the projectile script attached. The projectile script expects the projectile to have an impact animation, so I'll add one using the same impact sprites as my other projectile. This will need to be monitoring and masking the player's Hurt Box layer. We can connect the area entered and body entered signals to the projectile script to trigger the impact animation and damage. O. With the fireball assembled, we can save it as its own scene in the projectile's folder. I'll add a marker Toti node as a child of the fireball to act as the location where the projectiles will spawn, just like we did with the player character. Then reorder the marker Tutti node to be a sibling to the fireball, maintaining its relative position and delete the fireball. Then reset the barrels offset back to its default position. In addition to flipping the body left and right, I also want the body of the Mc mouse to rumble but not the wheels. So I'll put everything I want to rumble as children of another no two D named body. Anytime the Mc mouse is moving, I want dust and rocks to be kicked up from the ground by the wheels. So I'll add a GP particle two D node for the rocks. Then assign a rock sprite as the texture and make a particle process material. Okay. Make the rocks kick up from the ground, all I really need to do is change the velocity direction to up and give each rock a random velocity 32-64 pixels per second. For dust, I'll start by duplicating the rock particle emitter and rename it. Then assign the same particle sprite sheet I used for attack impacts as the texture property. Since this is a sprite sheet which is intended to be animated, this node will need a canvas item material with its particles animation property turned on with the correct number of frames. After making the particle process material unique, I'll keep the velocity the same, but remove gravity and add enough damping to counteract the initial velocity. Under display, I'll add an animation speed of one to animate the particles and an Alpha curve to make them fade away over time. I want this drawn in front, so I'll set its order property to one. This should be sufficient, so I'll duplicate the two particle nodes and add them to the other wheel. I don't want them emitting by default, so I'll group select all of them and turn their emitting properties off. Just like every other enemy, this one will need a collision shape to collide with the environment plus hurt boxes and hit boxes. Since this enemy has more of a unique shape, I'll use a collision polygon rather than a collision shape. Adding a few vertices to the polygon to get the general shape of the boss' body, I'll need to make sure that the collision polygon makes contact with the ground. I also don't really want to bother with flipping this polygon, so I'll make it symmetrical. This will suffice for collisions with the environment, but will also allow the player character to stand on top of the enemy, too. For the Hurt Box, I'm a little less concerned with matching the shape of the enemy so closely, so I'll use an ordinary capsule. If you want to combine multiple standard shapes to make a more complex Hurt Box, you can simply add more collision shape nodes. I'll add a second capsule above the first one to cover more of the body. I also don't want to bother with flipping these Hurt boxes, so it helps that they are symmetrical. The area two D node will emit its signals as if all of the shapes were combined into one shape. But keep in mind that if the shapes are far apart, you may have something enter one collision shape, then exit it, then enter another collision shape causing multiple collisions. I'll also add a separate Hurt Box for the wind up key. Giving this key its own Hurt Box means it doesn't need to be connected to the boss' health, but can emit signals when the player attacks it. This can just have a simple circle collision shape, but will need to be flipped. So I'll make sure that the exposition of the shape node is what is determining which side the Hurt Box is on. And finally, this enemy needs a hit box. This will be used when the Mc mouse charges forward. I want this to hurt the player character if they are in front or on top of the boss. So I'll use a capsule rotated 90 degrees that extends beyond the body's collision polygon in front of it and above it, but not behind it. Selecting the character body root node, the enemy will need to be considered terrain in order for the player character to stand on it, but also exist on the enemy layer too and mask only the terrain. None of the Hurt boxes or hit boxes will need to be monitoring or monitorable by default. We'll turn them on through scripts or animations when they need to be turned on. But the Hurt boxes can be set to the enemy Hurt Box collision layer, and the hit boxes can be set to mask the player Hurt Box collision layer so that when they are turned on, they will work. We now have a boss assembled for our game, but it doesn't have any scripts controlling it yet. I'll see you in the next video. 63. 8-7 Boss 2: Hello, friends. In this video, we'll give our boss some more complex behaviors than our normal enemies. Actual progression mechanics will be covered in the next section. But for now, it will help to think about how our boss should exist in relation to its arena. So let's put this boss enemy in a simple arena with a wall on either side. Generally speaking, bosses in Metroidvania trap the player in an arena, and once the player enters, they must defeat the boss before they are allowed to leave. This is an event which will need to be triggered by the player character entering the Area two D node, so we'll add one to the scene and name it boss Encounter. Then give it a collision shape. I'll use a rectangle and make it cover a very large area so that the player can't get around it and have it monitor for the player character's body. This will need a script attached, but I'll create it from the file system tab in a new enemy scripts folder for bosses, which we can name Boss Encounter, extending Area two D. This script will be responsible for controlling the overall flow of the boss encounter, including when it starts, when it ends, and any phase transitions, things like title cards, changing music, et cetera. We'll need to give this a class name so we can extend it later. To react to the player character entering this boss encounter, we can go ahead and write the on body entered function, which will accept a parameter named body of type no two D and return nothing. Player character enters this boss encounter, first, we should turn off all future collisions with this area by using set deferred to set the monitoring property to false. Remember that when a function is called by a collision event, we shouldn't alter our collision objects at the same time, so we defer that until after the collisions have been handled. We can then close the arena, trapping the player inside and perform any initialization required. But we'll get to that later. For now, we'll declare an exported variable that holds the boss enemy itself and one for the player character as the target. When the player character enters the boss encounter, we can set the value of target to be the body, as well as a local phase variable as an integer. Then add a start function, an end function, and also a phase transition function, accepting the phase as a parameter and updating the variable to its new value. In the start function, we can set phase to one and call our decision making function to start the fight. We'll also need a function which tells the boss what to do next. For now, I'll call the Start function when the player character enters the boss arena. Make sure you save this script so the class name is registered. Now that we have the basic structure in place, let's attach a new script to our boss encounter node. I'll name this after my boss, Neck mouse encounter extending boss encounter and save it in the appropriate folder. With the script attached, we can assign the boss enemy to the exported variable, set the collision mask to the player character layer, and connect the body entered signal. Since the on body entered function already exists in the boss encounter superclass, we don't need to worry about it in this script. However, we will override the decision making function and give this particular boss encounter its own algorithm for deciding what it will do next. Structuring the boss encounter in this way makes it easier to alter a boss's behavior based on which phase it is currently in or add environmental hazards to the arena in reaction to the boss' actions or synchronize the behavior of multiple bosses in a single encounter. Any new boss encounters we want to create can also extend the boss encounter class and provide their own unique implementations for each specific boss. First, it's a good idea to check if the boss is dead before letting it decide what to do next. So if they are already dead, then we can just return. It's also a good idea to space out a boss's actions with a brief pause awaiting a timer's timeout signal. We can make it less predictable by randomizing the timer's duration and even make the cadence of the fight increase with each phase by dividing by the phase number. And no matter what the boss decides to do, they should probably face towards the target. So we can call a function to tell the boss to face left if the target's global exposition is less than their own. Using a match statement on the phase variable, we can easily provide different action options for different phases. Since my boss is so simple, I'll opt to increase the intensity of actions by phase rather than introduce new ones. With another match statement on a random integer 0-1, I'll create an even chance to decide between two different actions. Either the boss will move to a random position in the arena, which will be the global position of this boss encounter node plus a random number between negative 300 and positive 300, or the boss will shoot fireballs at the player. I'll randomize the number of fireballs based on the current phase. I also want the boss to have a third action, which is to charge at the player character at a higher speed, but only under certain conditions. So if the Mc mouse is ready to charge, then it will charge. If not charging, then the action will be randomized. And I'll give it the sign of the target's global position minus the Mc Mouse's global position as the direction. Planning out what actions you want your boss to use and when gives you exactly which public variables and functions you will need to write in their scripts. So we can see from this script, our boss' script needs an is dead variable, a ready to charge variable, and also face left, charge, move to position, and shoot fireballs functions. This is an easy way to approach encapsulation when writing complex scripts. We can write the script that needs to interface with it first so we already know exactly what we need. Encapsulation also helps us achieve abstraction, too. Since we only focus on what needs to be publicly accessible, we can also assume that everything else should be private. Next, let's attach a script to the McMus' root node and save it in the enemy scripts folder. We can declare all of the variables and functions accepting the matching parameters from the Boss encounter script. If we declare a signal and name it something like finished, then we can emit this signal from all of the actions which were selected from the decision making function. For now, let's just print out what we were expecting the boss to do. We can first check if this enemy is dead, and if so, emit the finished signal immediately and return so nothing will happen. Then wait for 1 second before emitting the finished signal. Then selecting the boss enemies node in the content scene, which we can child to the boss encounter itself if we want to and connect the finished signal to the decision making function. Now every time this boss enemy completes any of the actions, they will automatically decide what to do next. This should be sufficient to demonstrate the flow of our boss battle. So let's try it out. We can see from the output every second or two, the enemy is making a decision about what to do next. We now have our boss enemy being controlled by a boss encounter script, but we still need to implement how everything works internally within the boss scene. I'll see you in the next video. And 64. 8-8 Boss 3: Hello, friends. In this video, I'll go over how I wrote my boss's functions so you can compare my methods to the way you implemented yours. First, I attached the Hit box and Hurt Box scripts to the boss's hit boox and Hurt Boxes respectively. Then started on the boss enemy scripts. I declared signals for anything that would be used to start or end the boss fight or cause a phase transition. Another for when the boss changes directions so I know when to flip things around, and a couple more for knowing when the boss has arrived at its intended destination or crashed into a wall. Then there's also the signal, which will be omitted when the boss' current decided action is completed, and it needs to decide what it will do next. I connected the change direction signal to the Hit box and the wind up keys Hurt Box so they can be flipped around. The Hurt Box script didn't actually have this function, so I copied it from the Hit boox script. However, the wind up keys Hurt Box is behind the boss, so I needed to add an exported variable to reverse the direction for this specific case. Back in the Mac Mouse's script, I needed a reference to the boss's body in order to make it rumble. So this gets stored in a variable, since I'll be using it very frequently. And, of course, I'll need to know if the boss is alive or dead. I would like my boss fight to start with the Mcmuse turned off and have the player attack the wind up key multiple times to start it up. So I declared a variable for whether or not the engine is currently started plus a speed for the wind up key, starting it at one eighth of normal speed, and a tween I can use to alter the speed gradually over time. To create this effect, I had the wind up keys animation playing automatically, but with a speed scale of zero and its Hurt Box monitorable by default. I then connected its damaged signal to my boss script, calling a function named on key attack. To ensure the key's Hurt Box never dies, I call it its recover function, since this isn't a reflection of the boss' health, merely another target for the player character to attack. After setting the wind up keys animation speed scale to double, I used the tween to gradually reduce it back to its expected value over the course of 1 second. If the engine has already been started, then attacking the wind up key will cause the Mac mouse to charge toward the player character as their next action. Otherwise, I doubled the key speed and checked if it had reached a value of at least one. With the default value of one eighth, this will happen after the third time the wind up key is attacked. When that happens, I called a function to start the Mc mouse's engine. All this function does is tell the gears animation to start playing, start a rumble timer, which is set to one 12th of a second, so the engine is started variable to true and emit the signal. Every one 12th of a second, when the rumble timer times out, I moved the Y position of the body to a random integer between negative one and positive one so the body rumbles up and down. Making the boss face left was very similar to how it was done with the player character with the exception that this boss enemy is facing left by default. To make the boss drive around and charge toward the player character, I used many of the same locomotion variables in the character script just with different names, and also included the boss' destination and the distance to their destination as floats, so I can use them to determine where the boss needs to go in the physics process. I also included gravity just in case and set the movement speed to be the driving speed by default. I saved the wheels in an array so I can synchronize them easily. While this may seem unnecessary for only two wheels, writing it this way will work for any number of wheels. I used a public variable to say whether or not this Mc mouse has had its wind up key attacked and is ready to perform a charging attack and an integer to count the number of times that they have crashed into a wall. When the boss encounter script decides that the Mc mouse should move to a position within the boss arena and provides the position to move to as a parameter, quickly checking if the boss is dead so that the request can be ignored, I stored the destination, set the move direction, the move speed, and is moving to true. I called a private function to animate all of the wheels, then await my arrived signal before emitting the finished signal. The arrived signal will be emitted from the physics process later. The argument passed to spinning the wheels controls how fast they will animate, as well as whether the animation will play forward or in reverse. So if the Mac mouse is facing left and moving left or facing right and moving right, I'll play the animation with a speed of one. Otherwise, a speed of negative one plays the animations backwards. Charging at the player character does mostly the same things. However, I want the Mc mouse to always face toward the direction that it is charging and spin the wheels at double speed. Using a short pause, this will telegraph the charge to the player so they can react and get out of the way. Since the charge is an attack, the hit box has its monitoring property set to true. After setting is charging to true, I await the signal which will be emitted after the Mcmuse has crashed into a wall and use another brief pause to keep the boss stunned for a short moment. Remember to connect the hit boxes area entered signal to actually deal damage. Spinning the wheels and accepting the direction of the spin as a parameter, I iterated through each of the wheels in my wheels array and told each to play their default animation using the absolute value of direction as the animation speed and whether or not the direction was negative to determine if the animation should be played in reverse. I also started the rock and dust particle emitters attached to each wheel at the same time if the value of direction was anything other than zero. Otherwise, they will be turned off. Now for the physics process, which will make both of these actions possible, if the boss is already dead, then it cannot willingly move, so its move direction will be set to zero. I then applied acceleration or deceleration to the boss' X velocity, as well as gravity to its Y velocity before calling the move and slide function to apply the velocity to the character body. If the boss is currently driving, I'll need to check if it has arrived at its destination, or if it is charging and is currently touching a wall, then I know it has crashed. To know if the boss has arrived at its intended destination, I first need to know the distance to its destination, which is the absolute value of the destination minus the boss' current global exposition. Reversing the jump height formula, we can determine if the boss is close enough to the destination to start decelerating. If distance is less than velocity squared divided by deceleration, divided by two. When this is true, I stop spinning the wheels, set move direction to zero is driving to false and emitted the arrived signal. When the boss crashes into a wall, I did most of the same things, but also added velocity away from the wall so they will bounce off of it. And turned off the hit box. I also counted the number of times that this happens, and on the third crash, damaged the Mc mouse's armor plating so they will enter the second phase. When the armor is damaged from crashing too many times, I increased the speed of the wind up key to two. To damage the armor plating, all I needed to do was change the region of the sprite sheet being drawn by the armor sprite node. I also doubled the speed of both the wind up key and the gear animations as well as the rumble timer to make the boss scene even more unstable. And I turned the monitorable property of the Hurt Box to true so the player character can now attack the Mc mouse's body to deal damage to it. Then emitted the signal to trigger the phase transition. To shoot the fireballs, I needed a packed scene for the fireballs, as well as the new fireballs, I would be instantiating the marker for where to instantiate them, plus the barrel and a tween for moving it. When the boss is told to shoot a number of fireballs, I first awaited extending the barrel, then looped a number of times, pausing a quarter of a second before each shot, then shooting a fireball. After shooting all of the fireballs, I awaited retracting the barrel before emitting the finished signal. Extending or retracting the barrel was a simple tween tweening the barrels offset X to the desired number over a quarter of a second. Shooting a single fireball is much the same as with the player character's projectiles. Just adding it as a sibling putting it at the correct position and firing in the forward facing direction. In my projectile script, when telling a Hurt Box to take damage, I'll also need to provide the direction of the damage. Finally, when the boss runs out of health, I use set deferred to turn off the hit box and both Hurt boxes and stop all of the animations before emitting the dyed signal to end the boss encounter. In the room content scene, I connected the engine started signal to the boss encounters start function and the died signal to end the encounter two. I also connected the armor damaged signal to the set phase function, binding two as an argument to trigger the transition to phase two. I commented out the call to the start function so I could trigger the start of the battle to happen after the engine is started. Yes. Oh. We now have our boss performing a complex sequence of actions with escalating intensity over multiple phases. But our game lacks any kind of meaningful progression. I'll see you in the next section. And 65. 9-1 Save Point: Hello, friends. In this section, we'll turn this two D platform into a Metroidvania by properly tracking the player's progress. We'll start by adding a save point to our game. I've moved my boss encounter into its own room and moved it off to the side of my map in a separate volcano region and also made a new save room for my forest region on the left. I left my player character up here. I'll just reset their position back to the origin. Let's start in the players save data where we will need to store the location where the player last saved their game. How you store this information will vary based on the structure of your game. But for my game, I'll use an array of strings. While it might be more efficient to store save points as numbers, this is only a single variable, so the impact will be negligible. It also helps to keep the save file human readable for debugging purposes. The first element of the array will be the name of the region, and the second will be the name of the room. This pattern follows the structure of my scene tree. When I load the players save data, all I have to do is find the region that matches the first string, then find the room that matches the second string and position the player character at the save point in that room. Note that this save point should exist in the game at the time when the save data is being loaded and no room contents have been loaded yet. Therefore, the save Point object should be in the room scene rather than the rooms contents scene. I'll add a node two D to my save room scene and name it savepoint. To keep things simple, I'll place this node to be at the position where I want to spawn the character. Since we will need to have multiple of these save points in our game, it should be saved as its own scene. We can then edit it in isolation. How you want to activate your save point is up to you. I'll just use a simple collision event. So anytime the player touches a save point, their progress will be saved. So I'll add an area two D node to the scene and offset it up off the ground 96 pixels so the player has to jump to touch it. I've imported some assets for my save point, which is a book and also an effect sprite that I'll draw behind the book to make it glow. I'll add a Sprite two D no to the area node to draw the glow first. Then add an Animated Sprite two D for the book. Give it a Sprite frames resource and populate it with the book sprites. Playing the animation forwards and then again backwards. The area needs a collision shape, so I'll give it a circle and use the radius of the glow, which is 32 pixels. Since this is in the room scene, not the room contents scene, it will need to be ordered in front of the room's contents. It will need to monitor for the player character's body. A attaching a script to the save point, we can then connect the area's body entered signal to the script. When the player character touches the save point, we'll set the value of last saved at in their save data to be a new array with the first element of the array being the name of the region and the second element, the name of the room. We can automate these names using the on ready tag, since the parent of the save point will always be the room it is in, and the parent of the room will always be the region. The nature of the scene tree requiring siblings to have unique names will ensure that this is sufficient to determine exactly which room they last saved in. We can also grab a reference to the Animated Sprite two D node and tell it to play its animation when this happens. Don't forget to tell the file global node to save the game anytime you want to make something permanent. Switching over to the game manager script, we'll need to load the player's last saved position. After all of the initialization is done, we'll call a function to position the player character. If the player's save data has a value for last saved at, then we can get the region by its name since we know it is a child of the world node. And then do the same to retrieve the room, knowing that the room is a child of the region. We can then position the player character at the global position of the save point in that room. This will trigger the player entering that room, load its contents, and fade in as we expect. If the player hasn't reached a safe point yet, I'll put them at the origin to start the game. A problem arises when the player dies, however, since we will also want to put the player character at the last saved position at that time. Both the death event sequence and the player entering a room will be manipulating the fade at the same time. There are multiple ways to solve this problem, but to keep things simple, I'll check if the player character is changing rooms when they die. If they die and respawn in the same room, then the player entered room will not be triggered, so the death sequence can handle the fade. Otherwise, I'll replace it with a simple pause for 1 second. To know if reposition the player character has changed rooms, I'll make that function return a boolean. And return if the new room is the same as the current room or false as a default. Et's try it out. When I first load my game, my character starts at the default position in my start room. If I go over to the left, I'll find the save point. Jumping into the save point triggers saving my game. If I close the game and play again, now my player character starts at the save point. To make my save point a little more magical, I'll have it float up and down. I'll export a couple of variables for the oscillation range and speed with a default of 16 pixels and one oscillation per second. The thing that will be moving is the area node, which will also move the sprites as it's children. I'll need to know its initial Y position and how much time has passed. Overriding the process function, I'll set T to be itself plus Delta multiplied by oscillation speed wrapped between zero and tau. This will allow the oscillation to repeat infinitely without ever getting to the maximum of a floating point number. The area's Y position can then be set to its initial position plus sine of T multiplied by the oscillation range. To draw the save point on the map, we can simply add a Sprite two D to the save point scene and give it the texture we want to draw. Scale it up really big so it will be visible to the map camera, then hide it so it isn't in the way and attach the map icon script. It will need to be on visibility layer two, not layer one, and all of its ancestors in the scene tree must be visible on both layer one and layer two. I want to test out the death problem too, so I'll add a simple input function to my player character script. So when I press the space bar, the character dies. Now when I run the game, the save point is floating up and down, and I can see it on the map. When I press the space bar, the character dies and responds at the save point. If I move to another room, I can do it again and respond back at the save point once more. We now have a save point that will remember the player's last saved position and load them at the correct location. In the next video, we'll add the ability unlocking items that define a Metroidvania. I'll see you in the next video. 66. 9-2 Metroidvania: Hello, friends. In this video, we'll add items for the player to collect that will unlock their Metroidvania abilities. I'll start by creating a new scenes folder named Upgrades. Then duplicate my save Point scene, name it Gem, and put it in the new Upgrades folder. Then open it up to edit it. Unlike the save point, I don't want this to be drawn on my map, so I'll delete the map icon. There's no need for this to be offset from its origin, so I'll reset the area nodes position. Then make the area two D node, the root node, rename it GEM and delete the old Save Point node two D. I'll replace the book Sprite frames with new ones drawing a spinning gem Then have this animation autoplay and loop at 12 frames per second. We can then attach a new script for our upgrades. Reconnecting the Area two D nodes body entered signal to the script, we can unlock the player characters new ability when they touch this upgrade. To do that, we'll export a variable of type Enums dot abilities, referencing the Enums script we created earlier. This creates a convenient drop down in the inspector that will let us select one of our abilities for this upgrade item to unlock. But remember that an enumeration is just an integer with a name. In my game, the gem will unlock the ability for the player character to shoot projectiles. Back in the script, when the player character's body enters this area, we can set the value of file dot data dot Abilities unlocked indexed at ability to true. Then save the game to make this upgrade permanent. To make this feel more impactful, we'll probably want to do a lot of fancy stuff before and after this, then eventually remove this node when we're done. We can ensure that only a single collision event will ever happen with this node by using set deferred to turn off its monitoring property after the first collision. In my save rooms contents scene, I'll put this somewhere so I can test it out. A however, I wanted to float up and down like the save Point does. Rather than copy and paste to the code from one script into another, we should make it a component that can be attached to any node two D, much like the enemy behavior scripts. Back in the gem scene, I'll add just a normal node and name it float. Then copy and paste to the code from the save Point script into this script. Instead of an area tote, this will just be the float node's parent, although it must be a no tutti or some ancestor of a no tutti in order to have a position. We can then copy this node into the Save Point scene and remove the code from the Save Point script. These three scripts are much more reusable when they are written to only serve a single purpose, as well as being easier to understand and debug. Another thing we should do is not have this upgrade item B here if the player has already unlocked this ability. Overriding the ready function, we'll check the value of abilities unlocked in the players save data to see if this ability is already unlocked and queue this node to be freed if it has. Now let's try it out. When I run the game, I can see my upgrade item floating there. Pressing the button to shoot projectiles does not work since I have not unlocked this ability yet. Jumping up to grab the upgrade item, it disappears, and I can now shoot projectiles. Closing the game then playing again, the upgrade item is not there since I have already collected it, and I can still shoot projectiles because the ability was permanently unlocked. Now we want to make collecting this item feel a lot more impactful to the player. In order to test this, we'll need to delete our save data. In the project menu, select Open user data folder to open this folder in your file manager, where we can see save dot s, and we can delete it. Now when we run the game, the player has no last saved position and no abilities unlocked. I'll just go back to the save room and hit my Save Point, then close the game. If you want to try to make the event sequence for collecting the item by yourself, you'll probably want to add particle effects or other nodes to achieve your result. Pause the video here, and then we'll go over what I did. After implementing my event sequence, here is what my results look like. First, I declared a constant color bright white. This is not just white but has a maximum intensity, meaning it will make other colors near white, too. When the player character touches the item, I immediately stop the floating up and down and freeze the player character by disabling the processing. I then started a particle emitter emitting the particles from around the item moving inward. Then tween the item to the player character's position plus halfway up to be around their body center while also tweening the colors of both the item and the character to bright white. Adding parallel to a tween function means that these will all happen at the same time rather than waiting for one to finish before starting the next one. I then unlock the ability and save the game just as before. And after the ability is unlocked, hide all of the visual elements, stop the inward particles, start an outward burst of particles, and update the character's sprite to include the new item. After tweening the player character's color back to normal white, I then turn the player character's processing back on and remove this item from the scene tree. The inward particles are reusing the same sprites from the boss' dust particles. They are emitting from a sphere with a radius of 32 pixels. The particles have a negative radial velocity to draw them toward the center point, no gravity. Alpha curving 1-0 and animated. Outward burst particles are mostly the same except it's a one shot burst with explosiveness. Emitting from a point with maximum spread and enough damping to cancel out the initial velocity. We now have collectible items in our game that will give the player new ways to explore the world. But our game would be a lot more fun to explore if it had shortcuts. I'll see you in the next video. And 67. 9-3 Shortcut: Friends. In this video, we'll add a shortcut that the player can unlock to make exploration of our game smoother and more rewarding. I'll be using the second room to the right of my start room to build my shortcut in the second room content scene. I've divided the room into two distinct halves, and I'll place my shortcut in the middle so that the player cannot get through this room if they entered from the left. Starting with a static body to denode to provide collision detection, and a Sprite Toti node to draw it. I'll use an atlas texture and populate my forest terrain as the atlas, then use the three vertical tiles to draw a wall. Adding a collision shape as a box and covering the wall with terrain collision, the player will not be able to get through. We can then add another Sprite tutti node using an atlas texture, and I've already imported a new sprite sheet for a wooden brace. I'll use the first undamaged sprite and position it on the right side of the wall. So if the player enters this room from the right side, they will be able to attack this brace, damage it, and destroy the wall to open the shortcut. The brace will need a Hurt Box area two D node with a collision shape two D node child. I'll use another box shape and have it cover the sprite. O. It would be a good idea to put this on a different collision layer so we can carefully control what can damage it. I'll use layer two for my shortcuts. Don't forget to name your physics layers in the project settings so you can remember what they're for. We'll need to have our characters male attack hit box mask this layer in order for them to be able to damage the brace. As well as any projectiles they can shoot. Back in the room contents scene, we should make this shortcut its own scene, since we'll probably have multiple of them in our game. This shortcut will be specifically made only for the forest region. So I'll save it in that folder, and I'll need to make other shortcut scenes for my other regions. Opening up this scene, we can attach the Hurt Box script to the Hurt Box node and also attach a new script to the root node for the shortcuts behavior. The main purpose of this script will be to save when this shortcut is destroyed and keep it destroyed when loading this room's contents. To do that, we'll use something commonly called a flag by exporting a string variable for the flag's name. In the room contents scene, we will need to specify a flag name for this shortcut. We can name this flag anything we want, but it must be unique for every shortcut in our game. I'll name mine Forest second Room shortcut. Switching over to the data script, we will need a new exported variable to hold our flags in a dictionary. Using the string as a key and the value will be a boolean. Back in the shortcut script, when this shortcut is destroyed, we can set the value of file dot data dot flags with the key being our flag name and set the value to true. Then save the game to make this permanent and after doing some visual stuff, remove the shortcut from the scene tree. Overriding the ready function, we can check the value of our flag to see if the shortcut has already been destroyed and remove it from the scene tree if it was. However, if the shortcut has not been destroyed yet, the dictionary will not have any value for this flag name yet. So we actually need to check if the value exists first before trying to access it. Let's connect the Hurt box's damage received signal to the shortcuts destroy function, unbinding the argument. I'll move the second room to be to the left of the save room so I can test it out. Entering this room from the right, the wall prevents my character from passing through. I can attack the brace to destroy it, which destroys the wall, and I can pass through. Closing the game and running it again, the shortcut remains open. However, there's a small problem. I'll rearrange my rooms so the second room is back on the right, then delete my safe file to restore the shortcut. Running the game again. The wall does prevent my character from proceeding. However, I can still break the shortcut open from this side. So we'll need to prevent our character from being able to attack through walls. Opening up the character scene, we can add a cast two D node to the hit box. We'll use the same strategy we used for enemy aggression to check if there is a clear line from the hit box to the Hurt Box. To do this, the ray should be originating from the character's center, and we'll need to mask everything that this hit box can hit plus anything that can obstruct it like terrain, including both bodies and areas. The ray may also originate inside of some of these colliders, so it will need to be able to hit areas and bodies from inside their shapes, too. In the Hit box script, we can grab a reference to this cast node with an on ready tag, but I'll change the node path to use get node or null. This way, not every hit box in our game needs to have a raycast attached. When a collision occurs, we'll check if the ray exists first, then tell the ray to point at the Hurt Box that was hit by the hit box and force it to update immediately. If the ray cast is hitting something and that something is anything other than the area, that means that there is something else in the way. Returning will prevent the damage from occurring. If we run the game now, attacking the shortcut from the wrong side will be blocked by the walls collider. If I move this room to the left side and enter from the right, I can attack the brace and destroy the wall since the ray fired from the character's hit box is not obstructed. If you want, you can pause here and add particle effects, shake and even require multiple attacks to break the brace before destroying the wall. Now that I've added my effects, here's what my results look like. After one attack, the wall emits dust particles, shakes, and the brace is visibly damaged. After a second attack, the brace shatters into several pieces, the wall is removed, and the particles are emitted again. To achieve this effect, I added a GPU particles to the node emitting the same dust particles used previously, but this time emitting from a box shape. I added a debris node as a folder containing three different pieces as rigid bodies. And a shake timer which will emit a signal 12 times per second. Connecting the Hurt box's damaged received signal to an damaged received function, I restarted the particle emitter, then checked if this is the first time it is being damaged. If already damaged, I turn off collision detection on the Hurt Box and destroyed the wall. Otherwise, set damage to true, update the brace, sprite to draw the damaged version, then start the shake timer and stop it 1 second later. The shake timer's timeout signal calls this shake function, which just offsets the wall by one pixel in a random direction. When the wall is destroyed, I updated the save data as before, but also scattered some debris, hide both the wall and the brace. Then wait for the particles to finish before removing the entire scene. To scatter the debris, I made it visible, then unfreeze all of the pieces and add impulse forces to them equal to their positions relative to the parent. We now have a shortcut that the player can break open, making backtracking much easier as they explore our world. But the entire map is plainly visible for them to see from the start of the game. I'll see you in the next video. 68. 9-4 Map: Hello, friends. In this video, we'll hide our map from the player and only reveal rooms as the player enters them. Starting in our save data script, we can use the same strategy as we did with the save point to track which rooms the player has entered. We'll export a new variable named map of type dictionary. The keys of this dictionary will be the names of regions, and the values will be another dictionary. The keys of the second dictionary will be the names of rooms in that region, and the values will be booleans tracking whether or not the player has entered that room. If we look in any of our room scenes, the map will be drawn by some form of Tu Di node. In my game, they are being drawn by a Tile Map layer node. Whatever the node is, toggling its visibility is fairly trivial. We'll describe a reference to this node in our room script. You can use the ready tag if the map node is uniform for all rooms across your entire project or an export tag if you want more flexibility, but you'll have to manually assign it for every room. We'll also need each room to know the name of the region it's in, which we can automate with an ready tag, getting the name of the room's parent node in the scene tree. We can then write a function to update the visibility of the map based on whether or not the player has entered this room before. Checking file dot data dot Map indexed at the name of the region gives us another dictionary for the region, which we can index at the name of the room. This will be a boolean for whether or not the player has entered this room previously. And if it is true, we will set the maps visibility to true. However, just like with the flag dictionary, we need to be careful not to try to access dictionary entries which do not exist. But this provides an extra opportunity in this case, since we can use the absence of the dictionary entry as additional information. For example, we can check if the map dictionary has an entry for this region. If it doesn't, that means the player has never entered this region before. Likewise, if the room entry doesn't exist, then the player has no knowledge that this room even exists. We'll first need to check if the region entry exists in the map dictionary. If it doesn't the player should not be able to see this map, and we can just return out of this function. Then do the same thing with the room entry in the region dictionary. If the entry does not exist, I'll hide the map. However, if it does exist, then we can set the visibility to true and check the value of the boolean. If the boolean is true, then I'll set the maps modulate Alpha to full opacity. But if it is false, I'll set it to be semi transparent. This means I can differentiate rooms on the map the player knows about from ones that they have previously entered. Alternatively, you may want to use a different node to draw these different states and toggle the visibility of each individual node. We can initialize the visibility of the map during the ready function. We can write another function to discover a room and reveal its map. To create our two different map states, we can accept a boolean parameter for whether this room is being discovered by entering it. All this function needs to do is set the value of file dot data dot Map, indexed at the region name, indexed at the room's name to the value of Entered. But if this value already exists and has been set to true, we don't want to set it back to false. We can use the logic operation or to set it to false only if it isn't already true. Then call the Update Map visibility function to start drawing it accordingly. If the region does not yet exist in the Map dictionary, then this is the first room the player has discovered in this region. This would be a good opportunity to do something interesting, like display a title card or play a shortcut scene to introduce the player to the new region. I'll call a function not yet written for the region named Discover. This will be responsible for initializing the map dictionary entry for the region. Switching over to the game manager script, when the player character enters a new room, we can discover that room with an argument of true to draw it opaque on the map. In the main game scene, we can attach a script to our game's regions. Writing the discover function, this will be called the first time a player enters any room in this region. So we can start by creating the map dictionary entry for this region in the player save data. I'll also take this opportunity to add the region to the player's map by calling a reveal Map function. This will just iterate through every room in this region and discover it, but with an argument of false, so it will be drawn semi transparent. This script will need to be attached to all regions. In each of my region scenes, I'll also add a label node to display the name of that region on the map. For this to be readable on the map camera, it will need to have a very large font size. I'll set the font size to 256. Then set the font color to match the color of the rooms. To make it stand out more, I'll also add a shadow and an outline. It will need to be on visibility layer two for the map camera to see it. I'll copy this label note into my volcano region and change its text color to match that region. Adding a canvas layer to the region scene two, we can make a simple title card to display when the player first enters each region. With another label node, I'll display the name of this region in the center of the screen with a large font size. I'll make this invisible by default and only display it when the player enters this region for the first time. In the region script, I'll animate this display with a tween, tweening the Alpha of the label node up to one, waiting 1 second, then tweening it back down to zero. It will need to have an Alpha of zero to start and be made visible. This can be copied into my other regions and updated with their names. When the region is discovered, I'll save the game and make the map label visible. This will also need to be initialized in the ready function based on whether or not the region entry exists in the Map dictionary. Now that regions have children that are not rooms, I'll need to check if the children are actually rooms before calling their discover functions. Let's try it out. When the game starts, the player enters the forest region automatically. So the title card for the forest region is displayed. It's map data is revealed, but only the start room is opaque since that is the only room I have entered. As I enter each room, the rooms change from transparent to opaque. I have removed the shortcut from the other room to make things easier. The volcano region has not been entered yet, so it is not revealed on the map. And when I enter the volcano region, it displays its title card and its rooms are added to the map. Although I haven't built anything appropriate for this region yet. Returning to the forest region, the region has already been discovered, and so it does not display its title card. We now have a system for revealing our game's map gradually as the player explores our world. But the regions of our game would feel a lot more distinct with some background music. I'll see you in the next video. 69. 9-5 Music: Hello, friends. In this video, we'll add some background music that will change with each region in our game. This will not only help emphasize the unique look and feel of each region, but also provide an extra sense of progression too. When you import background music into your game project, select the files in the File System tab, then open the Import tab and change loop mode to forward. Otherwise, your music will only play once and then stop. This will require a reimport of the assets. In each of our region scenes, we could put an audio stream player node to play these music tracks. But that would require us to manage which one is playing at any given time, which is kind of a hassle. Typically, we won't want to have more than one background music track playing at a time anyway. So it would be easier to only have one audio stream player node dedicated to playing background music that we can access from anywhere in our game. But each region still needs to know which background music should be played for it. In our region script, we can export a variable to hold the background music for this region as an audio stream. Then populate the exported variable for each region in their individual scenes with different music files. We can then create a global script to manage our background music, inheriting from Audio stream Player. Then open the project settings, the Globals tab, and add this script to the list of global nodes. Returning to the script, we really just need two public functions, one which will tell the script to play a music track and accept the audio stream as a parameter, and another to stop playing music. But stopping music suddenly would be kind of jarring. It's fairly common to fade music out by reducing its volume gradually before stopping. Starting with the play track function, we can set the stream being played to the track parameter and call the audio stream players play function to start playing it. But if something is already playing, we need to stop it first. So if this script is already playing something and what it is playing is the same as what we want to play, we can just return. If it's not the same, then we can await fading out to the volume first, and after that, we can switch to the new track and fade back in. To fade the volume gradually, we will need a tween and a function for tweening the volume, accepting the final value for the volume as a parameter. After checking if the tween already exists and killing it, we'll create a new tween and tween the volume to its final value over some duration. I'll just use 1 second. Volume is usually measured in decibels, which is not a linear unit of measurement. So tweening the volume in decibels is not recommended. Instead, we use the linear volume value, which is a simple float ranging from zero for mute to one for normal volume. We can return the tween finished signal so we can await it. To stop playing music, we just need to wait for the volume to reach zero, then call stop and return the finished signal. And after switching tracks, we should fade the volume back up to one so we can hear the new music. Now we just need to tell this node which music track to play and when. For simplicity, I'll assume that when a room's contents are loaded, that I want to play the music that is associated with that region. So calling music play Track, I'll pass the region, which is the parent of the room dot Music. If told to play the same music that is already playing, the music global node will just ignore the request. So there's no need to check what's already playing. Let's try it out. Running the game, I start in the forest region, and so the forest music theme plays. Changing rooms, the music does not change as long as I am still in the forest region. But if I transition to the volcano region, the forest region theme fades out and the volcano region theme plays instead. There will probably be times when you want to play a specific music track temporarily, such as during a cut scene or a boss fight. So let's add two more functions to our music script. One to override the current music track and another to revert back to the previous music track. We'll need a variable to hold the current region's background music. When told to override the background music with a new music track, we'll first store the region's background music in our variable. Then wait for the fade out and play our new track. After the event has completed, we can revert back to our Region's background music by fading out, then playing the old music track. In our boss encounter script, we can export a variable to hold a special music track for our boss music. Then in the Mc Mouse boss encounter, we can give this boss fight its own special music track. The music global node can be told to play this music when the boss encounter starts. And reverts back to the region's background music when the fight is over. And finally, this music node should be told which bus it belongs to on the Audio Bus so we can control its volume in other settings more easily. So overriding the ready function, we can manually set the value of bus to be music. We now have a global music system that we can use to play any music we need for our game, but our boss encounter still needs some work. I'll see you in the next video. 70. 9-6 Final Boss: Hello, friends. In this video, we'll complete the boss encounter with an arena and stop the boss from respawning once it is defeated. I have rearranged my rooms so that the player will start in the save room right next to the boss room. Opening the boss rooms contents, I've gone ahead and built up the Boss room's content scene with multiple Parallax layers. In order for the Tile Map layer two D node to be able to Parallax scroll, I had to create a new script inheriting from Tile Map layer without the auto scroll. Since the Tile Map layer two D node does not have the region wrecked property, it can't use the same method if we wanted it to auto scroll. Each of the Tile Map layer to Di nodes have a different scroll scale and modulate more toward the background color. To keep track of which bosses the player has defeated, let's open up the data script and add a new variable named Boss is defeated. An alternative to using a dictionary is an array. Anytime a boss is defeated, we can just append its name to this array. In the boss encounter script, we can override the ready function to check if this boss has already been defeated. If file dot data dot boss is defeated has an entry matching the boss's name, then the player has already defeated this boss and we can remove this boss encounter. When the boss is defeated, we can append the boss' name to the array of bosses defeated. Then save the game to make it permanent. Unlike dictionaries, arrays can have duplicates. So when you're storing information in this way, you may want to check if the entry already exists before adding it. Removing the boss encounter node from the scene tree at this time will also remove the boss itself, which I don't want to do. But if the player were to leave the room and come back in, the boss will be removed by the ready function. This will be sufficient to make the boss stay defeated, but the encounter itself could use some improvements. The first thing I want to do when the boss battle begins is shut a door locking the player inside the arena. Just like with the shortcut, I used a static body to de node, reusing the same wall sprite as the terrain. And a rectangle collision shape. However, the sprite and the collision shape are positioned far above the door's origin point, so the door is open by default. When told to close, the door repositions the collision shape, then tweens the sprites position to match, and opening the door returns the collision shape and the sprite back to their original positions. Giving this door a unique name, we can close it in the boss' encounter script when the battle starts and open it when the battle ends. Remember to call the superclass functions to also do everything else in the start and end functions, too. I also want to make the boss fight harder by dropping flaming boulders from the ceiling. So I've made another scene for the flaming boulders, which are a rigid body two D node. This will just automatically handle the gravity for me. They are being drawn and animated by an Animated Sprite two D node, looping through the imported sprites at 12 frames per second automatically. The rigid body is on a new collision layer set aside specifically for hazards and is not masking anything. So they will not collide with anything, including the terrain. They will just fall from above the arena to below the arena, and there is a hit box attached, monitoring for the player character's Hurt Box. Don't forget to connect to the area entered signal to deal damage. In my Mc mouse encounter script, I've exported a variable to hold this flaming boulder as a packed scene, which I can instantiate as a new flaming boulder rigid body. When told to spawn flaming boulders, I looped a number of times randomly between the phase number and the phase number times two so that the number of boulders will increase in the second phase. Instantiate a new boulder, I added it to the scene and positioned it above the arena at a random position between the left and right walls. To make the crash event even more impactful, I'll also add a camera shake. Godot makes it pretty easy to access the current camera from anywhere. First, we just need to call the Get Viewport function, then call the viewports Get camera two D function. We can then call our shake function to shake the camera. I'll give it an intensity of two pixels for a quarter of a second. I can then connect the Mc mouse's crashed signal to the encounter script to trigger the spawning of the flaming boulders in response to this happening. Since the boulders are rigid bodies, they will fall naturally due to gravity. Then I can clean them up after they have fallen below the bottom of the arena using an area two D. This garbage collection area is just a rectangular area that covers the bottom of the arena, far enough below that anything entering this area will not be visible to the camera. The area node is monitoring and masking the hazards layer. Its body entered signal connects back to the Mc mouse counter script to remove them from the scene tree. With the changes made to our character's hit box, requiring the cast from the hit box to the Hurt Box to be unobstructed, I had to reorganize my Mac mouse's key Hurt Box. So now the Hurt Box is determining the position and the collision shape is at its local origin position. Since the Area two D node is determining the position itself, the script no longer needs to reference the collision shape and instead flips its own position in response to the character changing directions. Let's try it out. I'll enter my boss room and find my boss enemy sitting there. I can attack the key to wind it up and start the boss encounter. This triggers the start of my boss fight, which changes the music and shuts the door on the arena, trapping me inside. As I fight the boss, anytime it crashes into a wall, it triggers a camera shake and drops flaming boulders from the ceiling. While we can't see what happens to the flaming boulders as they drop below the camera, they are being cleaned up by the garbage collection area. When the boss enters its second phase, the number of flaming boulders spawned increases, and defeating the boss ends the encounter, changing the music back to the volcano region music and opening the door. Leaving the room and coming back, the boss encounter is no longer there. We now have everything we need to build an entire Metroidvania game. In the next video, we'll go over our next steps. I'll see you in the next video. 71. 9-7 Final: Hello, friends. Congratulations on completing the course. You now have all the tools and skills necessary to build a basic Metroidvania game, complete with unlockable abilities that will gradually give the player access to more of the world to explore a map that shows the player's current location and save points to keep track of their progress. All that's left to do now is repeat many of the lessons taught throughout the course to create more of it. More rooms, filling out more regions with more enemies to fight and more shortcuts to unlock. Follow these steps to get more out of this course and continue learning more skills you can use to build your own Metroidvania game. Step one. If this is your first major game project, you should be able to build a game following this template with at least three different regions, each with multiple rooms containing different enemy types, platforming challenges, and these regions should feel distinct from each other. Following Metroidvania game design principles, your game should demonstrate to the player obstacles that they're unable to overcome before unlocking the ability to do so. The critical path through the game should require backtracking through previous areas and give the player multiple new opportunities to explore with their new abilities. Try to make a game that takes at least 10 minutes to complete from start to finish without being too repetitive. Step two, after you're done with your game and want more of a challenge, you may want to think of something that you want to change. Maybe you don't like the way the wall jumping works or you want to add recoil force to the player character's attack. Try to come up with a way of adjusting the mechanic to function the way you would prefer and change the way it is implemented in the game. Try to avoid making something entirely new yet. Just focus on making adjustments to what's already there. Step three. Once you're able to make changes to the mechanics that already exist, your next step should be to add a new mechanic that uses the tools already provided. Some things you may want to add to the game might be a collectible item that increases the player's maximum health or a new spell that heals the player character. Adding these mechanics to the game requires no new skills, but requires that you learn how to integrate multiple existing systems together in a new way. You may also want to look for some new assets to use for your new mechanics or try creating your own. Step four. Now that you can make existing mechanics work the way you want and integrate them together with new systems, it's time to design and build a new mechanic from scratch. This could be an entirely new Metroidvania ability that allows the player to access more of the world, adding NPCs for the player to talk to with a dialogue system, making enemies drop money for the player to collect and allowing them to spend that money in a shop to upgrade their weapon. Before you start, make sure you take some time to think about all of the existing systems that will need to communicate with the new mechanic. Consider which parts of your design should be abstracted or what can be inherited from existing scripts. Step five. If you can create an entirely new system and successfully integrate it into this game, then you should be ready to make a new game from scratch. Either find a different asset pack online or create your own assets to use as you build a completely new Metroidvania game from the ground up entirely of your own design. You're welcome to share your work on our discord server as you continue to learn and grow your skills with this project. Step six. Once your game is ready, you can share it on platforms like Ich to get player feedback and even sell the game when it's complete. You probably won't make very much money selling games on IH, but the feedback will be invaluable for improving your game development skills. If you have a game that has performed well on Ich, then you could try selling it on steam to reach a much larger audience. But the bar for quality for a game to be successful on steam is much higher. And you'll need to have a market strategy if you want your game to be noticed by players. Good luck on completing your project. I look forward to seeing your work.