«Zonage» des zones sur une grande carte de tuiles, ainsi que des donjons


10

Mon jeu a une carte comme celle de Minecraft, dans la façon dont elle est pseudoinfinie et générée aléatoirement. Et grand. Supposons que l'utilisateur ait exploré une zone de 1 000 x 1 000 (2D ici), c'est donc 1 000 000 de tuiles.

Évidemment, je ne vais pas pouvoir tout stocker en mémoire. Je ne veux pas non plus simplement ignorer tout ce qui se trouve sur une tuile 10 ou dans un rayon quelconque - les deux ne seront pas mis à jour (tous les PNJ, peut-être des tuiles réactives) et je devrais travailler avec des positions maladroites comme 1382,12918.

Donc, si je devais le diviser en morceaux ou en zones ou quoi que ce soit, disons des tuiles 64x64, je devrais stocker la position de chaque tuile et objet comme:

Morceau a, b. Position x, y.

Mais que faire si je voulais des donjons et autres dans ma carte? Donc, une seule tuile qui mène à peut-être un donjon de 40 étages, chacun d'une superficie de 40x40. Je ne peux pas exactement les stocker sur la même carte.

Et il y a le côté mémoire; combien pourrais-je potentiellement stocker en mémoire en même temps raisonnablement? Je pourrais faire des tuiles par ID assez facilement, et y avoir le tableau 2D normal. Ou encore, existe-t-il une solution plus efficace que d'avoir un fichier de données de types de tuiles ? Donc, pour un 64x64 ... ça ne fera que 20K ou autre. J'aimerais autant que possible entourer pour être chargé pour la mise à jour la plus réaliste. Mais je ne sais pas quelles sortes de limites je peux atteindre sans devenir un problème de mémoire.

Réponses:


9

Bien que je sois d'accord avec le sentiment: "ne vous inquiétez pas à moins que ce soit un problème avéré", je pense qu'il vaut la peine d'y penser dès le début: le réaménagement d'une solution est beaucoup plus douloureux. Et oui, seule la mise à jour des tuiles «à proximité» est la solution. Mais le stockage et l'adressabilité des éléments dans votre monde de jeu de manière efficace sont très importants pour des raisons de performances.

Ce à quoi vous pensez vraiment ici, c'est un ensemble de données clairsemé: quelque chose où les indices potentiels sont grands (ou illimités), mais seule une petite proportion est réellement utilisée. Le point clé est que vous ne savez pas exactement quelle proportion sera utilisée.

La solution standard à un problème d'ensembles de données clairsemés consiste à séparer l'index / l'adressabilité du stockage de données réel. Donc, si l'objet tuile est cher, stockez-le sous une forme compacte (par exemple un tableau plat). Mais permettez-lui d'être indexé via un objet moins cher. Dans sa forme la plus simple, il peut s'agir d'une matrice 2D (ou 3D) que vous pouvez facilement indexer par coordonnées, mais chaque élément de la matrice est simplement un index. Vous utilisez ensuite cet index pour rechercher le contenu réel des tuiles dans un tableau compact séparé. Si le contenu des tuiles n'existe pas encore, ajoutez-les à la fin du tableau et stockez l'index dans la matrice 3D.

La solution devient plus complexe si vous souhaitez prendre en charge la suppression du contenu (car cela conduit à la fragmentation du tableau de contenu), et si le contenu de votre mosaïque est bon marché, alors le poids supplémentaire de l'index (indices 32 bits ou 64 bits) dépassera probablement les économies de ne pas stocker chaque tuile potentielle unique. C'est également une recherche supplémentaire, qui nuira aux performances de votre cache.

Vous pouvez obtenir encore plus d'efficacité de stockage en introduisant des couches supplémentaires d'indirection. Disons que vous organisez vos tuiles en morceaux, et les morceaux ont une granularité de 64x64x64. Étant donné une tuile à 125, 1, 132, vous savez qu'elle appartient en morceaux (1,0,2). Vous avez donc un monde, qui se compose d'un tableau de blocs compact et d'une matrice d'indices de blocs (-1 si le bloc n'existe pas). Le contenu de chaque bloc (s'il est présent) est une matrice 64x64x64 d'index de tuiles (-1 si la tuile n'existe pas encore), et un tableau compact de tuiles utilisées. De cette façon, vous ne gravez pas une énorme quantité d'index de tuiles pour des morceaux qui ne sont jamais utilisés. En suivant ce type d'approche et en choisissant des nombres raisonnables pour la granularité des morceaux, vous pouvez augmenter massivement votre univers et garder votre utilisation de la mémoire sous contrôle. En fait, si vous faites vos morceaux 32x32x32,

Vous pouvez également faire des astuces sournoises, comme utiliser le bit de poids fort de vos morceaux ou index de tuiles pour signifier quelque chose de spécial. Donc, si une entrée dans la matrice de tuiles a le bit supérieur défini, les 31 bits inférieurs ne signifient pas un index de tuile, ils signifient plutôt un `` index de distorsion '' ou quelque chose de similaire, et vous pouvez le rechercher dans une liste maintenue séparément pour connaître les coordonnées qu'il mène.


Je mettrais en garde quiconque tomberait sur cette question à l'avenir: bien que rien dans ce post ne soit incorrect, c'est vraiment horrible pour un jeu comme Minecraft, qui a un monde non clairsemé. (Et MrCranky dit plus.) Votre monde ne bénéficie que si elle a de quelques somethings et beaucoup de riens , pas quelques somethings et beaucoup de bouteilles vides .

1
Je suis tout à fait d'accord pour dire que les frais généraux impliqués dans le maintien d'une solution de stockage d'ensembles de données clairsemés sont assez élevés, vous ne devriez donc y aller que si l'équilibre était clairement biaisé vers la rareté. Les facteurs clés ici sont: le coût de l'élément de données et la probabilité que l'élément de données soit inutilisé. Minecraft a un ensemble de données potentiel énorme et massif (un grand nombre de tuiles dans chaque direction). La proportion réelle du monde de jeu potentiel utilisé est minime. Même si vous en revendiquez de larges pans lorsque vous vous déplacez dans le monde, il s'agit toujours d'une infime fraction des éléments de données potentiels.
MrCranky

1
Ce qui rend Minecraft différent, c'est que le coût unitaire est minuscule - c'est peut-être un seul octet de stockage? Une valeur indique «rien là-bas», les autres sont des types de blocs et vous pouvez facilement les intégrer dans un octet. Donc, vous n'auriez pas d'index pour un seul bloc, c'est juste fou, l'index est beaucoup plus grand que de simplement stocker la valeur réelle du bloc. Mais les règles de jeu de données clairsemées fonctionnent toujours aux niveaux supérieurs. Chaque bloc (par exemple 64x64x64) est coûteux à stocker (blocs de 256 Ko), donc si vous pouvez utiliser une seule petite valeur pour indiquer son absence, ou son adresse si elle existe, alors vous avez économisé un stockage massif.
MrCranky

6

Pourquoi ne pouvez-vous pas stocker un million de tuiles en mémoire? Même mon téléphone a 256 Mo de RAM; un million de tuiles vides va être quoi, 4-32 Mo?


Cela semblait juste un nombre énorme à stocker. En plus de cela, cela va prendre beaucoup de temps pour les mettre à jour.
The Communist Duck

2
Il est beaucoup plus facile d'ignorer simplement un ensemble de tuiles pour les mises à jour que de gérer une sorte de solution de pagination sur disque. Ignore les. Ne mettez pas à jour les tuiles à plus de X distance d'un acteur connu.

2
@TheCommunistDuck La seule chose qui compte, c'est la quantité totale de mémoire utilisée pour stocker les tuiles, pas le nombre de tuiles.
Justin

1
D'accord avec Kragen et Joe. Ne vous inquiétez pas de ce qui ressemble beaucoup - faites les mathématiques et travaillez-les. Il est peu probable que ce soit un problème.
Kylotan
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.