Najlepsze rozwiązanie dla „łańcucha poziomu”?

10

Mam grę, która generuje losową mapę poziomów na początku poziomu. Chcę wdrożyć jakiś sposób na zapisanie i załadowanie poziomu.

Pomyślałem, że może XML byłby dobrym rozwiązaniem do zapisania całej zmiennej, wtedy łatwo byłoby mi zbudować coś, co może przeanalizować ten XML i wygenerować dokładnie ten sam poziom.

Ale XML jest prawdopodobnie przesadą w stosunku do moich potrzeb. Pamiętam, jak kiedyś ze starą konsolą Sega, która nie miała możliwości zapisania twojej gry (myślę, że zrobiła to również gra Worms), dałaby ci mnóstwo charakteru, który możesz zapisać. Jeśli później wybijesz ten ciąg, załaduje on dokładny poziom.

Czy „ciąg znaków poziomu” byłby dobrym rozwiązaniem? Czy byłby to rodzaj konwersji „base60”? Jak mam to zaimplementować?

Adam Harte
źródło

Odpowiedzi:

20

Prawdopodobnie wszystko, co musisz uratować, to losowe ziarno, które na ogół jest tylko int. Możesz zakodować int do base64, jeśli chcesz uczynić go nieco bardziej nieprzejrzystym, ale prawdopodobnie nie jest to konieczne.

koderanger
źródło
Możesz również wygenerować ziarno z prawdziwego słowa (być może kupując sumowanie znaków, aby uzyskać ziarno) i w ten sposób zwrócisz coś bardziej znaczącego. Będziesz musiał zapisać słowa w grze lub je odzyskać.
Jonathan Fischoff
To dobrze, ale musiałoby również przechowywać dynamiczne dane, jeśli gra się w nią gra. Ciąg musiałby zapisać pozycje znaków / wynik itp. Więc jaki jest najlepszy sposób na wygenerowanie tego ciągu?
Adam Harte
Prawdopodobnie użyłbym JSON (lub XML, jeśli wolisz) do serializacji stanu świata, a następnie kodowania tego ciągu base64. Nie jestem pewien, czy to wszystko jest przydatne, dlaczego nie stworzyć systemu zapisu / ładowania bardziej zorientowanego na GUI? Zakładam, że jest to oparte na sieci i nie chcesz obsługiwać tej strony serwera. Może na przykład zajrzyj na shygypsy.com/farm/p.cgi ?
coderanger
23

Bez względu na to, jakiego formatu używasz do zapisywania gier, na miłość boską, wpisz numer wersji. Będziesz mógł uzyskać ładowanie kompatybilne wstecz, rozgałęziając numer wersji, lub będziesz w stanie bezpiecznie rozpoznać zapisy, które są zbyt stare załadować.

Będziesz tego żałował, jeśli tego nie zrobisz.

tenpn
źródło
7
+1 tak naprawdę nie odpowiada na pytanie, ale jest tak ważne.
Jonathan Fischoff
10

JSON jest dobry, ale YAML jest lepszy. :) http://www.yaml.org/ i http://code.google.com/p/yaml-cpp/ dla jednej z ładniejszych implementacji.

YAML to nadzbiór JSON, który dodaje obsługę kilku fajnych funkcji, w szczególności:

  • Węzły binarne. Jest to świetne do szeregowania danych, z którymi możesz mieć do czynienia przy opisach poziomów. JSON wymaga tłumaczenia na wybrany format pośredni, taki jak Base64, przed zapisaniem / po analizie. YAML ma typ węzła binarnego !!, który każe bibliotece parserów zrobić to za Ciebie.
  • Odniesienia do dokumentu. Jeśli ten sam obiekt pojawi się dwa razy w dokumencie, JSON zapisze go dwa razy, a po ponownym odczytaniu otrzymasz dwie kopie. Wiele emiterów YAML może wykryć tę sytuację i zamiast drugiej kopii wyprowadza odniesienie do pierwszej, kiedy można ją wykryć podczas ładowania.
  • Niestandardowe typy węzłów. Możesz oflagować każdą mapę na liście za pomocą np. !! Player, !! Enemy itp., Dzięki czemu Twoje informacje o typie będą bardziej pozapasmowe.
  • YAML obsługuje bardziej czytelne formatowanie.
  • Ponieważ JSON jest podzbiorem YAML, większość czytników YAML nie będzie mieć problemów z odczytaniem dokumentów JSON.

źródło
1
Yaml jest bardzo fajny, a jeśli unikniesz niektórych funkcji supersetu, możesz go bez trudu przekonwertować na json.
Jethro Larson
3

Jeśli chcesz serializować wszystkie dane w grze, polecam JSON jako format pliku, dlatego łatwiej jest używać XML, a obsługa jest bardzo dobra w wielu językach.

Użyłem tej biblioteki dla C ++ i działa ona bardzo dobrze.

http://jsoncpp.sourceforge.net/

Jonathan Fischoff
źródło
3

XML jest dobrym wyborem, jeśli nie jesteś ograniczony przez rozmiar i jest obsługiwany natywnie (np. W .NET i Flash), ale jeśli chcesz mieć wąski format, możesz łatwo stworzyć własny format i analizator składni. Zwykle używam 1 znaku, np. przecinek, aby oddzielić każdy obiekt. Aby zdekodować ciąg, wykonaj podział przecinkiem. Teraz każdy obiekt potrzebuje różnych właściwości, więc oddziel je innym znakiem, np. Średnikiem, i użyj innego znaku, aby oddzielić nazwy właściwości od wartości właściwości, np. Dwukropek. W ten sposób wszystko można łatwo zdekodować bez wyrażenia regularnego za pomocą string.split. Oto przykład:

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

możesz zaoszczędzić jeszcze więcej miejsca, ograniczając nazwy właściwości do 1 znaku, np. h dla zdrowia. Na przykład.

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

Porównaj z alternatywą JSON:

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

Ponadto, jeśli chcesz zmniejszyć rozmiar swoich liczb, możesz je zakodować przy użyciu pełnego zestawu drukowalnych znaków UTF16. Ten wątek zainspirował mnie do zadania pytania na temat przepełnienia stosu, ile danych można spakować w jedną postać na ekranie . Odpowiedź wydaje się być gdzieś ponad 40 000 wartości dla liczby całkowitej, jeśli nie masz nic przeciwko posiadaniu elementów Brail, Kanji i szachów: ♔♕♖♗♘♙♚♛♜♝♞♟

Aby uzyskać dalsze zmniejszenie rozmiaru, możesz użyć kolejności odczytu / zapisu, aby określić, która wartość jest która, więc pierwsze dwa znaki reprezentują identyfikator, następne dwa to pozycja x, następne dwa y, następnie kąt, a następnie zdrowie itd. Więc:

F5DGP@%&002DFTK#OP1F

może przechowywać wszystkie te same informacje, co inne przykłady.

Siatki kafelków można przechowywać jako ciąg znaków, a każdy znak reprezentuje inny typ kafelka, np .:

i789pog5h3kl

gdzie mógłbym oznaczać lawę, 9 znaczy trawę itp

Iain
źródło
Jest to bardziej zgodne z tym, o co proszę. Wspominam XML w moim pytaniu, ale ludzie to sugerują!
Adam Harte
1
Dodaj {} wokół tego, a w zasadzie masz JSON ;-)
coderanger
Trzeba też dodać wiele znaków cudzysłowu. Prawdopodobnie podwoiłaby liczbę znaków, ale można by zagnieżdżać obiekty.
Iain
1

Jeśli kodujesz w .Net, XML jest bardzo łatwy w obsłudze, ponieważ możesz serializować / deserializować klasę poziomów do / z XML za pomocą zaledwie kilku linii, a następnie wszystko jest w ładnie zarządzanej klasie.

TheMap byłaby zmienną typu Mapa, do której załadowano wszystkie dane.

Dim TheMap As New Map

Zakładając, że masz już zbudowaną klasę Map, zapisuje to twoją mapę w formacie 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()

Spowodowałoby to załadowanie tego kodu XML z powrotem do klasy mapy, do ponownego użycia w kodzie.

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

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

Od tego momentu plik XML jest teraz ładowany do klasy w celu łatwego użycia.

Jeśli chodzi o problem z „ciągiem poziomów”, to, co zostało powiedziane wcześniej, działałoby świetnie, możesz po prostu użyć numeru początkowego jako „ciągu ciągu”.

W przeciwnym razie możesz po prostu wstępnie wygenerować dowolną liczbę różnych map i zapisać je za pomocą „Ciągu poziomów”, a następnie użyć go, aby pobrać odpowiednią mapę.

jblaske
źródło
+1, chociaż nienawidzę XML - jeśli język zapewnia domyślny format serializacji, najlepiej najpierw go wziąć pod uwagę, szczególnie jeśli jest to format, który teoretycznie inne narzędzia mogłyby przeanalizować (np. XML / JSON / YAML).
0

Użyłbym prostego structlub podobnego urządzenia (w zależności od języka) do przechowywania wszystkich stanów gry w centralnym miejscu. Jeśli chcesz zabezpieczyć ustawiających / pobierających, możesz owinąć strukturę w class.

Jeśli masz na to ochotę, skorzystaj z pól bitowych lub po prostu sam manipuluj bitami za pomocą operatorów bitowych.

Pamiętaj, że w niektórych językach reguły wypełniania struktur i pakowania mogą być nieco skomplikowane - ale może również nie mieć większego znaczenia dla twojej sprawy, jeśli masz bajt lub dwa wypełnienia.

Możesz także użyć #pragma(np. #pragma pack(1)) Lub __attribute__do ścisłego spakowania struktury, eliminując wypełnianie. To może, ale nie musi działać, w zależności od kompilatora i architektury docelowej.

Zauważ, że użycie pól bitowych i pragmów lub atrybutów pakietów może zmniejszyć przenośność. We wszystkich architekturach sprzętowych endianness pola strukturalnego (kolejność bajtów) może również ulec zmianie. Możesz więc tego uniknąć, jeśli próbujesz przenieść.

(Na przykład Pac-Man, ta struktura może naiwnie zawierać identyfikator mapy lub ziarno mapy, pozycję x-y Pac-Mana, cztery pozycje x i y ducha oraz duże pole bitowe na obecność lub brak 32-64 granulek bez względu na maksimum).

Gdy masz już strukturę, przekaż ją do czegoś takiego jak funkcja xxencode :

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

Pisanie tej funkcji jest nieco podatne na błędy; musisz przesuwać i łączyć bajty w razie potrzeby, aby uzyskać np. 6 bitów na raz, a następnie przełożyć na odpowiedni znak. Osobiście starałbym się wyłudzić czyjś kod, chyba że robiłem to dla „zabawy” (i prawdopodobnie potrzebowałbym do tego pakietu testowego).

Nigdy nie lekceważ potęgi starej szkoły structwe właściwych miejscach. Wykorzystaliśmy go tutaj w przypadku gier GBA i DS.

leander
źródło
Serializacja surowej struktury jest nieprzenośnym i wyjątkowo delikatnym nowym kodem. O ile nie jest to ostatni etap przygotowywania danych dla platformy o bardzo ograniczonych zasobach, jest to bardzo przedwczesna optymalizacja. Czy istnieje powód, dla którego wolisz 6 bitów niż 8? Format i tak nie będzie czytelny dla człowieka, równie dobrze możesz skorzystać z szybkości i możliwości debugowania prawdziwego układu struktury.
@Joe: Wspomniałem, że nie jest przenośny i niektóre z potencjalnych obaw. Pytanie dotyczyło w szczególności czytelnego dla człowieka „łańcucha poziomu”, takiego jak stare gry Sega i wspomniana konwersja base60; przekazanie struktury przez coś takiego jak xxencode zrobi to. Niekoniecznie jest to przedwczesna optymalizacja: prosta struktura, a nie pakiet bitów, jest ładnym „centralnym” sposobem przechowywania zapisanych danych i może uprościć znaczną część kodu, który wchodzi w interakcję z danymi. Zobacz np. Ostatnie artykuły Noela Llopisa na temat struktur nieprzyjaznych nieprzyjaznych i programowania „na lewą stronę”. To tylko KISS.
leander
1
Całkowicie się z tym zgadzam. Tak, nie jest przenośny między wersjami ani systemami, ale z drugiej strony gry save nie są zasobami, które należy ponownie wykorzystać podczas programowania lub skopiować na różne platformy. Debugowalność nie stanowi problemu w przypadku odczytu / zapisu w miejscu, zaimplementuj go raz, a będzie działał na zawsze. JEŻELI rozszerzalność jest naprawdę problemem - dodaj numer wersji jako pierwszy ybyte / słowo i możesz to włączyć (chociaż wprowadziłoby to lukę w danych). To nie jest tak, że xml rozwiąże problemy z wersjonowaniem - zwykle są bardziej zaangażowani niż tylko ustawianie wartości. Tak, jestem pragmatyczny.
Kaj
0

XML jest dobry dla dokumentów o dowolnej strukturze (elementy mogą pojawiać się na różnych poziomach drzewa) lub do osadzania w obcym formacie (np. Umieszczanie svg na stronie xhtml). Jeśli nie masz tych wymagań, jest to naprawdę nieefektywny format i preferowane jest coś prostszego, np. Csv lub json.

Jethro Larson
źródło