β€’

tech

Godot game engine introduction for beginners

I finally tried the Godot game engine for the first time after years of Unity. I share some of the lessons I learned building my first project, explaining how to implement a 2d top-down game using all the features provided by Godot.


Sandro Maglione

Sandro Maglione

Games

For a long time I have been using Unity as a game engine to implement most of my games. I considered Unity as the standard, with more tutorials, features, community. Apparently an obvious choice (especially years ago, ~2014).

Nonetheless, Unity has always been frustrating to use. The software is slow and heavy (6Gb), I find the tutorials and documentation not user-friendly, and it has been always a struggle to implement complex behaviors in my games.

Lately this frustration convinced my to give a try to Godot, and I am glad that I did!

In this post I share my experience implementing some core functionalities which are needed for most 2D games using Godot:

  • Animations
  • Collisions
  • Movements
  • Tilemaps
  • Objects interactions

This experience convinced my to switch to Godot πŸ‘‡

This is not a Godot step by step tutorial, but more of a general overview of how to get started with the game engine.

I left links to every node and class so you can read more from the documentation about each topic covered in the post.


Nodes

Each Scene contains a tree of nodes.

godot-scene-overviewgodot-scene-overview

Each node encodes some functionality:

Nodes can be then nested into each other to implement more complex behaviors. Furthermore, a node itself can contain other Scene as children.

This makes each Scene and Node composable, which allows to build complex behaviors and interactions.

Animations

Animations (2D) are as easy as it gets to setup and use.

I used the AnimatedSprite2D node, which allows to select a sequence of frames (images) to build an animation.

godot-animated-sprite-2d-framesgodot-animated-sprite-2d-frames

The process is simple:

  • Insert an AnimatedSprite2D node
  • Add a SpriteFrame from the AnimatedSprite2D node inspector
  • Cut and select the frames for each animation (idle, walking, running) and give each animation a unique name identifier
  • From the player script set the animation and call the play() function
func _ready():
	$AnimatedSprite2D.animation = "idle_down"
	$AnimatedSprite2D.play()

Now it is only a matter of updating the current animation each time the player state changes:

func _physics_process(delta):
	var animation_state = $AnimatedSprite2D.animation
	var input_direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
	velocity = input_direction * speed

	if velocity.length() == 0:
		if animation_state.begins_with("walk_"):
			if animation_state == "walk_up":
				animation_state = "idle_up"
			elif animation_state == "walk_x":
				animation_state = "idle_x"
			else:
				animation_state = "idle_down"
		
	elif velocity.length() > 0:
		if velocity.x != 0:
			animation_state = "walk_x"
			if velocity.x < 0:
				$AnimatedSprite2D.flip_h = true
			else: 
				$AnimatedSprite2D.flip_h = false
				
		elif velocity.y > 0:
			animation_state = "walk_down"
		elif velocity.y < 0:
			animation_state = "walk_up"
		
		move_and_slide()
	
	$AnimatedSprite2D.animation = animation_state
	$AnimatedSprite2D.play()

Movements

Implementing a top-down movement system is also a breeze.

I created my character using the CharacterBody2D class. CharacterBody2D provides a move_and_slide() method that allows to move the node in any direction and speed specified by the node's own velocity (Vector2).

From the settings (Project > Project Settings... > Input Map) I defined the input buttons for each of the 4 movement directions (left, right, top, down).

godot-input-map-project-settingsgodot-input-map-project-settings

In the player script I then update the velocity based on the clicked buttons, and then call move_and_slide():

func _physics_process(delta):
	var input_direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
	velocity = input_direction * speed
    move_and_slide()

These 3 lines of code are enough to implement a complete 2d movement system.

Collisions

Collisions for the player are implemented using the CharacterBody2D class in combination with CollisionShape2D.

CollisionShape2D allows to define visually from the editor the shape of the collision area (using a Shape2D).

godot-collision-shape-2d-examplegodot-collision-shape-2d-example

I then added a similar setup for all the nodes that are expected to collide with the player:

  • I added a Physics Layer to the TileSet, which allows to add a collider shape to a tile before painting a TileMap
  • I added a CollisionShape2D together with a RigidBody2D to NPCs (Non Playable Characters)

There is more 🀩

Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.

Detecting collisions using Raycast

After adding collision shapes and rigidbody it becomes possible to check for collisions between nodes.

I used a RayCast2D node to define a line in front of the player for checking collisions.

From the player script I have full control over the properties of RayCast2D. I added some code to change the direction of the raycast based on the direction in which the player is facing:

func _physics_process(delta):
	# Get current raycast direction
	var raycast_direction = $RayCast2D.target_position
	
    # Set new value for target_position
	$RayCast2D.target_position = raycast_direction

This allows to then detect collisions with objects in the scene from the player perspective.

For NPCs instead, I attached an Area2D node to each of them, which I use to check if the player is nearby (using signals, ready below πŸ‘‡).

Signals

Signals are the real game-changer when using Godot.

Signals allow to listen for any event from any script (Observer pattern)

Instead of hard-coding connection between nodes and manually define events and interactions, you can use Signals to send and react to any event between any node.

In my example I defined a custom signal inside the NPC script (by adding a signal at the top of the script):

signal interacted(text)

I then emit this signal every time the NPC detects the player nearby (by listening to an Area2D signal) and the user presses the "interaction" button:

func _process(delta):
	if is_player_inside_area && Input.is_action_pressed("interact"):
		interacted.emit(speech_dialogue)

func _on_area_2d_body_entered(body):
	is_player_inside_area = true

I also passed a custom value text to the signal, which represent the sentence that the NPC is going to say to the player.

godot-signals-with-custom-example-panelgodot-signals-with-custom-example-panel

This signal triggers a speech bubble and starts the interaction between the player and the NPC:

func _on_npc_interacted(text):
	set_text(text)

TimeMap

I am mostly interested in 2d games. For a 2d games, having a fully-featured tilemap editor is critical. Godot provides all that you need using the TileMap node.

I added TileMap as root node of a new Scene. TileMap contains a TileSet, which represents the actual tiles that we are then going to "paint" inside the TileMap.

Godot allows you to:

  • Create a TileSet from a tile sheet image, by splitting the image based on the resolution of each tile (I used a 16x16 tile sheet in my project)
  • Define layers and ordering for each tile in the TileSet
  • Add collision, physics, navigation (path finding), and more to the TileSet
  • Visually print your level into the TileMap using the tiles inside TileSet

godot-tilemap-with-tileset-layers-previewgodot-tilemap-with-tileset-layers-preview

The editor is feature complete while also being easy to navigate. I definitely found every feature that I needed for my game example by just using TileMap + TileSet.


Overview - Putting all together

This is all that you need to start working on your game.

You can now add and control any animation to any node using AnimatedSprite2D. AnimatedSprite2D allows to control the duration of each single frame and preview the final result from the inspector.

You can use TileMap to build a complete 2D level by adding all the objects, NPCs, and player. TileMap allows to define collisions, layers, handles path finding and navigation, and much more.

You can use CharacterBody2D to implement movements for your player, or you can also use RigidBody2D.

You can use Signals to define and implement any interaction between any node (picking up objects, breaking a wall, attacking an enemy).

All of these blocks can be then composed together using Nodes and Scene to create each level for your game.


After this initial experience I am going to switch to Godot for my next games:

  • More lightweight (200Mb instead of 6Gb of Unity)
  • Integrated IDE
  • Documentation easier to navigate
  • Open source
  • Better developer experience

These are all important points for me when working with any technology. So, welcome to Godot, more content coming soon πŸš€

You can subscribe to my newsletter here below for more updates and tips πŸ‘‡

πŸ‘‹γƒ»Interested in learning more, every week?

Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.