Una introducción a la creación de un motor de mapas de mosaicos
() translation by (you can also view the original English article)
En este tutorial, te ayudaré a crear niveles para cualquier género de juegos y a hacer que los niveles de diseño sean mucho más fáciles. Aprenderá cómo crear su primer motor de mapas de mosaicos para usar en cualquiera de sus proyectos futuros. Usaré Haxe con OpenFL, pero deberías poder seguirlo en cualquier idioma.
Esto es lo que trataremos:
- ¿Qué es un juego basado en fichas?
- Encontrar o hacer tus propios cuadros.
- Escribir el código para mostrar los mosaicos en la pantalla.
- Edición de diseños de mosaicos para diferentes niveles.
¡También puedes comenzar bien una nueva idea de juego!
¿Qué es un juego basado en mosaicos?
Naturalmente, Wikipedia tiene una definición en profundidad de lo que es un juego basado en mosaicos, pero para obtener la esencia básica hay solo algunas cosas que debes saber:
- Un mosaico es una imagen pequeña, generalmente rectangular o isométrica, que actúa como una pieza de arte del rompecabezas para crear imágenes más grandes.
- Un mapa es una agrupación de mosaicos reunidos para crear una (esperemos) "sección" visualmente atractiva (como un nivel o área).
- Basado en mosaicos se refiere al método de construcción de niveles en un juego. El código diseñará mosaicos en ubicaciones específicas para cubrir el área prevista.
Para ser aún más básico, lo pondré así:
Un juego basado en mosaicos establece fichas para crear cada nivel.
En referencia a los tipos de mosaicos comunes, rectangulares e isométricos, usaremos mosaicos rectangulares en este artículo por su simplicidad. Si decides probar los niveles isométricos algún día, hay matemática adicional involucrada para que funcione. (Una vez que hayas terminado aquí, echa un vistazo a este gran manual para crear mundos isométricos).
Hay algunos beneficios geniales que obtienes al usar un motor de mosaicos. La ventaja más evidente es que no necesitará crear imágenes masivas a mano para cada nivel individual. Esto reducirá el tiempo de desarrollo y reducirá el tamaño de los archivos. Tener 50 imágenes de 1280x768px para un juego de 50 niveles vs tener una imagen con 100 hace una gran diferencia.
Otro efecto colateral es que ubicar cosas en tu mapa usando código se vuelve un poco más fácil. En lugar de verificar cosas como una colisión basada en un píxel exacto, puede usar una fórmula rápida y fácil para determinar a qué mosaico necesita acceder. (Voy a repasar eso un poco más tarde).
Encontrar o hacer tus propios azulejos
Lo primero que necesitará al construir su motor de mosaico es un conjunto de mosaicos. Tienes dos opciones: usar las de otra persona, o hacer las tuyas propias.
Si decide utilizar mosaicos que ya se han creado, puede encontrar arte disponible gratuitamente en toda la web. La desventaja de esto es que el arte no se hizo específicamente para tu juego. Por otro lado, si solo estás creando prototipos o intentando aprender un nuevo concepto, las fichas gratuitas funcionarán.
Donde encontrar tus azulejos
Existen bastantes recursos para el arte libre y de código abierto. Aquí hay algunos lugares para comenzar su búsqueda:
Esos tres enlaces deberían darle lugares más que suficientes para encontrar algunos para sus prototipos. Antes de enloquecer agarrando todo lo que encuentres, asegúrate de entender en qué licencia está cubierto todo y con qué restricciones vienen. Muchas licencias le permitirán usar el arte libremente y para uso comercial, pero pueden requerir atribución.
Para este tutorial, utilicé algunas fichas de The Open Game Art Bundle para plataformas. Puede descargar mis versiones reducidas o los originales.
Cómo hacer tus propios azulejos
Si aún no te has dado el lujo de hacer arte para tus juegos, puede ser un poco intimidante. Afortunadamente, hay algunas piezas de software asombrosas y simples que te llevan al centro de todo para que puedas comenzar a practicar.
Muchos desarrolladores comienzan con el arte de píxeles para sus juegos y aquí hay algunas excelentes herramientas para eso:
- Aseprite
- Pyxel Edit
- Graphics Gale
- Pixen para los usuarios de Mac
Estos son algunos de los programas más populares para hacer pixel art. Si quieres algo un poco más poderoso, GIMP es una excelente opción. También puede hacer algunas ilustraciones vectoriales con Inkscape y seguir algunos tutoriales sorprendentes en 2D Game Art For Programmers.
Una vez que agarras el software, puedes comenzar a experimentar con tus propios mosaicos. Como este tutorial está destinado a mostrarte cómo crear tu motor de mapas de mosaicos, no entraré en demasiados detalles sobre cómo crear los mosaicos, pero hay algo que siempre debes tener en cuenta:
Asegúrate de que tus fichas encajen perfectamente y agrega alguna variación para mantenerlas interesantes.
Si tus fichas muestran líneas obvias entre ellos cuando se juntan, tu juego no se verá muy bien. Asegúrate de dedicar algo de tiempo a la creación de un bonito "rompecabezas" de mosaicos al hacerlos perfectos y agregar alguna variación.
Escribir el código para mostrar los mosaicos
Ahora que hemos sacado todo el material del arte, podemos sumergirnos en el código para poner las fichas recién adquiridas (o creadas) en la pantalla.
Cómo mostrar un solo mosaico en la pantalla
Comencemos con la tarea básica de mostrar un solo mosaico en la pantalla. Asegúrate de que tus fichas sean del mismo tamaño y se guarden en archivos de imagen separados (más adelante hablaremos sobre las hojas de sprites).
Una vez que tenga los mosaicos en la carpeta de activos de su proyecto,
puede escribir una clase de mosaico Tile
muy simple. Aquí hay un ejemplo en
Haxe:
1 |
import flash.display.Sprite; |
2 |
import flash.display.Bitmap; |
3 |
import openfl.Assets; |
4 |
|
5 |
class Tile extends Sprite { |
6 |
|
7 |
private var image:Bitmap; |
8 |
|
9 |
public function new() { |
10 |
super(); |
11 |
|
12 |
image = new Bitmap(Assets.getBitmapData("assets/grassLeftBlock.png")); |
13 |
addChild(image); |
14 |
} |
15 |
} |
Como
todo lo que estamos haciendo ahora es colocar un solo mosaico en la
pantalla, lo único que hace la clase es importar la imagen del mosaico
desde la carpeta de activos assets
y agregarla como un elemento secundario al
objeto. Es probable que esta clase varíe mucho
según el lenguaje de programación que utilice, pero debería poder
encontrar fácilmente una guía sobre cómo mostrar una imagen en la
pantalla.
Ahora que tenemos una clase de mosaico Tile
, necesitamos crear una
instancia de un mosaico Tile
y agregarlo a nuestra clase principal:
1 |
import flash.display.Sprite; |
2 |
import flash.events.Event; |
3 |
import flash.Lib; |
4 |
|
5 |
class Main extends Sprite { |
6 |
|
7 |
public function new() { |
8 |
super(); |
9 |
|
10 |
var tile = new Tile(); |
11 |
addChild(tile); |
12 |
} |
13 |
|
14 |
public static function main() { |
15 |
Lib.current.addChild(new Main()); |
16 |
} |
17 |
} |
La clase principal Main
crea un nuevo mosaico Tile
cuando se llama al
constructor (se llama a la función new()
cuando se inicia el juego) y
se agrega a la lista de visualización.
Cuando se ejecuta el juego, también se llamará a la función main()
y se
agregará un nuevo objeto Main
al escenario. Ahora debería tener su
mosaico en la parte superior izquierda de la pantalla. Hasta aquí todo
bien.
Sugerencia: si nada de eso tiene sentido,¡no se preocupe! Es solo
el código estándar de Haxe. La clave para entender es que esto crea un
objeto Tile
y lo agrega a la pantalla.
Usar matrices para diseñar una pantalla completa de mosaicos
El siguiente paso es encontrar una forma de llenar toda la pantalla con mosaicos. Una de las formas más sencillas de hacerlo es completar una matriz con números enteros que representan una ID de mosaico tile ID. Luego puede iterar a través de la matriz para decidir qué mosaico mostrar y dónde mostrarlo.
Usted tiene la opción de usar una
matriz típica o utilizar una matriz bidimensional. En
caso de que no esté familiarizado con las matrices en 2D, es
básicamente una única matriz que está llena de matrices múltiples. La
mayoría de los lenguajes indicarán esto como nameOfArray[x][y]
donde
x
e y
son índices para acceder a un solo elemento.
Como x
e y
también se
usan para coordenadas de pantalla, podría tener sentido usar x
e y
como
coordenadas para nuestros mosaicos. El truco con las matrices 2D es
entender cómo se organizan los enteros. Es posible que tenga que
invertir la y
y la x
al iterar a través de las matrices (ejemplo a
continuación).
1 |
private var exampleArr = [ [0, 0, 0, 0, 0], |
2 |
[0, 0, 0, 0, 0], |
3 |
[0, 0, 0, 0, 0], |
4 |
[0, 0, 0, 0, 0], |
5 |
[0, 0, 0, 0, 0] ]; |
Observe que en esta implementación, el elemento "0º" en la matriz es en sí mismo una matriz de cinco enteros. Esto significa que accedes a cada elemento con y primero y
, luego x
. Si
intenta acceder al exampleArr[1][0]
, accederá al sexto mosaico.
Si aún no comprende cómo funcionan las matrices en 2D, no se preocupe. Para este tutorial usaré una matriz normal para mantener las cosas simples y hacer que las cosas sean más fáciles de visualizar:
1 |
private var exampleArr = [ 0, 0, 0, 0, 0, |
2 |
0, 0, 0, 0, 0, |
3 |
0, 0, 0, 0, 0, |
4 |
0, 0, 0, 0, 0, |
5 |
0, 0, 0, 0, 0 ]; |
El ejemplo anterior muestra cómo una matriz normal puede ser un poco más simple. Podemos visualizar exactamente dónde estará el mosaico y todo lo que tenemos que hacer es usar una fórmula simple (¡no te preocupes, viene!) Para obtener el azulejo que queremos.
Ahora vamos a escribir un código para crear nuestra matriz y llenarla con unos. El número uno será el ID que representa nuestro primer mosaico.
Primero, necesitamos crear una variable dentro de nuestra clase principal para mantener nuestra matriz:
1 |
private var map:Array<Int>; |
Esto puede parecer un poco extraño, así que lo analizaré por ti.
El nombre de la variable es un mapa y le he dado un tipo muy específico: Matriz Array
. La porción <Int>
simplemente le dice a nuestro programa que la matriz solo contendrá enteros. Las
matrices pueden albergar casi cualquier tipo que desee, de modo que si
está utilizando otra cosa para identificar sus teselas, puede cambiar el
parámetro aquí.
A continuación tenemos que
agregar un código al constructor de nuestra clase principal Main
(recuerde,
esta es la función new()
) para que podamos crear una instancia de
nuestro mapa:
1 |
map = new Array<Int>(); |
Esta línea creará un conjunto vacío que podemos llenar pronto con nuestros para probarlo. Primero, definamos algunos valores que nos ayudarán con nuestras matemáticas:
1 |
public static var TILE_WIDTH = 60; |
2 |
public static var TILE_HEIGHT = 60; |
3 |
public static var SCREEN_WIDTH = 600; |
4 |
public static var SCREEN_HEIGHT = 360; |
He hecho
que estos valores sean public static
porque esto nos dará acceso a
ellos desde cualquier lugar de nuestro programa (a través de
Main.Tile_WIDTH
, etc.). Además, habrás notado que
dividir SCREEN_WIDTH
por TILE_WIDTH
nos da 10
y dividir SCREEN_HEIGHT
por TILE_HEIGHT
nos da 6
. Esos dos números se usarán para decidir
cuántos enteros almacenar en nuestra matriz.
1 |
var w = Std.int(SCREEN_WIDTH / TILE_WIDTH); |
2 |
var h = Std.int(SCREEN_HEIGHT / TILE_HEIGHT); |
3 |
|
4 |
for (i in 0...w * h) { |
5 |
map[i] = 1 |
6 |
} |
En este bloque de código, asignamos 10
y 6
a w
y h
, como mencioné anteriormente. Luego, debemos saltar a un bucle for
para crear una matriz que pueda
contener números enteros de 10 * 6
. Esto representará suficientes
teselas para llenar toda la pantalla.



Ahora tenemos nuestro mapa básico construido, pero ¿cómo vamos a decirle a los mosaicos que se coloquen en el lugar correcto? Para eso tenemos que volver a la clase Tile
y crear una función que nos permita mover fichas a voluntad:
1 |
public function setLoc(x:Int, y:Int) { |
2 |
image.x = x * Main.TILE_WIDTH; |
3 |
image.y = y * Main.TILE_HEIGHT; |
4 |
} |
Cuando
llamamos a la función setLoc ()
, pasamos las coordenadas x
e y
de acuerdo
con nuestra clase de mapa (¡la fórmula llegará pronto, ¡lo prometo!). La
función toma esos valores y los traduce en coordenadas de píxeles
multiplicándolos por TILE_WIDTH
y TILE_HEIGHT
, respectivamente.
Lo
único que queda por hacer para obtener nuestros mosaicos en la pantalla
es decirle a nuestra clase principal Main
que cree los mosaicos y los
coloque en su lugar en función de su ubicación dentro del mapa. Volvamos a Main
e implementamos eso:
1 |
for (i in 0...map.length) { |
2 |
var tile = new Tile(); |
3 |
var x = i % w; |
4 |
var y = Math.floor(i / w); |
5 |
tile.setLoc(x, y); |
6 |
addChild(tile); |
7 |
} |
¡Oh si! Así es, ahora tenemos una pantalla llena de mosaicos. Analicemos lo que está sucediendo arriba.
La formula
La fórmula que sigo mencionando finalmente está aquí.
Calculamos x
tomando el módulo (%
) de i
y w
(que es
10, recuerda).
El módulo es solo el resto después de la división entera: \(14 \div 3 = 4 \text{ resto } 2\) así \(14 \text {modulo} 3 = 2 \).
Usamos
esto porque queremos que nuestro valor de x
vuelva a 0
en el inicio
de cada fila, así que dibujamos el mosaico respectivo en el extremo
izquierdo:



En cuanto a
y
, tomamos el floor()
de i / w
(es decir, redondeamos ese resultado)
porque solo queremos que y
aumente una vez que hayamos llegado al final
de cada fila y bajado un nivel:



Math.floor()
al dividir enteros; Haxe solo maneja la división entera de manera diferente. Si está utilizando un idioma que hace división de enteros, puede usar i /
w
(suponiendo que ambos sean enteros).Por último, quería mencionar un poco sobre los niveles de desplazamiento. Por lo general, no creará niveles que se ajusten perfectamente a su ventana gráfica. Es probable que tus mapas sean mucho más grandes que la pantalla y no quieras seguir dibujando imágenes que el jugador no podrá ver. Con algunas matemáticas rápidas y fáciles, deberías poder calcular qué fichas deberían estar en la pantalla y qué fichas para evitar dibujar.
Por ejemplo: el tamaño de su pantalla es 500x500, sus mosaicos son 100x100 y su tamaño mundial es 1000x1000. Simplemente necesitaría hacer una comprobación rápida antes de dibujar fichas para descubrir qué fichas están en pantalla. Usando la ubicación de su ventana gráfica, digamos 400x500, solo necesitaría dibujar las fichas de las filas 4 a 9 y las columnas 5 a 10. Puede obtener esos números dividiendo la ubicación por el tamaño de la tesela, y luego compensando esos valores con la pantalla tamaño dividido por el tamaño del azulejo. Sencillo.
Puede que aún no se parezca mucho, ya que todas las fichas son iguales, pero la base está casi lista. Lo único que queda por hacer es crear diferentes tipos de mosaicos y diseñar nuestro mapa para que se alineen y creen algo agradable.
Edición de diseños de mosaico para diferentes niveles
Muy bien, ahora tenemos un
mapa que está lleno de los que cubre la pantalla. En
este punto, debe tener más de un tipo de mosaico, lo que significa que
tenemos que cambiar nuestro constructor de mosaico Tile
para dar cuenta de
eso:
1 |
public function new(id:Int) { |
2 |
super(); |
3 |
|
4 |
switch(id) { |
5 |
case 1: |
6 |
image = new Bitmap(Assets.getBitmapData("assets/grassLeftBlock.png")); |
7 |
case 2: |
8 |
image = new Bitmap(Assets.getBitmapData("assets/grassCenterBlock.png")); |
9 |
case 3: |
10 |
image = new Bitmap(Assets.getBitmapData("assets/grassRightBlock.png")); |
11 |
case 4: |
12 |
image = new Bitmap(Assets.getBitmapData("assets/goldBlock.png")); |
13 |
case 5: |
14 |
image = new Bitmap(Assets.getBitmapData("assets/globe.png")); |
15 |
case 6: |
16 |
image = new Bitmap(Assets.getBitmapData("assets/mushroom.png")); |
17 |
} |
18 |
addChild(image); |
19 |
} |
Como utilicé seis fichas diferentes para mi mapa, necesitaba una declaración de cambio switch
que cubriera los números del uno al seis. Notarás que el constructor ahora toma un entero como parámetro para que
sepamos qué tipo de mosaico crear.
Ahora, tenemos que volver a nuestro
constructor Main
y arreglar la creación del mosaico en nuestro
ciclo for
:
1 |
for (i in 0...map.length) { |
2 |
var tile = new Tile(map[i]); |
3 |
var x = i % w; |
4 |
var y = Math.floor(i / w); |
5 |
tile.setLoc(x, y); |
6 |
addChild(tile); |
7 |
} |
Todo lo que teníamos que hacer era pasar el map[i]
al constructor de Tile
para que funcione nuevamente. Si intenta ejecutar sin pasar un número entero a Tile
, le dará algunos
errores.
Casi todo está en su lugar, pero ahora necesitamos una forma de
diseñar mapas en lugar de llenarlos con mosaicos aleatorios. Primero,
elimine el bucle for
en el constructor Main
que establece cada
elemento en uno. Entonces podemos crear nuestro propio mapa a mano:
1 |
map = [ 0, 4, 0, 0, 0, 0, 0, 5, 0, 0, |
2 |
0, 0, 0, 0, 0, 1, 2, 2, 3, 0, |
3 |
0, 0, 0, 6, 0, 0, 0, 0, 0, 0, |
4 |
1, 2, 2, 3, 0, 0, 0, 0, 0, 0, |
5 |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
6 |
0, 0, 0, 0, 0, 0, 1, 2, 2, 3 ]; |
Si formatea la matriz como lo hice anteriormente, puede ver fácilmente cómo se organizarán nuestras fichas. También puede simplemente ingresar la matriz como una larga línea de números, pero la primera es agradable porque puede ver 10 a través y 6 abajo.
Cuando intente ejecutar el programa ahora, debería obtener algunas
excepciones de puntero nulo. El problema es que estamos usando ceros en
nuestro mapa y nuestra clase Tile
no sabe qué hacer con un cero. Primero, arreglaremos el constructor agregando una marca antes de
agregar la imagen secundaria:
1 |
if (image != null) addChild(image); |
Esta comprobación rápida asegura que no estamos agregando ningún nulo a los objetos de mosaico Tile
que creamos. El último cambio para esta clase es la función setLoc()
. En este
momento estamos tratando de establecer los valores x
e y
para una
variable que no se ha inicializado.
Agreguemos otra verificación rápida:
1 |
public function setLoc(x:Int, y:Int) { |
2 |
if (image != null) { |
3 |
image.x = x * Main.TILE_WIDTH; |
4 |
image.y = y * Main.TILE_HEIGHT; |
5 |
} |
6 |
} |
Con esas dos correcciones simples en su lugar, y las fichas que proporcioné arriba, deberías poder ejecutar el juego y ver un nivel de plataformas simple. Como dejamos 0 como un "no mosaico", podemos dejarlo transparente (vacío). Si desea darle vida al nivel, puede poner un fondo antes de diseñar las fichas; Acabo de agregar un degradado azul claro para que parezca un cielo en el fondo.



Eso es prácticamente todo lo que necesita para configurar una forma sencilla de editar sus niveles. Intente experimentar un poco con la matriz y vea qué diseños puede obtener para otros niveles.
Software de terceros para diseñar niveles
Una vez que tenga lo básico, puede considerar usar otras herramientas para ayudarlo a diseñar niveles rápidamente y ver cómo son antes de lanzarlos al juego. Una opción es crear tu propio editor de niveles. La alternativa es tomar algún software que esté disponible de forma gratuita. Las dos herramientas populares son Tiled Map Editor y Ogmo Editor. Ambos hacen la edición de niveles mucho más fácil con múltiples opciones de exportación.
¡Acabas de crear un motor basado en mosaico!
Ahora ya sabe qué es un juego basado en fichas, cómo obtener algunas fichas para sus niveles, y cómo ensuciarse las manos y escribir el código para su motor, e incluso puede hacer una edición de nivel básico con una matriz simple . Solo recuerda que esto es solo el comienzo; hay mucha experimentación que puedes hacer para hacer un motor aún mejor.
Además, proporcioné los archivos fuente para que los revises. Así es como se ve el SWF final:
...y aquí, de nuevo, está la matriz de la que se genera:
1 |
map = [ 0, 4, 0, 0, 0, 0, 0, 5, 0, 0, |
2 |
0, 0, 0, 0, 0, 1, 2, 2, 3, 0, |
3 |
0, 0, 0, 6, 0, 0, 0, 0, 0, 0, |
4 |
1, 2, 2, 3, 0, 0, 0, 0, 0, 0, |
5 |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
6 |
0, 0, 0, 0, 0, 0, 1, 2, 2, 3 ]; |
¡No te detengas aquí!
Tu próxima tarea es investigar un poco y probar algunas cosas nuevas para mejorar lo que hiciste aquí. Las hojas de Sprite son una excelente manera de mejorar el rendimiento y facilitar la vida. El truco es obtener un código sólido diseñado para leer desde una hoja de sprites, de modo que su juego no tenga que leer cada imagen individualmente.
También deberías comenzar a practicar tus habilidades artísticas haciendo algunos tilesets propios. De esa forma puedes obtener el arte correcto para tus juegos futuros.
Como siempre, deje algunos comentarios a continuación sobre cómo funciona su motor para usted, y tal vez incluso algunas demostraciones para que podamos probar su próximo juego de fichas.
Elías Nicolás