web-dev-qa-db-fra.com

Dessin des mondes de jeu isométriques

Quelle est la bonne façon de dessiner des tuiles isométriques dans un jeu 2D?

J'ai lu des références (telles que celle-ci ) suggérant que les carreaux soient restitués de manière à zigzaguer chaque colonne de la représentation en tableau 2D de la carte. J'imagine qu'ils devraient être dessinés davantage en diamant, où ce qui est dessiné à l'écran est plus étroitement lié à l'apparence de la matrice 2D, juste légèrement pivotée.

Y a-t-il des avantages ou des inconvénients à l'une ou l'autre méthode?

176
Benny Hallett

Mise à jour: algorithme de rendu de carte corrigé, ajout d'illustrations, formatage modifié.

Peut-être l’avantage de la technique "zig-zag" pour mapper les mosaïques à l’écran peut être dit que les coordonnées x et y de la mosaïque sont sur les axes vertical et horizontal.

Approche "Dessin au diamant":

En dessinant une carte isométrique en utilisant "dessiner dans un diamant", ce qui, je pense, fait simplement référence à la carte en utilisant une boucle imbriquée for- sur le tableau à deux dimensions, comme dans cet exemple:

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)
        )

Avantage:

L’avantage de cette approche est qu’il s’agit d’une simple boucle imbriquée for- avec une logique relativement simple qui fonctionne de manière cohérente dans toutes les tuiles.

Inconvénient:

Un inconvénient de cette approche est que les coordonnées x et y des tuiles sur la carte augmenteront en diagonales, ce qui pourrait rendre plus difficile la cartographie visuelle de l'emplacement à l'écran. carte représentée sous forme de tableau:

Image of tile map

Cependant, la mise en oeuvre de l'exemple de code ci-dessus présentera un piège: l'ordre de rendu provoquera le tracé des tuiles supposées se trouver derrière certaines tuiles au-dessus des tuiles devant:

Resulting image from incorrect rendering order

Afin de modifier ce problème, l'ordre de la boucle intérieure for- doit être inversé - en partant de la valeur la plus élevée et en effectuant le rendu vers la valeur la plus basse:

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)
        )

Avec le correctif ci-dessus, le rendu de la carte devrait être corrigé:

Resulting image from correct rendering order

Approche "en zigzag":

Avantage:

L'avantage de l'approche "en zig-zag" est peut-être que la carte rendue peut sembler un peu plus compacte verticalement que l'approche "en losange":

Zig-zag approach to rendering seems compact

Inconvénient:

En essayant d'implémenter la technique en zig-zag, l'inconvénient peut être qu'il est un peu plus difficile d'écrire le code de rendu car il ne peut pas être écrit aussi simple qu'une boucle imbriquée for- sur chaque élément d'un tableau. :

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
        )

En outre, il peut être un peu difficile d'essayer de déterminer les coordonnées d'une tuile en raison de la nature décalée de l'ordre de rendu:

Coordinates on a zig-zag order rendering

Remarque: Les illustrations incluses dans cette réponse ont été créées avec une implémentation Java du code de rendu de mosaïque présenté), avec le tableau int suivant comme carte:

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

Les images de tuiles sont:

  • tileImage[0] -> Une boîte avec une boîte à l'intérieur.
  • tileImage[1] -> Une boîte noire.
  • tileImage[2] -> Une boîte blanche.
  • tileImage[3] -> Une boîte contenant un grand objet gris.

Remarque sur les largeurs et hauteurs de tuile

Les variables tile_width et tile_height qui sont utilisés dans les exemples de code ci-dessus font référence à la largeur et à la hauteur de la mosaïque de sol dans l'image représentant la mosaïque:

Image showing the tile width and height

L'utilisation des dimensions de l'image fonctionnera tant que les dimensions de l'image et les dimensions des carreaux correspondent. Sinon, la carte des tuiles pourrait être rendue avec des espaces entre les tuiles.

493
coobird

De toute façon, le travail est fait. Je suppose qu'en zigzag, vous entendez quelque chose comme ceci: (les nombres sont en ordre de rendu)

..  ..  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  ..  ..

Et par diamant, vous voulez dire:

..  ..  ..  ..  ..
  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
..  ..  ..  ..  ..

La première méthode nécessite plus de mosaïques rendues pour que tout l'écran soit dessiné, mais vous pouvez facilement vérifier les limites et ignorer toutes les mosaïques hors de l'écran. Les deux méthodes nécessiteront quelques calculs pour déterminer la position de la mosaïque 01. En fin de compte, les deux méthodes sont à peu près égales en termes de calculs pour un certain niveau d'efficacité.

10
zaratustra

Si vous avez des tuiles qui dépassent les limites de votre diamant, je vous recommande de dessiner en ordre de profondeur:

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

Le vrai problème, c’est quand vous avez besoin de dessiner des tuiles/images-objets intersectant/couvrant deux ou plusieurs autres tuiles.

Après 2 mois (difficiles) d'analyses personnelles du problème, j'ai finalement trouvé et mis en œuvre un "dessin de rendu correct" pour mon nouveau jeu cocos2d-js. La solution consiste à cartographier, pour chaque mosaïque (sensible), les images-objets qui sont "avant, arrière, haut et derrière". Une fois cela fait, vous pouvez les dessiner en suivant une "logique récursive".

0
Carlos Lopez

La réponse de Coobird est la bonne, complète. Cependant, j'ai combiné ses conseils avec ceux d'un autre site pour créer un code qui fonctionne dans mon application (iOS/Objective-C), que je souhaitais partager avec tous ceux qui viennent ici à la recherche d'une telle chose. S'il vous plaît, si vous aimez/votez pour cette réponse, faites de même pour les originaux; tout ce que j'ai fait était "me tenir sur les épaules de géants".

En ce qui concerne l'ordre de tri, ma technique est un algorithme de Painter modifié: chaque objet a (a) une altitude de la base (j'appelle "niveau") et (b) un X/Y pour la "base" ou le "pied" de l'image (exemples: la base de l'avatar est à ses pieds; la base de l'arbre est à ses racines; la base de l'avion est au centre de l'image, etc.) Ensuite, je trie du plus bas au plus haut niveau, puis du plus bas (plus haut à l'écran) au plus bas- Y, puis le plus bas (le plus à gauche) jusqu'à la base-X la plus élevée. Cela rend les carreaux comme on peut s'y attendre.

Code pour convertir l’écran (point) en mosaïque (cellule) et inversement:

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
Olie