Dibujando mundos de juego isométricos

178

¿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 el aspecto de la matriz 2D, solo gira un poco.

¿Hay ventajas o desventajas de cualquiera de los métodos?

Benny Hallett
fuente

Respuestas:

505

Actualización: Se corrigió el algoritmo de representación del mapa, se agregaron más ilustraciones, se cambió el formato.

Quizás la ventaja de la técnica de "zig-zag" para mapear los mosaicos a la pantalla se puede decir que los mosaicos xy las ycoordenadas están en los ejes vertical y horizontal.

Enfoque "Dibujar en un diamante":

Al dibujar un mapa isométrico usando "dibujar en un diamante", lo que creo que se refiere a solo renderizar el mapa usando un forbucle anidado 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 forbucle anidado simple con una lógica bastante sencilla que funciona de manera consistente en todos los mosaicos.

Desventaja:

Una desventaja de ese enfoque es que las coordenadas xy yde los mosaicos en el mapa aumentarán en líneas diagonales, lo que podría hacer que sea más difícil asignar visualmente la ubicación en la pantalla al mapa representado como una matriz:

Imagen del mapa de mosaico

Sin embargo, habrá un obstáculo para implementar el código de ejemplo anterior: el orden de representación hará que los mosaicos que se supone que estén detrás de ciertos mosaicos se dibujen encima de los mosaicos en el frente:

Imagen resultante de un orden de representación incorrecto

Para corregir este problema, el fororden del bucle interno debe invertirse, comenzando desde el valor más alto y volviéndose hacia el valor más bajo:

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 representación correcto

Enfoque "zig-zag":

Ventaja:

Quizás 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 la representación parece compacto

Desventaja:

Al intentar implementar la técnica de zig-zag, la desventaja puede ser que es un poco más difícil escribir el código de representación porque no puede escribirse tan simple como un forbucle anidado sobre cada elemento en una matriz:

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 se crearon con una implementación Java del código de representación de mosaico presentado, con la siguiente intmatriz como mapa:

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

Las imágenes de mosaico son:

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

Una nota sobre los anchos y alturas de los azulejos

Las variables tile_widthy las tile_heightque se usan en los ejemplos de código anteriores se refieren al ancho y la altura del mosaico de tierra en la imagen que representa el mosaico:

Imagen que muestra el ancho y la altura del azulejo

El uso de las dimensiones de la imagen funcionará, siempre que las dimensiones de la imagen y las dimensiones del mosaico coincidan. De lo contrario, el mapa de mosaicos podría representarse con espacios entre los mosaicos.

Coobird
fuente
136
incluso hiciste dibujos. Eso es esfuerzo.
zaratustra
Saludos coobird. Estoy usando el enfoque de diamante para el juego actual que estoy desarrollando y está funcionando de maravilla. Gracias de nuevo.
Benny Hallett
3
¿Qué pasa con varias capas de altura? ¿Puedo dibujarlos de forma sonriente, comenzando con el nivel más bajo y continuar hasta llegar al nivel más alto?
NagyI
2
@DomenicDatti: Gracias por sus amables palabras :)
coobird
2
Esto es asombroso Acabo de utilizar el enfoque de diamante y también, en caso necesita a alguien para obtener coordenadas de cuadrícula de la posición de la pantalla, lo hice: j = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;.
Kyr Dunenkoff
10

De cualquier manera se hace el trabajo. Supongo que por zigzag te refieres a algo como esto: (los números son orden de representació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ímites y omitir cualquier mosaico completamente fuera de la pantalla. Ambos métodos requerirán un poco de cálculo numérico para averiguar cuál es la ubicación de la ficha 01. Al final, ambos métodos son aproximadamente iguales en términos de matemática requerida para un cierto nivel de eficiencia.

zaratustra
fuente
14
En realidad me refería a la inversa. La forma de diamante (que sale de los bordes de la transformación suave) y el método de zig-zag, dejando los bordes SPIKEY
Benny Hallett
1

Si tiene algunas fichas que exceden los límites de su diamante, le recomiendo dibujar en orden de profundidad:

...1...
..234..
.56789.
..abc..
...d...
Wouter Lievens
fuente
1

La respuesta de Coobird es la correcta, completa. Sin embargo, combiné sus sugerencias con las de otro sitio para crear un código que funciona en mi aplicación (iOS / Objective-C), que quería compartir con cualquiera que venga a buscar tal cosa. Por favor, si le gusta / vota esta respuesta, haga lo mismo con los originales; todo lo que hice fue "estar sobre los hombros de gigantes".

En cuanto al orden de clasificació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á en sus raíces; la base del avión es la imagen central, etc.) Luego solo clasifico de menor a mayor nivel, luego de menor a mayor (en la pantalla) a la base más alta Y, luego la más baja (más a la izquierda) a la más alta base-X. Esto hace que los mosaicos de la manera que uno esperaría.

Código para convertir la pantalla (punto) a mosaico (celda) y viceversa:

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;
}
Olie
fuente
1

Puede usar la distancia euclidiana desde el punto más alto y más cercano al espectador, excepto que no es del todo correcto. Resulta en un orden de clasificación esférico. Puede enderezarlo mirando desde más lejos. Más lejos, la curvatura se aplana. Entonces solo agregue say 1000 a cada uno de los componentes x, y y z para dar x ', y' y z '. La ordenación en x '* x' + y '* y' + z '* z'.

Siobhan O'Connor
fuente
0

El verdadero problema es cuando necesita dibujar algunos mosaicos / sprites que se cruzan / cruzan dos o más mosaicos.

Después de 2 (duros) meses de análisis personal del problema, finalmente encontré e implementé un "dibujo de renderizado correcto" para mi nuevo juego cocos2d-js. La solución consiste en mapear, para cada mosaico (susceptible), qué sprites son "frontal, posterior, superior y posterior". Una vez que lo hagas, puedes dibujarlos siguiendo una "lógica recursiva".

Carlos lopez
fuente