Unendliche 3D-Höhle in der Einheit

8

Ein Freund und ich hoffen, in Unity ein Spiel zu machen, in dem Sie durch eine unendliche 3D-Höhle fliegen, die sich in jede Richtung drehen und winden kann (obwohl dies offensichtlich nicht so weit ist, dass die Drehungen unmöglich sind). Wir haben darüber nachgedacht, eine Reihe von Tunnel- "Stücken" zu erstellen, die jeweils eine bestimmte Menge krümmen und jeweils am Ende des vorherigen erscheinen.

Wir haben jedoch keine Ahnung, wie wir sicherstellen sollen, dass die Mündung eines Tunnelstücks immer perfekt (sowohl in Position als auch in Drehung) zum Ende des vorherigen ausgerichtet ist. Kann jemand einen Rat geben, wie dies erreicht werden kann?

Gehen wir überhaupt richtig vor oder gibt es einen besseren Weg, um die Höhle prozedural zu erzeugen? Bonuspunkte: Es wäre fantastisch, wenn sich der Durchmesser und / oder die Form der Höhle ändern könnten, obwohl das nur Soße wäre.

richardmherndon
quelle

Antworten:

6

Es gibt selten einen "richtigen" oder "falschen" Weg, wenn es um Spieldesign geht. Es gibt viele, viele Möglichkeiten, um dieses Problem zu lösen, aber hier sind einige mögliche Ansätze, die untersucht werden müssen:

  • Beschränken Sie die Tunnelstücke so, dass sie nur in bestimmte Richtungen beginnen und enden. zum Beispiel nur entlang der Achsen. Dann müssen Sie nur noch den Versatz vom Anfang bis zum Ende eines Segments verfolgen, zusammen mit Aufzählungen, die die Bewegungsrichtung am Anfang und Ende des Segments beschreiben. Auf diese Weise müssen Sie sich keine Gedanken über das Drehen Ihrer Tunnelnetze machen, solange Sie immer das nächste so auswählen, dass es in der gleichen Richtung beginnt, in der das letzte endete.

  • Lassen Sie jedes Segment am Ursprung seines lokalen Modellraums beginnen, wobei der Tunnel entlang einer bestimmten Achse verläuft (+ X, + Z oder -Z wären die logischste Wahl, aber alle Modelle sollten dieselbe verwenden) Speichern Sie die Position des Tunnelendes und die endgültige Fahrtrichtung auf irgendeine Weise, damit das nächste Netz korrekt transformiert werden kann. (Eine Transformationsmatrix ist wahrscheinlich der einfachste Weg, diese Informationen zu speichern. Sie können jedoch auch einen Verschiebungsvektor + Quaternion, eine doppelte Quaternion, eine Verschiebung + neue Basisvektoren, eine Verschiebung + Euler-Winkelrotationen usw. verwenden.)

  • Generieren Sie Ihre Höhle prozedural, indem Sie neue Scheitelpunktdaten auf einige Netze streamen. Sie können dies mit der MeshKlasse tun . Wenn Sie neue Scheitelpunktdaten generieren, ist es wahrscheinlich am einfachsten, einen Punkt in ungefähr derselben Richtung wie das vorherige Höhlensegment auszuwählen und dann die Mitte der Höhle in Richtung dieses Punkts zu bewegen. Anschließend können Sie Zylinderkoordinaten verwenden, um prozedural Details an den Wänden der Höhle zu erstellen. Stellen Sie sich vor, Sie extrudieren das Ende eines Zylinders und verschieben dann jeden Scheitelpunkt einzeln näher oder weiter von der Mitte dieses Zylinders.

Bei jeder Lösung, bei der vorgefertigte Segmente verwendet werden, müssen Sie sicherstellen, dass alle Netze in der Mitte des Tunnels dieselbe Form und denselben Durchmesser haben. Sie können dies jedoch etwas umgehen, indem Sie die Segmente bis zu einem gewissen Grad überlappen und jedes Segment aufflackern lassen an den Enden raus. Wenn es richtig gemacht wird, sollte es dem Spieler nicht zu offensichtlich sein, dass es eine Naht gibt.

Wenn Sie sich jedoch für eine vollständig prozedural generierte Geometrie entscheiden, müssen Sie mehr Arbeit leisten, um sicherzustellen, dass Sie keine Abschnitte generieren, die nicht durchlaufen werden können, und es kann zu Problemen mit der Kollisionserkennung kommen.

Denken Sie daran, dass Sie bei jedem "unendlichen" Spiel die Einschränkungen von Gleitkomma-Darstellungen kennen sollten. Wenn der Spieler zu weit vom Ursprung der Welt entfernt ist, kann es leicht zu Genauigkeitsverlusten bei Gleitkommaberechnungen kommen (wenn beispielsweise zwei große Werte voneinander subtrahiert werden). Um dies zu vermeiden, können Sie die Welt um den Spieler herum bewegen, anstatt sich durch die Welt zu bewegen. In der Regel ist es jedoch einfacher, die Position des Spielers von Zeit zu Zeit zu überprüfen. Wenn sie zu weit vom Ursprung entfernt sind, bauen Sie die Position neu auf Welt mit dem Spieler am oder in der Nähe des Ursprungs.

bcrist
quelle
2
+1 speziell für den Kommentar "kein richtiger Weg" (obwohl ich etwas anderer Meinung sein muss: Es gibt viele, viele falsche Wege ...)
Steven Stadnicki
Vielen Dank! Am Ende haben wir einige verschiedene Tunnelstücke mit vorhergesehenen Endpositionen und Richtungen verwendet, Markierungen an diesen Positionen / Winkeln gesetzt und jedes neue Stück relativ zur Markierung des vorherigen Stücks platziert. Es wäre cool gewesen, etwas Ausgefeilteres zu tun, aber vorerst lag eine legitimere prozedurale Generierung weit außerhalb unseres Kompetenzbereichs und unseres Zeitlimits. Danke noch einmal!
Richard Mherndon
3

Hier ist eine Technik, mit der ich kürzlich experimentiert habe. Mein RenderMonkey-Prototyp zeigt einen Abschnitt einer Schlucht im Ödland-Stil, aber das gleiche Prinzip sollte in Höhlen funktionieren.

Die Idee ist, mit Fliesen zu beginnen, die generisch, geradezu langweilig und mit einfachen vorhersehbaren Kanten versehen sind, damit sie ohne Nähte oder Lücken leicht ausgerichtet werden können:

Stumpfe, vorhersehbare Fliese

Diese Startkacheln können von Ihnen modellierte Formen oder prozedural erzeugte Makkaroni-Röhren mit zylintrischer Geometrie sein (diese Form ist eine Variante der Vorschläge von bcrist und Steven Stadnicki). Die Verwendung von Modellen, die Sie erstellt haben, erleichtert die Verarbeitung beliebiger Topologien wie Verzweigungspfade oder Sonderziele wie offener Kavernen. Dies ist immer noch mit reinem Verfahren möglich (siehe Gyroninjas Vorschlag zu Metaball-Techniken), aber herausfordernd.

Sobald eine Kachel in die Welt gelegt wurde, verschieben Sie ihre Scheitelpunkte mithilfe der im Weltraum angewendeten Rauschfunktionen. Dies bewahrt die Konnektivität und Nahtlosigkeit zwischen Kacheln (da zusammenfallende Scheitelpunkte dieselbe Weltraumeingabe haben und dieselbe Verschiebungsausgabe erhalten), lässt jedoch jede Kachel einzigartig und organisch aussehen:

Das ist interessanter

Texturen und Normalen werden auch im Weltraum angewendet - hier mithilfe der triplanaren Abbildung -, sodass benachbarte Kacheln ohne schwierige UV-Entpackungsbeschränkungen vollständig nahtlos sind.

Auch interessanter

Die Hoffnung ist, dass eine solche Technik Ihnen die einfache Planung und Kontrolle des Level-Designs einer gekachelten Karte ermöglicht, ohne sichtbare Wiederholungen oder mechanisch aussehende Strukturen im spielbaren Ergebnis.

Sie können ein Netz mit niedrigerer Auflösung nur mit den niederfrequenten Rauschkomponenten verwenden, die zum Erstellen der Kollisionsdarstellung angewendet werden. Wie bcrist feststellt, müssen Sie die maximale Amplitude des Rauschens im Verhältnis zum Radius und zur Schärfe der Tunnelwindungen steuern, um sicherzustellen, dass es niemals vollständig abklemmt.

Ein weiterer Hinweis: Wenn Ihre Höhle wirklich unendlich ist, müssen Sie sie möglicherweise regelmäßig "neu zentrieren", wenn sich der Spieler immer weiter vom Ursprung entfernt. Da Gleitkommazahlen bei hohen Größen an Genauigkeit verlieren, können sich Physik- und Rendering-Artefakte in extremen Entfernungen einschleichen. Wenn Sie dies tun, möchten Sie, dass Ihr Weltraumrauschen in großem Maßstab periodisch ist, wobei der Zeitraum genau auf Ihren Versatz der erneuten Zentrierung abgestimmt ist, sodass Sie nach der erneuten Zentrierung nicht auf Nähte stoßen.

DMGregory
quelle
2

Sie können Ihre Höhle als eine Folge von Punkten modellieren, die jeweils eine zugeordnete Größe haben und mit Linien verbunden sind. Dann behandeln jeden Punkt und Linie als metaballs und metacylinders. Auf diese Weise erhalten Sie eine Grundform für Ihre Höhle, zu der Sie möglicherweise Variationen hinzufügen möchten, z. B. durch zufälliges Versetzen von Scheitelpunkten.

Gyroninja
quelle
2

Hier ist ein weiterer Ansatz zur prozeduralen Generierung, der noch nicht explizit erwähnt wurde: Spline-Skinning. Sie können eine Version von Hermite Splines verwenden(die eine Kurve bereitstellen, die Positionen und Tangenten interpoliert), um die Kurven zu definieren: Wenn es Zeit ist, ein neues Segment zu generieren, wählen Sie einfach eine Position (ungefähr in Richtung des vorherigen Segments, wie bcrist sagt) und eine Richtung (ungefähr in derselben Richtung) Richtung - z. B. innerhalb eines genau definierten Kegels der vorherigen Richtung), verwenden Sie dann die neue Position + Richtung und Ihre vorherige Position + Richtung, um eine neue 'Wirbelsäule' für Ihre Höhle zu bauen. Sobald Sie dieses Rückgrat haben, können Sie es mit einer zylindrischen Konstruktion versehen: Bestimmen Sie die Positionen und Tangenten von (zum Beispiel) 10 Punkten entlang der Kurve, verwenden Sie diese Positionen / Tangenten, um einen orthogonalen 'Rahmen' zu finden, und verwenden Sie dann diese Rahmen, um zylindrische Segmente bauen. Eine kleine Vorsicht dabei ist , dass die Höhle kann nicht Kurve zu viel, oder Sie können auf Selbstüberschneidungsprobleme stoßen.

EDIT: Hier ist eine grobe Pseudocode-Aufschlüsselung des Algorithmus:

Parameters:
  L = (average) segment length,
  V = segment length variation,
  R = cylinder radius,
  T = segment angular variation
  S = number of 'rings' per segment

Setup:
Choose an initial point P_0 and direction D_0 (for concreteness' sake, these can be
the origin and the X axis).  Set P_prev and D_prev to these values.
Initialize u_prev to be the Y axis and v_prev to be the Y and Z axes.
  (Note that (D_prev, u_prev, v_prev) form a mutually-orthogonal 'coordinate frame')

Generate a segment (do this as many times as you want):
{
  Choose a (temporary) direction D within a cone of size T around the previous direction D_prev
  Choose a segment length L_cur = at random from within the range [L-V, L+V].
  Set the current terminal point P_cur to P_prev+D*L_cur - this is the position
  we'll interpolate to
  Set the current terminal direction D_cur to a direction chosen at random from
  within a cone of size T around the previous direction.  (There are good ways
  of doing this - if you look back through gamedev.SE you should find some)
  'Build' the Hermite spline H that goes from (P_prev, D_prev) to (P_cur, D_cur)

  Now, skin that spline:
  for ( i = 1; i <= S; i++ ) {
    find the position P of the hermite spline H at t=i/S
    find the direction D of the spline at t (this will be just the derivative)
    'transport' the orthogonal frame to the new spot: for instance,
      v_new = D x u_prev
      u_new = v_new x D
    (note that this keeps u_new, v_new close to their old values, and orthogonal
    to each other and to D)
    Use the previous and current frames and positions to build a cylindrical 'ring':
    For theta from 0 to 2pi {
      find the points (P+(u_new, v_new, D) * (cos theta, sin theta, 0))
      and connect them to their counterparts from the previous ring
      (note that that multiplication is a matrix-vector multiply)
    }
    update u_prev and v_prev to u_new and v_new
  }
  update the other prev variables to their 'new' values
}

Dies ist offensichtlich ein sehr grober Pseudocode; Wenn es etwas Unklares gibt, lass es mich wissen und ich werde versuchen es zu erklären, aber es wird schwierig sein, alle Details ohne einen riesigen Code-Dump abzudecken ...

Steven Stadnicki
quelle
(Übrigens, wenn Sie einen Pseudocode für diesen Ansatz wünschen, lassen Sie es mich wissen. Ich musste bei einem früheren Job so etwas tun, also habe ich alle kleinen Details herausgearbeitet.)
Steven Stadnicki
Ich wäre gespannt auf Ihre Implementierung. Ich habe auch einmal etwas Ähnliches gemacht, aber stattdessen kubische 3D-Bezier-Kurven verwendet.
bcrist