Saltar al contenido principal

Módulo GridMap

El módulo GridMap proporciona una estructura de datos de cuadrícula 2D optimizada para juegos y aplicaciones que requieren mapas de tiles, pathfinding y detección de colisiones basada en celdas. Cada celda almacena un valor entero de 0 a 255, permitiendo representar diferentes tipos de terreno, obstáculos o estados.

Captura del módulo GridMap de JARU para crear mapas de tiles, niveles y rutas en juegos

Este módulo es especialmente útil para desarrollar juegos tipo arcade, puzzles basados en cuadrículas, simulaciones de mapas y cualquier aplicación que necesite navegación inteligente entre puntos.

Uso

use GridMap

Constructor

new

La función GridMap.new(ancho, alto) crea una nueva cuadrícula con las dimensiones especificadas. Todas las celdas se inicializan a 0.

use GridMap

// Crear una cuadrícula de 20x15 celdas
var mapa = GridMap.new(20, 15)
ParámetroTipoDescripción
anchointegerNúmero de columnas de la cuadrícula
altointegerNúmero de filas de la cuadrícula

load

La función GridMap.load(archivo) carga un mapa desde un fichero .gmap.

var mapa = GridMap.load("nivel1.gmap")
ParámetroTipoDescripción
archivostringRuta al fichero .gmap

Retorno

Devuelve un objeto GridMap con los datos cargados del fichero, incluyendo dimensiones y tamaño de tile.


Propiedades

Las propiedades se acceden y asignan directamente como campos del objeto.

x / y

Posición del mapa en el espacio de pantalla. Útil cuando se renderiza con draw.gridBitmap o colisiones con offset.

mapa.x = 32
mapa.y = 16
println(mapa.x) // 32.0
PropiedadTipoDescripción
xfloatPosición horizontal del mapa
yfloatPosición vertical del mapa

wrapX / wrapY

Activan el movimiento toroidal (los bordes del mapa se conectan). Cuando están activos, las operaciones de pathfinding y colisiones los tienen en cuenta automáticamente.

mapa.wrapX = true   // Los bordes izquierdo y derecho se conectan
mapa.wrapY = false
println(mapa.wrapX) // true
PropiedadTipoDescripción
wrapXbooleanConecta el borde derecho con el izquierdo
wrapYbooleanConecta el borde inferior con el superior

solids

Lista de valores de celda considerados sólidos (obstáculos). Las funciones path, dMap, nearest y hits usan esta lista automáticamente si no se les pasan parámetros adicionales.

mapa.solids = [1, 2, 3]   // Los tiles 1, 2 y 3 son sólidos
println(mapa.solids) // [1, 2, 3]
mapa.solids = nil // Sin sólidos definidos
PropiedadTipoDescripción
solidslist | nilLista de valores de celda sólidos

platforms

Lista de valores de celda que actúan como plataformas unidireccionales (solo bloquean desde arriba). Usada por hits.

mapa.platforms = [4]
PropiedadTipoDescripción
platformslist | nilLista de valores de celda para plataformas one-way

tileset

Bitmap vinculado al mapa, usado por draw.gridMap para renderizar automáticamente los tiles.

var ts = Bitmap.load("tileset.bmp")
mapa.tileset = ts
println(mapa.tileset) // <bitmap>
mapa.tileset = nil // Desvincular
PropiedadTipoDescripción
tilesetBitmap | nilTileset asociado al mapa

bouncy / elasticity

Controlan el comportamiento de rebote al resolver colisiones con sprites mediante hits.

mapa.bouncy = true
mapa.elasticity = 0.8
PropiedadTipoDescripción
bouncybooleanActiva el rebote al colisionar
elasticityfloatFactor de rebote (0.0 = sin rebote, 1.0 = rebote total)

Métodos de instancia

set

El método set(x, y, valor) establece el valor de una celda específica.

mapa.set(5, 3, 1)   // Pared
mapa.set(0, 0, 0) // Suelo vacío
mapa.set(2, 0, 5) // Punto de interés
ParámetroTipoDescripción
xintegerCoordenada X (columna)
yintegerCoordenada Y (fila)
valorintegerValor a asignar (0-255)

get

El método get(x, y) obtiene el valor de una celda específica.

var valor = mapa.get(5, 3)
println("Valor en (5,3): ", valor)
ParámetroTipoDescripción
xintegerCoordenada X (columna)
yintegerCoordenada Y (fila)

Retorno

Devuelve el valor entero (0-255) almacenado en la celda. Lanza excepción si las coordenadas están fuera de rango.

width / height

Los métodos width() y height() devuelven las dimensiones de la cuadrícula.

println("Ancho: ", mapa.width())
println("Alto: ", mapa.height())

clear

El método clear(valor) establece todas las celdas al valor especificado.

mapa.clear(0)   // Limpiar todo el mapa
mapa.clear(1) // Llenar todo de paredes
ParámetroTipoDescripción
valorintegerValor a asignar a todas las celdas (0-255)

clone

El método clone() crea una copia profunda de la cuadrícula.

var copia = mapa.clone()
copia.set(0, 0, 99)
println(mapa.get(0, 0)) // Valor original inalterado
println(copia.get(0, 0)) // 99

Retorno

Devuelve un nuevo objeto GridMap con los mismos valores que el original.

tSize

El método tSize() / tSize(ancho, alto) actúa como getter/setter del tamaño en píxeles de cada tile. Este valor se almacena en el fichero .gmap y es usado por el sistema de renderizado.

mapa.tSize(16, 16)           // Establecer tiles de 16x16 px
var tam = mapa.tSize() // Obtener -> [16, 16]
println(tam[0], "x", tam[1])
UsoParámetrosRetorno
Getterninguno[tileAncho, tileAlto] (list)
Setterancho:integer, alto:integer (1-255)nil

tEmpty

El método tEmpty() / tEmpty(valor) actúa como getter/setter del índice de tile vacío. Las celdas con ese valor no se dibujan al renderizar el mapa.

mapa.tEmpty(0)           // El tile 0 es el "vacío" (por defecto: 255)
println(mapa.tEmpty()) // 0
UsoParámetrosRetorno
Getterningunointeger (0-255)
Settervalor:integernil

Métodos de consulta

count

El método count(valor) cuenta cuántas celdas tienen un valor específico.

var numParedes = mapa.count(1)
println("Paredes: ", numParedes)
ParámetroTipoDescripción
valorintegerValor de celda a buscar (0-255)

Retorno

Devuelve un entero con el número de celdas que tienen ese valor.

has

El método has(valor) comprueba si existe al menos una celda con el valor dado.

if (mapa.has(5)) then
println("Quedan puntos en el mapa")
end
ParámetroTipoDescripción
valorintegerValor de celda a buscar (0-255)

Retorno

Devuelve true si existe al menos una celda con ese valor, false en caso contrario.

find

El método find(valor) busca todas las celdas con un valor específico y devuelve sus coordenadas.

var posiciones = mapa.find(5)   // Todas las celdas con valor 5
for (var i = 0; i < posiciones.size(); i += 2)
var x = posiciones[i]
var y = posiciones[i + 1]
println("Punto en (", x, ", ", y, ")")
end
ParámetroTipoDescripción
valorintegerValor de celda a buscar (0-255)

Retorno

Devuelve una lista plana [x1, y1, x2, y2, ...] con las coordenadas de todas las coincidencias (en orden fila por fila). Lista vacía si no hay ninguna.

around

El método around(x, y [, usar8 [, permitirWrap]]) obtiene los valores de las celdas vecinas a una posición.

var vecinos4 = mapa.around(5, 3)            // 4 vecinos cardinales
var vecinos8 = mapa.around(5, 3, true) // 8 vecinos (+ diagonales)
var vecinosW = mapa.around(0, 0, false, true) // 4 vecinos con wrap toroidal
ParámetroTipoDescripción
xintegerCoordenada X de la celda central
yintegerCoordenada Y de la celda central
usar8boolean(Opcional) Si true, incluye las 4 diagonales. Por defecto: false
permitirWrapboolean(Opcional) Si true, los bordes se conectan. Por defecto: false

Retorno

Devuelve una lista con los valores de las celdas vecinas válidas. Sin wrap, las celdas fuera de los límites se ignoran (la lista puede tener menos de 4/8 elementos).

see

El método see(x1, y1, x2, y2, bloqueantes) comprueba si hay línea de visión libre entre dos celdas usando el algoritmo de Bresenham.

// ¿Puede el jugador ver al fantasma? Las paredes (valor 1) bloquean
if (mapa.see(jugX, jugY, fanX, fanY, 1)) then
println("El fantasma te ve")
end

// Múltiples valores bloqueantes
if (mapa.see(jugX, jugY, fanX, fanY, [1, 2, 3])) then
println("Línea de visión libre")
end
ParámetroTipoDescripción
x1, y1integerCoordenadas de origen
x2, y2integerCoordenadas de destino
bloqueantesinteger | list | arrayValores de celda que bloquean la visión

Retorno

Devuelve true si el trayecto entre origen y destino no pasa por ninguna celda bloqueante, false en caso contrario.


Métodos de pathfinding

Los métodos de pathfinding derivan automáticamente las celdas transitables a partir de la propiedad solids del mapa (transitable = NO sólido). El wrap toroidal se toma de las propiedades wrapX y wrapY. Si se necesita un comportamiento diferente, los métodos dMap y nearest admiten parámetros opcionales para especificar manualmente los transitables y el wrap.

path

El método path(sx, sy, gx, gy) encuentra el camino más corto entre dos puntos usando BFS.

// Definir qué tiles son sólidos (obstáculos)
mapa.solids = [1]

var camino = mapa.path(0, 0, 10, 10)

if (camino != nil) then
println("Camino con ", camino.size() / 2, " pasos")
for (var i = 0; i < camino.size(); i += 2)
println(" -> (", camino[i], ", ", camino[i+1], ")")
end
else
println("No hay camino posible")
end
ParámetroTipoDescripción
sxintegerCoordenada X de inicio
syintegerCoordenada Y de inicio
gxintegerCoordenada X de destino
gyintegerCoordenada Y de destino

Retorno

Devuelve una lista plana [x0, y0, x1, y1, ..., gx, gy] con las coordenadas del camino, o nil si no existe camino posible o si el origen/destino no son transitables.

Celdas transitables

Las celdas transitables se calculan automáticamente como el complemento de mapa.solids. Asegúrate de asignar mapa.solids antes de llamar a path. El wrap se toma de mapa.wrapX y mapa.wrapY.

dMap

El método dMap(targets [, transitables [, permitirWrap]]) construye un mapa de distancias BFS desde todas las celdas que tengan los valores objetivo.

// Mapa de distancias al tipo de celda 5 (puntos/píldoras)
mapa.solids = [1]
var distancias = mapa.dMap(5)

// Consultar distancia desde cualquier posición
var dist = distancias.get(jugadorX, jugadorY)
println("Distancia al punto más cercano: ", dist)

// Con múltiples tipos de objetivo
var distancias2 = mapa.dMap([5, 6])

// Con transitables y wrap manual
var distancias3 = mapa.dMap(5, [0, 5, 6], true)
ParámetroTipoDescripción
targetsinteger | list | arrayValor(es) de celda desde los que se mide la distancia
transitableslist | array(Opcional) Valores de celda por los que se puede pasar
permitirWrapboolean(Opcional) Permite movimiento toroidal

Retorno

Devuelve un nuevo GridMap donde cada celda contiene la distancia mínima al target más cercano. Las celdas inalcanzables tienen el valor 255.

Modo automático

Con un solo argumento, dMap deriva los transitables de mapa.solids (transitable = NO sólido) y el wrap de mapa.wrapX/mapa.wrapY.

nearest

El método nearest(targets, sx, sy [, transitables [, permitirWrap]]) encuentra el camino más corto desde una posición hasta la celda más cercana que tenga uno de los valores objetivo.

// Posición del jugador: ¿cuál es el punto más cercano?
mapa.solids = [1]
var camino = mapa.nearest(5, jugadorX, jugadorY)

if (camino != nil) then
// El último par de valores es la posición del objetivo encontrado
var n = camino.size()
var tx = camino[n - 2]
var ty = camino[n - 1]
println("Punto más cercano en (", tx, ", ", ty, ")")
println("Pasos: ", n / 2)
else
println("No hay puntos alcanzables")
end
ParámetroTipoDescripción
targetsinteger | list | arrayValor(es) de celda que se buscan
sxintegerCoordenada X de inicio
syintegerCoordenada Y de inicio
transitableslist | array(Opcional) Valores de celda transitables
permitirWrapboolean(Opcional) Permite movimiento toroidal

Retorno

Devuelve una lista plana [sx, sy, ..., tx, ty] con el camino desde el origen hasta la celda objetivo más cercana, o nil si ningún objetivo es alcanzable.

dir

El método dir(x, y [, usar8]) obtiene la mejor dirección a seguir desde una posición en un mapa de distancias (resultado de dMap). Permite mover un agente hacia el objetivo más cercano sin recalcular el camino completo cada frame.

mapa.solids = [1]
var distancias = mapa.dMap(5) // Mapa de distancias a los puntos

// IA del fantasma: moverse hacia el punto más cercano
var dir = distancias.dir(fantasmaX, fantasmaY)
if (dir == 1) then fantasmaY -= 1 // Arriba
elsif (dir == 2) then fantasmaY += 1 // Abajo
elsif (dir == 3) then fantasmaX -= 1 // Izquierda
elsif (dir == 4) then fantasmaX += 1 // Derecha
end
ParámetroTipoDescripción
xintegerCoordenada X actual
yintegerCoordenada Y actual
usar8boolean(Opcional) Si true, considera también las diagonales. Por defecto: false

Retorno

Devuelve un entero indicando la dirección óptima:

ValorDirección
0Sin movimiento (ya en objetivo o celda inalcanzable)
1Arriba (y-1)
2Abajo (y+1)
3Izquierda (x-1)
4Derecha (x+1)
5Arriba-izquierda (x-1, y-1) — solo con 8 direcciones
6Arriba-derecha (x+1, y-1) — solo con 8 direcciones
7Abajo-izquierda (x-1, y+1) — solo con 8 direcciones
8Abajo-derecha (x+1, y+1) — solo con 8 direcciones

Colisiones

hits

El método hits(sprite_o_lista) resuelve la colisión física entre uno o varios sprites y el mapa. Lee las propiedades solids y platforms directamente del GridMap para determinar qué tiles bloquean y cuáles son plataformas one-way.

mapa.solids = [1, 2]
mapa.platforms = [3]

// Colisión con un sprite
var tilesGolpeados = mapa.hits(jugador)

// Colisión con varios sprites a la vez
var todos = [jugador, enemigo1, enemigo2]
var golpeados = mapa.hits(todos)
ParámetroTipoDescripción
sprite_o_listaSprite | listSprite individual o lista de sprites a resolver

Retorno

Devuelve una lista con los valores de los tiles con los que ha habido colisión.


Conceptos importantes

Valores de celda

Cada celda almacena un valor de 0 a 255 que puede representar:

ValorUso típico
0Suelo vacío / transitable
1Pared / obstáculo
2-254Diferentes tipos de terreno o estados
255Reservado para "inalcanzable" en mapas de distancia

Movimiento toroidal (Wrap)

Cuando wrapX o wrapY son true, los bordes de la cuadrícula se conectan:

  • wrapX: moverse a la derecha desde la última columna lleva a la primera columna.
  • wrapY: moverse hacia abajo desde la última fila lleva a la primera fila.

Esto es útil para juegos estilo Pac-Man donde el personaje puede atravesar los bordes del mapa.

mapa.wrapX = true
mapa.wrapY = true
var camino = mapa.path(0, 5, 19, 5) // Puede rodear por los bordes

Complejidad algorítmica

Todas las operaciones de pathfinding usan BFS con complejidad O(W×H), donde W es el ancho y H es el alto de la cuadrícula.


Ejemplo encontrar el camino entre dos puntos

use GridMap

var mapa = GridMap.new(8, 5)
mapa.solids = [1]

// Construir un pequeño mapa con un pasillo en medio
// . . . . . . . .
// . # # # # # . .
// . . . . . . . .
// . . # # # # # .
// . . . . . . . .
for (var x = 1; x <= 5; x++) mapa.set(x, 1, 1) end
for (var x = 2; x <= 6; x++) mapa.set(x, 3, 1) end

var camino = mapa.path(0, 0, 7, 4)

if (camino != nil) then
for (var i = 0; i < camino.size(); i += 2)
println("(", camino[i], ", ", camino[i+1], ")")
end
else
println("Sin camino")
end

Ejemplo enemigo que persigue al jugador con dMap + dir

use GridMap

var mapa = GridMap.new(10, 6)
mapa.solids = [1]

// Unas pocas paredes
mapa.set(3, 0, 1) mapa.set(3, 1, 1) mapa.set(3, 2, 1)
mapa.set(6, 3, 1) mapa.set(6, 4, 1) mapa.set(6, 5, 1)

var jugX = 0 var jugY = 0
var enX = 9 var enY = 5

// Calculamos el dMap centrado en el jugador (valor 0 = objetivo)
// Para eso marcamos la celda del jugador como tipo 9 temporalmente
mapa.set(jugX, jugY, 9)
var dist = mapa.dMap(9)
mapa.set(jugX, jugY, 0) // restaurar

// El enemigo consulta la dirección óptima
var dir = dist.dir(enX, enY)

// Tabla de movimiento
var ddx = [0, 0, 0, -1, 1, -1, 1, -1, 1]
var ddy = [0, -1, 1, 0, 0, -1, -1, 1, 1]

enX = enX + ddx[dir]
enY = enY + ddy[dir]

println("Enemigo se mueve a (", enX, ", ", enY, ")")

Uso con el módulo Draw

El módulo Draw incluye la función gridBitmap para renderizar eficientemente cuadrículas:

use Display, GridMap

var draw = Display.draw

const COLS = 12
const ROWS = 8
const CELL_SIZE = 16

// 0 = vacío
// 1 = punto pequeño
// 2 = punto grande
var grid = GridMap.new(COLS, ROWS)

var imgPoint = Bitmap.load("Images/punto.bmp")

func InitGrid()
for (var y = 0; y < ROWS; y++)
for (var x = 0; x < COLS; x++)
// Rellenamos casi toda la cuadrícula con puntos pequeños
grid.set(x, y, 1)
end
end

// Dejamos algunos huecos
grid.set(3, 2, 0)
grid.set(4, 2, 0)
grid.set(5, 2, 0)
grid.set(6, 2, 0)

// Cuatro puntos grandes, como en Pac-Man
grid.set(1, 1, 2)
grid.set(COLS - 2, 1, 2)
grid.set(1, ROWS - 2, 2)
grid.set(COLS - 2, ROWS - 2, 2)
end

func DrawGridEfficient()
const X0 = 20
const Y0 = 20

// Render eficiente:
// dibuja de una vez todas las celdas cuyo valor sea 1
draw.gridBitmap(grid, 1, imgPoint, X0, Y0, CELL_SIZE, CELL_SIZE)

// Los elementos especiales se dibujan aparte
draw.color = 0x0000FF

if (grid.get(1, 1) == 2) then
draw.ellipse(X0 + 1 * CELL_SIZE, Y0 + 1 * CELL_SIZE, 4, 4, true)
end

if (grid.get(COLS - 2, 1) == 2) then
draw.ellipse(X0 + (COLS - 2) * CELL_SIZE, Y0 + 1 * CELL_SIZE, 4, 4, true)
end

if (grid.get(1, ROWS - 2) == 2) then
draw.ellipse(X0 + 1 * CELL_SIZE, Y0 + (ROWS - 2) * CELL_SIZE, 4, 4, true)
end

if (grid.get(COLS - 2, ROWS - 2) == 2) then
draw.ellipse(X0 + (COLS - 2) * CELL_SIZE, Y0 + (ROWS - 2) * CELL_SIZE, 4, 4, true)
end
end

func main()
Display.open(320, 240)
Display.autoClear = true
Display.showBG = false

InitGrid()

while (true)
DrawGridEfficient()
Display.update()
pause(16)
end
end

main()

Consideraciones de rendimiento

Memoria y rendimiento
  • Una cuadrícula de 100×100 ocupa aproximadamente 10 KB.
  • Las operaciones de pathfinding son O(W×H); considera el tamaño del mapa para aplicaciones en tiempo real.
  • Usa dMap + dir cuando necesites que múltiples agentes naveguen hacia los mismos objetivos: calcula dMap una sola vez y llama a dir una vez por agente cada frame.

Plataformas soportadas

PlataformaSoporteNotas
WindowsSoporte completo
ESP32Soporte completo
Emscripten (Web)Soporte completo