Generowanie procedur, aktualizacje gier i efekt motyla


10

UWAGA: Poprosiłem o to na Przepełnienie stosu kilka dni temu, ale miałem bardzo mało wyświetleń i brak odpowiedzi. Pomyślałem, że zamiast tego powinienem zapytać na gamdev.stackexchange.

Jest to ogólne pytanie / prośba o poradę w sprawie utrzymania systemu generowania procedur poprzez wiele aktualizacji po wydaniu, bez niszczenia wcześniej wygenerowanej treści.

Staram się znaleźć informacje i techniki pozwalające uniknąć problemów z efektem motyla podczas tworzenia treści proceduralnych do gier. Podczas korzystania z generatora liczb losowych zaszczepionych można użyć powtarzającej się sekwencji liczb losowych w celu stworzenia odtwarzalnego świata. Podczas gdy niektóre gry po prostu zapisują wygenerowany świat na dysku po wygenerowaniu, jedną z potężnych funkcji generowania procedur jest fakt, że można polegać na odtwarzalności sekwencji numerów w celu wielokrotnego odtworzenia regionu w ten sam sposób, eliminując potrzebę trwałość. Ze względu na ograniczenia związane z moją szczególną sytuacją muszę zminimalizować wytrwałość i w jak największym stopniu polegać na czysto zaszczepionej koncentracji.

Głównym niebezpieczeństwem tego podejścia jest to, że nawet najmniejsza zmiana w proceduralnym systemie generowania może spowodować efekt motyla, który zmienia cały świat. Utrudnia to aktualizację gry bez niszczenia światów eksplorowanych przez graczy.

Główną techniką, której użyłem, aby uniknąć tego problemu, jest zaprojektowanie generowania proceduralnego w wielu fazach, z których każda ma swój własny generator liczb losowych. Oznacza to, że każdy podsystem jest samowystarczalny, a jeśli coś się zepsuje, nie wpłynie to na wszystko na świecie. Wydaje się jednak, że wciąż ma duży potencjał do „złamania”, nawet jeśli jest to odizolowana część gry.

Innym możliwym sposobem radzenia sobie z tym problemem może być utrzymanie kompletnych wersji generatorów w kodzie i używanie odpowiedniego generatora dla danej instancji świata. Wydaje mi się to jednak koszmarem konserwacyjnym i jestem ciekawy, czy ktoś to robi.

Tak więc moje pytanie jest tak naprawdę prośbą o ogólne porady, techniki i wzorce projektowe dotyczące radzenia sobie z tym problemem efektu motyla, szczególnie w kontekście aktualizacji po wydaniu gry. (Mam nadzieję, że nie jest to zbyt szerokie pytanie).

Obecnie pracuję w Unity3D / C #, chociaż jest to pytanie agnostyczne z języka.

AKTUALIZACJA:

Dziękuję za odpowiedzi.

Coraz bardziej przypomina to, że dane statyczne są najlepszym i najbezpieczniejszym podejściem, a także, że podczas przechowywania dużej ilości danych statycznych nie ma opcji, długa kampania w wygenerowanym świecie wymagałaby ścisłej wersji używanych generatorów. Powodem tego ograniczenia jest w moim przypadku potrzeba zapisywania / synchronizacji w chmurze opartej na urządzeniach mobilnych. Moim rozwiązaniem może być znalezienie sposobu przechowywania niewielkich ilości zwartych danych o istotnych sprawach.

Uważam, że koncepcja „klatek” Stormwind jest szczególnie użytecznym sposobem myślenia o rzeczach. Klatka jest w zasadzie punktem ponownie wysuniętym, zapobiegającym efektowi niewielkich zmian, np. Umieszczaniu w klatce motyla.


Głosuję za zamknięciem tego pytania jako nie na temat, ponieważ jest ono zbyt szerokie.
Almo

Zdaję sobie sprawę, że jest bardzo szeroki. Czy poleciłbyś spróbować zamiast tego forum gamedev? Naprawdę nie ma sposobu, aby sprecyzować pytanie. Miałem nadzieję, że usłyszę od kogoś z dużym doświadczeniem w tej dziedzinie, z pewnymi sprytnymi sztuczkami, które mi się nie przydały.
zero

2
Almo się myli. To wcale nie jest szerokie. To doskonałe pytanie i dość wąskie, aby udzielić dobrych odpowiedzi. To coś, co myślę, że wielu z nas proceduralnych ludzi często się zastanawiało.
Inżynier

Odpowiedzi:


8

Myślę, że omówiłeś tutaj podstawy:

  • Używanie wielu generatorów lub ponowne rozsiewanie w odstępach czasu (np. Przy użyciu skrótów przestrzennych), aby ograniczyć efekt uboczny zmian. Prawdopodobnie działa to w przypadku treści kosmetycznych, ale jak zauważysz, wciąż może powodować uszkodzenie zawarte w jednej sekcji.

  • Śledzenie wersji generatora użytej w zbiorze składowania i odpowiednie reagowanie. Co oznacza „właściwe”, może być ...

    • Przechowywanie historii wszystkich poprzednich wersji generatorów w pliku wykonywalnym gry i korzystanie z tych, które pasują do zapisu. Utrudnia to łatanie błędów, jeśli gracze nadal używają starych zapisów.
    • Ostrzeżenie odtwarzacza, że ​​ten plik zapisu pochodzi ze starej wersji, oraz łącze do dostępu do tej wersji jako osobnego pliku wykonywalnego. Dobry dla gier z kampaniami trwającymi kilka godzin lub dni, zły dla kampanii, w której spodziewasz się grać przez tygodnie lub dłużej.
    • Zachowaj tylko ostatnie nwersje generatora w pliku wykonywalnym. Jeśli plik składowania używa jednej z tych ostatnich wersji, (zaoferuj) zaktualizuj plik składowania do najnowszej wersji. Używa odpowiedniego generatora, aby rozpakować dowolny nieaktualny stan do literałów (lub do delt z danych wyjściowych nowego generatora na tym samym ziarnie, jeśli są bardzo podobne). Odtąd każdy nowy stan pochodzi z najnowszych generatorów. Gracze, którzy nie grają przez długi czas, mogą jednak pozostać w tyle. A w najgorszym przypadku zapisujesz cały stan gry w dosłownej formie, w którym to przypadku możesz równie dobrze ...
  • Jeśli spodziewasz się często zmieniać logikę generowania i nie chcesz naruszać kompatybilności z poprzednimi wersjami, nie polegaj na determinizmie generatora: zapisz cały swój stan w pliku zapisu. (tj. „Nuke to z orbity. To jedyny sposób, aby się upewnić”)


Jeśli zbudowałeś reguły generowania, to czy istnieje sposób na odwrócenie generacji? IE, biorąc pod uwagę stan gry, czy możesz przywrócić to do zarodka? Jeśli jest to możliwe w przypadku danych, zamiast łączenia odtwarzacza z inną wersją gry, możesz udostępnić narzędzie do aktualizacji, które generuje świat ze źródła za pomocą starszego systemu, a następnie używa wygenerowanego stanu do wygenerowania ziarna dla nowego generatora. Być może jednak będziesz musiał ostrzec swoich graczy przed czekaniem na konwersję.
Joe

1
Zasadniczo nie jest to możliwe. Nie masz nawet gwarancji, że istnieje ziarno dla nowego generatora, który daje taką samą moc wyjściową jak stary. Zwykle te nasiona zawierają około 64 bitów, ale liczba możliwych światów obsługiwanych przez grę może być większa niż 2 ^ 64, więc każdy generator wytwarza tylko ich podzbiór. Zmiana generatora najprawdopodobniej spowoduje powstanie nowego podzbioru poziomów, który może mieć niewielkie lub nawet żadne przecięcie z zestawem poprzedniego generatora.
DMGregory

Trudno było wybrać odpowiedź „właściwą”. Wybrałem ten, ponieważ był zwięzły i streścił główne kwestie w jasny sposób. Dzięki.
null

4

Podstawowym źródłem takiego efektu motyla jest prawdopodobnie nie generowanie liczb - co powinno być na tyle łatwe, aby utrzymać deterministyczność w jednym generatorze liczb - ale raczej użycie tych liczb przez kod klienta. Zmiany w kodzie są prawdziwym wyzwaniem dla utrzymania stabilności.

Kod: Testy jednostkowe Najlepszym sposobem upewnienia się, że drobne zmiany gdzieś się nie zamanifestują gdzie indziej, jest włączenie dokładnych testów jednostkowych dla każdego generatywnego aspektu w twojej kompilacji. Dotyczy to każdego kompaktowego kodu, w którym zmiana jednej rzeczy może wpłynąć na wiele innych - potrzebujesz testów dla wszystkich, aby zobaczyć na jednej kompilacji, co zostało zmienione.

Liczby: okresowe sekwencje / sloty Załóżmy, że masz jeden generator liczb, który służy do wszystkiego. Nie przypisuje znaczenia, po prostu wyrzuca liczby w sekwencji - jak każdy PRNG. Biorąc pod uwagę to samo ziarno w dwóch przebiegach, otrzymujemy te same sekwencje, tak? Teraz zastanowisz się nad tym i zdecydujesz, że może być 30 aspektów twojej gry, które będą musiały być regularnie dostarczane z losową wartością. Tutaj przypisujemy sekwencję cykliczną złożoną z 30 miejsc, np. Każda pierwsza liczba w sekwencji to nierówny układ terenu, co druga liczba to zaburzenia terenu ... itd. ... co 10 liczba dodaje pewien błąd do stanu AI dla realizmu. Twój okres wynosi 30 lat.

Po 10 masz 20 automatów do wykorzystania w innych aspektach w miarę rozwoju projektu. Kosztem jest oczywiście to, że musisz wygenerować liczby dla miejsc 11-30, nawet jeśli nie są one aktualnie używane , tj. Ukończyć okres, aby wrócić do następnej sekwencji 1-10. Ma to koszt procesora, choć powinien być niewielki (w zależności od liczby wolnych gniazd). Drugą wadą jest to, że musisz mieć pewność, że twój ostateczny projekt może zostać uwzględniony w liczbie miejsc, które udostępniłeś na samym początku procesu programowania ... a im więcej przypisujesz na początku, tym więcej „pustych” miejsc potencjalnie musisz przejść przez każdy z nich, aby wszystko działało.

Skutki tego są:

  • Masz jeden generator generujący liczby do wszystkiego
  • Zmiana liczby aspektów, dla których musisz wygenerować liczby, nie wpłynie na determinizm (pod warunkiem, że twój okres jest wystarczająco duży, aby uwzględnić wszystkie aspekty)

Oczywiście upłynie długi okres, w którym twoja gra nie będzie publicznie dostępna - w wersji alfa, że ​​tak powiem - abyś mógł zmniejszyć z powiedzmy 30 do 20 aspektów bez wpływu na graczy, tylko na ciebie, jeśli zdasz sobie sprawę, że trzeba było przypisane sposób zbyt wiele szczelin na początku. To oczywiście uratuje niektóre cykle procesora. Pamiętaj jednak, że dobra funkcja skrótu (którą możesz sam napisać) powinna być błyskawiczna. Dlatego uruchomienie dodatkowych miejsc nie powinno być kosztowne.


Cześć. Brzmi podobnie do niektórych rzeczy, które robię. Zwykle generuję z góry kilka sub-nasion w oparciu o początkowe ziarno świata. Niedawno zacząłem generować zbyt długi układ szumów, a następnie każde „gniazdo” jest po prostu indeksem w tym układzie. W ten sposób każdy podsystem może po prostu pobrać odpowiednie ziarno i pracować w izolacji. Inną świetną techniką jest użycie współrzędnych x, y do wygenerowania zarodka dla każdej lokalizacji. Używam kodu z odpowiedzi Euphoric na tej stronie stosu: programmers.stackexchange.com/questions/161336/…
null

3

Jeśli chcesz wytrwałości w PCG, sugeruję, aby traktować sam kod PCG jako dane . Podobnie jak utrwaliłbyś dane w różnych wersjach z normalną treścią, z wygenerowaną treścią, jeśli chcesz zachować je w różnych wersjach, musisz utrwalić generator.

Oczywiście, jak już wspomniałeś, najpopularniejszym podejściem jest konwersja wygenerowanych danych na dane statyczne.

Nie znam przykładów gier, które trzymają się wielu wersji generatora, ponieważ trwałość jest niezwykła w grach PCG - dlatego permadeath często idzie w parze z PCG. Istnieje jednak wiele przykładów wielu PCG, nawet tego samego typu, w tej samej grze. Na przykład Unangband ma wiele oddzielnych generatorów dla pomieszczeń lochów, a gdy dodawane są nowe, stare nadal działają tak samo. To, czy będzie to możliwe do utrzymania, zależy od twojej implementacji. Jednym ze sposobów utrzymania go w utrzymaniu jest użycie skryptów do implementacji generatorów, utrzymując je w izolacji z resztą kodu gry.


To sprytny pomysł, aby po prostu używać różnych generatorów dla różnych obszarów.
zero

2

Utrzymuję powierzchnię około 30000 kilometrów kwadratowych, mieszczącą około 1 miliona budynków i innych obiektów, a także losowe rozmieszczenie różnych rzeczy. Symulacja na zewnątrz ofc. Przechowywane dane to około 4 GB. Mam szczęście, że mam miejsce do przechowywania, ale nie jest nieograniczone.

Losowe jest losowe, niekontrolowane. Ale można to trochę uwięzić:

  • Kontroluj jego początek i koniec (jak wspomniano w innych postach, numer początkowy i liczbę wygenerowanych liczb).
  • Ogranicz jego przestrzeń numeryczną, np. generuj liczby całkowite od 0 do 100 tylko.
  • Przesunąć jego przestrzeń numeryczną, dodając wartość (np. 100 + [generowane liczby od 0 do 100] dają losowe liczby od 100 do 200)
  • Skaluj (np. Pomnóż przez 0,1)
  • I zastosuj wokół niego różne klatki. To redukuje, złomuje część generacji. Na przykład. generując w przestrzeni dwuwymiarowej, można umieścić prostokąt nad parami liczb i zeskrobać to, co jest na zewnątrz. Lub okrąg lub wielokąt. W przestrzeni 3D można zaakceptować na przykład tylko trojaczki, które znajdują się w kuli lub w innym kształcie (teraz myślą wizualnie, ale niekoniecznie ma to związek z faktyczną wizualizacją lub pozycjonowaniem).

O to chodzi. Niestety klatki również zużywają dane.

W języku fińskim jest powiedzenie Hajota ja hallitse. Przekłada się na Dziel i podbijaj .

Szybko porzuciłem pomysł precyzyjnego definiowania najmniejszych szczegółów. Random chce wolności, więc ma wolność. Pozwól motylowi latać - w jego klatce. Zamiast tego skupiłem się na bogatym sposobie definiowania (i utrzymywania !!) klatek. Nie ma znaczenia, jakie to samochody, o ile są niebieskie lub ciemnoniebieskie (nudny pracodawca powiedział kiedyś :-)). „Niebieski lub ciemnoniebieski” jest tutaj (bardzo małą) klatką, wzdłuż wymiaru koloru.

Co jest możliwe do zarządzania, do kontrolowania i zarządzania przestrzeniami numerycznymi?

  • Siatka boolowska jest (bity są małe!)
  • Punkty narożne są
  • Podobnie jak struktura drzewa (= należy przejść do „klatek trzymających klatki”)

Jeśli chodzi o konserwację i kompatybilność wersji ... mamy
: jeśli wersja = n, to
: elseif wersja = m, to ...
Tak, baza kodu rośnie :-).

Znane rzeczy. Waszym właściwym sposobem na kontynuację byłoby zdefiniowanie bogatej metody dzielenia i podbijania oraz poświęcenia niektórych danych na ten temat. Następnie, o ile to możliwe, daj swobodę (lokalną) randomizacji, w przypadku której nie jest konieczne jej kontrolowanie.

Nie do końca nie do pogodzenia ze śmiesznym „nuke it fom orbit” zaproponowanym przez DMGregory, ale może używasz małych i dokładnych broni nuklearnych? :-)


Dzięki za odpowiedź. To brzmi jak imponująco duży obszar proceduralny do utrzymania. Widzę, jak w przypadku tak dużego obszaru, nawet jeśli masz dostęp do dużej ilości miejsca, nadal nie można po prostu przechowywać wszystkiego. Wygląda na to, że generatory wersji będą musiały być drogą do przodu. Niech tak będzie :)
null

Ze wszystkich odpowiedzi myślę o tym najbardziej. Podobały mi się twoje nieco filozoficzne opisy rzeczy. Uważam, że termin „klatka” jest bardzo przydatny przy wyjaśnianiu pomysłów, więc dziękuję za to. Pozwól motylowi latać ... w swojej klatce :)
null

PS Naprawdę ciekawi mnie, w której grze pracujesz. Czy możesz udostępnić te informacje?
zero

Można dodać jeszcze jedną rzecz do przestrzeni numerycznej: warto zawsze pozostać blisko zera. Że prawdopodobnie już wiedziałeś. Blisko zera daje najlepszą dokładność numeryczną i większość uderzeń za najmniej bitów. Zawsze możesz później zrównoważyć całą masę liczb zbliżonych do zera, ale do tego potrzebujesz tylko jednej liczby. Podobnie możesz (powiedziałbym, że MUSISZ) przesunąć obliczenia odległe bliżej zera, z przesunięciem. - Stormwind
Stormwind 18.04.16

Oceniając poprzednie, rozważ niewielki ruch pojazdu, przyrost o 1 klatkę o 0,01 [metry, jednostki]: Nie możesz dokładnie obliczyć 10000,1 + 0,01 w 32-bitowej dokładności numerycznej (pojedynczej), ale MOŻESZ obliczyć 0,1 + 0,01. Dlatego jeśli „akcja” rozgrywa się daleko (za górami :-)), nie idź tam, zamiast tego przenieś góry do siebie (przesuń z 10000, to jesteś teraz na 0,1). Dotyczy również miejsca do przechowywania. Można być chciwym, przechowując wartości liczbowe blisko siebie. Przechowuj ich wspólną część raz, a odmiany osobno - mogą zaoszczędzić kawałki! Znalazłeś link? ;-)
Stormwind,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.