web-dev-qa-db-fra.com

Java: Quelle est la bonne structure de données pour stocker une carte de coordonnées pour un monde de jeu infini?

Je suis habitué à coder en PHP mais je ne maîtrise pas très bien Java et cela pose un problème depuis quelque temps maintenant. Je m'attends à ce que ce soit une solution assez simple, mais je ne trouve aucun bon exemple de code de quelque manière que je le cherche, alors voici:

Je programme un jeu qui se déroule dans un monde infini généré de manière aléatoire en 2D sur une carte basée sur des tuiles (nitpicking: je sais que ce ne sera pas vraiment infini. Je m'attends juste à ce que le monde soit assez grand). L’approche habituelle du tableau multidimensionnel map [x] [y] a commencé comme une idée de base, mais comme Java ne permet pas de créer des shenanigans de clé de tableau non entiers (c’est-à-dire négatifs) comme PHP, I ne peut pas correctement avoir un système de coordonnées (-x, + x, -y, + y) avec des clés de tableau.

Je dois pouvoir trouver les objets sur une tuile à une coordonnée x, y spécifique, ainsi que trouver les "tuiles adjacentes" d'une certaine tuile. (Trivial si je peux obtenirObjetAt (x, y), je peux obtenir (x + 1, y) et ainsi de suite)

J'ai lu sur les arbres quadruples et R-trees et autres. Le concept est excitant, mais je n’ai pas vu de bon exemple simple de mise en œuvre en Java. Et puis je ne suis pas vraiment sûr de savoir si c'est exactement ce dont j'ai besoin.

Tout conseil est le bienvenu

Je vous remercie

43
Aykın

Je suis venu à ce fil avec le même problème, mais ma solution a été d'utiliser Map/HashMaps , mais ce sont une dimension. 

Pour surmonter cela, au lieu d'utiliser une carte dans une carte (ce qui serait désordonné et très inefficace), j'ai utilisé un générique Pair class (quelque chose que vous ne trouverez pas dans la bibliothèque de stock Java) bien que vous puissiez le remplacer. avec une classe de position (pratiquement le même code, mais pas générique, à la place des entiers ou des flottants). 

Donc, lors de la définition de la carte: Map<Pair, Tile> tiles = new HashMap<Pair, Tile>; 

Pour placer des objets en mosaïque sur la carte, j'ai utilisé tiles.put(new Pair(x, y), new GrassTile()); et pour récupérer l'objet tiles.get(new Pair(x, y));

[x/y serait n'importe quelle coordonnée que vous souhaitez placer ( cela permet des coordonnées négatives sans aucun désordre!), "new GrassTile ()" est juste un exemple de placement d'une tuile d'un certain type lors de la création de la carte . Évidemment - comme indiqué précédemment - la classe Pair est remplaçable.]

Pourquoi ne pas ArrayLists vous pouvez demander? Parce que les listes de tableaux sont beaucoup plus linéaires que le mappage, et sont à mon avis plus difficiles à ajouter et à récupérer, en particulier sur 2 Dimensions.

Mise à jour:

Pour ceux qui se demandent pourquoi il n’existe pas de classe Pair () en Java, voici une explication .

5
Liam Gray

1) Au lieu d’un tableau, vous pouvez utiliser un Map<Integer, Map<Integer, Tile>> ou Map<Point, Tile>, ce qui permettrait bien sûr d’indexer les index négatifs.

2) Si vous connaissez les dimensions de votre monde dès le début, vous pouvez simplement modifier votre getter pour permettre à l'API d'accepter les négatifs et de les transformer [linéairement] en positifs. Ainsi, par exemple, si votre monde correspond à 100 x 1000 tuiles et que vous voulez (-5, -100), vous auriez WorldMap.getTile(-5,-100) qui se traduirait par return tileArray[x+mapWidth/2][y+mapHeight/2]; qui est (45 400).

6
davin
2
Simon Heinen

Arbres, arbres à quatre faces, arbres binaires, arbres rouges et noirs - et tous les autres types d’arbres sont INUTILES pour vous (sauf si vous prévoyez d’avoir une carte avec une immense forêt).

Les structures de données spécialisées ont des utilisations spécifiques. À moins que vous ne trouviez une bonne raison pour laquelle votre jeu nécessite un index spatial, n'en créez pas. Si votre scénario typique est "itérer sur la zone visible, trouver quelle mosaïque est visible sur chacun des carrés", vous avez besoin d'une structure qui vous donne un accès rapide et aléatoire à une valeur stockée sous une clé spécifique. Une telle structure est une HashMap (ce que PHP utilise comme une sorte de LinkedHashMap, mais vous n'utilisiez probablement pas la partie "liée").

Vous devez suivre les conseils de xephox (et lui donner le crédit), à savoir:

  • faire une classe qui décrit un emplacement (Pair, Location, Point, peu importe);
  • rendre tous les champs (probablement x et y) finaux. Il est important qu’un lieu lui-même ne puisse pas changer (cela vous facilitera BEAUCOUP la vie);
  • générer des méthodes equals et hashcode (chaque IDE vous aidera à cela. Rappelez-vous que les implémentations DOIVENT utiliser x et y - un assistant dans votre IDE vous aidera);
  • votre carte sera: Map map = new HashMap ();

La meilleure chose à faire: si vous continuez à utiliser l'interface Carte, vous ne serez pas verrouillé et vous pourrez apporter de nombreuses améliorations. Comme envelopper la HashMap dans un objet qui crée des parties de la carte en utilisant des techniques algorithmiques.

2
fdreger

Je ne suis pas un expert en programmation de jeux, mais si les tableaux sont corrects, vous pouvez simplement traduire vos coordonnées de (-x, + x) à (0, 2x) (idem pour l'axe des ordonnées).

Ou, si vous êtes habitué aux tableaux associatifs tels que PHP, utilisez la structure correspondante en Java, qui est une carte (HashMap serait OK): définissez une classe Coordinate avec les méthodes égales et hashCode appropriées, et utilisez un HashMap<Coordinate>. Rendre Coordinate immuable rend le code plus robuste et permet de mettre en cache le hashCode.

2
JB Nizet

J'ai écrit quelques structures de données de rechange expérimentales en Java qui pourraient vous intéresser.

Le plus intéressant était le Octreap qui est, selon moi, un tout nouveau croisement entre un Treap et un Octree , qui présente les caractéristiques suivantes:

  • Coordonnées mondiales 60 bits (environ 1 000 000 * 1 000 000 * 1 000 000 de grille)
  • Coordonnées négatives prises en charge
  • L'espace vide ne nécessite aucun stockage (prend en charge les mondes très clairsemés)
  • Compresse des volumes de cellules identiques (par exemple, de grands blocs du même matériel seront stockés efficacement)
  • O (log n) pour les lectures et les écritures
1
mikera

Pourquoi ne pas diviser votre carte en morceaux (oui, fans de Minecraft, je sais où cela est également utilisé: P)? Donc, vous avez deux systèmes de coordonnées, les deux avec la même origine:

  • x/y coordonnées
  • c1/c2 coordonnées de morceau

Un morceau est toujours une taille fixe de coordonnées du monde réel (disons 128x128). Ensuite, vous avez une classe Chunk où vous avez un tableau fixe (128x128) avec toutes les informations pour chaque pixel. Et vous stockez vos morceaux dans un Map<ChunkCoordinates, Chunk> comme cela a déjà été expliqué par d'autres. Je recommanderais une HashMap.

Chaque fois que votre joueur se trouve dans une certaine région, les morceaux nécessaires sont chargés à partir de la carte et vous pouvez ensuite accéder au tableau de taille fixe qui s'y trouve. Si le morceau sait où il se trouve en coordonnées x/y, vous pouvez même avoir une fonction de support comme Pixel Chunk#getPixel(long x, long y) ou alors ...

Btw: Cela vous donne également un moyen facile de reporter la génération du monde entier jusqu'à ce que vous en ayez vraiment besoin: au début, rien n'est généré et dès qu'une Chunk est accédée sur la carte, elle n'est pas encore générée, vous pouvez simplement générer alors. Ou vous pouvez le remplir au démarrage si c'est plus facile pour vous. (Remplir un monde infini prendra beaucoup de temps, même s'il est pseudo infini)

1
brimborium

Les solutions de style hashmap sont terribles pour les calculs de contiguïté, elles nécessitent une itération de l'ensemble de données.

Quelque chose comme un quadtree ou octree est parfait SAUF que ce n’est pas infini, c’est une taille arbitraire (monde de différence là-bas).

Cependant, si vous y réfléchissez, un ArrayList n'est pas infini, c'est simplement une taille arbitraire qui augmente, n'est-ce pas?

Ainsi, un arbre à quatre arbres est un calcul rare et relativement bon, à l'exception de la disposition "infinie", il est parfait. Pourquoi ne pas simplement utiliser un de ceux dimensionné à 100 fois ce dont vous pensez avoir besoin (c’est rare, ce n’est pas vraiment grave). Si vous arrivez au point où vous vous trouvez près de Edge, attribuez un nouvel arbre à quatre arbres beaucoup plus grand.

Je pense que si vous êtes prudent (vous devrez peut-être implémenter votre propre quadtree), vous pourrez "mettre à niveau" le quadtree avec très peu d'effort et sans copie. Il suffit simplement de préfixer toutes vos adresses existantes avec des bits (les sont en binaire, chaque quadtree représente la division de l’univers existant en deux dans une dimension ou l’autre).

0
Bill K

Vous voudrez probablement utiliser une implémentation de Map. HashMap, SortedMap, etc. en fonction de la quantité de données que vous souhaitez stocker et de vos habitudes d'accès (Sorted Map est très bon pour un accès séquentiel, HashMap est meilleur pour un accès aléatoire).

Vous pouvez soit utiliser des cartes bidimensionnelles, soit convertir vos index 2D en une clé pour une carte 1D.

0
patros

Il s'agit de deux questions distinctes: comment simuler des indicateurs de tableau négatifs pour obtenir une carte "infinie" et comment stocker efficacement les mosaïques.

Sur la première question, un hack serait de maintenir quatre matrices distinctes (une?)? Une pour chaque quadrant. Tous les index peuvent alors être positifs. 

Sur la deuxième question, vous avez besoin d'une carte clairsemée. Un moyen peu efficace consiste à disposer d'une hashmap de hashmaps. En fait, cela pourrait également résoudre le premier problème:

HashMap<String, HashMap> x = new HashMap()
HashMap<String, Tile> y = new HashMap()

// get the tile at coordinates 1,2
Tile myTile = x.get("1").get("2");

// this would work as well
myTile = x.get("-1").get("-2");

Vous pouvez faire votre propre implémentation de Map qui prend des nombres entiers comme clés et qui est beaucoup, beaucoup plus efficace.

0
ccleve

Si vous voulez être vraiment rapide et très évolutif, optez pour un Octree Sparse. 

http://en.wikipedia.org/wiki/Octree

Je ne suis au courant d'aucune implémentation en Java, mais c'est trivial. Stockez simplement dans un seul octet un champ de bits pour lequel les nœuds sont actuellement liés et utilisez une liste liée pour conserver ces références (pour chaque nœud Octree, une liste distincte de 8 entrées maximum).

0
Bernd Elkemann