Dibujando mundos de juego isométricos


¿Cuál es la forma correcta de dibujar fichas isométricas en un juego 2D?

He leído referencias (como esta) que sugieren que los mosaicos se representen de una manera que zigzaguee cada columna en la representación de matriz 2D del mapa. Me imagino que deberían dibujarse más en forma de diamante, donde lo que se dibuja en la pantalla se relaciona más estrechamente con cómo se vería la matriz 2D, simplemente girada un poco.

Existen ventajas o desventajas método?

 170
Author: Timotei, 2009-05-21

5 answers

Actualización: Se ha corregido el algoritmo de renderizado del mapa, se han añadido más ilustraciones, se ha cambiado la configuración.

Tal vez la ventaja de la técnica de "zig-zag" para mapear los mosaicos a la pantalla se puede decir que las coordenadas x y y de los mosaicos están en los ejes vertical y horizontal.

"Dibujo en un diamante" enfoque:

Dibujando un mapa isométrico usando "dibujar en un diamante" , que creo que se refiere a simplemente representar el mapa mediante el uso de un anidado for - bucle sobre la matriz bidimensional, como este ejemplo:

tile_map[][] = [[...],...]

for (cellY = 0; cellY < tile_map.size; cellY++):
    for (cellX = 0; cellX < tile_map[cellY].size cellX++):
        draw(
            tile_map[cellX][cellY],
            screenX = (cellX * tile_width  / 2) + (cellY * tile_width  / 2)
            screenY = (cellY * tile_height / 2) - (cellX * tile_height / 2)
        )

Ventaja:

La ventaja del enfoque es que es un simple bucle anidado for con lógica bastante directa que funciona consistentemente en todas las fichas.

Desventaja:

Un inconveniente de este enfoque es que las coordenadas x y y de las fichas en el mapa aumentarán en líneas diagonales, lo que podría hacer más difícil mapee visualmente la ubicación en la pantalla al mapa representado como una matriz:

Imagen del mapa del azulejo

Sin embargo, va a haber una trampa para implementar el código de ejemplo anterior the el orden de representación hará que los azulejos que se supone que están detrás de ciertos azulejos se dibujen en la parte superior de los azulejos en frente:

Imagen resultante de un orden de representación incorrecto

Para enmendar este problema, el orden del bucle interno for debe invertirse starting comenzando desde el valor más alto, y renderizado hacia el valor inferior:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    for (j = tile_map[i].size; j >= 0; j--):  // Changed loop condition here.
        draw(
            tile_map[i][j],
            x = (j * tile_width / 2) + (i * tile_width / 2)
            y = (i * tile_height / 2) - (j * tile_height / 2)
        )

Con la corrección anterior, la representación del mapa debe corregirse:

Imagen resultante del orden de renderizado correcto

"zig-zag" enfoque:

Ventaja:

Tal vez la ventaja del enfoque "zig-zag" es que el mapa renderizado puede parecer un poco más compacto verticalmente que el enfoque "diamante":

El enfoque en zig-zag para el renderizado parece compacto

Desventaja:

De intentar implementar la técnica zig-zag, la desventaja puede ser que es un poco más difícil escribir el código de renderizado porque no se puede escribir tan simple como un bucle anidado for sobre cada elemento en un array:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    if i is odd:
        offset_x = tile_width / 2
    else:
        offset_x = 0

    for (j = 0; j < tile_map[i].size; j++):
        draw(
            tile_map[i][j],
            x = (j * tile_width) + offset_x,
            y = i * tile_height / 2
        )

Además, puede ser un poco difícil tratar de averiguar la coordenada de un mosaico debido a la naturaleza escalonada del orden de representación:

Coordenadas en una representación de orden en zig-zag

Nota: Las ilustraciones incluidas en esta respuesta fueron creadas con una implementación Java del código de representación de teselas presentado, con el siguiente array int como mapa:

tileMap = new int[][] {
    {0, 1, 2, 3},
    {3, 2, 1, 0},
    {0, 0, 1, 1},
    {2, 2, 3, 3}
};

Las imágenes del mosaico son:

  • tileImage[0] -> Una caja con una caja dentro.
  • tileImage[1] -> Una caja negra.
  • tileImage[2] -> Una caja blanca.
  • tileImage[3] -> Una caja con un objeto gris alto en ella.

Una Nota sobre Anchos y Alturas de Baldosas

Las variables tile_width y tile_height que se utilizan en los ejemplos de código anteriores se refieren a la anchura y la altura del suelo azulejo en la imagen que representa el azulejo:

Imagen que muestra el ancho y la altura del azulejo

El uso de las dimensiones de la imagen funcionará, siempre y cuando las dimensiones de la imagen y las dimensiones del mosaico coincidan. De lo contrario, el mapa de teselas se podría representar con espacios entre las teselas.

 483
Author: coobird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-21 22:04:37

De cualquier manera se hace el trabajo. Asumo que por zigzag quieres decir algo como esto: (los números son el orden de renderización)

..  ..  01  ..  ..
  ..  06  02  ..
..  11  07  03  ..
  16  12  08  04
21  17  13  09  05
  22  18  14  10
..  23  19  15  ..
  ..  24  20  ..
..  ..  25  ..  ..

Y por diamante quieres decir:

..  ..  ..  ..  ..
  01  02  03  04
..  05  06  07  ..
  08  09  10  11
..  12  13  14  ..
  15  16  17  18
..  19  20  21  ..
  22  23  24  25
..  ..  ..  ..  ..

El primer método necesita más mosaicos renderizados para que se dibuje la pantalla completa, pero puede hacer fácilmente una verificación de límite y omitir cualquier mosaico completamente fuera de la pantalla. Ambos métodos requerirán algún número de crujido para averiguar cuál es la ubicación del azulejo 01. Al final, ambos métodos son aproximadamente iguales en términos de matemáticas necesario para un cierto nivel de eficiencia.

 10
Author: zaratustra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-21 17:39:44

Si tienes algunas fichas que exceden los límites de tu diamante, te recomiendo dibujar en orden de profundidad:

...1...
..234..
.56789.
..abc..
...d...
 1
Author: Wouter Lievens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-24 21:59:02

La respuesta de Coobird es la correcta y completa. Sin embargo, combiné sus sugerencias con las de otro sitio para crear código que funcione en mi aplicación (iOS/Objective-C), que quería compartir con cualquiera que venga aquí buscando algo así. Por favor, si te gusta/up-vote esta respuesta, haz lo mismo para los originales; todo lo que hice fue " stand on the shoulders of giants."

En cuanto al orden de ordenación, mi técnica es un algoritmo de pintor modificado: cada objeto tiene (a) una altitud de la base (llamo "nivel") y (b) una X/Y para la "base" o "pie" de la imagen (ejemplos: la base del avatar está a sus pies; la base del árbol está a sus raíces; la base del avión es la imagen central, etc.) Luego solo clasifico el nivel más bajo al más alto, luego el más bajo (el más alto en la pantalla) al más alto base-Y, luego el más bajo (el más a la izquierda) al más alto base-X. Esto renderiza las fichas de la manera que uno esperaría.

Código para convertir pantalla (punto) a tile (celda) y volver:

typedef struct ASIntCell {  // like CGPoint, but with int-s vice float-s
    int x;
    int y;
} ASIntCell;

// Cell-math helper here:
//      http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511
// Although we had to rotate the coordinates because...
// X increases NE (not SE)
// Y increases SE (not SW)
+ (ASIntCell) cellForPoint: (CGPoint) point
{
    const float halfHeight = rfcRowHeight / 2.;

    ASIntCell cell;
    cell.x = ((point.x / rfcColWidth) - ((point.y - halfHeight) / rfcRowHeight));
    cell.y = ((point.x / rfcColWidth) + ((point.y + halfHeight) / rfcRowHeight));

    return cell;
}


// Cell-math helper here:
//      http://stackoverflow.com/questions/892811/drawing-isometric-game-worlds/893063
// X increases NE,
// Y increases SE
+ (CGPoint) centerForCell: (ASIntCell) cell
{
    CGPoint result;

    result.x = (cell.x * rfcColWidth  / 2) + (cell.y * rfcColWidth  / 2);
    result.y = (cell.y * rfcRowHeight / 2) - (cell.x * rfcRowHeight / 2);

    return result;
}
 0
Author: Olie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-11 16:30:33

El verdadero problema es cuando necesita dibujar algunas fichas / sprites que intersecan / abarcan dos o más fichas.

Después de 2 (duros) meses de análisis personal de problemas, finalmente encontré e implementé un "dibujo de renderizado correcto" para mi nuevo juego cocos2d-js. La solución consiste en mapear, para cada tile (susceptible), qué sprites son "front, back, top y behind". Una vez hecho esto, puedes dibujarlos siguiendo una "lógica recursiva".

 0
Author: Carlos Lopez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-09 16:22:01