La meilleure solution pour "chaîne de niveau"?


10

J'ai un jeu qui génère une carte de niveau aléatoire au début du niveau. Je veux implémenter un moyen d'enregistrer et de charger le niveau.

Je pensais que peut-être XML serait une bonne option pour enregistrer toutes les variables, alors il serait facile pour moi de créer quelque chose qui puisse analyser ce XML et générer exactement le même niveau.

Mais XML est probablement exagéré pour mes besoins. Je me souviens à l'époque avec l'ancienne console Sega qui n'avait pas la capacité de sauvegarder votre jeu (je pense que le jeu Worms l'a fait aussi), qu'ils vous donneraient un tas de personnages que vous pourriez écrire. Si vous insériez cette chaîne plus tard, elle chargera le niveau exact.

Une "chaîne de niveau" serait-elle une bonne option? Serait-ce une sorte de conversion "base60"? Comment pourrais-je implémenter cela?

Réponses:


20

Vraisemblablement, tout ce dont vous avez besoin pour sauvegarder est la graine aléatoire, qui n'est généralement qu'un entier. Vous pouvez encoder l'int en base64 si vous voulez le rendre un peu plus opaque, mais ce n'est probablement pas nécessaire.


Vous pouvez également générer la graine à partir d'un vrai mot (peut-être acheter en additionnant les caractères pour obtenir votre graine) et ainsi rendre quelque chose de plus significatif. Vous devrez cependant stocker les mots dans votre jeu ou les récupérer.
Jonathan Fischoff

C'est très bien, mais il faudrait également stocker des données dynamiques si le jeu est joué. La chaîne aurait besoin de sauvegarder les positions des caractères / le score, etc. Quelle est donc la meilleure façon de générer cette chaîne?
Adam Harte

J'utiliserais probablement JSON (ou XML si vous préférez) pour sérialiser l'état mondial, puis encoder en base64 cette chaîne. Vous n'êtes pas sûr que tout cela soit utile, pourquoi ne pas simplement créer un système de sauvegarde / chargement plus orienté GUI? Je suppose que cela est basé sur le Web et que vous ne souhaitez pas gérer ce côté serveur. Peut-être regardez shygypsy.com/farm/p.cgi pour un exemple?
coderanger

23

Quel que soit le format que vous utilisez pour vos sauvegardes, pour l'amour de Dieu, entrez un numéro de version. Vous pourrez avoir des chargements rétrocompatibles en vous ramifiant sur le numéro de version, ou vous pourrez reconnaître en toute sécurité les sauvegardes qui sont trop anciennes charger.

Vous le regretterez si vous ne le faites pas.


7
+1 ne répond pas vraiment à la question mais est si important.
Jonathan Fischoff

10

JSON est bon, mais YAML est meilleur. :) http://www.yaml.org/ et http://code.google.com/p/yaml-cpp/ pour l'une des implémentations les plus agréables à utiliser.

YAML est un sur-ensemble de JSON qui ajoute la prise en charge de quelques fonctionnalités intéressantes, notamment:

  • Noeuds binaires. C'est idéal pour sérialiser le type de données que vous pourriez avoir à traiter pour les descriptions de niveau. JSON vous oblige à traduire dans un format intermédiaire de votre choix, comme Base64, avant d'écrire / après l'analyse. YAML a un type de nœud !! binaire qui indique à la bibliothèque d'analyseur de le faire pour vous.
  • Références intra-document. Si le même objet apparaît deux fois dans le document, JSON l'écrira deux fois et lorsque vous le relirez, vous obtiendrez deux copies. De nombreux émetteurs YAML peuvent détecter cette situation et au lieu d'une deuxième copie, émettre une référence à la première, quand elle peut être détectée lors du chargement.
  • Types de nœuds personnalisés. Vous pouvez marquer chaque carte dans une liste avec par exemple !! Player, !! Enemy, etc., et ainsi garder vos informations de type plus hors bande.
  • YAML prend en charge un formatage plus lisible.
  • Étant donné que JSON est un sous-ensemble de YAML, la plupart des lecteurs YAML n'auront aucun problème à lire les documents JSON.

1
Yaml est très cool, et si vous évitez certaines des fonctionnalités du sur-ensemble, il peut être converti en json sans difficulté.
Jethro Larson

3

Si vous souhaitez sérialiser toutes les données du jeu, je recommanderais JSON comme format de fichier, c'est pourquoi il est plus facile d'utiliser le XML et le support est très bon dans de nombreuses langues.

J'ai utilisé cette bibliothèque pour C ++ et cela fonctionne très bien.

http://jsoncpp.sourceforge.net/


3

XML est un bon choix si vous n'êtes pas limité par sa taille et qu'il est pris en charge nativement (par exemple en .NET et Flash) mais si vous voulez un format mince, vous pouvez créer votre propre format et analyseur assez facilement. J'utilise normalement 1 caractère par exemple. virgule pour séparer chaque objet. Pour décoder la chaîne, effectuez une scission sur virgule. Maintenant, chaque objet a besoin de propriétés différentes, alors séparez-les avec un caractère différent, par exemple un point-virgule, et utilisez un autre caractère pour séparer les noms de propriété des valeurs de propriété, par exemple. Côlon. Tout peut donc être décodé facilement sans regex simplement en utilisant string.split. Voici un exemple:

id:1;x:5;y:45.2;angle:45,id:28;x:56;y:89;angle:12;health:78

vous pouvez économiser encore plus d'espace en gardant les noms de propriété à 1 caractère, par exemple h pour la santé. Par exemple.

i:1;x:5;y:45.2;a:45,i:28;x:56;y:89;a:12;h:78

Comparer à l'alternative JSON:

{"o":[{"i":1, "x":5, "y":45.2, "a":45}, {"i":28, "x":56, "y":89, "a":12, "h":78}]}

De plus, si vous souhaitez réduire la taille de vos numéros, vous pouvez les encoder en utilisant l'ensemble complet de caractères UTF16 imprimables. Ce fil m'a inspiré à poser une question sur Stack Overflow sur la quantité de données que vous pourriez emballer dans un caractère à l'écran . La réponse semble être quelque part au-dessus de 40000 valeurs pour un entier, si cela ne vous dérange pas d'avoir des pièces de brail, de kanji et d'échecs: ♔♕♖♗♘♙♚♛♜♝♞♟

Pour obtenir une réduction de taille supplémentaire, vous pouvez utiliser l'ordre de lecture / écriture pour déterminer quelle valeur est laquelle, de sorte que les deux premiers caractères représentent l'id, les deux suivants sont la position x, les deux suivants l'y, puis l'angle, puis la santé , etc. Donc:

F5DGP@%&002DFTK#OP1F

pourrait stocker toutes les mêmes informations que les autres exemples.

Les grilles de tuiles peuvent être stockées comme une simple chaîne, chaque caractère représentant un type de tuile différent, par exemple:

i789pog5h3kl

où je pourrais dire la lave, 9 signifie l'herbe, etc.


C'est plus dans le sens de ce que je demande. Je mentionne XML dans ma question, mais les gens le suggèrent toujours!
Adam Harte

1
Ajoutez {} autour de cela et vous avez essentiellement JSON ;-)
coderanger

Vous devez également ajouter une charge de guillemets. Cela doublerait probablement le nombre de caractères, mais vous obtiendriez des objets imbriqués.
Iain

1

Si vous codez en .Net, XML est très facile à utiliser, car vous pouvez sérialiser / désérialiser votre classe de niveau en / hors de XML avec seulement quelques lignes, puis tout cela dans une classe bien gérée.

TheMap serait une variable de type Map dans laquelle toutes vos données sont chargées.

Dim TheMap As New Map

En supposant que vous avez une classe Map déjà construite, cela enregistrerait votre carte en XML:

Dim Serializer As New System.Xml.Serialization.XmlSerializer(GetType(TheMap))
Dim Strm As New FileStream("c:\Map.xml", FileMode.Create, FileAccess.Write, FileShare.None)
Serializer.Serialize(Strm, TheMap)
Strm.Close()

Cela chargerait ensuite ce XML dans votre classe de carte, pour être à nouveau utilisé dans le code.

Dim Reader As New StreamReader("map.xml")
Dim Serializer As New System.Xml.Serialization.XmlSerializer(GetType(TheMap))

TheMap = Serializer.Deserialize(Reader)
Reader.Close()

À partir de ce moment, votre fichier XML est maintenant chargé dans votre classe pour une utilisation facile.

En ce qui concerne votre problème "Level String", ce qui a été dit auparavant fonctionnerait très bien, vous pouvez simplement utiliser le numéro de graine comme "Level String".

Sinon, vous pouvez simplement pré-générer autant de cartes différentes que vous le souhaitez, et les avoir toutes enregistrées avec une "chaîne de niveau", puis l'utiliser pour afficher la carte appropriée.


+1 même si je déteste XML - Si le langage fournit un format de sérialisation par défaut, il est préférable de le considérer d'abord, surtout s'il s'agit d'un format que d'autres outils pourraient théoriquement analyser (comme XML / JSON / YAML).

0

J'utiliserais un appareil simple structou similaire (selon votre langue) pour stocker tous les états du jeu dans un endroit central. Si vous voulez la protection des setters / getters, vous pouvez envelopper la structure dans a class.

Si vous vous sentez à la hauteur, utilisez des champs de bits ou faites vous-même la manipulation de bits à l'aide d'opérateurs au niveau du bit.

Sachez que dans certaines langues, les règles de remplissage et d'emballage des structures peuvent être un peu compliquées - mais cela peut aussi ne pas avoir beaucoup d'importance pour votre cas si vous avez un ou deux octets de remplissage.

Vous pouvez également être en mesure d'utiliser un #pragma(tel que #pragma pack(1)) ou un __attribute__pour emballer étroitement la structure, éliminant ainsi le rembourrage. Cela peut ou non fonctionner selon votre compilateur et votre architecture cible.

Notez que l'utilisation de champs de bits et de pragmas ou d'attributs de pack peut réduire la portabilité. À travers les architectures matérielles, l'endianité du champ struct (ordre des octets) peut également changer. Donc, vous voudrez peut-être éviter cela si vous essayez la portabilité.

(Par exemple, Pac-Man, cette structure peut contenir naïvement un identifiant de carte ou une graine de carte, une position Pac-Man x et y, quatre positions fantômes x et y et un grand champ de bits pour la présence ou l'absence de 32 à 64 pastilles, quel que soit le maximum.)

Une fois que vous avez votre structure, passez-la à quelque chose comme une fonction xxencode :

encode_save( char * outStringBuf, size_t outStringBufSize,
             const SaveStruct * inSaveData, size_t inSaveDataSize )

L'écriture de cette fonction est un peu sujette aux erreurs; vous devez décaler et combiner les octets si nécessaire pour obtenir par exemple 6 bits à la fois, puis traduire en un caractère approprié. J'essaierais personnellement de traquer le code de quelqu'un d'autre, à moins que je ne le fasse pour le "plaisir" (et je voudrais probablement une suite de tests pour cela).

Ne sous-estimez jamais le pouvoir de la vieille école structaux bons endroits. Nous l'avons utilisé une tonne pour les jeux GBA et DS ici.


La sérialisation de la structure brute est non portable et extrêmement fragile par rapport au nouveau code. À moins que ce ne soit la dernière étape de la cuisson des données pour une plate-forme avec des ressources très limitées, c'est une optimisation très prématurée. Y a-t-il une raison pour laquelle vous préférez 6 bits à 8? Le format ne sera pas lisible par l'homme de toute façon, vous pourriez aussi bien profiter de la vitesse et du débogage d'une véritable structure.

@Joe: J'ai mentionné qu'il n'était pas portable et certaines des préoccupations potentielles. La question demandait spécifiquement une "chaîne de niveau" lisible par l'homme comme les anciens jeux Sega et mentionnait la conversion base60; passer une structure à travers quelque chose comme xxencode fera cela. Ce n'est pas nécessairement une optimisation prématurée: une structure simple, non compressée, est un moyen bien "central" de stocker les données de sauvegarde, et peut simplifier une grande partie du code qui interagit avec les données. Voir, par exemple, les récents articles de Noel Llopis sur les structures non-membres non-amis et la programmation "inside out". C'est juste KISS.
leander

1
Je suis totalement d'accord avec cette façon. Oui, ce n'est pas portable sur toutes les versions, ni sur les systèmes, mais encore une fois, les sauvegardes ne sont pas des ressources qui doivent être réutilisées pendant le développement ou copiées sur plusieurs plateformes. Le débogage n'est guère un problème avec une lecture / écriture en place, implémentez-le une fois et cela fonctionnera pour toujours. L'extensibilité IF est vraiment un problème - ajoutez un numéro de version comme premier octet / mot et vous pouvez l'activer (bien que cela introduirait une vulnérabilité de données). Ce n'est pas comme si XML allait résoudre les problèmes de version - ils ont tendance à être plus impliqués que la simple définition d'une valeur. Oui, je suis pragmatique.
Kaj

0

XML est bon pour les documents à structure arbitraire (les éléments peuvent apparaître à différents niveaux de l'arborescence) ou pour l'incorporation de format étranger (comme mettre svg dans une page xhtml). Si vous n'avez pas ces exigences, c'est un format vraiment inefficace, et quelque chose de plus simple comme csv ou json est préférable.

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.