Conception de carte entière vs conception de réseau de tuiles

11

Je travaille sur un RPG 2D, qui comportera les cartes donjon / ville habituelles (pré-générées).

J'utilise des tuiles, que je combinerai ensuite pour faire les cartes. Mon plan initial était d'assembler les carreaux à l'aide de Photoshop ou d'un autre programme graphique, afin d'avoir une image plus grande que je pourrais ensuite utiliser comme carte.

Cependant, j'ai lu à plusieurs endroits que les gens parlent de la façon dont ils ont utilisé des tableaux pour créer leur carte dans le moteur (vous donnez donc un tableau de x tuiles à votre moteur et il les assemble comme une carte). Je peux comprendre comment cela se fait, mais cela semble beaucoup plus compliqué à mettre en œuvre, et je ne vois pas d'avantages évidents.

Quelle est la méthode la plus courante et quels sont les avantages / inconvénients de chacun?

Cristol.GdM
la source
1
Le problème principal est la mémoire. Une texture qui remplit un écran 1080p une fois est d'environ 60 Mo. Ainsi, une image utilise un int par pixel, une tuile utilise un int par (pixel / (tilesize * tilesize)). Donc, si les tuiles sont 32,32, vous pouvez représenter une carte 1024 fois plus grande en utilisant des tuiles vs un pixel. De plus, les temps d'échange de texture vont nuire à la mémoire, etc.
ClassicThunder

Réponses:

19

Tout d'abord, permettez-moi de dire que les RPG 2D me tiennent à cœur et que je travaille avec les vieux moteurs DX7 VB6 MORPG (ne riez pas, il y a 8 ans, maintenant :-)) est ce qui m'a d'abord intéressé au développement de jeux . Plus récemment, j'ai commencé à convertir un jeu sur lequel j'ai travaillé dans l'un de ces moteurs pour utiliser XNA.

Cela dit, ma recommandation est que vous utilisiez une structure basée sur des tuiles avec des couches pour votre carte. Avec n'importe quelle API graphique que vous utilisez, vous allez avoir une limite sur la taille des textures que vous pouvez charger. Sans parler des limites de mémoire de texture de la carte graphique. Donc, compte tenu de cela, si vous souhaitez maximiser la taille de vos cartes tout en minimisant non seulement la quantité et la taille des textures que vous chargez en mémoire, mais également en diminuant la taille de vos actifs sur le disque dur de l'utilisateur ET les temps de chargement, vous êtes va certainement vouloir aller avec des tuiles.

En ce qui concerne l'implémentation, je suis entré dans les détails sur la façon dont je l'ai géré sur quelques questions ici sur GameDev.SE et sur mon blog (tous deux liés ci-dessous), et ce n'est pas exactement ce que vous demandez donc je vais juste aller dans les bases ici. Je noterai également les caractéristiques des tuiles qui les rendent bénéfiques lors du chargement de plusieurs grandes images pré-rendues. Si quelque chose n'est pas clair, faites-le moi savoir.

  1. La première chose que vous devez faire est de créer une feuille de tuiles. Ceci est juste une grande image qui contient toutes vos tuiles alignées dans une grille. Ce (et peut-être un supplément en fonction du nombre de tuiles) sera la seule chose que vous aurez besoin de charger. Une seule image! Vous pouvez en charger 1 par carte ou une avec chaque tuile du jeu; quelle que soit l'organisation qui vous convient.
  2. Ensuite, vous devez comprendre comment vous pouvez prendre cette "feuille" et traduire chaque tuile en nombre. C'est assez simple avec quelques calculs simples. Notez que la division est ici une division entière , donc les décimales sont supprimées (ou arrondies vers le bas, si vous préférez). Comment convertir des cellules en coordonnées et inversement.
  3. OK, maintenant que vous avez divisé la feuille de mosaïque en une série de cellules (nombres), vous pouvez prendre ces nombres et les brancher dans le conteneur de votre choix. Par souci de simplicité, vous pouvez simplement utiliser un tableau 2D.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. Ensuite, vous voulez les dessiner. L'une des façons dont vous pouvez faire beaucoup plus efficace (en fonction de la taille de la carte) est de calculer uniquement les cellules que la caméra visualise actuellement et de les parcourir. Vous pouvez le faire en récupérant les coordonnées du tableau de tuiles de carte des coins supérieur gauche ( tl) et inférieur droit ( br) de la caméra . Faites une boucle à partir de tl.X to br.Xet, dans une boucle imbriquée, à partir de tl.Y to br.Ypour les dessiner. Exemple de code ci-dessous:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
    
  5. Cagnotte! Ce sont les bases du moteur de tuiles. Vous pouvez voir qu'il est facile d'avoir même une carte 1000x1000 avec peu de frais généraux. De plus, si vous avez moins de 255 tuiles, vous pouvez utiliser un tableau d'octets réduisant la mémoire de 3 octets par cellule. Si un octet est trop petit, une courte durée suffira probablement à vos besoins.

Remarque: J'ai laissé de côté le concept de coordonnées universelles (sur lequel se basera la position de votre caméra) car cela, je pense, sort du cadre de cette réponse. Vous pouvez le lire ici sur GameDev.SE.

Mes ressources Tile-Engine
Remarque: Tous ces éléments sont destinés à XNA, mais cela s'applique à peu près à tout - il vous suffit de modifier les appels de tirage.

  • Ma réponse à cette question décrit comment je gère les cellules de la carte et la superposition dans mon jeu. (Voir le troisième lien.)
  • Ma réponse à cette question explique comment je stocke les données au format binaire.
  • Ceci est le premier (enfin, techniquement deuxième, mais premier "technique") blog sur le développement du jeu sur lequel je travaillais. Toute la série contient des informations sur des éléments tels que les pixel shaders, l'éclairage, le comportement des carreaux, le mouvement et toutes ces choses amusantes. J'ai en fait mis à jour le message pour inclure le contenu de ma réponse au premier lien que j'ai publié ci-dessus, vous pouvez donc le lire à la place (j'ai peut-être ajouté des choses). Si vous avez des questions, vous pouvez déposer un commentaire ici ou ici et je serais heureux de vous aider.

Autres ressources Tile-Engine

  • Le didacticiel du moteur de tuiles de ce site m'a donné la base que j'ai utilisée pour créer mes cartes.
  • Je n'ai pas encore regardé ces didacticiels vidéo parce que je n'ai pas eu le temps, mais ils sont probablement utiles. :) Ils peuvent cependant être obsolètes, si vous utilisez XNA.
  • Ce site propose d'autres didacticiels qui (je pense) sont basés sur les vidéos ci-dessus. Cela vaut peut-être la peine de vérifier.
Richard Marskell - Drackir
la source
Waow, c'est deux fois plus d'informations que je ne le demandais, et répond à peu près à toutes les questions que je n'ai pas posées, super, merci :)
Cristol.GdM
@Mikalichov - Heureux d'avoir pu aider! :)
Richard Marskell - Drackir
En règle générale, avec un tableau 2D, vous souhaitez utiliser la première dimension comme votre Y et la suivante comme votre X. En mémoire, un tableau va être séquentiellement 0,0-i puis 1,0-i. Si vous faites des boucles imbriquées avec X première poule, vous sautez en arrière dans la mémoire au lieu de suivre un chemin séquentiel de lecture. Toujours pour (y) puis pour (x).
Brett W
@BrettW - Un point valide. Merci. Cela m'a amené à me demander comment les tableaux 2D sont stockés dans .Net (ordre de ligne principale. J'ai appris quelque chose de nouveau aujourd'hui! :-)). Cependant, après avoir mis à jour mon code, j'ai réalisé que c'est exactement la même chose que ce que vous décrivez, juste avec les variables commutées. La différence est dans quel ordre les tuiles sont dessinées. Mon exemple de code actuel dessine de haut en bas, de gauche à droite alors que ce que vous décrivez dessinerait de gauche à droite, de haut en bas. Donc, par souci de simplicité, j'ai décidé de revenir à l'original. :-)
Richard Marskell - Drackir
6

Les systèmes à base de tuiles et un système de modèle / texture statique peuvent être utilisés pour représenter un monde et chacun a des forces différentes. Que l'un soit meilleur que l'autre se résume à la façon dont vous utilisez les pièces et à ce qui fonctionne le mieux pour vos compétences et vos besoins.

Cela étant dit, les deux systèmes peuvent être utilisés séparément ou également ensemble.

Un système à base de tuiles standard présente les atouts suivants:

  • Tableau 2D simple de tuiles
  • Chaque tuile a un seul matériau
    • Ce matériau peut être une texture unique, multiple ou mélangé à partir de carreaux environnants
    • Peut réutiliser les textures pour toutes les tuiles de ce type, ce qui réduit la création d'actifs et les retouches
  • Chaque tuile peut être passable ou non (détection de collision)
  • Rendu et identification faciles des parties de la carte
    • La position des caractères et la position sur la carte sont des coordonnées absolues
    • Simple pour parcourir les tuiles pertinentes pour la région de l'écran

L'inconvénient des tuiles est que vous devez créer un système pour construire ces données basées sur les tuiles. Vous pouvez créer une image qui utilise chaque pixel comme une tuile, créant ainsi votre réseau 2D à partir d'une texture. Vous pouvez également créer un format propriétaire. À l'aide de bitflags, vous pouvez stocker un grand nombre de données par tuile dans un espace assez petit.

La plupart des gens font des tuiles parce que cela leur permet de créer de petits actifs et de les réutiliser. Cela vous permet de créer une image beaucoup plus grande avec des morceaux plus petits. Cela réduit les retouches car vous ne modifiez pas la carte du monde entière pour effectuer un petit changement. Par exemple si vous vouliez changer l'ombre de toute l'herbe. Dans une grande image, il faudrait repeindre toute l'herbe. Dans un système de tuiles, vous mettez simplement à jour la ou les tuiles herbe.

Dans l'ensemble, vous vous retrouverez probablement en train de faire beaucoup moins de retouches avec un système basé sur des tuiles qu'une grande carte graphique. Vous pouvez finir par utiliser un système basé sur des tuiles pour la collision même si vous ne l'utilisez pas pour votre carte au sol. Ce n'est pas parce que vous utilisez une tuile en interne que vous ne pouvez pas utiliser de modèles pour représenter les objets de l'environnement qui peuvent utiliser plus d'une tuile pour leur espace.

Vous devrez fournir plus de détails sur votre environnement / moteur / langage pour fournir des exemples de code.

Brett W
la source
Merci! Je travaille sous C #, et je vais pour une version similaire (mais moins talentueuse) de Final Fantasy 6 (ou essentiellement la plupart des Square SNES RPG), donc des carreaux de sol ET des carreaux de structure (c'est-à-dire des maisons). Ma plus grande préoccupation est que je ne vois pas comment construire le tableau 2D sans passer des heures à vérifier "il y a un coin d'herbe là, puis trois horizontaux droits, puis un coin de maison, puis ..", avec des tonnes d'erreurs possibles le long du façon.
Cristol.GdM
On dirait que Richard vous a couvert en termes de code spécifique.J'utiliserais personnellement une énumération de types de tilet et utiliserais des bitflags pour identifier la valeur de la tuile. Cela vous permet de stocker plus de 32 valeurs dans un entier moyen. Vous pourrez également stocker plusieurs drapeaux par tuile. Vous pouvez donc dire Grass = true, Wall = true, Collision = true, etc. sans variables distinctes. Ensuite, vous attribuez simplement des "thèmes" qui déterminent la feuille de tuiles pour les graphiques pour cette région de carte particulière.
Brett W
3

Dessiner la carte: les tuiles sont faciles à utiliser, car la carte peut alors être représentée comme un tableau géant d'indices de tuiles. Le code de dessin n'est qu'une boucle imbriquée:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

Pour les grandes cartes, une énorme image bitmap pour la carte entière peut occuper beaucoup d'espace en mémoire et peut être plus grande que ce qu'une carte graphique prend en charge pour une seule taille d'image. Mais vous n'aurez aucun problème avec les tuiles car vous n'allouez de la mémoire graphique qu'une seule fois pour chaque tuile (ou un total, de manière optimale, si elles sont toutes sur une seule feuille)

Il est également facile de réutiliser les informations de tuile pour, par exemple, les collisions. par exemple, pour obtenir la tuile de terrain dans le coin supérieur gauche du sprite du personnage:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Supposons que vous ayez besoin de vérifier les collisions contre les rochers / arbres, etc.

Jimmy
la source