This is the fourth part of the tutorial creating platformer game on Godot Engine. In this tutorial I will explain how to setup tilemap and camera.

  1. Part 1 : Preparation
  2. Part 2 : Player Creation
  3. Part 3 : Player Creation 2
  4. Part 4 : Tilemap and Camera
  5. Part 5 : Player Animation
  6. Part 6 : Parallax Background and Level Bounds
  7. Part 7 : Character Controller and Enemy

Tileset and Tilemap

Godot has a special tilemap node, so we don’t need 3rd party software like Tiled to make our level. Create and setup tilemap is not that hard, what you need to do is just create new scene and fill it with bunch of node for the tileset.

Tileset

Before we go into level design, we need to make our tileset. Create a new scene and create a Node2D (rename to “Tileset”) as the root. Then create a sprite node as a child of Tileset. So we have 2 node, one is Tileset and the other one is Sprite. Each child of Tileset will become tiles (only sprite node) and the name of sprite node will become the ID. Rename the sprite into “0”.

Tilemap Scene

I want to create my first tile with the left top ground, assign the Texture with Tiles.png, set the VFrames = 20 and HFrames = 22. Set the sprite into “left top ground” image by changing the Frame = 79. So we got our first tile.

Setup Tiles 0

This tile is ground type, which means need a collision. The collision is static, so, add a new StaticBody2D Node as a child of “0”. Then add a new CollisionShape2D as a child of StaticBody2D. Set the CollisionShape2D with RectangleShape2D and setup the size of RectangleShape2D along with the size of ground sprite.

Setup Collision Tiles 0

PRO TIPS : You can edit collision shape into precise value by activating the Snap Option. Click Edit - Use Snap, setup the snap value by clicking Edit - Configure Snap.

Okay, we are done with one tile, 12 left! You can duplicate the node by pressing CTRL + D and setup it as needed, until you got all this tileset

Setup Tileset All Tiles

Save the scene as Tileset.tscn, then click Scene - Convert To.. - Tileset, save it as tileset.tres make sure enable Merge With Existing Option. Yeay, we’ve done creating the Tileset.

Tilemap

We’ve create the tileset, now we can create our World level. Open World.tscn delete our StaticBody2D Node and create a Tilemap Node. View at Inspector Window, find Tile Set option, click the button, and select Load, navigate to our tileset.tres file. Because our tiles size is 16x16 px so we need to change the Cell Size. Find option Size inside Cell section, change it into (16, 16).

Tilemap

Now create a simple level! Left click to paint, right click to delete.

Setup World

PRO TIPS : In my tileset view maybe different from yours, you can configure it in TileMap Editor Settings. Click Settings - Editor Settings - Tilemap - Change the value as you wish.

Camera

Camera will always follow the player. But how? You can easily set the camera node as a child of Player, but that just dirty trick and not pleasing in the eyes. Or you can use Drag Margin (found in inspector), but I don’t like it, however it’s easy and good. If you using Drag Margin, when you moving on the right direction, the camera will be on the left of character, and otherwise. That makes user can’t see clearly what happen in the next area, because the camera always left behind the character. That was Drag Margin and that was opposite of my implementation. If the character moving right direction, I want the camera is on the right side, so user can see clearly in the next area, hope you understand what I mean :). Here you go example of using Drag Margin

Camera Drag Margin

PRO TIPS : As far as I know, godot automatically set pixel perfect for you (make sure your sprite import flags disabled). So, the camera movement must be not floating point. Image above is not good for pixel art, because the camera position will be at floating point. If you really need the camera at floating point, you can activate in use_2d_pixel_snap inside Project Setting - Display, this will force the render result always pixel perfect, but the result maybe little weird, in my opinion of course.

Configure Display Settings

So, before we go into coding, we need to modify our display settings at Project Setting. Open Project Setting by clicking Scene - Project Setting - Display. Now change the width = 160, height = 86, stretch_mode = viewport, stretch_aspect = keep_height and use_2d_pixel_snap = on.

Project Setting - Display

Scripting Camera

Modify Camera2D option in Inspector by disabling the H Enabled and V Enabled, and set the Smoothing Speed to 0. You can play a bit with Offset value, my setup is (0, -20).

Camera Settings

Create a new script and attach into Camera2D, save it as camera.gd, and write the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extends Camera2D
export var target = "../Player"
# onready prefix : Initializes a variable once the Node the script is attached to and its children are part of the scene tree.
onready var target_node = get_node(target)
func _ready():
# window maximized, comment this line code if you don't want to automatically maximized
OS.set_window_maximized(true)
var pos = target_node.get_pos()
# forcing set the position into character position
pos = Vector2(round(pos.x), round(pos.y))
set_pos(pos)
set_fixed_process(true)
func _fixed_process(delta):
# set the position into integer number
set_pos(Vector2(round(target_node.get_pos().x), round(target_node.get_pos().y)))

onready will initializes the variable when the node is ready, including the child of node

As I said before, we make the camera position value into integer number (ex 1, 2, 3, 10), so we need to call round to make it integer.

Camera Follow

Add New Code into Player Script

The camera always follow the player and the position is always same as player position, that was not what we want to achieve. The camera will in front of player, but how to know the character direction? currently we don’t have it, so modify our player script. Add a new variable called var facing_dir = 1 and modify our code at the end of func _fixed_process(delta) and add one new function to get the real center position

1
2
3
4
5
6
7
8
9
#other code
func _fixed_process(delta):
#other code
if velocity.x != 0:
facing_dir = sign(velocity.x)
func get_center_pos():
return get_pos() + get_node("CollisionShape2D").get_pos()

Interpolate the Camera Movement

Now, we have information about player direction and the real center position. Back to camera.gd script and create 2 new variable called export var forward_offset = 60 and export var x_smoothing = .05, then modify our code into like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
extends Camera2D
export var target = "../Player"
export var forward_offset = 60
export var x_smoothing = .05
# onready prefix : Initializes a variable once the Node the script is attached to and its children are part of the scene tree.
onready var target_node = get_node(target)
func _ready():
# window maximized, comment this line code if you don't want to automatically maximized
OS.set_window_maximized(true)
var pos = target_node.get_pos()
# forcing set the position into character position
pos = Vector2(round(pos.x), round(pos.y))
set_pos(pos)
set_fixed_process(true)
func _fixed_process(delta):
var target_pos = target_node.get_center_pos() + Vector2(1, 0) * target_node.facing_dir * forward_offset
# smoothing the movement
target_pos.x = lerp(get_pos().x, target_pos.x, x_smoothing)
#gap it when the next position is out of bound
if abs(target_pos.x - target_node.get_center_pos().x) > forward_offset:
target_pos.x = target_node.get_center_pos().x + target_node.facing_dir * forward_offset * -1
# set the position into integer number
set_pos(Vector2(round(target_pos.x), round(target_pos.y)))

lerp also called as Linear Interpolation. When calling lerp(from, to, weight), if weight = 0 will return same as from, and if weight = 1 will return same as to

Camera Follow X Interpolation

Now, the camera position will in front of player position. Now, let’s interpolate the y axis, create 2 new variable export var max_y_offset = 5 and export var y_smoothing = .1 and add 3 line code

1
2
3
4
5
6
7
8
9
10
11
#other code
func _fixed_process(delta):
#other code
if abs(target_pos.x - target_node.get_center_pos().x) > forward_offset:
target_pos.x = target_node.get_center_pos().x + target_node.facing_dir * forward_offset * -1
target_pos.y = lerp(get_pos().y, target_pos.y + max_y_offset, y_smoothing)
if abs(target_pos.y - target_node.get_center_pos().y) > max_y_offset:
target_pos.y = target_node.get_center_pos().y + (sign(target_pos.y - target_node.get_center_pos().y) * max_y_offset)
set_pos(Vector2(round(target_pos.x), round(target_pos.y)))
Camera Follow Y Interpolation

Done! This is the final code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
extends Camera2D
export var target = "../Player"
export var forward_offset = 60
export var max_y_offset = 5
export var x_smoothing = .05
export var y_smoothing = .1
onready var target_node = get_node(target)
func _ready():
OS.set_window_maximized(true)
var pos = target_node.get_pos()
pos = Vector2(round(pos.x), round(pos.y))
set_pos(pos)
set_fixed_process(true)
func _fixed_process(delta):
var target_pos = target_node.get_center_pos() + Vector2(1, 0) * target_node.facing_dir * forward_offset
target_pos.x = lerp(get_pos().x, target_pos.x, x_smoothing)
#gap it when the next position is out of bound
#salah
if abs(target_pos.x - target_node.get_center_pos().x) > forward_offset:
target_pos.x = target_node.get_center_pos().x + target_node.facing_dir * forward_offset * -1
target_pos.y = lerp(get_pos().y, target_pos.y + max_y_offset, y_smoothing)
if abs(target_pos.y - target_node.get_center_pos().y) > max_y_offset:
target_pos.y = target_node.get_center_pos().y + (sign(target_pos.y - target_node.get_center_pos().y) * max_y_offset)
set_pos(Vector2(round(target_pos.x), round(target_pos.y)))

My player script variables values has been changed, if you want to follow my setup, here we go. You can also playing a bit with script variables, don’t be scared about that

Player Script Variables

Make sure your camera always at the last order from another node.

Next part, we will setup our setup our player again, animation, direction and other stuff for the next part

Download Project Here