Prozedurale Generierung, Spielaktualisierungen und Schmetterlingseffekt


10

HINWEIS: Ich habe dies vor einigen Tagen bei Stack Overflow gefragt, hatte aber nur sehr wenige Aufrufe und keine Antwort. Ich dachte, ich sollte stattdessen auf gamdev.stackexchange fragen.

Dies ist eine allgemeine Frage / Bitte um Beratung zur Aufrechterhaltung eines prozeduralen Generierungssystems durch mehrere Aktualisierungen nach der Veröffentlichung, ohne zuvor generierte Inhalte zu beschädigen.

Ich versuche, Informationen und Techniken zu finden, um Probleme mit dem "Schmetterlingseffekt" beim Erstellen von prozeduralen Inhalten für Spiele zu vermeiden. Bei Verwendung eines gesetzten Zufallszahlengenerators kann eine sich wiederholende Folge von Zufallszahlen verwendet werden, um eine reproduzierbare Welt zu erstellen. Während einige Spiele die generierte Welt nach der Generierung einfach auf der Festplatte speichern, ist eines der leistungsstarken Merkmale der prozeduralen Generierung die Tatsache, dass Sie sich auf die Reproduzierbarkeit der Zahlenfolge verlassen können, um eine Region mehrmals auf dieselbe Weise neu zu erstellen, ohne dass dies erforderlich ist Beharrlichkeit. Aufgrund der Einschränkungen meiner besonderen Situation muss ich die Persistenz minimieren und mich so weit wie möglich auf rein gesäte Konzentration verlassen.

Die Hauptgefahr bei diesem Ansatz besteht darin, dass selbst die geringste Änderung des prozeduralen Generierungssystems einen Schmetterlingseffekt verursachen kann, der die ganze Welt verändert. Dies macht es sehr schwierig, das Spiel zu aktualisieren, ohne die Welten zu zerstören, die die Spieler erkunden.

Die Haupttechnik, mit der ich dieses Problem vermieden habe, besteht darin, die prozedurale Generierung in mehreren Phasen zu entwerfen, von denen jede ihren eigenen Zufallszahlengenerator hat. Dies bedeutet, dass jedes Subsystem in sich geschlossen ist und wenn etwas kaputt geht, wirkt es sich nicht auf alles auf der Welt aus. Dies scheint jedoch immer noch ein großes Potenzial für "Bruch" zu haben, selbst wenn es sich um einen isolierten Teil des Spiels handelt.

Eine andere Möglichkeit, dieses Problem zu lösen, besteht darin, vollständige Versionen Ihrer Generatoren im Code zu verwalten und weiterhin den richtigen Generator für eine bestimmte Weltinstanz zu verwenden. Dies scheint mir jedoch ein Alptraum für die Wartung zu sein, und ich bin gespannt, ob dies tatsächlich jemand tut.

Meine Frage ist also wirklich eine Bitte um allgemeine Ratschläge, Techniken und Entwurfsmuster, um dieses Problem des Schmetterlingseffekts zu lösen, insbesondere im Zusammenhang mit Spielaktualisierungen nach der Veröffentlichung. (Hoffentlich ist das keine allzu breite Frage.)

Ich arbeite derzeit in Unity3D / C #, obwohl dies eine sprachunabhängige Frage ist.

AKTUALISIEREN:

Vielen Dank für die Antworten.

Es sieht immer mehr so ​​aus, als ob statische Daten der beste und sicherste Ansatz sind. Wenn das Speichern vieler statischer Daten keine Option ist, würde eine lange Kampagne in einer generierten Welt eine strikte Versionierung der verwendeten Generatoren erfordern. Der Grund für die Einschränkung in meinem Fall ist die Notwendigkeit eines mobilen Cloud-Speicherns / Synchronisierens. Meine Lösung könnte darin bestehen, Wege zu finden, um kleine Mengen kompakter Daten über wesentliche Dinge zu speichern.

Ich finde Stormwinds Konzept von "Käfigen" eine besonders nützliche Art, über Dinge nachzudenken. Ein Käfig ist im Grunde ein Nachsaatpunkt, der die Auswirkungen kleiner Änderungen, dh des Käfigs des Schmetterlings, verhindert.


Ich stimme dafür, diese Frage als nicht zum Thema gehörend zu schließen, weil sie zu weit gefasst ist.
Almo

Mir ist klar, dass es sehr breit ist. Würden Sie mir empfehlen, stattdessen ein Gamedev-Forum oder etwas anderes auszuprobieren? Es gibt keine Möglichkeit, die Frage genauer zu formulieren. Ich hatte gehofft, ich könnte von jemandem mit viel Erfahrung auf diesem Gebiet hören, mit einigen listigen Tricks, die mir nicht in den Sinn gekommen sind.
null

2
Almo irrt sich. Es ist überhaupt nicht breit. Dies ist eine ausgezeichnete Frage und ziemlich eng genug, um gute Antworten zu geben. Ich denke, viele von uns Verfahrensleuten haben oft darüber nachgedacht.
Ingenieur

Antworten:


8

Ich denke, Sie haben die Grundlagen hier behandelt:

  • Verwenden mehrerer Generatoren oder erneutes Aussäen in Intervallen (z. B. mithilfe von räumlichen Hashes), um das Überlaufen von Änderungen zu begrenzen. Dies funktioniert wahrscheinlich für kosmetische Inhalte, aber wie Sie hervorheben, kann es dennoch zu Brüchen in einem Abschnitt kommen.

  • Verfolgen Sie die in der Sicherungsdatei verwendete Generatorversion und reagieren Sie entsprechend. Was "angemessen" bedeutet, könnte sein ...

    • Behalten Sie den Verlauf aller früheren Versionen der Generatoren in Ihrem Spiel bei und verwenden Sie diejenige, die dem Speicher entspricht. Dies macht es schwieriger, Fehler zu beheben, wenn Spieler weiterhin alte Speicher verwenden.
    • Warnen Sie den Player, dass diese Sicherungsdatei aus einer alten Version stammt, und stellen Sie einen Link bereit, um auf diese Version als separate ausführbare Datei zuzugreifen. Gut für Spiele mit Kampagnen, die einige Stunden bis Tage dauern, schlecht für eine Kampagne, die Sie voraussichtlich über Wochen oder länger spielen werden.
    • Behalten Sie nur die letzten nGeneratorversionen in Ihrer ausführbaren Datei. Wenn die Sicherungsdatei eine dieser neuesten Versionen verwendet, aktualisieren Sie die Sicherungsdatei auf die neueste Version. Dies verwendet den entsprechenden Generator, um veraltete Zustände in Literale (oder in Deltas aus der Ausgabe des neuen Generators auf demselben Seed, wenn sie sehr ähnlich sind) zu entpacken. Jeder neue Zustand kommt von nun an von den neuesten Generatoren. Spieler, die lange nicht spielen, könnten jedoch zurückgelassen werden. Und im schlimmsten Fall speichern Sie den gesamten Spielstatus in wörtlicher Form. In diesem Fall können Sie genauso gut ...
  • Wenn Sie erwarten, dass Ihre Generierungslogik häufig geändert wird und die Kompatibilität mit früheren Versionen nicht beeinträchtigt werden soll, verlassen Sie sich nicht auf den Generatordeterminismus: Speichern Sie Ihren gesamten Status in Ihrer Sicherungsdatei. (dh "Nuke es aus dem Orbit. Es ist der einzige Weg, um sicher zu sein")


Wenn Sie die Generierungsregeln erstellt haben, gibt es dann eine Möglichkeit, die Generierung umzukehren? IE, wenn Sie einen Spielstatus haben, können Sie dies in einen Startwert zurücksetzen? Wenn dies mit Ihren Daten möglich ist, können Sie anstelle der Verknüpfung des Spielers mit einer anderen Spielversion ein Aktualisierungsdienstprogramm bereitstellen, das mit dem älteren System eine Welt aus einem Startwert generiert und dann den generierten Status verwendet, um einen Startwert für den neuen Generator zu erstellen. Möglicherweise müssen Sie Ihre Spieler jedoch vor einem Warten auf die Konvertierung warnen.
Joe

1
Dies ist im Allgemeinen nicht möglich. Sie können nicht einmal garantieren, dass für den neuen Generator ein Startwert vorhanden ist, der die gleiche Leistung wie der alte liefert. Normalerweise enthalten diese Seeds ungefähr 64 Bit, aber die Anzahl der möglichen Welten, die Ihr Spiel unterstützen könnte, ist wahrscheinlich größer als 2 ^ 64, sodass jeder Generator immer nur eine Teilmenge davon erzeugt. Ein Wechsel des Generators führt sehr wahrscheinlich zu einer neuen Teilmenge von Pegeln, die möglicherweise kaum oder gar keinen Schnittpunkt mit der vorherigen Generatormenge haben.
DMGregory

Es war schwer, die "richtige" Antwort zu wählen. Ich habe dieses ausgewählt, weil es prägnant war und die Hauptthemen klar zusammengefasst hat. Vielen Dank.
null

4

Die Hauptquelle für einen solchen Schmetterlingseffekt ist wohl nicht die Generierung von Zahlen - was einfach genug sein sollte, um von einem einzelnen Zahlengenerator deterministisch zu bleiben -, sondern die Verwendung dieser Zahlen durch den Clientcode. Codeänderungen sind die eigentliche Herausforderung, um die Dinge stabil zu halten.

Code: Komponententests Der beste Weg, um sicherzustellen, dass sich geringfügige Änderungen nicht unbeabsichtigt an anderer Stelle manifestieren, besteht darin, gründliche Komponententests für jeden generativen Aspekt in Ihren Build aufzunehmen. Dies gilt für jeden kompakten Code, bei dem sich das Ändern einer Sache auf viele andere auswirken kann. Sie benötigen Tests für alle, damit Sie bei einem einzelnen Build sehen können, was betroffen ist.

Zahlen: Periodische Sequenzen / Slots Nehmen wir an, Sie haben einen Zahlengenerator, der für alles dient. Es weist keine Bedeutung zu, es spuckt nur Zahlen nacheinander aus - wie jedes PRNG. Bei gleichem Startwert über zwei Läufe erhalten wir dieselben Sequenzen, ja? Jetzt überlegen Sie sich etwas und entscheiden, dass es vielleicht 30 Aspekte Ihres Spiels geben wird, die regelmäßig mit einem zufälligen Wert versehen werden müssen. Hier weisen wir eine Zyklussequenz von 30 Slots zu, z. B. ist jede erste Nummer in der Sequenz ein unebenes Geländelayout, jede zweite Nummer ist Geländestörungen ... usw. ... jede zehnte Nummer fügt dem AI-Zustand einen Fehler für den Realismus hinzu. Ihre Periode ist also 30.

Nach 10 haben Sie 20 freie Plätze, die Sie im Verlauf des Spieldesigns für andere Aspekte nutzen können. Die Kosten hier sind natürlich, dass Sie Zahlen für die Slots 11-30 generieren müssen, obwohl sie derzeit nicht verwendet werden , dh den Zeitraum abschließen, um zur nächsten Sequenz von 1-10 zurückzukehren. Das hat CPU-Kosten, sollte aber gering sein (abhängig von der Anzahl der freien Steckplätze). Der andere Nachteil ist , dass Sie sicherstellen müssen , dass Ihr endgültiges Design in der Anzahl der Slots untergebracht werden kann, die Sie zu Beginn Ihres Entwicklungsprozesses zur Verfügung gestellt haben. Je mehr Sie zu Beginn zuweisen, desto mehr "leere" Slots Sie müssen möglicherweise jeden einzelnen durchlaufen, damit die Dinge funktionieren.

Die Auswirkungen davon sind:

  • Sie haben einen Generator, der Zahlen für alles produziert
  • Das Ändern der Anzahl der Aspekte, für die Sie Zahlen generieren müssen, wirkt sich nicht auf den Determinismus aus (vorausgesetzt, Ihr Zeitraum ist groß genug, um alle Aspekte zu berücksichtigen).

Natürlich wird es einen langen Zeitraum geben, in dem Ihr Spiel der Öffentlichkeit nicht zur Verfügung steht - sozusagen in Alpha -, sodass Sie von 30 auf 20 Aspekte reduzieren können, ohne einen Spieler zu beeinflussen, nur Sie selbst, wenn Sie dies bemerken Sie hatten zu Beginn viel zu viele Slots zugewiesen . Dies würde natürlich einige CPU-Zyklen einsparen. Aber denken Sie daran, dass eine gute Hash-Funktion (die Sie selbst schreiben können) sowieso blitzschnell sein sollte. Es sollte also nicht teuer sein, zusätzliche Slots zu betreiben.


Hallo. Das klingt ähnlich wie einige Dinge, die ich tue. Normalerweise generiere ich eine Reihe von Untersamen im Voraus, basierend auf dem anfänglichen Weltsamen. Vor kurzem habe ich angefangen, ein längeres Array von Rauschen vorab zu generieren, und dann ist jeder "Slot" einfach ein Index in dieses Array. Auf diese Weise kann jedes Subsystem einfach den richtigen Startwert finden und isoliert arbeiten. Eine weitere großartige Technik besteht darin, x, y-Koordinaten zu verwenden, um für jeden Ort einen Startwert zu generieren. Ich verwende den Code aus Euphorics Antwort auf dieser Stapelseite: programmers.stackexchange.com/questions/161336/…
null

3

Wenn Sie eine Persistenz mit PCG wünschen, empfehle ich Ihnen , den PCG-Code selbst als Daten zu behandeln . So wie Sie Daten über Revisionen hinweg mit regulären Inhalten und generierten Inhalten beibehalten würden, müssen Sie den Generator beibehalten, wenn Sie sie über Revisionen hinweg beibehalten möchten.

Der beliebteste Ansatz ist natürlich die Generierung generierter Daten in statische Daten, wie Sie bereits erwähnt haben.

Ich kenne keine Beispiele für Spiele, bei denen es viele Generatorversionen gibt, da die Persistenz in PCG-Spielen ungewöhnlich ist - deshalb geht Permadeath oft Hand in Hand mit PCG. Es gibt jedoch viele Beispiele für mehrere PCGs, auch vom selben Typ, innerhalb desselben Spiels. Zum Beispiel hat Unangband viele separate Generatoren für Dungeon-Räume, und wenn neue hinzugefügt werden, funktionieren die alten immer noch gleich. Ob dies wartbar ist, hängt von Ihrer Implementierung ab. Eine Möglichkeit, die Wartung aufrechtzuerhalten, besteht darin, Ihre Generatoren mithilfe von Skripten zu implementieren und sie mit dem Rest des Spielcodes isoliert zu halten.


Das ist eine clevere Idee, einfach verschiedene Generatoren für verschiedene Bereiche zu verwenden.
null

2

Ich unterhalte eine Fläche von ca. 30000 Quadratkilometern mit ca. 1 Million Gebäuden und anderen Objekten sowie zufälligen Platzierungen verschiedener Dinge. Eine Outdoor-Simulation von c. Die gespeicherten Daten betragen ca. 4 GB. Ich habe das Glück, Speicherplatz zu haben, aber es ist nicht unbegrenzt.

Zufällig ist zufällig, unkontrolliert. Aber man kann es ein wenig einsperren:

  • Kontrollieren Sie das Start-Ende-Ende (wie in anderen Posts erwähnt, die Startnummer und die Anzahl der generierten Nummern).
  • Begrenzen Sie den numerischen Raum, z. Generieren Sie nur Ganzzahlen zwischen 0 und 100.
  • Versetzen Sie den numerischen Raum durch Hinzufügen eines Werts (z. B. 100 + [generierte Zahlen zwischen 0 und 100] ergeben Zufallszahlen zwischen 100 und 200).
  • Skaliere es (zB multipliziere mit 0,1)
  • Und wenden Sie verschiedene Käfige an. Dies reduziert und verschrottet einen Teil der Generation. Z.B. Wenn man im zweidimensionalen Raum erzeugt, kann man ein Rechteck über Zahlenpaare legen und das, was draußen ist, verschrotten. Oder ein Kreis oder ein Polygon. Im 3D-Raum kann man beispielsweise nur Drillinge akzeptieren, die sich innerhalb einer Kugel befinden, oder eine andere Form (jetzt visuell denken, aber dies hat nicht unbedingt etwas mit der tatsächlichen Visualisierung oder Positionierung zu tun).

Das ist alles. Käfige verbrauchen leider auch Daten.

Es gibt ein finnisches Sprichwort: Hajota ja hallitse. Übersetzt in Teilen und Erobern .

Ich gab die Idee der präzisen Definition kleinster Details schnell auf. Zufällig will Freiheit, also hat es die Freiheit. Lass den Schmetterling fliegen - in seinem Käfig. Stattdessen konzentrierte ich mich darauf, die Käfige auf eine reichhaltige Art und Weise zu definieren (und zu pflegen !!). Es spielt keine Rolle, welche Autos sie sind, solange sie blau oder dunkelblau sind (ein langweiliger Arbeitgeber sagte einmal :-)). "Blau oder Dunkelblau" ist hier der (sehr kleine) Käfig entlang der Farbdimension.

Was ist handhabbar, um numerische Räume zu steuern und zu verwalten?

  • Ein boolesches Gitter ist (Bits sind klein!)
  • Eckpunkte sind
  • Wie ist eine Baumstruktur (= in "Käfige halten Käfige" folgen)

In Bezug auf Wartung und Versionskompatibilität der Version ... wir haben
: wenn version = n dann
: elseif version = m dann ...
Ja, die Codebasis wird größer :-).

Vertraute Dinge. Ihr korrekter Weg wäre imho, eine reichhaltige Methode zum Teilen und Erobern zu definieren und einige Daten dazu zu opfern. Geben Sie dann, wo möglich, die (lokale) Randomisierungsfreiheit, wo es nicht entscheidend ist, sie zu kontrollieren.

Nicht völlig unvereinbar mit dem von DMGregory vorgeschlagenen lustigen "Nuke it fom Orbit", aber vielleicht kleine und genaue Atomwaffen verwenden? :-)


Danke für deine Antwort. Das klingt nach einem beeindruckend großen Verfahrensbereich, der beibehalten werden muss. Ich kann sehen, dass es für einen so großen Bereich immer noch unmöglich ist, einfach alles zu speichern, selbst wenn Sie Zugriff auf viel Speicher haben. Es hört sich so an, als müssten versionierte Generatoren der Weg nach vorne sein. So sei es :)
null

Von allen Antworten denke ich am meisten über diese nach. Ich habe Ihre leicht philosophischen Beschreibungen der Dinge genossen. Ich finde den Begriff "Käfig" sehr nützlich, wenn ich die Ideen erkläre, also danke dafür. Lass den Schmetterling fliegen ... in seinem Käfig :)
null

PS Ich bin sehr gespannt, an welchem ​​Spiel du arbeitest. Können Sie diese Informationen teilen?
null

Könnte noch etwas zum numerischen Raum hinzufügen: Es lohnt sich, immer nahe Null zu bleiben. Das wusstest du wohl schon. Nahe Null ergibt die beste numerische Genauigkeit und den meisten Knall für die kleinsten Bits. Sie können später jederzeit eine ganze Reihe von Zahlen nahe Null versetzen, benötigen dafür jedoch nur eine einzige Zahl. Ebenso können Sie (ich würde fast sagen, Sie MÜSSEN) die Fernberechnung mit einem Versatz näher an Null bringen. - Sturmwind
Sturmwind

Berücksichtigen Sie bei der vorherigen Bewertung eine kleine Fahrzeugbewegung, ein 1-Frame-Inkrement von 0,01 [Meter, Einheiten]: Sie können 10000,1 + 0,01 in 32-Bit-Zahlengenauigkeit (einfach) nicht genau berechnen, aber Sie KÖNNEN 0,1 + 0,01 berechnen. Wenn also die "Aktion" weit weg stattfindet (hinter den Bergen :-)), gehe nicht dorthin, sondern bewege die Berge zu dir (bewege dich mit 10000, dann bist du jetzt bei 0,1). Gültig auch für Speicherplatz. Man kann gierig sein, wenn man numerische Werte nahe beieinander speichert. Speichern Sie den gemeinsamen Teil von ihnen einmal und die Variationen einzeln - kann Bits sparen! Hast du den Link gefunden? ;-)
Sturmwind
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.