Godot 3 to 4, TileMapLayer differences

Godot Version

4.5.1

Question

I am very new to Godot but have an idea for something I want to work on so found some template code I wanted to make use of.

Below is some code from Godot 3 and this works just fine. It was a demo project I found and i wanted to move it up to 4 so that i can build upon it.

const N = 1
const E = 2
const S = 4
const W = 8

var cell_walls = { Vector2i(0, -1): N, Vector2i(1, 0): E, Vector2i(0, 1): S, Vector2i(-1, 0): W }

func make_maze():
	print("making maze")
	var unvisited = []  # array of unvisited tiles
	var stack = []
	# fill the map with solid tiles
	Map.clear()
	for x in range(width):
		for y in range(height):
			unvisited.append(Vector2(x, y))
			Map.set_cellv(Vector2(x, y), N|E|S|W)
	var current = Vector2(0, 0)
	unvisited.erase(current)
	# execute recursive backtracker algorithm
	while unvisited:
		print("unvisited")
		var neighbors = check_neighbors(current, unvisited)
		if neighbors.size() > 0:
			var next = neighbors[randi() % neighbors.size()]
			stack.append(current)
			# remove walls from *both* cells
			var dir = next - current
			# This is getting the TileSet tile by ID, e.g. 15 == all grass tile
			var current_walls = Map.get_cellv(current) - cell_walls[dir]
			var cell_walls_a = cell_walls[dir]
			var current_a  = Map.get_cellv(current)
			var next_walls = Map.get_cellv(next) - cell_walls[-dir]
			print("Current walls: " + str(current_walls))
			print("Next walls: " + str(next_walls))
			Map.set_cellv(current, current_walls)
			Map.set_cellv(next, next_walls)
			current = next
			unvisited.erase(current)
		elif stack:
			print("pop_back")
			current = stack.pop_back()
		yield(get_tree(), 'idle_frame')

Godot 4 deprecated TileMaps and this does not work like for like. I have made some changes to it and it runs, but it does not generate anything. The issue is the current_walls is returning negative values as opposed to positive in 3. This is due to the way it retrieves or cannot retrieves the values for N | E | S | W in cell_walls.

I am struggling to understand what needs to change to get this to work the same way in 4. I’ve Googled so much and looked at the docs and the mostly what I come across is people complaining about the rework to TileMaps to TileMapLayers and it being more difficult to do than before.

Can anyone shed some light on what it is I need to amend?

What is Map?
Any errors with that script?

@onready var Map: TileMapLayer = $TileMapLayer

No errors. I fixed the initial errors and now it runs but it just never gets the correct value for current walls or next walls.

I added some print statements to aid me in debugging and this is what gets returned:

making maze
unvisited
Checking neighbours
Finished checking neighbours
Current walls: -3
Next walls: -9

How are TileMapLayer::set_cellv() and TileMapLayer::get_cellv() not causing errors? According to documentation, those functions do not exist.

Apologies, think there was some confusion maybe around the way I worded the question.

That code is from the Godot 3 version which works just fine. I am trying to work out what the equivalent function for get_cellv which in Godot 3, returns a value I am expecting in Godot 4.

As you say, that doesn’t work in 4 and I mistakenly it seems, thought it would have been get_cell_source_id but I keep getting negative values.

This is what I have in Godot 4, which does not cause any errors, but doesn’t draw anything.

func make_maze():
	print("making maze")
	var unvisited = [] # array of unvisited tiles
	var stack = []
	# fill the map with solid tiles
	Map.clear()
	for x in range(width):
		for y in range(height):
			unvisited.append(Vector2i(x, y))
			Map.set_cell(Vector2i(x, y), N | E | S | W)
	var current = Vector2i(0, 0)
	unvisited.erase(current)
	# execute recursive backtracker algorithm
	while unvisited:
		print("unvisited")
		var neighbors = check_neighbors(current, unvisited)
		if neighbors.size() > 0:
			var next = neighbors[randi() % neighbors.size()]
			stack.append(current)
			# remove walls from *both* cells
			var dir = next - current
			var _cell_walls_dir = cell_walls[dir]
			var current_walls = Map.get_cell_source_id(current - cell_walls[dir])
			var next_walls = Map.get_cell_source_id(next) - cell_walls[-dir]
			print("Current walls: " + str(current_walls))
			print("Next walls: " + str(next_walls))
			Map.set_cell(current, current_walls)
			Map.set_cell(next, next_walls)
			current = next
			unvisited.erase(current)
		elif stack:
			print("pop_back")
			current = stack.pop_back()

Look at the set_cell() reference in the docs to see what arguments it expects.

I am looking at that, but that’s not the first problem I am running into.

The code should as per version 3, be retrieving a positive integer which determines what tile it should draw for the maze. Current may = 14 for instance, which should be a bit of road going downwards into a dead end.

It is not returning a positive number because get_cellv doesn’t work in 4 and I don’t know how to get the equivalent function to work in 4.

This means when I am trying to determine the tile to draw, it is returning a negative int, -5 typically, but this doesn’t point to a tile, therefore nothing gets drawn.

It ends up in a loop of current where current walls and next walls equal the same value over and over again (it varies between -2, -5 and -3, -9), before it eventually stops.

#All the code from the working Godot version 3 where it successfully draws each tile
extends Node2D

const N = 1
const E = 2
const S = 4
const W = 8

var cell_walls = {Vector2(0, -1): N, Vector2(1, 0): E, 
				  Vector2(0, 1): S, Vector2(-1, 0): W}

var tile_size = 64  # tile size (in pixels)
var width = 20  # width of map (in tiles)
var height = 12  # height of map (in tiles)

# get a reference to the map for convenience
onready var Map = $TileMap

func _ready():
	randomize()
	tile_size = Map.cell_size
	make_maze()
	
func check_neighbors(cell, unvisited):
	# returns an array of cell's unvisited neighbors
	var list = []
	for n in cell_walls.keys():
		if cell + n in unvisited:
			list.append(cell + n)
	return list
	
func make_maze():
	print("making maze")
	var unvisited = []  # array of unvisited tiles
	var stack = []
	# fill the map with solid tiles
	Map.clear()
	for x in range(width):
		for y in range(height):
			unvisited.append(Vector2(x, y))
			Map.set_cellv(Vector2(x, y), N|E|S|W)
	var current = Vector2(0, 0)
	unvisited.erase(current)
	# execute recursive backtracker algorithm
	while unvisited:
		print("unvisited")
		var neighbors = check_neighbors(current, unvisited)
		if neighbors.size() > 0:
			var next = neighbors[randi() % neighbors.size()]
			stack.append(current)
			# remove walls from *both* cells
			var dir = next - current
			# This is getting the TileSet tile by ID, e.g. 15 == all grass tile
			var current_walls = Map.get_cellv(current) - cell_walls[dir]
			var cell_walls_a = cell_walls[dir]
			var current_a  = Map.get_cellv(current)
			var next_walls = Map.get_cellv(next) - cell_walls[-dir]
			print("Current walls: " + str(current_walls))
			print("Next walls: " + str(next_walls))
			Map.set_cellv(current, current_walls)
			Map.set_cellv(next, next_walls)
			current = next
			unvisited.erase(current)
		elif stack:
			print("pop_back")
			current = stack.pop_back()
		yield(get_tree(), 'idle_frame')

#The code from the Godot 4 version which doesn't draw anything at all.
extends Node2D

const N = 1
const E = 2
const S = 4
const W = 8

var cell_walls = { Vector2i(0, -1): N, Vector2i(1, 0): E, Vector2i(0, 1): S, Vector2i(-1, 0): W }

var tile_size = 64
var width = 10
var height = 10

@onready var Map: TileMapLayer = $TileMapLayer
@onready var tile_set = Map.tile_set


# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	randomize()
	tile_size = Map.tile_set.get_tile_size()
	make_maze()


func check_neighbors(cell, unvisited):
	print("Checking neighbours")
	# returns an array of cell's unvisited neighbors
	var list = []
	for n in cell_walls.keys():
		if cell + n in unvisited:
			list.append(cell + n)
	print("Finished checking neighbours")
	return list


func make_maze():
	print("making maze")
	var unvisited = [] # array of unvisited tiles
	var stack = []
	# fill the map with solid tiles
	Map.clear()
	for x in range(width):
		for y in range(height):
			unvisited.append(Vector2i(x, y))
			Map.set_cell(Vector2i(x, y), N | E | S | W)
	var current = Vector2i(0, 0)
	unvisited.erase(current)
	# execute recursive backtracker algorithm
	while unvisited:
		print("unvisited")
		var neighbors = check_neighbors(current, unvisited)
		if neighbors.size() > 0:
			var next = neighbors[randi() % neighbors.size()]
			stack.append(current)
			# remove walls from *both* cells
			var dir = next - current
			var _cell_walls_dir = cell_walls[dir]
			var current_walls = Map.get_cell_source_id(current) - cell_walls[dir]
			var next_walls = Map.get_cell_source_id(current) - cell_walls[-dir]
			print("Current walls: " + str(current_walls))
			print("Next walls: " + str(next_walls))
			Map.set_cell(current, current_walls)
			Map.set_cell(next, next_walls)
			current = next
			unvisited.erase(current)
		elif stack:
			print("pop_back")
			current = stack.pop_back()
		#await get_tree().process


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
	pass

current_walls and next_walls are the problematic areas in V4.

In 3.x the second argument to set_cell() was tile id. This is not the case in 4.x any more. The tiles are identified using atlas coordinates instead of ids. The second argument is source id which represent the atlas (or scene collection) the tiles are drawn from.

1 Like

But is that the current cause of the issues I have when the following is always returned?
Current walls: -3
Next walls: -9

Setting the cell may be incorrect, but before I even get to that stage, I do not have a valid value.

It likely is one of the main causes. Hard to tell for sure without seeing how you set up your tile set.