Creating a game "Like coins" on Godot Engine. Part 1
- Tutorial
"Godot Engine" is developing very fast and winning the hearts of game developers from around the world. Perhaps this is the most friendly and easy-to-learn tool for creating games, and to make sure of this, we will try to make a small 2D game. For a good understanding of the game development process, you should start with 2D games - this will reduce the threshold for entering a more serious game development. Although the transition to 3D itself is not as difficult as it may seem, because most of the functions in Godot Engine can be successfully used in both 2D and 3D.
Introduction
The simplest thing you can think of is a game in which our main character will collect coins. To complicate it a bit, let's add an obstacle and time as a limiting factor. The game will be 3 scene: Player
, Coin
and HUD
(in this article are not considered), which will be combined into a single Main
scene.
Project Settings
Before you dive into writing scripts (scripts), which is about 80-90% of the total time spent on creating the game, the first thing to do is to set up our future project. In large projects, it is useful to create separate folders for storing scenarios, scenes, images and sounds, and we should definitely take note of this, because who knows what end result we will come to.
I want to immediately make a reservation that this article implies that you are a little familiar with the "Godot Engine" and that you have some knowledge and skills to use this tool, although I will be guided by the fact that you encounter "Godot Engine" for the first time, I am still I advise you to start to get acquainted with the basic component of the engine, study the syntax of GDScript and come to an understanding of the terminology used (nodes, scenes, signals, etc.), and then come back here and continue acquaintance.
In the program menu, go to Project -> Project Settings
.
Another small digression. I will always give examples based on the fact that the end user uses the English interface of the engine, despite the fact that in "Godot Engine" there is support for the Russian language. This is done in order to get rid of possible misunderstandings or embarrassments associated with the incorrect / inaccurate translation of certain elements of the program interface.
Find the section Display/Window
and set the width - 800
, and the height - 600
. Also in this section should be set Stretch/Mode
to 2D
, and Aspect
to Keep
. This will prevent stretching and deformation of the contents of the window when it is resized, but in order to prohibit resizing the window simply uncheck it Resizable
. I advise you to play with these options.
Now go to the section Rendering/Quality
and turn on in the right pane Use Pixel Snap
. What is it for? The coordinates of the vectors in Godot Engine are floating point numbers. Since objects cannot be drawn only half a pixel, this discrepancy can cause visual defects for games where used pixelart
. And it is worth noting that in 3D this parameter is useless. Keep this in mind.
Scene "Player"
Let's start creating the first scene Player
.
The advantage of any scene is that initially they are independent of other parts of the game and this makes it possible to freely test them and get the result that was originally incorporated in them. In general, dividing game objects into scenes is a useful tool in creating complex games - it is easier to catch mistakes, make changes to the scene itself, without affecting other parts of the game, they can also serve as templates for other games and they will definitely work exactly , as worked before the transfer.
Creating a scene is a trivially simple action - Scene
click on the tab +
(Add / Create) and select the node Area2D
and immediately change its name so as not to be confused. This is our parent node, and to extend the functionality, you need to add child nodes. In our case, this AnimatedSprite
and CollisionShape2D
, but we will not hurry, but begin in order. Then you should immediately "lock" the root node:
If the shape of the body collision (CollisionShape2D) or the sprite (AnimatedSprite) is shifted, stretched relative to the parent node, this will definitely lead to unexpected errors and subsequently it will be difficult to correct them. With this option enabled, the "parent" and all his "children" will always move together. It sounds ridiculous, but using this feature is extremely useful.
AnimatedSprite
Area2D
a very useful node in case you need to know about the overlap event with other objects or about their collision, but by itself it is invisible to the eye and Player
we will add an object to make it visible AnimatedSprite
. The name of the node suggests that we will deal with animation and sprites. In the window Inspector
go to the parameter Frames
and create a new one SpriteFrames
. Working with the panel SpriteFrames
is to create the necessary animations and download the corresponding sprites to them. We will not analyze in detail all the stages of creating animations, leaving it to independent study, I will just say that we should have three animations: walk
(walking animation), idle
(rest) and die
(death or failure animation). Do not forget the value SPEED (FPS)
should be equal8
(although you can choose another value - the appropriate one).
CollisionShape2D
To be Area2D
able to detect collisions it is necessary to provide him with the shape of an object. Forms are determined by the parameter Shape2D
and include rectangles, circles, polygons and other more complex types of shapes, and the dimensions are already edited in the editor itself, but you can always use it Inspector
for more precise settings.
Scenarios
Now, in order to “revive” our game object, you need to set a script for it, according to which the actions specified in this scenario will be carried out. In the tab, Scene
create a script, leave the settings "default", delete all comments (lines starting with the '#' sign) and proceed to declaring variables:
export (int) var speed
var velocity = Vector2()
var window_size = Vector2(800, 600)
Using the keyword export
allows you to set the value of a variable speed
in the panel window Inspector
. This is a very useful method if we want to get custom values that are convenient to edit in the window Inspector
. Specify Speed
(speed) value 350
. The value velocity
will determine the direction of movement, and window_size
- the area limiting the movement of the player.
The further order of our actions is this: use the function get_input()
to check whether the input is from the keyboard. Then we move the object according to the keys pressed and then play the animation. The player will move in four directions. By default, in Godot Engine there are events assigned to the arrow keys ( Project -> Project Settings -> Input Map
), so we can apply them to our project. To find out if a key is pressed, you should use Input.is_action_pressed()
it by slipping the name of the event you want to track.
func get_input():
velocity = Vector2()
if Input.is_action_pressed("ui_left"):
velocity.x -= 1if Input.is_action_pressed("ui_right"):
velocity.x += 1if Input.is_action_pressed("ui_up"):
velocity.y -= 1if Input.is_action_pressed("ui_down"):
velocity.y += 1if velocity.length() > 0:
velocity = velocity.normalized() * speed
At first glance, everything looks good, but there is one small nuance. The combination of several keystrokes (for example, down and left) will cause the addition of vectors and in this case the player will move faster than if he simply moved down. To avoid this, we will use the method normalized()
- it will return the length of the vector to 1
.
So, the keystroke events were tracked, now you need to move the object Player
. This will help us. The function _process()
that is called every time a frame is changed, so it is advisable to use it for those objects that will change frequently.
func _process(delta):
get_input()
position += velocity * delta
position.x = clamp(position.x, 0, window_size.x)
position.y = clamp(position.y, 0, window_size.y)
I hope you have noticed the parameter delta
, which in turn is multiplied by the speed. It is necessary to give an explanation of what it is. The game engine is initially configured to work at a speed of 60 frames per second. However, there may be situations when the computer, or the "Godot Engine" itself, slows down. If the frame rate is not coordinated (the time for which the frames are replaced), this will affect the “smoothness” of movement of game objects (as a result, the movement is “jerky”). "Godot Engine" solves this problem (like most similar engines) by entering a variable delta
- it gives the value for which the frames were replaced. Thanks to these values, you can "align" the movement. Pay attention to another remarkable feature -clamp
(returns a value within two specified indicators), thanks to it, we have the ability to limit the area over which it can move Player
, simply by setting the minimum and maximum value of the area.
Do not forget to animate our object. Please note that when an object moves to the right, you need to mirror it AnimatedSprite
(using flip_h
) and our hero will look in that direction where he is moving. Make sure that the AnimatedSprite
option is Playing
on to start playing the animation.
if velocity.length() > 0:
$AnimatedSprite.animation = "walk"
$AnimatedSprite.flip_h = velocity.x < 0else:
$AnimatedSprite.animation = "idle"
Birth and death
When starting the game, the main scene needs to inform the key scenes about the readiness to start a new game, in our case, you should inform the object Player
about the start of the game and set initial parameters for it: appearance position, default animation, run set_process
.
func start(pos):
set_process(true)
#глобальная позиция объекта в формате Vector2(x, y)
position = pos
$AnimatedSprite.animation = "idle"
We will also provide for a player’s death event when the time runs out or the player stumbles upon an obstacle, and setting this set_process (false)
will cause the function to _process ()
no longer be executed for this scene.
func die():
$AnimatedSprite.animation = "die"
set_process(false)
Adding collisions
It was the turn to make the player detect collisions with coins and obstacles. The easiest way is through signals. Signals are a great way to send a message so that other nodes can detect and respond to them. Most nodes already have built-in signals, but it is possible to define "custom" signals for your own purposes. Signals are added if you declare them, at the beginning of the script:
signal pickup
signal die
Look through the list of signals in the window Inspector
(tab Node
), and pay attention to our signals, and signals that already exist. Now we are interested area_entered ()
, it assumes that the objects with which the collision will occur also have a type Area2D
. We connect the signal area_entered ()
using the button Connect
, and in the window we Connect Signal
select the highlighted node (Player), all the rest is left by default.
func _on_Player_area_entered( area ):
if area.is_in_group("coins"):
#посылаем сигнал
emit_signal("pickup")
area.pickup()
In order for objects to be easily detected and interact with them, they need to be defined in appropriate groups. The creation of the groups themselves is now omitted, but we will definitely return to them later. The function pickup()
determines the behavior of the coin (for example, it may be playing an animation or sound, deleting an object, etc.).
Scene "Coin"
When creating a scene with one coin, you need to do everything that we have done with the scene "Player", except that there AnimatedSprite
will be only one animation (flare). The value Speed (FPS)
can be increased to 14
. Another change the scale AnimatedSprite
- 0,5, 0,5
. And the dimensions CollisionShape2D
should correspond to the image of the coin, the main thing is not to scale it, but change the size using the appropriate markers on the form in the 2D editor, which
controls the radius of the circle.
Groups are a kind of labeling node that allows you to identify similar nodes. In order for the object to Player
react to the touch with the coins, the coins must belong to the group, let's call it coins
. Select the node Area2D
(with the name "Coin") and Node -> Groups
assign a tag to it in the tab , creating the corresponding group.
For the node, Coin
create a script. The function pickup()
will be called by the object's script Player
and will tell the coin what to do when it worked. The method queue_free()
will safely remove a node from the tree with all its children and clean up the memory, but the deletion will not work immediately, at first it will be moved to the queue to be deleted at the end of the current frame. It is much safer than deleting a node immediately, because other "participants" (nodes or scenes) in the game may still need the existence of this node.
func pickup ():
queue_free ()
Conclusion
Now we can create a scene Main
, drag and drop both scenes into the 2D editor: Player
and Coin
, and check how the player’s movement and its contact with the coin work by running the scene (F5). Well, I hurry to say that the first part of the creation of the game "Like Coins" is over, let me take my leave and thank everyone for their attention. If you have something to say, add material or you see errors in the article - be sure to report this by writing a comment below. Do not be afraid to be tough and critical. Your "reverse response" will tell you whether I am moving in the right direction and what can be corrected so that the material is even more interesting and useful.