Creating a game "Like coins" on Godot Engine. Part 1
"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.
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:
HUD(in this article are not considered), which will be combined into a single
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/Windowand set the width -
800, and the height -
600. Also in this section should be set
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/Qualityand 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.
Let's start creating the first scene
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 -
Sceneclick on the tab
+(Add / Create) and select the node
Area2Dand 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
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.
Area2Da 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
Playerwe 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
Inspectorgo to the parameter
Framesand create a new one
SpriteFrames. Working with the panel
SpriteFramesis 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:
die(death or failure animation). Do not forget the value
SPEED (FPS)should be equal
8 (although you can choose another value - the appropriate one).
Area2Dable to detect collisions it is necessary to provide him with the shape of an object. Forms are determined by the parameter
Shape2Dand 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
Inspectorfor more precise settings.
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,
Scenecreate 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
exportallows you to set the value of a variable
speedin 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
350. The value
velocitywill 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
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
flip_h) and our hero will look in that direction where he is moving. Make sure that the
Playingon 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
Playerabout the start of the game and set initial parameters for it: appearance position, default animation, run
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)
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
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 Signalselect 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.).
When creating a scene with one coin, you need to do everything that we have done with the scene "Player", except that there
AnimatedSpritewill be only one animation (flare). The value
Speed (FPS)can be increased to
14. Another change the scale
0,5, 0,5. And the dimensions
CollisionShape2Dshould 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
Playerreact 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 -> Groupsassign a tag to it in the tab , creating the corresponding group.
For the node,
Coincreate a script. The function
pickup()will be called by the object's script
Playerand 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 ()
Now we can create a scene
Main, drag and drop both scenes into the 2D editor:
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.