Quelle est la bonne façon de stocker des données de tilemap?


13

Je développe un jeu de plateforme 2D avec des amis uni. Nous l'avons basé sur le kit de démarrage XNA Platformer qui utilise des fichiers .txt pour stocker la carte des tuiles. Bien que cela soit simple, cela ne nous donne pas assez de contrôle et de flexibilité avec la conception de niveau. Quelques exemples: pour plusieurs couches de contenu, plusieurs fichiers sont requis, chaque objet est fixé sur la grille, ne permet pas la rotation des objets, un nombre limité de caractères, etc. Je fais donc des recherches sur la façon de stocker les données de niveau et fichier de carte.

Cela ne concerne que le stockage du système de fichiers des cartes de tuiles, pas la structure de données à utiliser par le jeu pendant son exécution. La carte de tuiles est chargée dans un tableau 2D, donc cette question concerne la source à partir de laquelle remplir le tableau.

Raisonnement pour la base de données: De mon point de vue, je constate moins de redondance des données à l'aide d'une base de données pour stocker les données de tuiles. Les carreaux dans la même position x, y avec les mêmes caractéristiques peuvent être réutilisés d'un niveau à l'autre. Il semble qu'il serait assez simple d'écrire une méthode pour récupérer toutes les tuiles utilisées à un niveau particulier de la base de données.

Raisonnement pour JSON / XML: fichiers visuellement modifiables, les modifications peuvent être suivies via SVN beaucoup plus facilement. Mais le contenu est répété.

L'un ou l'autre présente-t-il des inconvénients (temps de chargement, temps d'accès, mémoire, etc.) par rapport à l'autre? Et qu'est-ce qui est couramment utilisé dans l'industrie?

Actuellement, le fichier ressemble à ceci:

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1 - Point de départ du joueur, Sortie de niveau X,. - Espace vide, # - Plateforme, G - Gemme


2
Quel «format» existant utilisez-vous? Le simple fait de dire "fichiers texte" signifie simplement que vous n'enregistrez pas de données binaires. Lorsque vous dites «pas assez de contrôle et de flexibilité», quels sont précisément les problèmes que vous rencontrez? Pourquoi le basculement entre XML et SQLite? Cela déterminera la réponse. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad

J'opterais pour JSON car il est plus lisible.
Den

3
Pourquoi les gens pensent-ils à utiliser SQLite pour ces choses? C'est une base de données relationnelle ; pourquoi les gens pensent-ils qu'une base de données relationnelle fait un bon format de fichier?
Nicol Bolas

1
@StephenTierney: Pourquoi cette question est-elle soudainement passée de XML à SQLite à JSON à tout type de base de données? Ma question est précisément pourquoi n'est-ce pas simplement "Quelle est une bonne façon de stocker des données de tilemap?" Cette question X contre Y est arbitraire et dénuée de sens.
Nicol Bolas

1
@Kylotan: Mais cela ne fonctionne pas très bien. C'est incroyablement difficile à modifier, car vous ne pouvez modifier la base de données que via des commandes SQL. C'est difficile à lire, car les tableaux ne sont pas vraiment un moyen efficace de regarder un tilemap et d'avoir une compréhension de ce qui se passe. Et bien qu'il puisse effectuer des recherches, la procédure de recherche est incroyablement compliquée. Il est important de faire fonctionner quelque chose, mais si cela va rendre le processus de développement du jeu beaucoup plus difficile, cela ne vaut pas la peine de prendre le chemin court.
Nicol Bolas

Réponses:


14

Donc, en lisant votre question mise à jour, il semble que vous soyez plus préoccupé par les "données redondantes" sur le disque, avec quelques préoccupations secondaires concernant les temps de chargement et ce que l'industrie utilise.

Tout d'abord, pourquoi vous inquiétez-vous des données redondantes? Même pour un format gonflé comme XML, il serait assez trivial d'implémenter la compression et de réduire la taille de vos niveaux. Vous êtes plus susceptible de prendre de la place avec des textures ou des sons que vos données de niveau.

Deuxièmement, un format binaire va plus que probablement se charger plus rapidement qu'un format texte que vous devez analyser. Doublement donc si vous pouvez simplement le déposer en mémoire et avoir vos structures de données juste là. Cependant, il y a quelques inconvénients à cela. D'une part, les fichiers binaires sont presque impossibles à déboguer (en particulier pour les créateurs de contenu). Vous ne pouvez pas les différencier ou les versionner. Mais ils seront plus petits et se chargeront plus rapidement.

Ce que certains moteurs font (et ce que je considère comme une situation idéale) consiste à implémenter deux chemins de chargement. Pour le développement, vous utilisez un format texte quelconque. Peu importe le format spécifique que vous utilisez, tant que vous utilisez une bibliothèque solide. Pour la publication, vous passez au chargement d'une version binaire (plus petit, plus rapide, éventuellement dépourvu d'entités de débogage). Vos outils pour éditer les niveaux crachent les deux. C'est beaucoup plus de travail qu'un seul format de fichier, mais vous pouvez en quelque sorte tirer le meilleur parti des deux mondes.

Cela étant dit, je pense que vous sautez un peu le pistolet.

La première étape de ces problèmes est de toujours décrire le problème auquel vous vous heurtez. Si vous savez de quelles données vous avez besoin, vous savez quelles données vous devez stocker. De là, c'est une question d'optimisation testable.

Si vous avez un cas d'utilisation à tester, vous pouvez tester différentes méthodes de stockage / chargement de données pour mesurer tout ce que vous considérez comme important (temps de chargement, utilisation de la mémoire, taille du disque, etc.). Sans rien savoir, il est difficile d'être plus précis.


9
  • XML: facile à modifier à la main, mais une fois que vous commencez à charger un grand nombre de données, il ralentit.
  • SQLite: Cela peut être mieux si vous souhaitez récupérer de nombreuses données à la fois ou même de petits morceaux. Cependant, à moins que vous n'utilisiez cela ailleurs dans votre jeu, je pense que c'est exagéré pour vos cartes (et probablement trop compliqué).

Ma recommandation est d'utiliser un format de fichier binaire personnalisé. Avec mon jeu, j'ai juste une SaveMapméthode qui passe et enregistre chaque champ en utilisant a BinaryWriter. Cela vous permet également de choisir de le compresser si vous le souhaitez et vous donne plus de contrôle sur la taille du fichier. Par exemple, enregistrez un shortau lieu d'un intsi vous savez qu'il ne sera pas supérieur à 32767. Dans une grande boucle, enregistrer quelque chose en tant que shortau lieu d'un intpeut signifier une taille de fichier beaucoup plus petite.

De plus, si vous suivez cette voie, je recommande que votre première variable dans le fichier soit un numéro de version.

Prenons par exemple une classe de carte (très simplifiée):

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

Maintenant, supposons que vous vouliez ajouter une nouvelle propriété à votre carte, dire un Listdes Platformobjets. J'ai choisi cela parce que c'est un peu plus impliqué.

Tout d'abord, vous incrémentez le MapVersionet ajoutez le List:

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

Ensuite, mettez à jour la méthode de sauvegarde:

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

Ensuite, et c'est là que vous voyez vraiment l'avantage, mettez à jour la méthode de chargement:

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

Comme vous pouvez le voir, la LoadMapsméthode peut non seulement charger la dernière version de la carte, mais aussi des versions plus anciennes! Lorsqu'il charge les anciennes cartes, vous contrôlez les valeurs par défaut qu'il utilise.


9

Histoire courte

Une alternative intéressante à ce problème consiste à stocker vos niveaux dans une image bitmap, un pixel par tuile. En utilisant RGBA, cela permet facilement de stocker quatre dimensions différentes (calques, identifiants, rotation, teinte, etc.) sur une seule image.

Longue histoire

Cette question m'a rappelé quand Notch a diffusé en direct son entrée à Ludum Dare il y a quelques mois, et j'aimerais partager au cas où vous ne savez pas ce qu'il a fait. Je pensais que c'était vraiment intéressant.

Fondamentalement, il a utilisé des bitmaps pour stocker ses niveaux. Chaque pixel de la bitmap correspondait à une "tuile" dans le monde (pas vraiment une tuile dans son cas car il s'agissait d'un jeu diffusé en streaming mais assez proche). Un exemple d'un de ses niveaux:

entrez la description de l'image ici

Donc, si vous utilisez RGBA (8 bits par canal), vous pouvez utiliser chaque canal comme une couche différente, et jusqu'à 256 types de tuiles pour chacun d'eux. Serait-ce suffisant? Ou par exemple, l'un des canaux pourrait maintenir la rotation de la tuile comme vous l'avez mentionné. En outre, la création d'un éditeur de niveau pour travailler avec ce format devrait également être assez triviale.

Et le meilleur de tous, vous n'avez même pas besoin d'un processeur de contenu personnalisé car vous pouvez simplement le charger dans XNA comme tout autre Texture2D et lire les pixels en boucle.

IIRC, il a utilisé un point rouge pur sur la carte pour signaler un ennemi, et en fonction de la valeur du canal alpha pour ce pixel, il déterminerait le type de l'ennemi (par exemple, une valeur alpha de 255 pourrait être une chauve-souris tandis que 254 serait un zombie ou autre chose).

Une autre idée serait de subdiviser vos objets de jeu en ceux qui sont fixés dans une grille et ceux qui peuvent se déplacer "finement" entre les tuiles. Conservez les tuiles fixes dans le bitmap et les objets dynamiques dans une liste.

Il pourrait même y avoir un moyen de multiplexer encore plus d'informations dans ces 4 canaux. Si quelqu'un a une idée à ce sujet, faites-le moi savoir. :)


3

Vous pourriez probablement vous en tirer avec presque n'importe quoi. Malheureusement, les ordinateurs modernes sont si rapides que cette quantité de données probablement relativement petite ne ferait pas vraiment de différence de temps notable, même si elle est stockée dans un format de données gonflé et mal construit qui nécessite une énorme quantité de traitement à lire.

Si vous voulez faire la "bonne" chose, vous faites un format binaire comme Drackir l'a suggéré, mais si vous faites un autre choix pour une raison stupide, il ne reviendra probablement pas et ne vous mordra pas (du moins pas en termes de performances).


1
Oui, cela dépend définitivement de la taille. J'ai une carte d'environ 161 000 tuiles. Chacun a 6 couches. Je l'avais à l'origine en XML, mais il faisait 82 Mo et a pris une éternité à charger. Maintenant, je l'enregistre dans un format binaire et c'est environ 5 Mo avant la compression et les charges dans environ 200 ms. :)
Richard Marskell - Drackir

2

Voir cette question: «XML binaire» pour les données de jeu? J'opterais également pour JSON ou YAML ou même XML, mais cela a beaucoup de frais généraux. Quant à SQLite, je ne l'utiliserais jamais pour stocker des niveaux. Une base de données relationnelle est pratique si vous prévoyez de stocker beaucoup de données qui sont liées (par exemple, a des liens relationnels / clés étrangères) et si vous prévoyez de poser un grand nombre de requêtes différentes, le stockage de tilemaps ne semble pas faire ce genre de les choses et ajouterait une complexité inutile.


2

Le problème fondamental de cette question est qu'elle confond deux concepts qui n'ont rien à voir l'un avec l'autre:

  1. Paradigme de stockage de fichiers
  2. Représentation en mémoire des données

Vous pouvez stocker vos fichiers en texte brut dans un format arbitraire, JSON, script Lua, XML, un format binaire arbitraire, etc. Et rien de tout cela ne nécessitera que votre représentation en mémoire de ces données prenne une forme particulière.

C'est le travail de votre code de chargement de niveau de convertir le paradigme de stockage de fichiers en votre représentation en mémoire. Par exemple, vous dites: «Je constate moins de redondance des données à l'aide d'une base de données pour stocker les données de tuiles». Si vous voulez moins de redondance, c'est quelque chose que votre code de chargement de niveau devrait rechercher. C'est quelque chose que votre représentation en mémoire devrait être capable de gérer.

Ce n'est pas quelque chose dont votre format de fichier doit se préoccuper. Et voici pourquoi: ces fichiers doivent provenir de quelque part.

Soit vous allez les écrire à la main, soit vous allez utiliser une sorte d'outil qui crée et modifie les données de niveau. Si vous les écrivez à la main, la chose la plus importante dont vous avez besoin est un format facile à lire et à modifier. La redondance des données n'est pas quelque chose que votre format doit même prendre en compte, car vous passerez la plupart de votre temps à modifier les fichiers. Voulez-vous réellement utiliser manuellement les mécanismes que vous proposez pour assurer la redondance des données? Ne serait-il pas préférable d'utiliser votre temps pour que votre chargeur de niveau s'en occupe?

Si vous disposez d'un outil qui les crée, le format réel ne compte (au mieux) que pour la lisibilité. Vous pouvez mettre n'importe quoi dans ces fichiers. Si vous souhaitez omettre des données redondantes dans le fichier, concevez simplement un format qui peut le faire et faites en sorte que votre outil utilise correctement cette capacité. Votre format de tilemap peut inclure la compression RLE (encodage de longueur), la compression de duplication, quelles que soient les techniques de compression que vous souhaitez, car c'est votre format de tilemap.

Problème résolu.

Les bases de données relationnelles (RDB) existent pour résoudre le problème de l'exécution de recherches complexes sur de grands ensembles de données qui contiennent de nombreux champs de données. Le seul type de recherche que vous effectuez sur une carte de tuiles est «obtenir la tuile à la position X, Y». N'importe quel tableau peut gérer cela. L'utilisation d'une base de données relationnelle pour stocker et maintenir des données de tilemap serait extrêmement exagérée et ne vaut vraiment rien.

Même si vous intégrez une certaine compression dans votre représentation en mémoire, vous pourrez toujours battre facilement les RDB en termes de performances et d'encombrement de la mémoire. Oui, vous devrez réellement implémenter cette compression. Mais si la taille des données en mémoire est une préoccupation telle que vous envisagez un RDB, vous souhaiterez probablement implémenter des types spécifiques de compression. Vous serez plus rapide qu'un RDB et plus faible en mémoire en même temps.


1
  • XML: vraiment simple à utiliser, mais la surcharge devient ennuyeuse lorsque la structure devient profonde.
  • SQLite: plus pratique pour les grandes structures.

Pour des données vraiment simples, je choisirais probablement la voie XML, si vous êtes déjà familier avec le chargement et l'analyse XML.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.