Sposób przechowywania potencjalnie nieskończonych danych mapy 2D?

29

Mam platformówkę 2D, która obecnie obsługuje porcje po 100 na 100 kafelków, przy czym współrzędne porcji są przechowywane jako długie, więc jest to jedyny limit map (maxlong * maxlong). Wszystkie pozycje podmiotów itp. Itp. Są istotne, więc nie ma tam limitu.

Mam problem z tym, jak przechowywać te fragmenty i uzyskiwać do nich dostęp bez posiadania tysięcy plików. Jakieś pomysły na najlepiej szybki i tani format archiwum HD, w którym nie trzeba otwierać wszystkiego naraz?

Blam
źródło
2
Niektóre struktury danych, w których można znaleźć więcej inspiracji, to rzadkie macierze i (wielopoziomowe) tabele stron .
Andrew Russell,
Niski priorytet: czy możesz wyjaśnić, czy „długi” typ danych jest 32- czy 64-bitowy?
Randolf Richardson
2
@Randolf, biorąc pod uwagę, że jest to C #, prawdopodobnie oznacza to C #, longktóry jest 64-bitowy (więc maxlong jest Int64.MaxValue).
Andrew Russell,
Notch ma kilka ciekawych rzeczy do powiedzenia na temat nieskończonych map w Minecraft na swoim blogu tutaj: notch.tumblr.com/post/3746989361/terrain-generation-part-1
dlras2

Odpowiedzi:

17

Utwórz niestandardowy format mapy dla swojej gry. To łatwiejsze niż myślisz. Wystarczy użyć klasy BinaryWriter. Najpierw napisz nagłówek w kilku ints lub uintach. Informacje, które należy uwzględnić w nagłówku:

  • Liczby magiczne / liczba magiczna formatu pliku użytkownika.
  • Początek / koniec / rozmiar porcji opisanych w tym pliku

a także (i tutaj pojawia się część krytyczna pod względem wydajności

  • int, które opisują pozycję początkową w pliku. Więc nie musisz szukać określonych fragmentów.

Za pomocą powyższej metody możesz (i powinieneś) utworzyć indeks zawartości swoich plików, zawierający pewien opis (nazwę określoną przez użytkownika dla regionu / porcji lub tylko współrzędne) i jako drugą wartość pozycję w pliku.

Następnie, gdy chcesz załadować konkretną porcję, musisz po prostu przeszukać indeks. Po otrzymaniu pozycji ustaw fileStream.Position = PositionOfChunkFromIndex i możesz ją załadować.

Chodzi przede wszystkim o projekt formatu pliku z nagłówkiem opisującym zawartość pliku w najbardziej efektywny sposób.

Po prostu zapisz pliki z niestandardowym rozszerzeniem, które utworzyłeś i gotowe.

BONUS: Dodaj kompresję BZip2 do określonych regionów pliku / całej zawartości (nie nagłówka !!), abyś mógł rozpakować określone fragmenty z pliku, aby uzyskać bardzo mały rozmiar pamięci.

Riki
źródło
12
Warto zauważyć, że jeśli zamierzasz modyfikować ten plik w locie, będziesz potrzebować nagłówka / indeksu o stałej wielkości lub zewnętrznego, tak abyś mógł dodawać fragmenty do pliku bez konieczności przepisywania cały plik (z powodu zmiany przesunięć).
Andrew Russell
Czy w tym momencie nie wdrażasz tylko bazy danych Flatfile?
Ape-inago
13

Zetknąłem się z podobnym problemem i postanowiłem stworzyć własną strukturę do obsługi danych. Opiera się luźno na poczwórnym drzewie, ale ma nieskończoną (przynajmniej tak dużą jak Int) rozszerzalność we wszystkich kierunkach. Został zaprojektowany do obsługi danych opartych na siatce, które rozszerzyły się z centralnego punktu, podobnie jak teraz Minecraft. Zajmuje mało miejsca w pamięci i jest bardzo szybki.

Możesz określić minimalną jasność dla każdego węzła (wielkość 7 wynosiłaby 128 x 128), a gdy dowolny węzeł ma określony procent wypełnienia swoich podwęzłów, automatycznie spłaszcza się do dwuwymiarowej tablicy. Oznacza to, że bardzo gęsto zaludniona część (np. Całkowicie zbadany kontynent) będzie miała wydajność tablicy (bardzo szybko), ale słabo zaludniona część (np. Linia brzegowa, którą ktoś wędrował w górę i w dół, ale nie eksplorował w głąb lądu), będzie mają dobrą wydajność i niskie zużycie pamięci.

Mój kod można znaleźć tutaj . Kod jest kompletny, przetestowany (testy jednostkowe i obciążenia) i dość zoptymalizowany. Jednak wewnętrzne działania nie są jeszcze zbyt dobrze udokumentowane, ale wszystkie publiczne metody są, więc powinno być możliwe do wykorzystania. Jeśli ktoś zdecyduje się go wypróbować, skontaktuj się ze mną z pytaniami lub komentarzami.

Nie użyłem go jeszcze do przechowywania danych w pliku, ale jest to interesujący problem i mogę go rozwiązać w następnej kolejności.

dlras2
źródło
Więc to jest w zasadzie rozszerzalne drzewo, prawda? czego mi brakuje?
kaoD
2
Największa poprawa w stosunku do drzewa rozwijanego polega na tym, że „spłaszcza” niektóre węzły drzewa, które są mocno zaludnione (domyślnie 70%), w tablice 2D, zamiast utrzymywać ich strukturę jak drzewa. Daje to szybkość wyszukiwania tablicy, bez (nieskończonego) rozmiaru nieskończonej tablicy.
dlras2
Zarówno liść, jak i węzły wewnętrzne, prawda? Ciekawy pomysł, może dać dobre wyniki, spróbuję, jeśli będę tego potrzebować. Btw, +1 za podanie kodu i szybką odpowiedź! Aha, a także testy jednostkowe, ja (niestety) nigdy tego nie robię w moich osobistych projektach :)
kaoD
W mojej pracy nie przeprowadzamy żadnych testów jednostkowych, więc niestety jest to mój sposób na buntowanie się. Stworzyłem dla niego aplikację demonstracyjną, która pokazuje, jak się zapełnia i spłaszcza, więc jeśli uda mi się to wyczyścić w ciągu kilku następnych dni, więc jest to prezentowalne, opublikuję je tutaj. To ma o wiele więcej sensu, gdy go widzisz.
dlras2
1
Przepraszam, straciłem go z oczu! Nadal chciałbym go wyczyścić, ale powoli przerabiam część kodu między klasą a pracą domową, więc to nie potrwa długo. Na razie dostępna jest stara, nie ładna wersja demo: j.mp/qIwKYt Przez „nie ładną” rozumiem częściowo, że wymaga wielu wyjaśnień, więc nie zapomnij przeczytać README i zadaj pytanie tutaj lub poprzez e-mail.
dlras2
3

Zamiast tego możesz użyć bazy danych - PostgreSQL ma pewne specjalne możliwości indeksowania zoptymalizowane dla tego typu danych, które są zlokalizowane za pomocą współrzędnych X i Y. Możesz również określić, że zwracane dane mieszczą się w określonym promieniu, a nie w obszarze o kształcie kwadratu lub podłużnego.

  PostgreSQL (darmowy i open source)
  http://www.postgresql.org/

Istnieją również inne bazy danych, a po stronie klienta może się okazać, że niektóre typy będą lepiej do tego dostosowane, ponieważ mogą one działać autonomicznie (inicjowane przez aplikację klienta gry) lub mogą być włączone jako część biblioteki kodów którego możesz „po prostu użyć”. Zaletą jest to, że nie trzeba projektować schematu indeksowania, ponieważ większość silników baz danych SQL już to robi całkiem dobrze.

Zaletą podejścia opartego na bazie danych jest to, że możesz zmniejszyć swoje fragmenty (lub całkowicie się ich pozbyć i po prostu bezpośrednio użyć kafelków, ale użycie co najmniej małych fragmentów / grup wielu kafelków może być bardziej wydajne w zależności od projektu), a następnie użyj zapytania SQL, aby wprowadzić większy obszar niż jest widoczny. Kafelki można przygotować wcześniej, ładując wstępnie tak, aby zachodziły na nie widoczne obszary gracz poruszy swoją postacią, co zapewni lepszą (miejmy nadzieję płynniejszą) rozgrywkę.

Zauważyłem, że niektóre gry przechowują „pamięć podręczną” danych mapy na lokalnym dysku twardym po ich uzyskaniu za pierwszym razem (niewątpliwie ma to na celu zmniejszenie liczby operacji we / wy w sieci), takich jak Ashen Empires:

  Ashen Empires (darmowa gra, piękna implementacja 2D)
  http://www.ashenempires.com/

Pomocne będzie również śledzenie znaczników czasu „ostatniej aktualizacji” dla każdego fragmentu / kafelka, ponieważ tam, gdzie dostępne są lokalnie przechowywane dane, zapytanie SQL może zawierać dodatkową klauzulę „GDZIE kolumna znacznika czasu> $ local_timestamp”, dzięki czemu tylko zaktualizowane fragmenty / kafelki otrzymają pobrane (dwie zalety oszczędzania przepustowości, takie jak to, to niższe koszty łączności i mniejsze opóźnienie dla graczy, co stanie się bardziej oczywiste, gdy twoja gra stanie się popularna).

Zrzut ekranu z Ashen Empires (kilka postaci znajduje się w lokalnym banku, a po wyglądzie tych kości na podłodze wygląda na to, że kilka szkieletowych potworów musiało wejść i prawdopodobnie zostało zabitych przez strażników miejscowego miasta):

wprowadź opis zdjęcia tutaj

Randolf Richardson
źródło
2

Nie przechowuj do nich dostępu, przechowuj tylko niezbędne losowe nasiona, a także zmiany gracza na mapie. Następnie wygeneruj wymagane porcje w czasie wykonywania (uruchom algorytm generowania, a następnie zastosuj zmiany odtwarzacza). Przy prawidłowej i spójnej procedurze generowania wynikowa mapa będzie zawsze taka sama dla tego samego początkowego materiału siewnego.

Teoretycznie możesz zrobić dosłownie nieskończoną mapę, która zapisze w ten sposób bardzo mały plik.

kod sh
źródło
@Josh Petrie dziękuje za znaczące i świetne poprawki językowe / stylistyczne w moim poście. szkoda, że ​​nie mogę głosować za edycją :-D
kod sh
1

Czy jest jakiś sposób na podzielenie fragmentów (jakiś rodzaj „subkontynentów / krajów” w waszym świecie)? Być może możesz mieć jakieś pliki indeksów, które pozwalają szybko znaleźć, który podplik / część większego pliku należy załadować, aby mieć fragment pamięci ...

phtrivier
źródło
zawsze istnieje sposób na dzielenie części. ZAWSZE. bez względu na to, czy są widoczne dla gracza / istotne dla reszty systemu, czy nie, zawsze istnieje sposób na podzielenie danych świata na części, zwykle na wiele różnych sposobów.
kod sh
0

Możesz wziąć pomysł z Minecraft. Pierwotnie mieli plik na porcję. Teraz używają formatu MCRegion, który grupuje porcje w obszary 32 x 32 i przechowuje jeden z nich na plik.

Kaczka komunistyczna
źródło