Lesson 03

The grid, coordinates, and the Cell scene

GridFall is played on a grid, so we need two vocabularies: pixel coordinates (where things draw) and grid coordinates (row, column). This lesson builds the reusable Cell scene and the math that converts between the two.

▸ By the end of this lesson: A Cell scene you can place at any (column, row) and a Main scene that draws the empty well border

3.1Two coordinate systems, never confuse them

In Godot 2D, position is measured in pixels, with (0,0) at the top-left and y increasing downward. GridFall also thinks in grid cells: column 0..9, row 0..17. We convert with one constant — the cell size in pixels.

text
# grid (col, row)  ->  pixels (x, y)
# pixel_x = col * CELL_SIZE
# pixel_y = row * CELL_SIZE
#
# Example with CELL_SIZE = 32:
#   cell (3, 5)  ->  pixel (96, 160)

Vector2 and Vector2i

Godot bundles an (x, y) pair into a Vector2 (floats) or Vector2i (integers). Grid coordinates are whole numbers, so we use Vector2i for them and Vector2 for pixel positions.

3.2Build the Cell scene

A cell is one colored square. We'll make it once and reuse it hundreds of times.

  1. Scene menu → New Scene.
  2. Click "Other Node", choose Node2D as the root, rename it Cell.
  3. Right-click CellAdd Child NodeColorRect. This is the visible square.
  4. Select the ColorRect. In the Inspector set Size to (30, 30) (leaving a 2px gap inside a 32px cell), and pick any Color for now.
  5. Save as cell.tscn.

Now give the Cell a script so it can recolor itself on command. Attach a script to the root Cell node:

cell.tscn → cell.gdgdscript
extends Node2D
class_name Cell

# Grid size in pixels. One source of truth for the whole game.
const CELL_SIZE: int = 32

@onready var rect: ColorRect = $ColorRect

# Place this cell at a grid coordinate.
func set_grid_position(grid_pos: Vector2i) -> void:
	position = Vector2(grid_pos.x * CELL_SIZE, grid_pos.y * CELL_SIZE)

# Recolor the visible square.
func set_color(c: Color) -> void:
	# Guard in case this is called before _ready wires up the node.
	if rect:
		rect.color = c

class_name and @onready

class_name Cell registers this scene as a type you can reference from other scripts. @onready delays a variable's assignment until the node is in the tree, so $ColorRect actually exists. $ColorRect means "the child named ColorRect".

3.3Create the Main scene and the well constants

  1. Scene → New Scene → "Other Node" → Node2D as root, rename it Main.
  2. Save as main.tscn.
  3. Project → Project SettingsApplication/Run → set Main Scene to main.tscn. Now ▶ (F5) launches GridFall.

Attach a script to Main and define the well's dimensions. These constants drive everything downstream.

main.tscn → main.gdgdscript
extends Node2D

# --- Well dimensions (the play field) ---
const COLS: int = 10
const ROWS: int = 18
const CELL_SIZE: int = 32

# Pixel size of the whole well, handy for centering / borders.
const WELL_WIDTH: int = COLS * CELL_SIZE   # 320
const WELL_HEIGHT: int = ROWS * CELL_SIZE  # 576

func _ready() -> void:
	print("Well is %d x %d cells (%d x %d px)" % [COLS, ROWS, WELL_WIDTH, WELL_HEIGHT])
	_draw_border()

# Draw a simple frame so we can see the play field while building.
func _draw_border() -> void:
	var frame := ColorRect.new()
	frame.color = Color(0.15, 0.18, 0.22)   # dark slate
	frame.size = Vector2(WELL_WIDTH, WELL_HEIGHT)
	frame.position = Vector2.ZERO
	frame.z_index = -1                        # behind everything else
	add_child(frame)

String format operator

"%d x %d" % [a, b] substitutes values into a string. %d = integer, %s = anything, %.2f = float with 2 decimals. Get the placeholder count wrong and Godot errors at runtime — count them.

3.4Drop one Cell in by code to prove it works

Temporarily add this to the bottom of Main._ready() to confirm the Cell scene loads and positions correctly:

main.gd (inside _ready)gdscript
	# --- temporary test, remove after Lesson 3 ---
	var cell_scene: PackedScene = preload("res://cell.tscn")
	var c: Cell = cell_scene.instantiate()
	add_child(c)
	c.set_grid_position(Vector2i(4, 2))   # column 4, row 2
	c.set_color(Color.ORANGE)

res:// paths

res:// is the project root. preload() loads a scene at compile time and is the standard way to reference other scenes. If the path is wrong, Godot underlines it in the editor.

  1. Press F5. You should see the dark well frame with a single orange square four columns in and two rows down.
  2. Change the Vector2i(4, 2) numbers and re-run to feel how grid coordinates map to the screen.
  3. Once satisfied, delete the temporary test block — the real spawner comes in Lesson 5.

Checkpoint — expected state