This tutorial explains how to implement “grid-based movement” in 2D games. Grid-based movement refers to the movement of objects such as characters on the game screen one grid at a time.

Many puzzle games such as the popular smartphone game “Puzzle & Dragons” and the original falling game “Tetris,” as well as tactical simulation games such as the “Fire Emblem” series and “Tactics Ogre” probably employ this type of movement.

On the other hand, RPGs such as the “Final Fantasy” series and the “Dragon Quest” series also use this grid-based movement when looking closely at character movement in the era of 2D graphics, when these games appeared as software for the NES and Super Nintendo.

Thus, grid-based movement is used in games of various genres, and its versatility is quite high.

In this article, we will focus as much as possible on only the implementation of grid-based movement. In addition, a sample game that uses grid-based movement will be introduced at the end of the article, so please feel free to refer to it as well.

Note that the project file that will be created at the end of this tutorial is located in the GitHub repository . You can also directly check the project by downloading the .zip file and importing the “project.godot” file in the “End” folder with the Godot Engine.


Environment
This tutorial was created in the following environment

Godot version: 3.4.4
Computer OS version: macOS 11.6.5


Memo:
Please also use the following articles to help you start creating your game.
Downloading Godot
Project Manager of Godot



Creating a new project

First, we would like you to start Godot Engine and create a new project. Let’s name the project “Grid Based Movement Tutorial”.

When the editor appears, set the display size of the game first.

  1. Open the “Project” menu > “Project Settings”.
  2. Search for “window” and select “Display” > “Window” in the sidebar.
  3. In the “Size” section, change the values of the following items.
    • Width: 256
    • Height: 160
    • Test Width: 1024
    • Test Height: 640
      Display>Window>Size
  4. In the “Stretch” section, change the values of the following items.
    • Mode: 2d
    • Aspect: keep
      Display>Window>Stretch

Next, let’s download the assets from KENNEY’s site and use them. This time, We will use an asset pack called 1-Bit Pack . I can’t help but be thankful for this wonderful free material.

After downloading, drag and drop the “colored-transparent_packed.png” file in the “Tilesheet” folder into the editor’s file system dock to import it into your project.

Immediately after importing the file, the image looks blurry. The following steps will give the image the edges characteristic of pixel art.

  1. Make the imported asset file selected in the file system dock.
    select the asset
  2. Select “Preset” > “2D Pixel” in the import dock.
    select 2D Pixel
  3. Click the “Re-import” button at the bottom.
    click reinport


Creating World scene

The first scene is the game world. Let’s create a scene named “World”.

  1. Select “Scene” menu > “New Scene”.
  2. Select “Other Node” in “Generate Root Node”.
  3. Select a node of the “Node2D” class as the root node.
  4. Rename the root node to “World”.
  5. Save the scene at this point. Create a folder and save the scene with the file path “res://World/World.tscn”.
  6. Add a “TileMap” node to the root node.

The scene tree should now look like this.
World Scene Tree



Editing the TileMap node

Editing the properties of the TileMap node

Select the “TileMap” node in the scene tree dock and edit its properties in the inspector.

  1. Assign a “New TileSet” resource to the “TileSet” property.
    TileSet property
  2. Set the value of the “Cell” > “Size” property to (x: 16, y: 16). The size was set to match the size of the texture (16 px height and width) of the sprite sheet that will be used.
    Cell_size property

Editing the TileSet resource

Continue to edit the resource “TileSet” assigned to the “TileSet” property.

  1. Click on the resource in the inspector.
    Click TileSet resource
  2. When the “TileSet” panel at the bottom of the Godot editor opens, drag the sprite sheet resource “res://colored-transparent_packed.png” from the file system dock to the left sidebar in the panel and add it. Click on the added resource to put it in edit mode.
    TileSet Pannel
  3. Select “New Single Tile”.
    New Single Tile
  4. With the “Region” tab selected, activate grid snapping.
    Enable grid snap
  5. In the inspector, set “Snap Options” > “Step” to (x: 16, y: 16), which is the same size as one texture on the sprite sheet.
    Snap options
  6. select the “grass” and “tree” texture areas in order and register them as Single Tile.
    Region
  7. For the “Tree” tile, select the “Collision” tab and set the collision shape as well.
    Collision tab Collision

This completes the editing of the TileSet resource.


Creating a TileMap

From here, we will create a TileMap by placing tiles from the “TileSet” resource created earlier.

  1. Select the “TileMap” node in the scene tree dock.
  2. Activate grid snap from the 2D workspace toolbar.
    Enable Grid Snap
  3. Click “Snapping Options” on the toolbar and select “Configure Snap”.
    Snapping options
  4. When the “Configure Snap” setting panel opens, set “Grid Step” to (x: 16, y: 16) and click “OK”.
    Configure Snap

You should now see a grid separated by 16px in height and width on the 2D workspace. Since “Grid Snap” is enabled, you should be able to easily place tiles along the grid when editing the “TileMap”.


Now we will place tiles in the 2D workspace. Let’s simply surround the screen with “tree” tiles and place “grass” tiles inside them so that the player character, which will be placed later, will not be outside the screen.
TileMap


This completes the editing of the “TileMap”.



Creating Player Scene

From here, we will create a scene for the player character. This object will actually be used to move the grid base in this tutorial.

  1. Select “Scene” menu > “New Scene”.
  2. Select “Other Node” under “Generate Root Node”.
  3. Select a node of the “KinematicBody2D” class as the root node.
  4. Rename the root node to “Player”.
  5. Save the scene at this point. Create a folder and save the scene with the file path “res://Player/Player.tscn”.
  6. Add a “Sprite” node to the root node.
  7. Add a “CollisionShape2D” node to the root node.
  8. Add a “RayCast2D” node to the root node.

The “Player” scene tree should now look like this.
Player Scene Tree


Editing each node in the Player scene

Sprite node

The method of setting the texture of a sprite by specifying the range of textures you want to use from a single sprite sheet that contains many textures is used.

  1. In the Inspector, apply the resource file “res://colored-transparent_packed.png” to the “Texture” property.
    Sprite node Texture property
  2. Turn off the “Offset” > “Centered” property. This will make the position of this node (“Position” property) in the upper left corner of the texture instead of the center so that it just fits in the grid square.
    Offset > Centered = on
  3. Turn on “Region” > “Enabled”.
    Region > Enabled = on
  4. Open the “Texture Region” panel at the bottom of the editor. The task here is to specify the region of the texture you want to use in the sprite sheet.
    Region panel
    1. Expand the panel by clicking on the expand icon to make it easier to work with.
      Expand Region pannel
    2. Select “grid snap” under “snap mode” at the top of the panel.
      Region pannel > choose grid snap
    3. Set the “step” at the top of the panel to 16px 16px. This will make the grid the same size as one texture on the sprite sheet.
      Region pannel > input grid step
    4. Select the “king” texture by dragging on the sprite sheet.
      Select region

CollisionShape2D node

This node is used to set the collision shape for the root node “Player”.

  1. In the inspector, apply the “New RectangleShape2D” resource to the “Shape” property.
    Shape property
  2. Set the Transform > Position property to (x: 8, y: 8) to match the texture position of the Sprite node.
    Position property
  3. In the 2D workspace, match the collision shape to the size of the “Sprite” node texture.
    CollisionShape in 2D workspace
  4. To enter directly in the inspector, the properties of the “RectangleShape2D” resource should be as follows.
    • Extents: (x: 8, y: 8)

RayCast2D node

This node is very useful for collision detection in grid-based movement, represented as an arrow-shaped collision shape in the 2D workspace. When the arrow and an object overlap, a collision is detected. This can be used to detect collisions with objects in front of the player character and prevent it from moving forward.

  1. In the Inspector, set the “Enabled” property to On. This will enable collision detection.
    RayCast2D Enabled=on
  2. Set the “Transform” > “Position” property to (x: 8, y: 8). This is to center the “Sprite” texture.
    Position property
  3. Set the “Cast To” property to (x: 16, y: 0). This is just a temporary initial value, and once the project is actually executed, the value will be changed each time the player character is moved by the script.
    RayCast2D Cast To On the 2D workspace it should now look like the following screenshot.
    RayCast2D 2D Workspace
  4. Leave “Collide With” as default, “Areas” Off, and “Bodies” On. The “Tree” panel in the tileset you just created is a physical body, so if “Bodies” is checked, collision will be detected.
    RayCast2D Collide With - Areas: Off, Bodies: On


Implementing grid-based movement in the Player node

Configuring the Input Map

First, add an action to the Input Map in the Project Settings so that the player character can be moved by keyboard keystrokes.

  1. switch to the “Input Map” tab after selecting “Project” menu > “Project Settings”.
  2. add the following 4 “Actions”.
    • move_right: D key
    • move_left: A key
    • move_down: S key
    • move_up: W key

*It is also OK to assign Up, down, left, and right arrow keys.
Project Settings - Input Map


Attaching and editing the script

Attach a script to the root node “Player”. Create a script file with the file path “res://Player/Player.gd”.

After creating the script file, open the script editor and edit the script code as follows.

extends KinematicBody2D

#1
const inputs = {
	"move_right": Vector2.RIGHT,
	"move_left": Vector2.LEFT,
	"move_down": Vector2.DOWN,
	"move_up": Vector2.UP
}

#2
var grid_size = 16

#3
onready var raycast = $RayCast2D

#4
func _unhandled_input(event):
	for action in inputs.keys():
		if event.is_action_pressed(action):
			move(action)

#5
func move(action):
	var destination = inputs[action] * grid_size
	raycast.cast_to = destination
	raycast.force_raycast_update()
	if not raycast.is_colliding():
		position += destination

I numbered the scripts #1 ~ #5 in the comments. I will explain in this order.

#1: Defined constants inputs of dictionary type. The value of the inputs is the direction vector of Vector2 type (vector of length 1) to be moved by each action. By the way, the built-in constants (RIGHT, LEFT, DOWN and UP) of the Vector2 class have the following values.

  • RIGHT: Vector2(1, 0)
  • LEFT: Vector2(-1, 0)
  • DOWN: Vector2(0, 1)
  • UP: Vector2(0, -1)

#2: Defined the property grid_size. The value is set to 16, the same as the size (px) of tiles in “TileMap”.

#3: Defined the property raycast. This is a property that refers to the “RayCast2D” node.


#4: Override the built-in function _unhandled_input. This is a callback function that is called as soon as there is input from the keyboard, mouse, joystick, etc. It is similar to another function _input, but don’t worry about the details of the difference here.

In _unhandled_input, a loop is performed on the dictionary type constants inputs defined earlier. If the input is an input map action with the same name as a key in inputs (such as move_left or move_up), the value for that key (for example, Vector2.RIGHT if the key is move_right) is passed as an argument and the method move RIGHT), and calls the method named move. This method move will be defined later.

#5: The method move is defined. When calling this method, it is necessary to pass a value for the argument action.

The variable destination is defined first. The value is the value for the key that matches the argument action from the dictionary type constant inputs (for example, Vector2.LEFT if action is passed move_left) multiplied by the property grid_size. In other words, if the player enters the “D” key, the value will be Vector2(1, 0) x 16 = Vector2(16, 0). This is a vector with a length of one grid in the right direction.

Next, the value of the variable destination defined earlier is passed to the cast_to property of the “RayCast2D” node. This replaces the direction and length of the “RayCast2D” arrow with the key entered by the player. The method force_raycast_update is called in the next line to immediately update this replacement. This is a built-in `RayCast2D’ node.

The next line describes the if syntax. The built-in method is_colliding of the “RayCast2D” node returns a Bool type (true or false) whether this node (arrow) is currently colliding with an object. Since there is a not after the if, the meaning of this if syntax is “if the RayCast2D node is not colliding with an object”.

If the RayCast2D node has not collided with other objects in the above if syntax, the value of destination is added to the value of the property position of the Player node. This means that the player character will move by the value of destination. For example, if the current position of the “Player” node is Vector2(64, 32), and the player presses the “S” key once, the player moves Vector2(0, 16) from the current position, and the destination position is Vector2(64, 48). In other words, the position is moved one grid position down from the current position.

Now, the grid-based movement control should be implemented.



Adding a Player scene instance to the World scene

Now that the “Player” scene is complete, let’s add an instance of it to the “World” scene and move it around on the tile map.

  1. Open the “World.tscn” scene.
  2. Add an instance of the “Player.tscn” scene to the root node “World.
    Instance child scene
  3. In the 2D workspace, move the “Player” node to an appropriate position around the center of the screen.
    Move Player node in 2D workspace

The work is now complete.



Checking the operation of the grid-based movement

Let’s check if the grid-based movement actually works without any problems.

In particular, to make it easier to check the arrow-shaped collision shapes of the “RayCast2D” child node of “Player,” let’s check the “Visible Collision Shapes” checkbox in the “Debug” menu and enable it first.
Debug menu - Visible Collision Shapes: On

Now, let’s run the project and see if the king can run around in the prairie with grid-based movement. If this is your first time executing the project, simply select “res://World/World.tscn” for the Main Scene.
Run project


It can be clearly seen that a single keystroke moves the character only one tile. We can also confirm that the character cannot move in the direction of the “tree” tile.

This completes the grid-based moving tutorial.


Sample game

We have prepared a sample game that uses this grid-based movement. The project file is located in GitHub repository , so you can download the .zip file from there and import the “project .godot” file in the “Sample” folder and import it into Godot Engine.



This sample is a so-called whack-a-mole game. In the game’s setup, the player controls a king who holds up a sacred ring to the ghosts of the kingdom’s soldiers who emerge from their graves to release their cursed souls.

The keyboard controls are as follows

  • D: Move right
  • A: Move left
  • S: Move down
  • W: Move up
  • Space: Raise the ring to release the ghost’s soul only when facing the ghost.

The ghost must be released within 1 second by the ring or the king will be cursed. If the king is cursed, his Life is reduced by one. The king is cursed to death and the game is over when all 10 lives are lost.

The interval between the appearance of the next ghost is gradually shortened from 2 seconds at the beginning of the game to as short as 1 second.

By the way, my highest score is 150. I don’t know if this is great or not.

Supplemental Explanation

Finally, I would like to add some additional information about this sample game project.

During the game, when the ghost appears from the tombstone, the “Ghost” instance with the collision shape set in the “KinematicBody2D” class is overlapping the tombstone tiles with the “TileMap” collision set. In this case, “RayCast2D” of the “Player” instance is slightly modified to give priority to the collision detection with the ghost over the tombstone.

When creating the tombstone tiles by editing the “TileSet,” a collision of 1/2 the size of the tiles is set. It is easier to set the collision shape after setting “Snap Options” > “Step” to (x: 4, y: 4) in the inspector first. Of course, the collision shape of “Ghost” is (x: 16, y: 16).

Snap Option - Step Tomb collision setting in TileSet



Conclusion

In this tutorial, we have explained the implementation of grid-based movement in 2D games. We hope you will find it useful for your puzzle games, simulation games, and other projects you are working on in the future.

Let’s summarize the key points of grid-based movement.

  1. Set up the following correctly.
    • Size of the tiles prepared in the “TileSet” resource (height and width)
    • Size and position of the sprite of the object to be moved (in this case, “Player”)
    • Size and position of the collision shape of the object to be moved
    • Grid Step in the 2D workspace
    • The value of the property (in this case grid_size) represents the distance to be moved by the script attached to the object’s node.
  2. Use “RayCast2D” to determine the collision between the object to be moved and other objects. The direction and size of “RayCast2D” are controlled by scripts.
  3. To move objects by player manipulation, register actions in the input map, and control the direction of movement for each input in scripts.




UPDATE
2022/06/09 Translated from Japanese into English