zapisywanie / ładowanie stanu gry?

12

jaka jest najczęściej stosowana metoda lub algorytm zapisywania stanu gry (profili), plików tekstowych baz danych, jak przebiega szyfrowanie i związane z tym rzeczy.

Widziałem, że Caesar IV używał mySQL.

jakieś sugestie.

NativeCoder
źródło

Odpowiedzi:

20

Jestem prawie pewien, że najczęściej odbywa się to w zastrzeżonym formacie (binarnym), po prostu zapisując każdą zmienną z dowolnej struktury stanu gry, której używasz, do pliku lub odczytując ten plik z powrotem do instancji struktury. Na przykład w C ++ możesz traktować obiekt jak tablicę bajtów i po prostu fwrite () do pliku lub fread () z pliku i rzutować go z powrotem do postaci obiektu.

Jeśli używasz języka Java lub innego języka, możesz wybrać opcję serializacji, aby zadanie było naprawdę łatwe. Wyjaśnienie Java znajduje się na dole tej odpowiedzi; inne języki (wyższy poziom niż C ++) z pewnością mają własne biblioteki serializacji lub wbudowane funkcje, których powinieneś użyć. Bez względu na to, jak nieefektywna może być procedura serializacji, obecnie miejsce na dysku twardym jest tanie, szczególnie gdy stan gry nie powinien być wcale duży i nie pozwala ci pisać i utrzymywać własnych procedur serializacji.

Absolutnie nie powinieneś używać MySQL (wystarczy przeczytać pierwsze zdanie tej recenzji ). Jeśli z jakiegoś powodu naprawdę potrzebujesz relacyjnej bazy danych , użyj SQLite ; jest to lekki system bazy danych i istnieje w jednym pliku bazy danych. Jednak w przypadku większości gier relacyjne bazy danych nie są dobrym rozwiązaniem, a firmy, które próbują z nich korzystać, zwykle używają ich jako tabeli odnośników klucz-wartość zamiast prawdziwej relacyjnej bazy danych.

Jakiekolwiek szyfrowanie lokalnych plików dyskowych jest jedynie zaciemnieniem ; każdy haker po prostu wącha dane zaraz po ich odszyfrowaniu przez Twój program. Jestem osobiście przeciwny tego typu, szczególnie w grach dla jednego gracza. Moim zdaniem właściciele gry (płacący klienci, pamiętajcie) powinni mieć możliwość włamania się do gry, jeśli chcą. W niektórych przypadkach może stworzyć większe poczucie wspólnoty, a dla twojej gry można opracować „mody”, które przyciągną więcej klientów. Najnowszym przykładem, który przychodzi mi na myśl, jest mod Portal in Minecraft, który został niedawno wydany. Jest publikowany w witrynach z wiadomościami dla graczy w Internecie i można się założyć, że zwiększył sprzedaż Minecraft.


Jeśli z jakiegoś powodu jesteś na tyle szalony, że używasz Javy do tworzenia gier tak jak ja, oto krótkie wyjaśnienie serializacji w Javie:

Zachowaj wszystkie dane o stanie gry w jednej klasie, spraw, aby klasa mogła zostać przekształcona do postaci szeregowej poprzez implementację Serializable, użyj transientsłowa kluczowego, jeśli wmieszasz do klasy specjalne zmienne utworzone w postaci instancji (obiekty przejściowe nie są serializowane) i po prostu użyjesz ObjectOutputStreamdo zapisu do pliku i ObjectInputStreamodczytu z pliku . Otóż ​​to.

Ricket
źródło
2
Hehe, Wiedźmin nie szyfrował swoich danych. Bardzo łatwo otworzyć plik zapisu gry w edytorze heksadecymalnym, zepsuć go trochę i zdobyć wszystkie nagie karty piskląt ... o Boże, przepraszam!
Anthony
Używałem binarnej serializacji .NET do zapisywania gier, a C # jest być może nieco bardziej rozsądną platformą niż Java do tworzenia gier. Podoba mi się, jak automatycznie zapisuje wykres całego obiektu. Kiedyś było to o wiele trudniejsze. Oczywiście teraz istnieje wyzwanie polegające na wykluczeniu niepotrzebnych elementów, takich jak tekstury, ale nie jest to nic wielkiego do osiągnięcia.
BlueMonkMN
Nie zgadzam się, że C # jest nieco bardziej popularny niż Java do tworzenia gier. Bardziej rozsądna implikuje pewną subiektywność, a jako twórca zarówno XNA, jak i Java, wolę Javę. Zapraszam do listy instrukcji dotyczących serializacji w języku C #, jeśli chcesz, nigdy tego nie zrobiłem, ale mogę edytować to w mojej odpowiedzi. Serializacja Javy zapisuje wykres całego obiektu, a słowo kluczowe „przejściowe” na zmiennych wyklucza niepotrzebne elementy, takie jak tekstury; członkowie nieprzechodni muszą mieć możliwość szeregowania lub można napisać niestandardową metodę writeObject, aby obejść ten problem.
Ricket
Marynata Pythona zapisze dla ciebie cały wykres obiektów i jest łatwa w użyciu. Kiedy przychodzi czas na deserializację, oddawane
drhayes
Podczas korzystania z wbudowanego formatera binarnego wykluczenie niektórych pól z serializacji w języku C # /. NET jest tak proste, jak ozdobienie ich atrybutem [NonSerialized]. Łatwo jednak przeoczyć to, że zdarzenia generowane przez kompilator (tj. Dowolne zdarzenie bez niestandardowych operatorów dodawania / usuwania) tworzą pole zapasowe do przechowywania subskrybentów. Dlatego łatwo jest przypadkowo (i nieświadomie) dołączyć procedury obsługi zdarzeń do serializowanego wykresu obiektowego. Aby obejść ten problem, musisz dodać atrybut NieSerializowany do deklaracji zdarzenia, ale musisz dodać kwalifikator „field:”:[field: NonSerialized]
Mike Strobel
3

Jeśli piszesz własny kod, powiedzmy w C ++, możesz używać plików binarnych. Pliki binarne zapewniają formę szyfrowania poprzez zaciemnianie. Ale jak udokumentowano w całym internecie, jest to bardzo słaba forma bezpieczeństwa.

LUB możesz użyć czegoś takiego jak RapidXML, jeśli chcesz rozwiązania czytelnego dla człowieka.

Jeśli używasz jakiegoś frameworka, sprawdź jego funkcje obsługi plików. HTH

JustBoo
źródło
0

Większość gier, które widziałem, używają odręcznie napisanego kodu do odczytu / zapisu z plików binarnych. Często formatem na płycie będzie dokładna replika układu pamięci obiektu, więc ładowanie jest po prostu:

  1. Wczytaj plik do pamięci.
  2. Rzuć wskaźnik na bufor na typ obiektu.

Jest szybki, ale utrzymanie go jest trudne, specyficzne dla platformy i nieelastyczne.

Ostatnio widziałem, że kilka gier korzysta z SQLite. Ludzie, z którymi rozmawiałem, wydają się lubić.

hojny
źródło
0

Serializacja jest wymieniona w niektórych innych odpowiedziach i zgadzam się, że jest to rozsądne rozwiązanie. Jednym ze sposobów niepowodzenia jest wersjonowanie.

Załóżmy, że wypuściłeś grę, a ludzie w nią grają i tworzą gry z zapisem. Następnie w późniejszej łatce chcesz naprawić błąd lub dodać niektóre funkcje. Jeśli użyjesz prostej binarnej metody serializacji i dodasz członka do swoich klas, serializacja może nie być zgodna ze starymi grami zapisu, gdy klienci instalują łatkę.

Niezależnie od tego, jakie zastosujesz podejście, pamiętaj o tym problemie przed wydaniem gry po raz pierwszy! Klienci nie będą zadowoleni, jeśli będą musieli zacząć od nowa po nałożeniu łatki.

Jednym ze sposobów uniknięcia tego jest posiadanie każdego podstawowego typu danych implementującego metody Save (wersja) i Load (wersja), które są wystarczająco inteligentne, aby wiedzieć, jakie dane zapisać i załadować dla każdej wersji gry. W ten sposób możesz wesprzeć wsteczną kompatybilność zapisywanych gier i z wdziękiem zawieść, jeśli użytkownik spróbuje załadować zapisaną grę z nowszej wersji gry niż jest uruchomiona.

kevin42
źródło
1
W skrócie: wdrożenie funkcji „zapisz” i „załaduj” dla każdej wersji gry szybko zamienia się w koszmar konserwacji. Znalazłem lepsze rozwiązanie, aby osadzić numer wersji, napisać funkcje „zapisz / załaduj” dla bieżącej wersji gry i napisać funkcję „konwersji” z najnowszej nieaktualnej wersji do bieżącej wersji. Następnie po prostu trzymaj wszystkie swoje funkcje konwersji. Próba załadowania starej gry z dziesięcioma wersjami uruchomiłaby dziesięć funkcji konwersji szeregowo, a następnie natywną funkcję „wczytaj bieżącą wersję gry”.
ZorbaTHut,
Znacznie łatwiejsze do utrzymania w dłuższej perspektywie - utrzymywanie szeregu funkcji „ładuj każdy poprzedni plik gry” szybko zamienia się w operację wielomianową.
ZorbaTHut,
W grach, nad którymi pracowałem, nie jest to aż tak skomplikowane - zamiast przechowywać wiele kopii metod ładowania / zapisywania, jedna metoda używa numeru wersji do określenia, które bajty do odczytu i zapisu. Jest to jeszcze łatwiejsze, jeśli nie piszesz tylko struktur, ale implementujesz ładowanie i zapisywanie jako operacje na poziomie pierwotnych typów danych (np. ReadByte / ReadFload / itp.). Następnie, jeśli dodasz nowego członka, możesz zrobić coś takiego jak if (wersja> 10) {someNewByte = ReadByte (); } Zaimplementowanie go w ten sposób ułatwia także obsługę różnych platform Endian.
kevin42,
Innym sposobem uniknięcia poprawy obsługi wersji jest uniknięcie sytuacji, w której metody serializacji / deserializacji obiektów zapisują / odczytują poszczególne wartości bezpośrednio ze strumienia. Zamiast tego obiekty mogą zapisywać swoje dane w torbie właściwości za pomocą par klucz / wartość, a następnie zapisywać torbę do strumienia. Podczas deserializacji obiekty mogły odczytywać wartości z torby według nazwy. Ale zamiast sprawdzania konkretnych numerów wersji, jak sugeruje to kevin42, możesz mieć reguły postępowania z brakującymi wartościami (tj. Użyć standardowej wartości domyślnej, gdy wymaganej wartości nie ma w torbie).
Mike Strobel,
0

Jeśli możesz użyć tego samego formatu, w którym przechowujesz swoje poziomy, jest to bonus.

Na przykład gra taka jak Peggle może mieć początkowy układ planszy, liczbę piłek, aktualny wynik (0 dla planszy początkowej) itp. Następnie zapisana gra może mieć dokładnie ten sam format.

Jeśli używasz tego samego formatu, wtedy gra zapisu i poziom ładowania mogą współdzielić kod, dzięki czemu praca jest łatwiejsza!

W przypadku gry z naprawdę dużymi plikami map gra zapisująca może zawierać plik mapy przez odniesienie. Pliki na poziomie początkowym mogą nawet zrobić to samo, co ułatwia powrót do tej mapy, jeśli z jakiegoś powodu postać powróci w to samo miejsce, z wyjątkiem niektórych NPC, którzy są martwi, a wszystkie zwłoki leżą wszystkie nad.

Zan Lynx
źródło