Dessiner des mondes de jeu isométriques

178

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

J'ai lu des références (comme celle-ci ) qui suggèrent que les tuiles soient rendues d'une manière qui zigzagera chaque colonne dans la représentation en tableau 2D de la carte. J'imagine qu'ils devraient être dessinés plus en forme de diamant, où ce qui est dessiné à l'écran est plus étroitement lié à ce à quoi le tableau 2D ressemblerait, juste un peu tourné.

Les deux méthodes présentent-elles des avantages ou des inconvénients?

Benny Hallett
la source

Réponses:

504

Mise à jour: algorithme de rendu de carte corrigé, ajout d'illustrations supplémentaires, mise en forme modifiée.

Peut-être que l'avantage de la technique "zig-zag" pour mapper les tuiles à l'écran peut être dit que les tuiles xet les ycoordonnées sont sur les axes vertical et horizontal.

Approche "Dessin dans un diamant":

En dessinant une carte isométrique à l'aide de "dessin dans un losange", qui, je crois, se réfère simplement au rendu de la carte en utilisant une forboucle imbriquée sur le tableau à deux dimensions, comme 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 l'approche est qu'il s'agit d'une simple forboucle imbriquée avec une logique assez simple qui fonctionne de manière cohérente dans toutes les tuiles.

Désavantage:

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

Image de la carte des tuiles

Cependant, il va y avoir un piège à l'implémentation de l'exemple de code ci-dessus - l'ordre de rendu provoquera le dessin des tuiles censées être derrière certaines tuiles au-dessus des tuiles en face:

Image résultante d'un ordre de rendu incorrect

Afin de corriger ce problème, l'ordre de la forboucle interne doit être inversé - en commençant par la valeur la plus élevée et en effectuant un rendu vers la valeur inférieure:

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 doit être corrigé:

Image résultante de l'ordre de rendu correct

Approche "zig-zag":

Avantage:

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

L'approche en zig-zag du rendu semble compacte

Désavantage:

En essayant d'implémenter la technique du 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 forboucle imbriquée 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 la coordonnée d'une tuile en raison de la nature décalée de l'ordre de rendu:

Coordonnées sur un rendu en ordre zig-zag

Remarque: Les illustrations incluses dans cette réponse ont été créées avec une implémentation Java du code de rendu de tuiles présenté, avec le inttableau 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 avec un grand objet gris dedans.

Remarque sur la largeur et la hauteur des carreaux

Les variables tile_widthet tile_heightqui sont utilisées dans les exemples de code ci-dessus font référence à la largeur et à la hauteur de la tuile au sol dans l'image représentant la tuile:

Image montrant la largeur et la hauteur des carreaux

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

coobird
la source
136
vous avez même dessiné des images. C'est un effort.
zaratustra
Vive coobird. J'utilise l'approche du diamant pour le jeu actuel que je développe et cela fonctionne à merveille. Merci encore.
Benny Hallett
3
Qu'en est-il de plusieurs couches de hauteur? Puis-je les dessiner avec un sourire, en commençant par le niveau le plus bas et continuer jusqu'à atteindre le niveau le plus élevé?
NagyI
2
@DomenicDatti: Merci pour vos aimables paroles :)
coobird
2
C'est génial. Je viens d' utiliser votre approche de diamant et aussi, en cas de besoins quelqu'un pour obtenir Coords de grille de la position de l' écran, je l' ai fait: j = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;.
Kyr Dunenkoff
10

Dans les deux cas, le travail est fait. Je suppose que par zigzag vous voulez dire quelque chose comme ceci: (les nombres sont dans l'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 entendez:

..  ..  ..  ..  ..
  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 tuiles rendues pour que le plein écran soit dessiné, mais vous pouvez facilement vérifier les limites et ignorer toutes les tuiles complètement hors de l'écran. Les deux méthodes nécessiteront un certain nombre de calculs pour savoir quel est l'emplacement de la tuile 01. En fin de compte, les deux méthodes sont à peu près égales en termes de mathématiques requises pour un certain niveau d'efficacité.

zaratustra
la source
14
Je voulais en fait dire le contraire. La forme du losange (qui laisse les bords de la carte lisses) et la méthode en zig-zag, laissant les bords pointus
Benny Hallett
1

Si vous avez des carreaux qui dépassent les limites de votre diamant, je vous recommande de dessiner dans l'ordre de la profondeur:

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

La réponse de Coobird est la bonne et complète. Cependant, j'ai combiné ses conseils avec ceux d'un autre site pour créer du code qui fonctionne dans mon application (iOS / Objective-C), que je voulais 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, c'est de «me tenir sur les épaules de géants».

Quant à l'ordre de tri, ma technique est un algorithme de peintre modifié: chaque objet a (a) une altitude de la base (j'appelle "niveau") et (b) un X / Y pour la "base" ou "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 l'image centrale, etc.) Ensuite, je trie simplement le niveau le plus bas au niveau le plus élevé, puis le plus bas (le plus haut à l'écran) à la base la plus élevée- Y, puis le plus bas (le plus à gauche) au plus haut base-X. Cela rend les tuiles comme on pourrait s'y attendre.

Code pour convertir l'écran (point) en tuile (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;
}
Olie
la source
1

Vous pouvez utiliser la distance euclidienne du point le plus élevé et le plus proche du spectateur, sauf que ce n'est pas tout à fait correct. Il en résulte un ordre de tri sphérique. Vous pouvez redresser cela en regardant de plus loin. Plus loin, la courbure s'aplatit. Donc, ajoutez simplement, disons, 1000 à chacun des composants x, y et z pour donner x ', y' et z '. Le tri sur x '* x' + y '* y' + z '* z'.

Siobhan O'Connor
la source
0

Le vrai problème est lorsque vous devez dessiner des tuiles / sprites qui se croisent / s'étendent sur deux autres tuiles ou plus.

Après 2 mois (difficiles) d'analyses personnelles des problèmes, j'ai finalement trouvé et implémenté un "dessin de rendu correct" pour mon nouveau jeu cocos2d-js. La solution consiste à cartographier, pour chaque tuile (sensible), quels sprites sont "avant, arrière, haut et derrière". Une fois cela fait, vous pouvez les dessiner en suivant une "logique récursive".

Carlos Lopez
la source