Java: что такое хорошая структура данных для хранения координатной карты для бесконечного игрового мира?

Я привык к кодированию на PHP, но я не очень хорошо разбираюсь в Java, и это было проблемой в течение некоторого времени. Я ожидаю, что это будет довольно простое решение, однако я не могу найти хороший пример кода любым способом его поиска, поэтому здесь:

я программирую игру, которая происходит в 2d случайном сгенерированном бесконечном мире на карте на основе плитки (nitpicking: я знаю, что это не будет действительно бесконечным. Я просто ожидаю, что мир будет довольно большим). Обычный подход map[x][y] многомерный массив начинался как основная идея, но поскольку Java не предоставляет способ для нецелочисленных (т. е. отрицательных) махинаций с ключами массива, таких как PHP, я не могу правильно иметь (- x,+x,- y,+y) систему координат с ключами массива.

Мне нужно иметь возможность находить объекты на плитке в определенной координате x,y, а также находить "смежные плитки" определенной плитки. (Тривиально, если я могу getObjectAt(x, y), я могу получить(x+1, y) и так далее)

Я читал о четырех деревьях и R-деревья и тому подобное. Концепция захватывающая, однако я не видел хорошей, простой реализации примера в Java. И кроме того, я не уверен, что это именно то, что мне нужно.

любые советы приветствуются

спасибо

11 ответов


Я пришел к этой теме с той же проблемой, но моим решением было использовать Карта / HashMaps, но они одномерны.

чтобы преодолеть это, вместо того, чтобы использовать карту в карте (что было бы грязно и очень неэффективно), я использовал общий пара class (не то, что вы найдете в библиотеке Java), хотя вы можете заменить это классом позиции (практически тот же код, но не общий, вместо целых чисел или поплавков).

поэтому при определении карты: Map<Pair, Tile> tiles = new HashMap<Pair, Tile>;

для размещения объектов плитки на карте я использовал tiles.put(new Pair(x, y), new GrassTile()); и для извлечения объекта tiles.get(new Pair(x, y));.

[x / y будет любой координатой, которую вы хотите разместить (это позволяет отрицательные координаты без каких-либо беспорядок!), "new GrassTile ()" - это просто пример размещения плитки определенного типа во время создания карты. Очевидно, что, как уже говорилось ранее, класс Pair можно заменить.]

почему не ArrayLists вы можете спросить? Поскольку списки массивов намного более линейны, чем отображение, и, на мой взгляд, сложнее добавлять и извлекать плитки, особенно в 2 измерениях.

обновление:

для тех, кто интересуется, почему в Java нет класса Pair (), вот объяснение.


1) вместо массива вы можете использовать Map<Integer, Map<Integer, Tile>> или Map<Point, Tile>, что, конечно, позволит отрицательные индексы

2) Если вы знаете размеры своего мира с самого начала, вы можете просто изменить свой геттер, чтобы позволить API принимать отрицательные и [линейно] преобразовывать их в положительные. Так, например, если ваш мир 100x1000 плитки, и вы хотите (-5, -100), вы бы WorldMap.getTile(-5,-100) что соответствует return tileArray[x+mapWidth/2][y+mapHeight/2]; что (45,400)


Я не эксперт в программировании игр, но если массивы в порядке, вы можете просто перевести свои координаты с (-x, +x) на (0, 2x) (idem для оси y).

или если вы привыкли к ассоциативным массивам, таким как PHP, используйте соответствующую структуру в Java ,которая является картой ( HashMap будет в порядке): определите Coordinate класс с соответствующими методами equals и hashCode и используйте HashMap<Coordinate>. Создание неизменяемой координаты делает код более надежным и позволяет кэшировать хэш-код.


деревья, четырехъядерные деревья, бинарные деревья, красные и черные деревья-и все другие виды деревьев бесполезны для вас (если вы не планируете иметь карту с огромным лесом).

специализированные структуры данных имеют свои специфические применения. Если вы не можете придумать вескую причину, почему вашей игре нужен пространственный индекс, не создавайте его. Если ваш типичный сценарий - "итерация по видимой области, узнайте, какая плитка видна на каждом из квадратов", то вам нужна структура, которая дает вам быстрый, случайный доступ к значению, хранящемуся под определенным ключом. Такая структура является HashMap (то, что использует PHP, является своего рода LinkedHashMap, но вы, вероятно, не использовали "связанную" часть).

вам нужно следовать совету ксефокса (и отдать ему должное), и это:

  • создайте класс, который описывает местоположение (пара, местоположение, Точка, что угодно);
  • сделать все поля (возможно, x и y) окончательными. Важно, что само местоположение не может измениться (оно сделает вашу жизнь намного проще);
  • генерировать методы equals и hashcode (каждая IDE поможет вам в этом. Помните, что реализации должны использовать как x, так и y - мастер в вашей IDE поможет вам);
  • ваша карта будет: Map map = new HashMap ();

самое лучшее: если вы продолжаете использовать интерфейс карты, вы не будете заблокированы, и вы сможете сделать много улучшений. Как упаковка HashMap в объект, который создает части карта с использованием некоторых алгоритмических методов.


вы можете попробовать QuadTree (хороший пример здесь:http://www.mikechambers.com/blog/2011/03/21/javascript-quadtree-implementation/ )


как насчет разделения вашей карты на куски (да, поклонники Minecraft, я знаю, где это используется: P)? Таким образом, у вас есть две системы координат, обе с одинаковым происхождением:

  • x/y координаты
  • c1/c2 чанк с координатами

кусок всегда является фиксированным размером координаты реального мира (скажем, 128x128). Тогда у вас есть класс Chunk где у вас есть фиксированный массив (128x128) со всей информацией для каждого пикселя. И вы храните ваши куски в Map<ChunkCoordinates, Chunk> как уже объясняли другие. Я бы рекомендовал HashMap.

всякий раз, когда ваш игрок находится в определенном регионе, необходимые куски загружаются с карты, а затем вы можете получить доступ к массиву фиксированного размера там. Если кусок знает, где он помещен в x/y координаты, вы даже можете иметь некоторые функции поддержки как Pixel Chunk#getPixel(long x, long y) или так...

Btw: это также дает вам простой способ отложить поколение всего мира, пока это действительно необходимо: при запуске ничего не генерируется и как только Chunk доступен на карте, которая еще не сгенерирована, вы можете просто сгенерировать ее. Или вы можете заполнить его при запуске, если это проще для вас. (заполнение бесконечного мира займет много времени, хотя, даже если он псевдо бесконечен)


Я написал пару экспериментальных запасных структур данных на Java, которые могут вас заинтересовать.

самым интересным был Octreap это то, что я считаю совершенно новым крестом между Treap и восьмеричного дерева набора, который имел следующие характеристики:

  • 60-битные мировые координаты (aprox 1,000,000 * 1,000,000 * 1,000,000 grid)
  • отрицательные координаты поддерживается
  • пустое пространство не требует хранения (поддерживает очень разреженные миры)
  • сжимает объемы одинаковых ячеек (например, большие блоки одного и того же материала будут эффективно храниться)
  • O (log n) для чтения и записи

вы, вероятно, хотите использовать реализацию Map. HashMap, SortedMap и т. д. В зависимости от того, сколько данных вы собираетесь хранить и ваши шаблоны доступа (Сортированная карта очень хороша для последовательного доступа, HashMap лучше для случайного доступа).

вы можете использовать двумерные карты или munge ваши 2-d indeces в ключ для 1-d карты.


Это два отдельных вопроса: как имитировать отрицательные индексы массива, чтобы у вас была "бесконечная" карта, и как эффективно хранить плитки.

на первый вопрос, один хак будет поддерживать четыре отдельные матрицы (матрицы?), по одному для каждого квадранта. Тогда все индексы могут быть положительными.

по второму вопросу вам нужна разреженная карта. Один не очень эффективный способ-иметь хэш-карту хэш-карт. На самом деле, это может решить первую проблему, как хорошо:

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");

вы могли бы сделать свою собственную реализацию карты, которая принимала целые числа в качестве ключей и была намного, намного эффективнее.


Если вы хотите быть действительно быстро и масштабируемо наверняка идут с редкими восьмеричного дерева набора.

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

Я не знаю никаких реализаций в Java, но это тривиально. Просто сохраните в одном байте битовое поле, для которого узлы в настоящее время связаны, и используйте связанный список, чтобы сохранить эти ссылки (для каждого Octree-узла отдельный список max. 8 записей).


решения в стиле hashmap ужасны для вычислений смежности, они требуют итерации всего набора данных.

Что-то вроде quadtree или octree идеально, за исключением того, что это не бесконечно, это произвольный размер (мир различий).

однако, если вы думаете об этом, ArrayList не бесконечен, это просто произвольный размер, который растет, верно?

таким образом, quadtree является разреженным и довольно хорошими вычислениями смежности объявлений, за исключением "бесконечное" обеспечение идеально. Почему бы просто не использовать один из тех, размер до 100x, что вы думаете, что вам может понадобиться (это редко, не очень большое дело). Если вы когда-нибудь доберетесь до точки, где вы находитесь рядом с краем, выделите новое четверное дерево, которое намного больше.

Я считаю, что если вы будете осторожны (возможно, вам придется реализовать свой собственный quadtree), вы можете "обновить" quadtree с очень небольшими усилиями и без копирования-это должно быть просто префикс всех существующих адресов с некоторыми биты (адреса в двоичном формате, каждый бит представляет собой деление существующей вселенной пополам в том или ином измерении).