How to declare 2d arrays / matrices in GDScript?

Godot Version

4.2.1

Question

I want to implement an adjacency matrix for a custom graph data structure. How can I declare 2d arrays / matrices in GDScript? When I try

var _adjacency_matrix: Array[Array[int]]

It says

Error at (3, 40): Nested typed collections are not supported.

Nested typed arrays aren’t supported yet. The most you can do is var _array:Array[Array]

1 Like

So I suppose this is the way to go for now? Are there plans to add support for nested typed arrays in the future?

var _adjacency_matrix: Array[Array]

func setup(p_size: int) -> void:
	size = p_size
	
	for i in range(size):
		_adjacency_matrix.append([]) 
		for j in range(size):
			_adjacency_matrix[i].append(j)


func populate() -> void:
	var n = 0
	for i in range(size):
		for j in range(size):
			n += 1
			var vertex := Vertex.new()
			vertex.label = str(n)
			
			_adjacency_matrix[i][j] = vertex
3 Likes

Currently my solution is to use a dictionary with a Vector2I as the key, the downside is that you lose some performance in lookups but depending on how fast you need it, might be a valid solution.

var grid = Dictionary[Vector2I, int]

That said you could just use linear arrays like mentioned. And remember if you know the size, you can calculate always calculate the linear index from the X,Y coordinate for example

# Note my for loops assume you want to go 
# "right-to-left" and "top-to-bottom"
# 0,0 being top left
var size : Vector2i = Vector2i(10, 15)
var _adjacency_matrix: Array[Array]

func setup(p_size: Vector2i) -> void:
	size = p_size
	_adjacency_matrix = Array[size.x * size.y]

func populate():
	for y in range(size.y):
		for x in range(size.x):
			var index = x + (y * size.x)
			var vertex := Vertex.new()
			vertex.label = str(index)
			_adjacency_matrix[index] = vertex

gdscript does not support multi-dimensional arrays and it might never do.
you have to pretend this is C and hard code it yourself.

luckily I already did, here’s my Voxel resource that I used to handle 2D matrices, you can adapt it to your needs (use of int):

class_name Voxel
extends Resource

var map : Array[int] = []#TODO multiple voxels for layers same resource
var voxel_height : Array[int] = []
var map_size : int = 64#size of matrix
var hmapsiz : int = 32#half size of matrix

func _init(mapsize : int) -> void:#new() with argument size of same side square. this can be done differently for uneven squares.
	map_size = mapsize
	@warning_ignore("integer_division")
	hmapsiz = map_size / 2
	populate_voxel()#generate matrix fill with 0

#func _restart(MapSize : int) -> void:
	#map_size = MapSize
	#map.clear()
	#_initialize(mapsize)

func populate_voxel() -> void:#I'm using 2 identical matrices
	for i in range(map_size):
		for j in range(map_size):
			map.append(0)
			voxel_height.append(0)#you can remove code for one of these

func set_voxel(pos : Vector2i, value : int, type : int = 0) -> void:#set item at pos in array ID type
	match type:
		1:
			if len(voxel_height) > pos.x * pos.y:#prevent overflow
				voxel_height[(pos.y * map_size) + (pos.x)] = value
		_:
			if len(map) > pos.x * pos.y:
				map[(pos.y * map_size) + (pos.x)] = value

#NOTE godot arrays can accept negative values for some stupid reason
func get_voxel(pos : Vector2i, type : int = 0) -> int:
	match type:
		1:
			if len(voxel_height) > pos.x * pos.y:
				return voxel_height[(pos.y * map_size) + (pos.x)]
			else:
				return voxel_height[0]
		_:
			if len(map) > pos.x * pos.y:
				return map[(pos.y * map_size) + (pos.x)]
			else:
				return map[0]

func check_inside(cpos : Vector2i) -> bool:#this ensures a tile has tiles around it
	return cpos.x > 1 and cpos.x < map_size-1 and cpos.y > 1 and cpos.y < map_size-1

Multidimensional arrays are relatively easy to embed in regular arrays, particularly if your array dimensions are power of two in size.

Say we want an array with dimensions (3, 7):

var x_size = 3
var y_size = 7
var cells  = x_size * y_size

var array = []

func init_array():
    for i in cells:
        array.append(0)

func dump():
    for y in y_size:
        for x in x_size:
            print("%d, %d -> %d" % [x, y, array[(y * x_size) + x]])

func get(x: int, y: int) -> int:
    var idx = (y * x_size) + x
    return array[idx]

func set(x: int, y: int, val: int):
    var idx = (y * x_size) + x
    array[idx] = val

If your array sizes are powers of two, you can do this with shifts rather than multiplication, which is much faster:

var x_size  = 64
var y_size  = 32
var x_shift =  6
var x_mask  = x_size - 1

var array = []

func get(x: int, y: int) -> int:
    return array[(y << x_shift) + x]

func set(x: int, y: int, val: int):
    array[(y << x_shift) + x] = val

func index_to_coords(index: int) -> Vector2i:
    return Vector2i(index >> x_shift, index & x_mask)

func coords_to_index(coords: Vector2i) -> int:
    return coords.x | (coords.y << x_shift)

Note that with this scheme, if you want to loop over coordinates, it’s better to have y in the outer loop; that will result in linear access to memory:

var x_size = 4
var y_size = 4

for y in y_size:
    for x in x_size:
        print((y * x_size) + x)
---
0
1
2
3
4
5...
---
for x in x_size:
    for y in y_size:
        print((y * x_size) + x)
---
0
4
8
12
1
5...

Modern systems run much faster with linear sequential memory access.

1 Like