Build a complete game with the Godot game engine
Button Smashing
Godot is prepared to read input from most sources. Keyboards and mice are obviously supported, but so are most game controllers and touchscreen buttons. Let's start simple, though, and link the turret's fire animation to when the player hits the space bar.
GDScript's Input
object simplifies reading from peripherals and lets you do things like what you can see in Listing 3. This listing shows how to check whether a key pressed is a specific key (in this case a SPACE
) and then trigger an action.
Listing 3
process() (Turret.gd)
01 func _process(delta): 02 if Input.is_key_pressed(KEY_SPACE): 03 $AnimatedSprite.play("fire", true)
GDScript's KEY_SPACE
inbuilt constant is one of many supported by Godot. You can find a list of other constants and variables on the website [6]. These cover most of the keys you can find on keyboards and the buttons, triggers, and joystick positions you'll find on most controllers. Note that the true
in the $AnimatedSprite
's play()
method (line 3) ensures the animation bounces back and the cannon does not stay retracted after the shot. When you run the scene, make sure that the AnimatedSprite's Playing and Loop properties are both off, and the animation will play only when you hit the space bar.
You would be right to feel chuffed already, but you can do one better. Apart from addressing specific keys and buttons, Godot has shortcuts for left, right, up, and down, so if you want to check if the player wants to move left, you can do
if Input.is_action_pressed("ui_left"):
and Godot will check for the left arrow button on a keyboard, but also the left button on the D-Pad on a game controller, and in a bunch of other places so you don't have to code them in explicitly.
What's great about this is that you can also create your own shortcuts. Although there is no preprogrammed shortcut for a "fire" button, you can make one easily. Visit the Project menu in the main menu (Figure 3, section 8) and select Project Settings… . A dialog will open with all the properties for your game. Select the Input Map tab, and you will see all the available shortcuts. Take a moment to review what's available. To add a new shortcut, fill in the name in the Action text box at the top of the dialog. Call the new action ui_fire. Click the Add button, and the action will appear at the bottom of the list.
To attach an input to the action, click on the + symbol to the right of your action and pick Key from the drop-down menu that appears. A pop-up dialog will appear urging you to press a key. Hit your space bar and the Ok button, and Space will appear under the ui_fire action. Click on the + again and pick Joy Button from the pop-up. Looking at the previously mentioned list [6], you see that JOY_R2 is the right trigger button on most controllers. Pick R2 from the drop-down and click Add.
Go back to your script and change the line:
if Input.is_key_pressed(KEY_SPACE):
to
if Input.is_action_pressed("ui_fire"):
Now the animation will play when you press space and when you hit the right trigger button on the game controller. While you're at it, let's add movement to the turret. It only needs to move left and right, so you can make do with something like what you can see in Listing 4.
Listing 4
Turret.gd (v2)
01 extends Area2D 02 03 var speed = 400 04 var screen_size 05 06 func _ready(): 07 screen_size = get_viewport_rect().size 08 position = (Vector2(screen_size.x/2, screen_size.y-32)) 09 10 func _process(delta): 11 var velocity = Vector2(0, 0) 12 13 if Input.is_action_pressed("ui_right"): 14 velocity.x += 1 15 if Input.is_action_pressed("ui_left"): 16 velocity.x -= 1 17 if velocity.length() > 0: 18 velocity = velocity.normalized() * speed 19 if Input.is_action_pressed("ui_fire"): 20 $AnimatedSprite.play("fire", true) 21 22 position += velocity * delta 23 position.x = clamp(position.x, 32, screen_size.x - 32)
There's a lot of interesting new stuff going on in Listing 4. On line 3, you set up a variable called speed
that contains the speed at which the turret will move along the screen. The number is in pixels per second. The screen_size
variable (line 4) will contain the width and height of the screen.
The _ready()
function (lines 6 to 8) uses the inbuilt get_viewport_rect()
GDScript function to get the details from the viewport and copy the size into the screen_size
variable you declared earlier. The size
attribute contains two values, x
for the horizontal length of the playing field, and y
for the vertical length. Use those values to position
the turret halfway across the bottom of the screen (line 8). The position
is of Area2D nodes.
Next up, edit the _process()
function by defining a velocity
variable (line 11). Note that velocity
in this context is not the same as speed
. While speed
is scalar value, velocity
is a vector. Indeed, Vector2
is a special kind of GDScript type that indicates the direction of the object. Usually, vector values vary between -1 and 1, so a vector with the values of, say, (1, 0) would point straight to the right; with a value of (1, -1), it would point up (lower numbers are higher up on the y axis in Godot) and to the right in a 45 degree angle; a value of (0.5, 1) would point down and to the right in a 63.4 degree angle.
Although the turret will be moving on a horizontal line (making vectors a bit of an overkill), it is a good habit to use vectors for movement and physical forces, as most inbuilt attributes use them. Either way, on line 11 velocity
is set to (0, 0). On lines 13 to 16, we check the input and add and subtract from the velocity
accordingly. If there has been movement, the length of velocity
will be larger than zero (line 17), and we will normalize it and multiply it by the speed
(line 18).
"Normalizing" entails figuring out the position of the node depending on the angle of the vector. Say the speed is 10 pixels per second. If the velocity is (1, 0), after one second, the sprite will have moved 10 pixels to the right from its prior position. If the velocity is (0, 1), the sprite will have moved 10 pixels down. But if the velocity is (1, 1), for example, it won't have moved 10 pixels to the right and 10 pixels left in one second, because then it would have traveled the square root of 200 (as per Pythagoras, the square root of 10 squared plus 10 squared) – that is, 14.1 pixels in one second. Godot's normalized()
function figures out the correct values for the vector by dividing each component by the length of the vector. Normalizing is not strictly necessary for sprites that move perfectly horizontally or vertically, but it is good practice to include it.
Line 22 calculates the new position
of your sprite by adding the velocity
to the current position and multiplying by the time that has passed since the last time this function was run. Line 23 clamp
s the turret's position
– that is, it limits it, in this case, to the left and right limits of the playing field. This stops the sprite from going over the edge and disappearing into gameland oblivion.
Taking Shape
The final piece the turret needs is its collision shape. You need a collision shape, because Godot doesn't know what bits of your image are meant to be solid.
Click on the + in the Scene dock to add a new node and look for CollisionPolygon2D. The moment you add it to your Turret node, the yellow warning sign disappears from the top node, but a new one appears next to your CollisionPolygon2D node. This is because the latter node is not complete without a defined shape. To add a shape, click on the CollisionPolygon2D node to select it, look at the Inspector dock on the right, and click on PoolVector2 Array (size 0). The zero indicates that there are no vertices in the shape yet.
Once you click PoolVector2 Array (size 0), the text will turn blue indicating it is in "edit" mode. In the workspace, use the Ctrl+mouse wheel to zoom in on the turret and click on one of its corners. A small dot will appear where you clicked. That is your first vertex. Move the cursor, and a red line between the first point and your cursor will appear. Follow the contour of the turret, clicking at every corner to set the vertices of the shape (Figure 6, left). To close the shape, move to the first vertex you set and click on it (Figure 6, right).
Congratulations! No more warning icons. Your turret now has a shape that can collide and be collided with. Let's just give it something to collide with. The turret's enemies are the aliens you can see in Figure 7. To incorporate them into your game, click on the + symbol over the central workspace to create a new scene and add an Area2D node as the top node. Rename the node Enemy and press Ctrl+S to save everything as Enemy.tscn.
Add an AnimatedSprite node under Enemy. As all the enemies will behave in the same way, you can add all the animations from Figure 7 to the same node (Figure 8). To load in the skully frames, proceed like you did with the turret. Then, click on the New Animation button (Animations dock, top left) and add in cthulhy and then repeat the process for medussy. This time you do want the animation to loop, so make sure the Loop switch is on. The default FPS speed of 5 is fine.
In the Inspector dock, you can choose which animation to preview in the Animation drop down. Clicking the Playing checkbox will play the animation on a loop so you can check that everything is working correctly.
Add a CollisionShape2D to the Enemy node. This is simpler than the CollisionPolygon2D we used for the turret, because you can pick a fixed Shape in the Inspector, and you don't have to faff around with vertices and segments. I picked a circle, and that works just fine. Listing 5 shows how you could move a medussy alien from left to right across the bottom of the screen and have it animated to boot.
Listing 5
Enemy.gd
01 extends Area2D 02 03 var speed = 80 04 var screen_size 05 var direction = 1 06 07 func _ready(): 08 screen_size = get_viewport_rect().size 09 position = Vector2(32, screen_size.y - 32) 10 11 func _process(delta): 12 var velocity = direction * speed 13 position.x += velocity * delta 14 15 $AnimatedSprite.play("medussy")
Collisions
What we need now is to combine both the turret and enemy scenes so that both elements are on screen at the same time. To do that, create a new scene by clicking on the + sign over the main workspace area, choose Other Nodes from the options in the Scene dock on the left, and pick a plain and simple Node from the list.
Change the name Node to Main and click on the icon showing three connected chain links (Instance a Scene) in the Scene dock toolbar located directly above the node you just created. This will open a dialog with the available scenes, namely Turret and Enemy. Select both and click the Open button.
The Turret and Enemy scenes will now appear as nodes of Main. If you run Main, both scenes will run as one (Figure 9). However, when the enemy and turret meet, nothing happens: The alien drifts over the turret as if it wasn't there. In fact, a collision is happening; it is just that you are not doing anything with it.
To solve this, click on the Turret node in Main to select it; over on the right of the Godot editor, click on the Node tab (located next to the Inspector tab). This will show all the signals/events available to the currently selected node, the Area2D Turret in this case.
The first one reads area_entered(area: Area2D), and it is a signal that is triggered when another body with a collision shape hits the current Area2D node. This is exactly what we need now. Click on it to select it and click the Connect button at the bottom of the dock.
A dialog will open with a list of nodes under Main. What Godot is asking you here is which node the signal is going to affect. As an experiment, let's just make the alien stop in its tracks when the turret hits it. As the node affected by the signal will be the alien, pick the Enemy node from the list.
In the text box at the bottom, Godot suggests _on_Turret_area_entered. This is the name of the function/method that will run when the signal is triggered. You could change it or make it point to functions you have already written to manage the signal, but in this case you can just click Connect.
Godot opens the scripting editor and provides you with an empty template for the _on_Turret_area_entered()
function. Edit the function so it looks like what you can see in Listing 6. Save your work, run Main, and when the alien hits the turret, it will stop in its tracks. You can also do something more exciting and make your turret explode.
Listing 6
_on_Turret_area_entered() (Enemy.gd)
01 func _on_Turret_area_entered(area): 02 speed = 0
« Previous 1 2 3 4 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.