Godot Shmup Tutorial
Godot Engine

Creating a new project

Godot is a lightweight, free and open-source game engine, that requires no installation. The engine works on Windows, Mac, or Linux, and can be downloaded directly from the Godot web site.

Creating the project directory

In Godot, projects are represented as a folder of files, where all files within the folder and its subfolders are considered part of the project. Therefore, we need to establish which folder our project will occupy.

Within the desired directory, such as the Desktop, Create an empty folder named Godot Shmup

We will proceed to put all the necessary project files in this folder.

The project folder must be empty in order for the Godot Project Manager to establish a new project there.

Creating the project files with the Project Manager

When opening Godot, we are introduced with the Godot Project Manager. Let's do this now.

Locate the Godot Engine file downloaded from the Godot web site, Run the Godot application

The Godot Project Manager should open. This is where we see a list of all the Godot projects that have been opened by Godot on this computer. After we create our new project, it should show up in this list for us to be able to quickly open in the future.

This is also where we can create a new project.

To start the new project process, Click New Project

The Create New Project dialog window appears.

The first field, Project Name allows us to have a name for the project, as we will see it in the Godot Project Manager project list.

To give the project a name, within the Project Name field, Type Godot Shmup

We now have to point Godot to the new folder we created for this new project. The easiest way to do this is to first copy the file path to the Godot Shmup folder we created.

Within the Godot Shmup directory, inside the address bar of the Copy the Godot Shmup file path

We will now supply this copied path to the Godot Project Manager.

In the Project Path field of the Create New Project window, Paste the copied file path.

We are now ready to create the project.

In the Create New Project window, Click Create & Edit

The Godot editor should now open with our new project.

Switching to the 2D view

Some engines only support 2D games, while others are primarily for 3D game development, but later came to support 2D games, despite its tools being mostly configured for 3D. Godot Engine natively supports 2D, as well as 3D games from the ground up. 2D and 3D mode are found in two entirely separate sandboxes.

Our new project by default opens to the 3D view. We are going to be making a 2D game, so let's switch to the 2D view.

At the top center of the editor, Click 2D

We are now in the 2D view. The project should now open directly to the 2D view once we re-open our project in the future.

Importing graphics into the new project

Now that we have the new project, let's bring in the graphical assets we need to make our game. To do this, we simply have to copy the graphics into the project folder.

If you look inside the Godot Shmup folder we created, you'll see that it now has a few files that the Godot Project Manager placed in there. The most noteworthy file is the project.godot file. This file should always remain in the root directory of the project, and serves to remember all the settings of our project.

Let's now bring the graphics folder for our shmup game into the project folder.

Locate the graphics folder containing our shmup graphics, Copy the graphics folder

This copied graphics should now be placed into the project folder.

Open the Godot Shmup project folder, Paste in the copied graphics directory

The graphics are now ready to be used in the project.

Return to the Godot editor.

Upon returning to the Godot editor, you may see a quick importing progress bar that appears. Once this finishes, Godot has successfully automatically loaded the graphics into the project.

You can see all the project files, including the new graphics folder, in the FileSystem panel on the left of the Godot editor interface.

Setting the viewport size

The rectangle in the middle of the Godot editor scene editing panel describes the bounds of the viewport, which is also called the window of our game.

The default dimensions are 1024 pixels wide by 600 pixels tall. We can change this in the project settings.

In the Project menu, Click Project Settings

The Project Settings dialog appears. As the name suggests, this is where project settings can be modified, including the viewport dimensions.

To find the viewport settings, scroll to the Display category on the left, Click Window

We can modify the viewport size here to be 480 by 800.

In the Width field, Type 480.

Next, the height field.

In the Height field, Type 800

We're done with the project settings for now.

To close the Project Settings window, Click Close

We should now see the viewport rectangle should now reflect the new size in the editing panel.

It might take a few seconds for the editing window to refresh the viewport rectangle to reflect the new size.

Introducing nodes into the scene

In Godot, all objects in the scene are made up of what are called nodes. There are many types of nodes, ranging from graphical, physics, text, or any other variety that is needed for the project.

Creating the root node

Every scene in Godot must have exactly one root node. We have been using the term scene, a lot without truly understanding what a scene is. A scene in Godot is composed of nodes organized in a tree structure. Each node in the tree can have any number of child nodes descending from it, and each node has exactly one parent node.

Our main scene requires a root node, so let's go ahead and create one.

To create a new node, in the Scene panel on the right, Click new node button

The Create New Node dialog appears, prompting us to choose which type of node we want to create.

Though the root node can technically be of any node type, the Node2D type serves as a good generic type of node to organize all of our nodes in our 2D game.

To select Node2D from within the list, Click Node2D, and click Create

The new node appears in the Scene dock, and is given the name Node2D by default. Let's rename the node so that it better describes this node's purpose.

Before creating our root Node2D node, the Scene dock offered a few buttons, one of which labeled 2D Scene. Clicking this also would have created a Node2D root node, as a shortcut for this common action when a new scene is created.

To rename the newly created Node2D node, in the Scene panel, Double-click Node2D, type Main

Saving the main scene

Now that our scene has a root node, we can save the scene and not lose our progress.

In order to save the scene, Press Ctrl+s

Let's keep the suggested name, Main.tscn. The file extension for scenes in Godot are tscn.

To finalize saving the file as Main.tscn, Click Save

The file should now appear in the root directory of our project folder, which you can see in the FileSystem panel.

Creating the background sprite graphic

Our background is really plain right now. Let's add a background graphic with a sprite node.

With the Main node selected, Create a new node of type Sprite

By default, Godot assigns the node the name Sprite. Let's give it a better name.

Rename the new Sprite node to BackgroundSprite

A Sprite node doesn't have any appearance until its Texture property is set.

In the Texture field of the Inspector panel, Click <null>, click Load

The graphic we wanted is within the graphics folder we copied into the project folder earlier.

To select the desired background graphic, within the graphics folder, Double-click bgGradient.png

We now see the graphic in the editing pane.

Modifying position and scale

Our background graphic, by design, is 1 pixel wide and 800 pixels tall. We want this to cover the entire viewport, so let's resize and reposition the sprite node.

Position, Rotation, and Scale properties can be found under the Transform group of the selected node's Inspector panel properties.

To modify the horizontal scale of the Sprite, within the Inspector panel, under the Transform drop-down, Click in the Scale field, and type 480 for x

The graphic is still off-center, so let's fix that now. Sprite nodes are referenced by their center point by default, so we need to offset the node by half the viewport height and width.

To position the background to fit the viewport, Click the Position field, and type 240 for x and 400 for y

Each type of node has its own set of properties relevant to it, as we can see separated into categories in the Inspector panel. For example, the Texture property can be found on Sprite nodes, but not Node2D nodes.

Locking the background node

As it stands, it is too easy to accidentally select and move the background sprite. Since we don't need to modify it anymore, we can lock it in editor so as to prevent any accidental modifications.

To modify the background sprite, with BackgroundSprite selected, in the editing pane toolbar, Click lock node button

We can no longer select the background sprite from the editing pane.

Testing the game

We want to frequently test our game to make sure that our game still functions after the changes we make. Let's start testing our game now.

To test the game, at the top right corner of the interface, Click test game button

Before we can test the game for the first time, we must first tell Godot which scene in our project should be the "main scene", i.e. the scene that loads first when the game is launched. Let's first clear this Please Confirm... dialog box.

To clear the Please Confirm... dialog box, Click Select To assign our Main.tscn file as our project's main scene, with Main.tscn selected, Click Open

The main scene has been selected, and the testing game window appears. We no longer have to select the main scene each time we play our game to test it.

The game works, but is not all too exciting. Let's close the game for now.

Close the testing game window that just appeared.

Let's proceed to make our game more interesting.

Setting up the player nodes

The player node will eventually have several nodes associated with it. To make managing these nodes a bit easier, we will contain all the nodes within a Node2D parent. We can think of the Node2D as somewhat of a keyring, where the keys of this keyring are the child nodes (e.g. sprites, etc.). The keyring just keeps the keys together, so that when the keyring moves, all the keys move with it.

Creating the player node

With the Main node selected, Create a Node2D node

Make sure the Player node is a child of the Main node, and not the background sprite. If the Player node is a child of background sprite, positioning the player will not work as expected.

To rename the new Node2D to better describe that it is for the player object, Rename the new Node2D to Player

Adding a player sprite graphic

With the Player node selected, Create a Sprite node child of Player

We need to give it a Texture now in order to see it.

With the new child Sprite of Player selected, Load into the Texture property the player.png file.

You should now see the player graphic.

Making child nodes not selectable

Nodes are positioned with respect to their parent node. For example, if a child node is positioned to 0 pixels on the x axis, and 0 pixels on the y axis, then that means the child node will be positioned exactly to the position of its parent.

Using the Node2D "keyring" analogy from before, we want to make sure that the keys (i.e. the Sprite in this case) to stay together with the keyring (i.e. Node2D Player node). We can do this by applying a setting to the Player node which locks its children to not be selectable on accident.

To make the Player node's children not selectable, with the Player node selected, Click button to make child nodes not selectable

You should now see the same button icon appear next to the Player node in the Scene panel.

Modifying position by dragging

Let's move the Player node to the bottom middle of the viewport.

With the Player node selected, Click and drag the Player node to the bottom middle of the viewport.

The Player node should be at a position of around 240, 700 now.

Introducing basic interactivity with scripts

Scripts are what make our games work. Scripts dynamically control the nodes in which they are embedded, as well as other nodes in the scene tree. Without scripts, nodes just sit lifeless, like they are now.

Creating a new godot script

With the Player node selected, Click button to create new script

The Attach Node Script dialog box appears. Here you can choose in which programming language to write the script, what to name the script, and where to put it. We're just going to go with the defaults.

To finish creating the new script for the Player node, Click Create

The script is created, as can be seen with the filename Player.gd in the FileSystem panel. The view has also shifted to the Script view.

You can shift between 2D view and Script view by clicking the tabs at the top middle of the interface.

Understanding functions and comments

Almost all gdscript code is contained within functions. Functions can be custom defined by the programmer, but several are built into the engine. When a script is created, both the _ready function and _process function are pre-written. These are the two most common functions used in scripting.

The code in the _ready function runs the moment the node begins to exist in the game. The _process function runs every moement of the game.

Code that is indented underneath the function definition line is considered part of the code block, which is the code that runs when that function runs.

Any line of code that is preceded by # is a comment, which means that the game program will ignore it when interpreting the code. Comments are purely for humans to read.

This means that the _process function, as we can see in the default code given to us, is what is called commented out, which means that the code will not run, unless the # signs are removed.

In order to enable the _process function, Remove the # characters which start each line

The _process function now looks something like this:

# Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): pass

Basic debugging with the print function

In order to use a function, the syntax is to type the name of the function, followed by a pair of parentheses, where the value or comma separated values between the parentheses are what are passed into the function for the function to perform some kind operation associated with that function's purpose. The values put between the parentheses are called arguments. Using a function in this way is called calling the function.

The first built-in function we will be using is the print function. The print function takes the arguments in the function call and "prints" them to the output panel. This is useful to test to make sure code is working properly at that particular part of the program.

Let's make sure that the _process function is working properly.

Above the pass line within the _process function, Type Player.gdprint("happens every frame") Test the game.

You should see "happens every frame" being printed every frame in the Output log at the bottom of the interface.

To prevent the output log from continuously printing, Comment out the print line

Setting the player position to the mouse

Properties of nodes can be accessed with the dot operator, which is represented as a period. The syntax is that the object name is typed, followed by the dot operator, followed by the property name. You can either get the property value or set the value with this technique.

To refer to the object on which the script is attached, we use the self keyword. For example, to get the node's position, we can type self.position.

To get the mouse position we can call the built-in function get_global_mouse_position().

Above the pass line in the _process function, Type Player.gdself.position = get_global_mouse_position()

Some functions take no arguments in order to work, as we can see with the get_global_mouse_position function. Even though the get_global_mouse_position function takes no arguments, we still need to type an empty set of parentheses in order to tell the engine to call the function.

Test the game

We see the Player node following the mouse position. We want it to only follow the mouse's x coordinate.

To have the Player node follow just the mouse x position, Add the code in bold Player.gdself.position.x = get_global_mouse_position().x Test the game.

The Player node now follows the mouse x position.

Introducing projectiles with separate scenes

What's a shmup without the ability to "shoot" projectiles? Unlike with the player node, we will potentially have many projectiles on the screen at once. We will handle that in this section.

Creating the projectile nodes

Let's create our basic projectile the same way we created the Player nodes.

With the Main node selected, Create a Node2D node Rename the Node2D just created to Projectile With the new Projectile node selected, Create a Sprite node To set the Texture property of the new Sprite, Load in projectile.png as the Texture property Make the children of the Projectile node not selectable Move the projectile just above the Player node

Setting up the projectile script

Create a new script with default settings on the Projectile node Uncomment the _process function

Making the projectile move

The Player node moves by setting its x position to the mouse cursor's x position. Projectiles won't move like that. Instead, projectiles will move on their own, regardless of player input. Specifically, projectiles should travel a few pixels upward on the y axis every frame.

We can handle that in the _process function.

Type the code shown in bold: Projectile.gdfunc _process(delta): self.position.y = self.position.y - 5

The projectile travels upward by itself.

It may seem like a counter-intuitive circular reference to use self.position.y on both sides of an equal sign, almost like using the word "the" in the dictionary to define the word "the." Rest assured that code like this works. The = operator simply sets whatever is on the left-hand side of it to the value of whatever is on the right-hand side of it. If each side is valid, then the operation will work fine.

Test the game

Adding to or subtracting from an existing value is so common that we have special operators to make these kinds of operations faster to type.

In our case, we will be using the -= operator, which reduces the left-hand side of it by the amount of what is on the right-hand side of it.

Modify the code as is shown in bold: Projectile.gdfunc _process(delta): self.position.y -= 5 Test the game

The projectile still travels up the screen as it had before using the -= operator.

Making the projectile its own scene

We learned earlier that a scene is composed of a tree of nodes. Let's understand trees in a more fundamental way. Each node and its children nodes can be represented in a tree structure, regardless of where that node is in the larger tree. Indeed, if you consider real-life botanical trees, each branch of a tree itself looks just like a small version of the bigger tree.

In Godot, we can save any such sub-tree as its own scene, since again, a scene is just a container which holds a tree of nodes. When we create a new scene to encapsulate one of the branches of our Main scene, we can then make however many copies of that new scene inside of our Main scene. Each such copy is called an instance. Each instance of the same scene file shares the same default characteristics saved within the scene file.

To further grasp these concepts, let's dive into creating our own new scene for projectiles.

To access the Projectile node's context menu options, Right-click the Projectile node To convert our Projectile node into its own scene file, Click Save Branch as Scene

We are now prompted to assign a file name for our new tscn file.

To accept the default name and file location suggestion, Click Save

We should now notice a new icon next to our Projectile node in the Scene panel of the Main scene. This icon indicates that this Projectile node is an instance of the Projectile.tscn scene. Let's open the new scene file in the editor.

To open the newly created Projectile.tscn file, on the Projectile node, Click button to open scene from which instance is created

Another way to open the Projectile scene file is by opening the Projectile.tscn file in the FileSystem panel.

We now have our Projectile scene open in another tab, alongside the Main scene. We are starting to see a deeper sense of Godot's nature of being an engine of scenes and nodes.

Creating scene instances within the editor

To switch back to editing the Main scene, at the top of the editing pane, Click the tab labeled Main

We see the single instance of the Projectile instance in the Main scene. Let's create some more copies of the Projectile scene.

To duplicate a the Projectile node, with it selected, Press CTRL + d

The new projectile instance appears directly over the existing projectile. We can see that the duplicate node shows up in the Scene panel.

The new instance is automatically named with a number tacked onto the end, because a node cannot have multiple child nodes with the same name.

Create a few more duplicates, and place them at different places of the scene. Test the game.

All instances of the Projectile node travel upward off the screen. All instances of the Projectile scene share the same default node structure and scripts.

Anything we modify in the Projectile scene file is automatically adopted by all the instances of the Projectile scene. This is particularly useful for level design, so that the designer can compose a dynamic level full of interactive objects (e.g. doors, treasure chests, etc.), and feel confident that they will all behave consistently, and update to reflect whatever new behavior comes with any change of each dynamic object's original scene file. Projectiles though are typically not placed by the level designer, but rather spawned from the code at runtime.

Installing custom assets using AssetLib

Before we work on spawning projectiles from the code, we're going to take brief break to learn about Godot's repository of user-contributed assets called the AssetLib.

Godot has all the base functionality we need to make a great game. However, a number of developers have done some additional leg-work for us, and have put that work for free online for us to download. The author of this tutorial has created a collection of functions to make certain common operations in Godot a bit easier. Packages of code like this are commonly refered to as libraries.

Downloading the GoGodot library

We can access the AssetLib from the Godot website or even within the Godot editor directly. Let's go with the latter option.

To open the AssetLib, at the top of the Godot interface, Click AssetLib

We see a listing of pages of user-contributed assets. We are looking for an asset package called GoGodot.

To find our desired asset package, in the Search field, Type gogodot, and click Search

We should see the GoGodot library in the list. The one we want is contributed by "oranjoose".

To select the GoGodot library from the list, Click GoGodot

A pop-up with a description of the asset appears.

To continue to download the GoGodot library, within the newly opened dialog box, Click Install

The files have been downloaded to a temporary directory, but the asset package hasn't been downloaded into our shmup project yet.

To continue the downloading process, at the bottom of the AssetLib pane, Click Install

A new window labeled Package Installer appears, showing exactly which files are going to be injected into the project folder and where. In this case, the primary files are go.gd and setup_gogodot.tscn, waiting to be put in the root directory of the project folder.

To finalize the file transfer, within the new Package Installer window, Click Install

The new files now show up in the FileSystem panel.

Setting up GoGodot

For many assets in the AssetLib, they are ready to go just by putting the files in the project folder. For GoGodot, there is one thing we have to do to make the library work.

To enable the GoGodot library, within the FileSystem panel, Double-click setup_gogodot.tscn

Just by opening this scene, it has injected the go.gd script into the project settings.

As of 1/14/2019, a Godot update broke the setup_gogodot.tscn file functionality. A fix has been uploaded but the AssetLib has not yet processed it. A temporary workaround is to go to Project Settings->Autoload tab->open the go.gd file->click Add. This should set up GoGodot.

Spawning projectiles with player input

We don't want our projectiles to appear until the player has hit a button. In this section, we will handle spawning instances of scenes in code using custom input actions.

Creating custom input actions in the input map

We can create custom actions in the Input Map, which is in the Project Settings.

To access the Input Map, we must first, Open the Project Settings At the top of the Project Settings window, Click the tab labeled Input Map

Let's make a custom action for firing a projectile.

In the Action field at the top of the Input Map, Type fire_projectile, and click Add

We have our custom input action now, but no actual physical input has been associated with it.

To reveal which type of human input device to use to associate with this custom input, to the right of the new fire_projectile action, Click new action controller drop-down button In the drop-down list that just appeared, Click Mouse Button To go with the default left mouse-click button, Click Add To close the Project Settings window, Click Close

Checking action input with if structures

Much of programming is asking questions. Does the player have a particular power-up? Is the player's health below a certain threshold? And so on. We control these kinds of questions with logical structures typically called if structures.

An example of if structure can be seen here: if 2 < 5: print("We can math!")

Note the colon at the end of the if line. Following that line, there's an indented block. Similar to function definitions, the code indented underneath it is associated with that structure. The code block of the if structure will only run if the condition evaluates to true.

Actually, the condition just needs to evaluate to what some programmers call "truthy", since the code block will run in a variety of other circumstances, such as a non-zero number, and a variety of other techniques we won't be discussing in these materials.

We are going to use an if structure to check each moment of the game if the fire_projectile action has occured. We can do this by using the is_action_just_pressed function, as part of the Input object.

Within the Player script, type the code shown in bold within the Player.gd_process function: Player.gdself.position.x = get_global_mouse_position().x if Input.is_action_just_pressed("fire_projectile"): print("projectile fired!")

Spawning scene instances within the code

We are going to use a function within the GoGodot library to spawn instances of the Projectile.tscn scene.

To spawn a projectile instance at the player position, modify the code as shown in bold: Player.gdif Input.is_action_just_pressed("fire_projectile"): go.spawn_instance("Projectile", self.position.x, self.position.y)

In order to spawn a projectile at the player position without the GoGodot library, you could type something like this: var newProjectile = load("res://Projectile.tscn").instance() newProjectile.position = self.position self.get_parent().add_child(newProjectile) The GoGodot library clearly simplifies this operation.

Test the game.

We should now be able to spawn projectiles by left-clicking the mouse.

Spawning incomers with a Timer node

Creating the incomer nodes

The "incomers" coming into the screen from the top of the viewport are going to functionally be like projectiles going the opposite direction, and spawned automatically, rather than by player input. As such, we are going to duplicate a lot of the code from before.

Practice on your own

Create the Incomer node as a Node2D, with a child Sprite node whose Texture property uses the incomer.png file.

Make sure to keep child nodes of Incomer from being selectable, as we did with Projectile.

Creating the incomer scene and script

Now that we have our Incomer node, let's go ahead and make it its own Incomer scene, with a script that makes it travel down a couple pixels per frame.

Practice on your own

Attach a new script to the Incomer node, keeping the default settings. Make it so that the Incomer travels down the screen 2 pixels each frame. Save the Incomer node as a scene with the name Incomer.tscn. Test the game.

If all went well, the Incomer should appear to travel down the screen on its own.

Creating the Timer node

Incomers, unlike projectiles, are to be spawned automatically on a 1 second interval. We can do this with a Timer node.

To create a Timer node child of Main, with the Main node selected, Create a Timer node Rename the new Timer node as IncomerSpawnTimer

We made this a child of Main because this Timer affects the incomer spawning across the game. If we had made it a child of Incomer, then each time an Incomer spawned, it would start a new timer to spawn more Incomers, exponentially spawning Incomers out of control. If the timer was a child of Player, then the Incomers would stop spawning after the Player was removed, or it would double the Incomers if there was a second player.

To have the spawn timer start immediately, with IncomerSpawnTimer selected, Set the Autostart field to On

We're going to keep the other properties as is, since we want the Timer to trigger on a one second interval and to loop indefinitely.

Using the timeout signal on the timer

We have been creating custom functionality with scripts. However, each type of node innately checks for certain events to occur, specific to that node type. These events are referred to as signals in Godot. We can think of them kind of like how each animal or plant may have a different kind of reflex or stimulus response they innately understand, like how a venus fly trap plant knows to close its "mouth" when an insect goes on the leaf.

To see the signals available to the IncomerSpawnTimer, with IncomerSpawnTimer selected, Click the tab labeled Node

We see under the Timer category a signal called "timeout". This signal is triggered or emitted, as it is called in Godot, every time the time indicated by the Wait Time property has elapsed.

When a signal is emitted, nothing happens automatically. If we want something to occur when a signal is emitted, we have to connect the signal to a function within a script. This function is called a callback function.

We can connect the signal to any script within this scene tree, but we should try to choose a script that makes sense. Up to this point, we have subscribed to the mentality that each node handles itself with its own script. We can continue that logic by creating a script for the spawn timer.

To have a script for our timer's callback function, Attach a new script to the IncomerSpawnTimer, choosing default settings

Let's now connect the signal to a function in our new script.

To start the process of connecting the timeout signal to a function, Double-click timeout()

A dialog window appears asking which node possesses the script where we want to call the function when the signal is emitted, that is, when the timer's time runs out.

To select the IncomerSpawnTimer as the node which possesses the script, Click IncomerSpawnTimer To confirm the _on_IncomerSpawnTimer_timeout as the name of the function that will be created, Click Connect

We should now see a new function defined at the bottom of the spawn timer script. Code inside that function's code block is executed when the timeout signal is emitted.

Practice on your own

Make an Incomer instance spawn at a position of 200, -50.

We should see a steady stream of Incomer instances spawning from the top of the screen in single-file.

In order to give the Incomer a random x position, we will be using the rand_range function, which takes two arguments, the first indicating the minimum value, and the second indicating the maximum value within the range of random values we can get.

To make it so the new instance spawns at an x position between 0 and 480, type the following code shown in bold: IncomerSpawnTimer.gdfunc _on_IncomerSpawnTimer_timeout(): go.spawn_instance("Incomer", rand_range(0, 480), -50)

Handling collisions

In games, it is common that some gameplay is centered around objects touching other objects, whether it is Mario touching goombas, or the hero triggering a cutscene. In our case, we want something to happen when projectiles touch incomers.

Enabling collision with the Area2D node

Currently, our Player, Incomer, and Projectile nodes share a similar structure, that is, a Node2D container and a Sprite child to give the node an appearance. However, neither of those node types support handling collision by default. We're going to add another "key" to the "keychain", to use the analogy we have been using throughout the tutorial. We will add collision handling with Area2D nodes.

Switch to the Projectile.tscn tab. Select the Projectile root node, Create a child node of type Area2D

The Area2D controls the collision behavior, but it needs some kind of shape in order to detect the collision.

With Area2D selected, Create a child node of the type CollisionShape2D

CollisionShape2D nodes allow an active region by which to check for overlapping areas, but we need to specify what kind of shape it is.

With the new CollisionShape2D selected, Choose New CircleShape2D for the Shape property

Now we can specify the exact dimensions of the circular collision shape.

With CollisionShape2D selected, Zoom in on the graphic in the editing pane To resize the circular shape, Click and drag on the control handle on the edge of the circle

We're all set for the Projectile's collision setup. Let's do the same for the Incomer.

Practice on your own

We are going to set up collisions for Incomer the same we did for Projectile.

Give Incomer an Area2D child, and CollionShape2D child of Area2D. Assign a RectangleShape2D for the CollisionShape2D's Shape property. Resize the rectangle to fit squarely with the Incomer Sprite dimensions.

Handling collision with the area_entered function

We have all the ingredients now for collisions to be detected between Incomers and Projectiles, but we now have to write some code for what happens when an collision occurs.

Using the GoGodot library, we can create a callback function for any signal that a node possesses by simply creating a function with the same name as the signal within a script attached to the node, or to the node's nearest ancestor node. By doing this, we usually won't have to manually connect signals like we did with the Timer node.

We can handle collisions in the code by using the Area2D's area_entered signal. We can make this work by defining an area_entered function within the Area2D's script.

To set up the area_entered function, with Incomer.gd open, at the bottom of the script, type: Incomer.gdfunc area_entered(otherArea): print("collision occurred")

This area_entered function is executed when the Incomer area and another area overlap. The parameter in the function definition, otherArea is a reference to the area that collided with the incomer.

To handle collisions in the code without the GoGodot library, use the area_entered signal on the Area2D, and attach it to a script, much like how we did with the timeout signal on the spawn timer.

While testing the game, Make a projectile collide with an incomer

We should see "collision occurred" appear in the output log. We will next make something more interesting happen when a collision occurs.

Removing nodes

Let's make it so that when a collision occurs, both the collided projectile and collided incomer are removed from the game. We can do this with go.destroy function from the GoGodot library.

Let's start with removing the collided incomer. Since the area_entered function is within the incomer itself, we can refer to it by using the self keyword.

To remove the collided incomer, modify the area_entered code as shown in bold: Incomer.gdfunc area_entered(otherArea): go.destroy(self) Test the game.

Incomer nodes now disappear when they collide with projectiles. However, projectiles keep on going. This might be intentional for your design, but let's say you want to balance your game such that projectiles are also removed when they collide with an incomer.

If your incomers appear to have started spawning more erratically, it's possible that your spawn interval is so short that two incomers are overlapping each other when they spawn. When two incomers overlap they destroy themselves, because of our new code. We will be learning how to tackle this kind of problem later.

Practice on your own

Repeat the steps above in order to make it so that projectiles are removed when they collide with an incomer.

Test the game.

The projectile and incomer should both be removed when they collide with each other.

Managing score with variables

Sure, we can destroy incomers, but it would be nice to offer some additional reward to the player for doing so. Let's learn how to make a score system.

Creating an autoload script

So far, all of our scripts have been attached to nodes in our scene. Sometimes, we want to have a script that can be accessed from anywhere, anytime. These are called autoload scripts.

First, we need to create a new script file, and then we can make it global.

To create a new blank script, in the top-left corner of the script editor, Click File, and click New

We see the familiar new script window, but this time we want to manually type in a file name for it. The file name can be anything, but we will go with the name global.gd

In the Path field, Type global.gd

We now need to tell the game engine that this new script we created is one we want to be able to access from all parts of our code. We do this from Project Settings.

Open the Project Settings window. Within the Project Settings window, Click the tab labeled AutoLoad To add a script which is loaded as global, to the right of the Path field, Click folder icon button browse for file In the new open dialog, select global.gd, Click Open To finalize the operation, and confirm that global will be the name of the object to use in the code, Click Add Close Project Settings.

Our global.gd script should now be accessible from anywhere by using the object called global

You may have noticed there already was an AutoLoad script in the list. This was the GoGodot library that was installed when we opened the setup_gogodot.tscn file. This is how we are able to run GoGodot functions anywhere in our project.

Creating a global variable using the autoload script

In order for us to keep track of score, we need to tell the game to remember a number. Computer applications, games in our case, use variables to remember numbers. In gdscript, and several other languages, we can create a variable with the var keyword.

But before we do that, there's a lot of clutter in the new global.gd file. All we want to do with it is define a variable or two. Let's remove all unnecessary code.

Delete all code in the new file except for the line which says extends Node To create a new global variable, type the code as shown in bold: extends Node var score = 0

We can now refer to this variable anywhere else in the code by typing global.score. Let's try it out in our collision function.

Switch to the Incomer.gd script.

Let's make it so that the global score increases each time an incomer collides with a projectile.

To increase the global score upon Incomer-Projectile collision, add the code shown in bold: func area_entered(otherArea): go.destroy(self) global.score += 10 print(global.score) Test the game.

We see in the output log that the score is increasing by 10 for each collision.

If you typed the above code in your Projectile.gd file instead of the incomer script, then this would have worked too. However, in the future, Incomers might be destroyed by other objects, and sometimes might take multiple "hits" from projectiles, so writing this code in Incomer still might be the better option.

Displaying text with a Label node

Only the developer sees what is printed to the output log. If we want our players to know what their current score is, we will have to create nodes for this.

Creating and modifying the score label

Godot has many nodes to build a rich user interface and menu system. For displaying score, we will just use the basic node to display text, which is the Label node.

Switch to editing Main.tscn. With the Main node selected, Create a child node of type Label Rename the new Label node as ScoreLabel.

This new Label node appears in the top-left corner of our viewport, but it currently has no text.

To supply placeholder text for the new Label, with ScoreLabel selected, Type Score: 0 for the Text property,

Next, let's make it a bit bigger.

To increase the size of the text, in the Rect group of ScoreLabel's properties, Type 2 for Scale in both x and y fields

The text looks the way we want it to, but it doesn't update to actually display the score at runtime.

Updating child label text with Main node script

Up until now, we held true to the notion that "nodes can handle themselves," meaning that if a node can control its own behavior with its own script. For example, the Player, Incomer, and Projectile nodes control their own movement with their own respective scripts. It then stands to reason that we would have the ScoreLabel control its own text with its own script. This is an entirely valid approach, but we will do things slightly differently to illustrate how a node can refer to a different node from within the code.

A script can refer to any other node in the scene tree by using the $ symbol, followed by the relative path of the desired node. The relative path refers to where the node is in the scene tree with respect to the script using $. You can think of it like explaining to someone how you are related to someone in your family tree. However, since ScoreLabel is simply a child of Main, all we have to do is type "ScoreLabel" after $. Let's modify ScoreLabel from within a script attached to Main.

Attach a new script with default settings to the Main node.

To ensure that the player always sees the most up-to-date score, we can have the label update every frame. We can refer to ScoreLabel from Main.gd by typing $"ScoreLabel"

To have ScoreLabel update every frame, within Main.gd, type the following code: func _process(delta): $"ScoreLabel".text = "Score: 1000"

This makes the label display "Score: 1000" each frame, but that doesn't solve our problem.

Typing $ScoreLabel without the quotes would work too in this case. However if you omit the quotes, it won't work in every situation, so we are sticking with using quotes.

Combining strings and numbers

We want to tack on the global score. We can achieve this with a concept called string concatenation, which is the process of joining two strings. This is done with the + operator. An example of such an operation is "happy " + "birthday", which would produce the single string, "happy birthday".

However, we cannot simply type "Score: " + global.score, because the lefthand side of the operator is a string, and the righthand side is a number. Doing this would produce and error called a type mismatch. We need to first cast the score as a string properly concatenate the two values. The str function allows us to cast values as strings.

To update ScoreLabel's text to combine "Score: " and the global score, modify the code as shown in bold: func _process(delta): $"ScoreLabel".text = "Score: " + str(global.score) Test the game.

The score now updates properly to display the player's current score. You may delete the print statement from the Incomer script since the score now displays in the game window.

Creating a game over system

The player can gain score, but there's no end point and no risk. Naturally, we'll make it so the player has to avoid the incomers.

Setting up the game over sprite

We'll have a graphic appear when the player collides with an incomer. We can start by putting a game over sprite in the screen.

With the Main node selected, Create a child node of type Sprite Rename the newly created Sprite to GameOverSprite To assign the game over texture to the newly created sprite, Load gameOver.png as the value for GameOverSprite's Texture property Position GameOverSprite in the center of the viewport.

The game over graphic is now prepared for what the screen should look like when the player collides with an incomer, but we don't want it to appear when the game starts. We can make it invisible.

With GameOverSprite selected, within the Visibility category of the Inspector, Set the Visible property to false (uncheck the box)

You may also change the visibility of the node in the editor by clicking the eye icon to the right of the node's name in the Scene dock.

GameOverSprite is no longer be visible, but don't worry! We can set it back to visible at runtime using the code.

Creating the game over state in autoload script

Many aspects of games are controlled through a series of states. States can be used to describe behavior of the game at an overarching scope, such as the game state of being paused, or if it is the player's turn in a turn-based game. States can also be used to control behavior of individual objects, such as if an enemy is in the frozen state, idle state, patrol state, or attacking state.

To control the game over state, we will do so from the global script, since many nodes across our entire game depend on whether or not the game has reached game over. States are often controlled with variables that possess the boolean data type, whose only two possible values are true or false.

Let's create the global boolean variable now to control our game over state.

Switch to editing global.gd To add a new global boolean variable named IsGameOver, type the following shown in bold: var score = 0 var isGameOver = false

We assigned its default value to false because the game starts out not being in the game over state.

Custom variables like this don't do anything unless we set up our scripts to make use of them. We will do that later.

Setting up collisions for the player node

Let's finally make it so that the player can detect collisions by adding the necessary nodes.

Practice on your own

Add a child Area2D node to Player Add a child CollisionShape2D node to the new Area2D node

When we chose for the Shape property in the past, it was a no-brainer to choose the CircleShape2D for Projectile, and RectangleShape2D for Incomer, but for our tringular Player, we find a notable lack of any obvious tringular shape options. The reason for this is that checking overlapping areas for rectangles and circles is less "expensive" in terms of processing cycles, than other shapes. We could create a tringular polygon shape with a different node, but a rectangular collision shape should suffice for player, despite its triangular appearance.

Choose RectangleShape2D for the Shape field on the new CollisionShape2D. Line up the dimensions of the rectangle over the bottom half of the player's triangular appearance.

Player now registers collisions.

Test the game.

The player isn't removed when it runs into Incomers. Let's set up the area_entered function in the player script.

Switch to Player.gd At the bottom of the player script, add the following code, Player.gdfunc area_entered(otherArea): go.destroy(self) Test the game.

The player ship is removed when it collides with an incomer, but you may have noticed that the player ship and projectile are removed whenever you spawn a projectile. We'll fix that next.

Fixing collision issues by checking names

It is very common in game development that we have to differentiate what happens between different colliding objects. For example, in a game with checkpoints, we don't want an enemy object to trigger a checkpoint or cutscene. We can handle this issue by checking the names of the areas involved in the collision.

All this time we have been writing otherArea between the parentheses in the area_entered function definitions, but we haven't used that value yet. The value between the parentheses in a function definition is called a parameter, and it is provided to give some additional pertinent information about this function when it is called. In the case of the otherArea parameter, it is used to pass to the function which area collided with the area associated with the script.

In the case of the player script, the otherArea parameter will be an incomer's Area2D node if the player ship collided with an incomer, and otherArea will be a projectile's Area2D node if the player ship collided with a projectile. The problem is that both the incomer's and the projectile's Area2D nodes have the name "Area2D". Let's rename those Area2D nodes so that we can tell them apart in the code.

Within the Incomer.tscn scene, Rename the Area2D node to IncomerArea

Let's do the same for the projectile area.

Within the Projectile.tscn scene, Rename the Area2D node to ProjectileArea

Now we can go back to the player script and only remove the player ship if it collided with an incomer by checking the otherArea node's name. We can do this by checking its name property using the == operator. ==, unlike =, is a comparison operator, where it will evaluate to either true or false, depending on if both sides of the operator are equal in value.

With the Player.gd script open, modify the code as shown in bold, Player.gdfunc area_entered(otherArea): if otherArea.name == "IncomerArea": go.destroy(self) Test the game.

The player ship is no longer removed when colliding with the projectile, but the projectile is still removed. This is because the projectile's script still has it remove the projectile whenever it collides with any other Area2D node. Let's perform the same treatment on the projectile's script as we did for the player's script.

Practice on your own

Modify the Projectile script to make it so projectile's are only removed when colliding with incomers. Test the game.

Everything looks in order now. We can continue on to make the game over sprite appear when the player ship is removed.

Displaying game over sprite

The player is removed upon collision with an incomer, but we also want the game over Sprite to appear. In fact, when a game over occurs in a game, typically many things change, and so it is useful to enable the game over state we prepared with the global boolean we created earlier.

Switch to editing Player.gd To set true our custom isGameOver boolean, within the area_entered function, type the code shown in bold: Player.gdif otherArea.name == "IncomerArea": go.destroy(self) print("Game Over") global.isGameOver = true Test the game.

"Game Over" printed to the output log when the player ship collides with an incomer, but the game over Sprite does not appear. We can use the modified global game over state to communicate to our game over Sprite that it is time to become visible.

At this point, we could either create a script for the GameOverSprite node, or we can have the Main node's script handle it. Either way is valid. We'll flip a coin and then choose to handle it in the Main node's script.

Switch to Main.gd Modify the code in the _process function as shown in bold, Main.gdfunc _process(delta): $ScoreLabel.text = "Score: " + str(global.score) if global.isGameOver == true: $"GameOverSprite".visible = true Test the game.

The game over Sprite should now appear when the player is removed.

Restarting game with player input

Once the Player node is removed, it would be nice if the player had a way to restart the game.

Let's first set up the custom input action, as we did with allowing the player to fire projectiles.

Practice on your own

Create a new input action named restart_game. Assign the Enter key to the new restart_game action that was just created.

We can restart the game from any script. However, there are several bad choices of scripts for this code, such as the player script, since that script won't be available when the player is removed. Let's just use the Main script.

Switch to editing Main.gd

We can restart the game with the go.restart_scene function.

To restart the Main scene when the custom restart_game action is pressed, within the _process function, add the code shown in bold: Main.gd$"ScoreLabel".text = "Score: " + str(global.score) if global.isGameOver == true: $"GameOverSprite".visible = true if Input.is_action_just_pressed("restart_game"): go.restart_scene()

This would allow the player to restart the game, even while not in the game over state. For this action, sinced we actually want multiple conditions to be true, we can add another condition by using the and logical operator. The condition on the left of and and the condition on the right of and must both evaluate to true in order for the entire expression to evaluate to true.

To add to the if condition such that the game must also be in the game over state in order to restart the scene, add the code shown in bold: Main.gdif Input.is_action_just_pressed("restart_game") and global.isGameOver: go.restart_scene()

When checking if a boolean is true, you can omit the == operator (as we did with global.isGameOver just now) since the boolean already evaluates to true, and thus satisfies the condition. If it were still false, it would fail the condition.

Test the game.

We can now restart our game in the game over state.

Instead of using a logical operator like and, you can use a nested if statement, which is an if structure within the block of an if structure. The inner block will only execute if both the outer and inner if conditions are true.

Resetting global values

We are now able to restart our game, but you may have noticed that the score stays as it was before restarting the game, and the game over Sprite remains visible. It's not quite a full restart until we reset those thing back to the state they were in when the game first launched.

When we reset the scene, only the scene goes back to its original state. Values inside of an autoload script remain the same, since they are in a more global state. This can come in handy when you want the player character to remember how much health it had going between zones, or perhaps a High Score variable. In our case, we do want to reset score back to 0 and the game over state back to false. We can do that by simply changing those values in the Main script, right after calling the go.restart_scene function.

With the Main.gd script open, modify the code as shown in bold, Main.gdif Input.is_action_just_pressed("restart_game") and global.isGameOver: go.restart_scene() global.score = 0 global.isGameOver = false Test the game.

The game now restarts as expected. All the issues are now fixed, so we can start looking at what else we might be able to add to make the game more interesting.

Creating a health system with custom properties

We have been using many properties so far, such as position on Node2D nodes, text on our Label node, and so on. These properties are all built into the nodes as part of the engine. However, when making a game, we usually need much more than those basic properties Godot gives us by default. For example, if we wanted to make a farming game, Godot possesses no "Crop" node,or properties like "harvest time", "water needed", "yield", or anything like that. We'd have to create that all ourselves. Fortunately, Godot, like other game engines, provide us the ability to create custom properties.

A property or member variable, is a variable written in a script that belongs to the node itself, where each node that possesses that script automatically gets that same property, though each instance can possess a different value for the same property. This is much like any other built-in property of a node. position for examplebelongs to every node that derives from Node2D, but each such node can have a different value for the position property.

Giving incomers a health property

So far, incomers are removed after just one collision. To make our game more interesting, offering up more depth of play, we can allow incomers to take multiple hits before being removed. We will do this by giving Incomer a health property.

To create a property, we simply define it like any other variable, but we do so outside of any function block, preferably at the top of the script.

Switch to editing Incomer.gd. To create a health property which belong to our Incomer nodes, add the code as shown in bold: Incomer.gdextends Node2D var health = 3

We've created the health property, which can now be accessed by the dot operator either within this script by typing self.health, or within another script.

The default value we've assigned the health property is 3. This means that each instance will spawn with a health value of 3, though this number can be changed immediately after the incomer is spawned, or after the incomer is "damaged." In fact, let's reduce the incomer's health upon collision.

To reduce the incomer's health property by 1, upon collision, within the area_entered function, add the following code in bold: Incomer.gdfunc area_entered(otherArea): self.health -= 1 go.destroy(self) global.score += 10

This code, as it stands, will still eliminate the incomer upon collision, because the go.destroy(self) line runs no matter what. Let's put that line within an if block whose condition checks if the incomer's health has reached zero.

To destroy the incomer only if its health property has reached 0, add and modify the code shown below in bold: Incomer.gdfunc area_entered(otherArea): self.health -= 1 if self.health < 1: go.destroy(self) global.score += 10

You may have noticed that we aren't checking if health is equal to exactly 0, but rather at most 0. The reason for this is that you might in the future have a projectile reduce more than 1 health from the incomer, and you wouldn't want to accidentally let an incomer have negative health and not be destroyed.

Test the game.

Incomer nodes now are removed after 3 collisions each.

This has been just the barebones of what you need to be able to get started making a highly complex game.


Author: Chabane Maidi