Entdecken Sie Millionen von E-Books, Hörbüchern und vieles mehr mit einer kostenlosen Testversion

Nur $11.99/Monat nach der Testphase. Jederzeit kündbar.

Erfolgreiche Spieleentwicklung: Minecraft-Welten erschaffen Teil 2
Erfolgreiche Spieleentwicklung: Minecraft-Welten erschaffen Teil 2
Erfolgreiche Spieleentwicklung: Minecraft-Welten erschaffen Teil 2
eBook256 Seiten1 Stunde

Erfolgreiche Spieleentwicklung: Minecraft-Welten erschaffen Teil 2

Bewertung: 0 von 5 Sternen

()

Vorschau lesen

Über dieses E-Book

Die Arbeit an unserer Blockwelt ist noch lange nicht abgeschlossen. In der Fortsetzung zum ersten Teil dieser shortcut-Serie beschäftigt sich Alexander Rudolph zunächst mit der Weiterentwicklung der Noise-Funktionen im 3-D-Bereich. Im zweiten Kapitel verleiht er mit Hilfe von Cloud und Water Rendering in OpenGL der Welt ein wenig Leben, während im nächsten Schritt die ersten menschlichen Bewohner animiert werden. Danach wird die Welt noch einmal realistischer: Ein dynamisches Wettersystem wird unter Einsatz verschiedener Shader implementiert, bevor der Autor sich im letzten Kapitel mit der Thematik und Problematik von Open-World-Spielen auseinandersetzt.
SpracheDeutsch
Herausgeberentwickler.press
Erscheinungsdatum11. Okt. 2016
ISBN9783868027440
Erfolgreiche Spieleentwicklung: Minecraft-Welten erschaffen Teil 2

Mehr von Alexander Rudolph lesen

Ähnlich wie Erfolgreiche Spieleentwicklung

Titel in dieser Serie (100)

Mehr anzeigen

Ähnliche E-Books

Programmieren für Sie

Mehr anzeigen

Ähnliche Artikel

Rezensionen für Erfolgreiche Spieleentwicklung

Bewertung: 0 von 5 Sternen
0 Bewertungen

0 Bewertungen0 Rezensionen

Wie hat es Ihnen gefallen?

Zum Bewerten, tippen

Die Rezension muss mindestens 10 Wörter umfassen

    Buchvorschau

    Erfolgreiche Spieleentwicklung - Alexander Rudolph

    GmbH

    1 Spieleweltgestaltung in drei Dimensionen

    Obwohl unsere zuletzt erzeugten Landschaften bereits recht ansprechend und abwechslungsreich wirkten, kamen bei ihrer Berechnung bislang lediglich zweidimensionale Noise-Funktionen zum Einsatz. Heute werden wir uns damit befassen, wie sich die zugrunde liegenden Konzepte um eine zusätzliche Dimension erweitern lassen – denn erst mithilfe von 3D-Noise-Funktionen und 3-D-Random-Walk-Simulationen wird es möglich, dreidimensionale Geländeformationen, Höhlensysteme oder unterirdische Rohstofflagerstätten zu generieren.

    Continuous Development ist das Stichwort – dieses Kapitel steht voll und ganz im Zeichen der Weiterentwicklung der von uns bislang eingesetzten prozeduralen Gestaltungstechniken und Beleuchtungsmodelle. Im ersten shortcut „Erfolgreiche Spieleentwicklung. Minecraft-Welten erschaffen" sind wir darauf zu sprechen gekommen, wie sich unterschiedliche, vom Breitengrad abhängige Landschaftstypen und Vegetationsformen auf mathematischem Wege erzeugen lassen. Wir haben ein einfaches CPU-basiertes Verfahren besprochen, mit dessen Hilfe sich die Ausbreitung des indirekten Tageslichts halbwegs realistisch simulieren lässt. Auch haben wir uns mit den wichtigsten Einzelheiten einer von der Jahreszeit abhängigen Landschafts- und Vegetationsdarstellung auseinandergesetzt.

    So weit, so gut. Bisher haben wir uns damit in unserem Spieleprototyp jedoch ausschließlich auf die Oberflächengestaltung und -darstellung beschränkt. Der Aufbau einer blockbasierten, von Minecraft inspirierten Welt lässt sich allerdings nicht mit den Spielewelten anderer Titel vergleichen, in denen eine Landschaft im Prinzip nichts weiter als eine in der xz-Ebene definierte Fläche ist, deren Höhenwerte man vorzugsweise unter Zuhilfenahme einer Height-Map von Ort zu Ort variiert. Sieht man einmal von der Tatsache ab, dass wir anstelle von Height-Maps auf 2D-Noise-Funktionen (2-D-Rauschen) zurückgegriffen haben, um das Höhenprofil unserer prozeduralen Landschaften zu verändern oder um die Spielewelt in unterschiedliche Regionen aufzuteilen, dann sind wir bisher auf eine ganz ähnliche Weise vorgegangen. Auch das von uns verwendete Beleuchtungsmodell ist noch extrem verbesserungswürdig, da wir bislang lediglich die Ausbreitung des indirekten Tageslichts simuliert haben. Eine zweite natürliche Lichtquelle, die uns im weiteren Verlauf noch häufiger begegnen wird, haben wir indes vollkommen außer Acht gelassen: Lava.

    Aber keine Sorge, weder was die Beleuchtungsberechnungen, noch was die prozedurale Generierung der Spielewelt betrifft, müssen wir wieder von vorne beginnen. Betrachten Sie einfach die bislang erzeugten Landschaften als eine Art Zwischenergebnis, das es in weiteren Schritten mehr oder weniger umfangreich zu überarbeiten gilt. So lassen sich beispielsweise Höhlensysteme realisieren, in denen man das Felsgestein durch unterschiedlich große Hohlräume (Caves) und Verbindungsgänge (Cave Passageways) ersetzt. Für die Ausformung der Hohlräume bietet sich der Einsatz von dreidimensionalen Noise-Funktionen an. Die Verbindungsgänge können wir hingegen auf ähnliche Weise wie die Rohstofflagerstätten (hierauf deutet nicht zuletzt die alternative Bezeichnung Erzader hin) mithilfe von einfachen Random-Walk-Simulationen verwirklichen. Was die Erschaffung von zusätzlichen dreidimensionalen Geländeformationen betrifft, setzen wir selbstredend wiederum auf 3D-Noise-Berechnungen, auch wenn wir in diesem Zusammenhang zwei unterschiedliche Strategien verfolgen. Einerseits werden wir in ausgewählten Regionen die zuvor generierte 2D-Noise-Landschaft durch neue 3D-Noise-basierte Strukturen ersetzen.

    Unabhängig davon orientieren wir uns bei der Modellierung von einer Reihe weiterer Terraindetails zudem an der Arbeitsweise eines Bildhauers. Hierfür überziehen wir unser 2D-Noise-Terrain mit einer zusätzlichen Schicht von Blöcken, aus der wir dann im zweiten Schritt die gewünschten Geländestrukturen ausformen (ausmeißeln) können. Und selbstverständlich werden wir in diesem Zusammenhang auch auf die Probleme und mögliche Lösungen eingehen, die mit dem Einsatz von 3D-Noise-Funktionen bei der Oberflächengestaltung zwangsläufig einhergehen: schwebende Blöcke und Inseln, die allenfalls in den aus dem Science-Fiction-Film Avatar bekannten Dschungellandschaften Pandoras ihre Daseinsberechtigung haben.

    3D-Noise-Funktionen

    1-D-, 2-D-, 3-D- oder gar 4-D-Rauschen – auf den ersten Blick klingt das alles überaus mathematisch und anfangs sogar ein wenig verwirrend, obwohl wir bereits einige Erfahrungen auf diesem Gebiet gesammelt haben. Die Einsatzmöglichkeiten von 2D-Noise-Funktionen im Rahmen der prozeduralen Landschaftsgestaltung beherrschen wir mittlerweile im Schlaf und wissen, wie sich in Gestalt von Value, Gradient oder Cell Noise drei ganz unterschiedliche Arten von Rauschwerten berechnen lassen. Worin unterscheiden sich jetzt aber zweidimensionale von dreidimensionalen oder gar vierdimensionalen Rauschwerten? Bevor wir an dieser Stelle allzu sehr ins Detail gehen, sollten wir zunächst einmal festhalten, dass es sich in allen Fällen um positions- bzw. zeitabhängige Zufallszahlen handelt. Eine 1D-Noise-Funktion, der wir die Systemzeit oder die momentane Spieldauer als Parameter übergeben, wäre beispielsweise nichts anderes als ein zeitabhängiger Zufallszahlengenerator. Mit den uns bereits vertrauten 2D-Noise-Funktionen können wir hingegen für jeden Punkt einer zunächst flachen Landschaft (in unserem Demoprogramm liegt diese in der xz-Ebene) einen Höhenwert (Rauschwert) ermitteln. Übergibt man einer 3D-Noise-Funktion als Parameter eine dreidimensionale Positionsangabe, so lässt sich der zugehörige Rauschwert als Dichte interpretieren: Ist die Dichte in einer blockbasierten Spielewelt an einem Ort zu klein, wird ein eventuell vorhandener Block entfernt; ist die Dichte hingegen groß genug, wird stattdessen ein neuer Block hinzugefügt. Würde man jedoch stattdessen eine zweidimensionale Positionsangabe mit einer zusätzlichen Zeitangabe kombinieren, könnte man mithilfe einer 3D-Noise-Funktion beispielsweise eine zweidimensionale Wasserfläche animieren. Dreidimensionale Noise-basierte Strukturen wie Wolken oder Rausch lassen sich im Unterschied dazu mit einer 4D-Noise-Funktion zum Leben erwecken. Die Berechnung der zugehörigen Rauschwerte erfordert in diesem Fall – Sie ahnen es bereits – sowohl eine dreidimensionale Orts- wie auch eine zusätzliche Zeitangabe.

    Die Berechnung der denkbar einfachsten Form des (dreidimensionalen) Rauschens – so genanntes „weißes Rauschen" ohne eine besondere Struktur – lässt sich anhand der Listings 1.1 und 1.2 am Beispiel der Funktionen RandomValue0To1() sowie GetNewDirection() nachvollziehen. Nun ja, genau genommen ist das Wort „Berechnung" ein wenig zu hoch gegriffen, da uns die besagten Funktionen in Abhängigkeit von der Blockposition lediglich eine bereits im Vorfeld berechnete Zufallszahl bzw. einen 3-D-Gradientenvektor (Richtungsvektor) zurückliefern. Wie Sie sehen können, verzichten wir aus Performancegründen darauf, die für die Noise-Berechnungen erforderlichen Zufallswerte zur Laufzeit zu erzeugen. Zufallszahlen und Gradientenvektoren (Listing 1.3) berechnen wir stattdessen bereits im Verlauf der Initialisierungsphase und speichern sie dann in zweidimensionalen (2D-Noise-) bzw. dreidimensionalen (3D-Noise-)Arrays ab. Um Zugriff auf die in den Arrays gespeicherten Daten zu erhalten, müssen wir zunächst die als Parameter übergebenen Positionswerte (ix, iy, iz) mithilfe einfacher Modulo-Operationen (hierbei wird der Rest einer Ganzzahldivision ermittelt, beispielsweise -31 % 11 = -9) in die korrespondierenden Array-Indizes (id_x, id_y, id_z) umrechnen.

    inline float RandomValue0To1(long ix, long iy, long iz)

    {

      long id_x = ix % HALF_SURFACE_VALUES_PER_DIR_PLUS1 +

                      HALF_SURFACE_VALUES_PER_DIR_MINUS1;

      long id_y = iy % HALF_SURFACE_VALUES_PER_DIR_PLUS1 +

                      HALF_SURFACE_VALUES_PER_DIR_MINUS1;

      long id_z = iz % HALF_SURFACE_VALUES_PER_DIR_PLUS1 +

                      HALF_SURFACE_VALUES_PER_DIR_MINUS1;

      return SurfaceDataPattern3D[id_x][id_y][id_z];

    }

    Listing 1.1: Weißes 3-D-Rauschen

    inline void GetNewDirection(long ix, long iy, long iz, float* pDirX, float* pDirY, float* pDirZ)

    {

      long id_x = ix % HALF_SURFACE_VALUES_PER_DIR_PLUS1 +

                      HALF_SURFACE_VALUES_PER_DIR_MINUS1;

      long id_y = iy % HALF_SURFACE_VALUES_PER_DIR_PLUS1 +

                      HALF_SURFACE_VALUES_PER_DIR_MINUS1;

      long id_z = iz % HALF_SURFACE_VALUES_PER_DIR_PLUS1 +

                      HALF_SURFACE_VALUES_PER_DIR_MINUS1;

      *pDirX = SurfaceDataPattern3D_gx[id_x][id_y][id_z];

      *pDirY = SurfaceDataPattern3D_gy[id_x][id_y][id_z];

      *pDirZ = SurfaceDataPattern3D_gz[id_x][id_y][id_z];

    }

    Listing 1.2: Richtungsbestimmung bei einer Random-Walk-Simulation

    #define SURFACE_VALUES_PER_DIR  21

    #define HALF_SURFACE_VALUES_PER_DIR_PLUS1  11    // (21 + 1) / 2

    #define HALF_SURFACE_VALUES_PER_DIR_MINUS1  10    // (21 - 1) / 2

    // for 3D Gradient Noise Calculations:

    float SurfaceDataPattern3D_gx[SURFACE_VALUES_PER_DIR]

                                              [SURFACE_VALUES_PER_DIR]

                                              [SURFACE_VALUES_PER_DIR];

    float SurfaceDataPattern3D_gy[SURFACE_VALUES_PER_DIR]

                                              [SURFACE_VALUES_PER_DIR]

                                              [SURFACE_VALUES_PER_DIR];

    float SurfaceDataPattern3D_gz[SURFACE_VALUES_PER_DIR]

                                              [SURFACE_VALUES_PER_DIR]

                                              [SURFACE_VALUES_PER_DIR];

    [...]

    for(ix = 0; ix < SURFACE_VALUES_PER_DIR; ix++)

    {

      for(iy = 0; iy < SURFACE_VALUES_PER_DIR; iy++)

      {

        for(iz = 0; iz < SURFACE_VALUES_PER_DIR; iz++)

        {

          f1 = RandomNumbersTerrain2.Next_FloatNumber(-1.0f, 1.0f);

          f2 = RandomNumbersTerrain2.Next_FloatNumber(-1.0f, 1.0f);

          f3 = RandomNumbersTerrain2.Next_FloatNumber(-1.0f, 1.0f);

          // mögliche Division durch null verhindern (beim späteren Normieren):

          if(f1 == 0.0f && f2 == 0.0f && f3 == 0.0f)

          {

            if(RandomNumbersTerrain2.Next_IntegerNumber(1, 3) == 1)

              f1 = RandomNumbersTerrain2.Next_FloatNumber(0.1f, 1.0f);

            else

              f1 = RandomNumbersTerrain2.Next_FloatNumber(-1.0f, -0.1f);

            if(RandomNumbersTerrain2.Next_IntegerNumber(1, 3) == 1)

              f2 = RandomNumbersTerrain2.Next_FloatNumber(0.1f, 1.0f);

            else

              f2 = RandomNumbersTerrain2.Next_FloatNumber(-1.0f, -0.1f);

            if(RandomNumbersTerrain2.Next_IntegerNumber(1, 3) == 1)

              f3 = RandomNumbersTerrain2.Next_FloatNumber(0.1f, 1.0f);

            else

              f3 = RandomNumbersTerrain2.Next_FloatNumber(-1.0f, -0.1f);

          }

          // Normieren:

          invLength = 1.0f/sqrtf(f1*f1 + f2*f2 + f3*f3);

          SurfaceDataPattern3D_gx[ix][iy][iz] = invLength*f1;

          SurfaceDataPattern3D_gy[ix][iy][iz] = invLength*f2;

          SurfaceDataPattern3D_gz[ix][iy][iz] = invLength*f3;

    }}}

    Listing 1.3: Berechnung der 3-D-Gradientenvektoren (Initialisierungsphase)

    Bevor wir gleich auf die Grundlagen der dreidimensionalen Rauschwerterzeugung zu sprechen kommen, verschaffen wir uns zunächst einmal eine Übersicht über die einzelnen 3D-Noise-Funktionen, die wir im Rahmen der prozeduralen Generierung unserer Spielewelt ergänzend zu den bereits ausführlich diskutierten 2D-Noise-Funktionen einsetzen werden:

    3D Value Noise ohne Interpolation der Rauschwerte:

    inline void ValueNoise3D_NoInterpolation(long* pResult,

                  float &x, float &y, float &z,

                  float &frequencyX, float &frequencyY, float &frequencyZ,

                  long &minNoiseValue, long &maxNoiseValueDiff)

    {...} /* siehe Listing 1.4*/

    3D Value Noise mit linearer Interpolation der Rauschwerte:

    inline void ValueNoise3D_LinearInterpolation(...){...}

    3D Value Noise mit kosinusförmiger Interpolation der Rauschwerte:

    inline void ValueNoise3D_CosineInterpolation(...){...}

    3D Gradient Noise mit linearer Interpolation der Rauschwerte:

    inline void GradientNoise3D_LinearInterpolation(...){...}

    3D Gradient Noise mit kosinusförmiger Interpolation der Rauschwerte:

    inline void GradientNoise3D_CosineInterpolation(...)

    {...} /* siehe Listing 1.5*/

    Können Sie sich noch daran erinnern, wie sich anstelle von „weißem Rauschen" ein Rauschsignal mit einer eindeutig definierten Frequenz erzeugen lässt? Die Lösung des Problems erwies sich als überaus einfach, denn unabhängig von der Art des Rauschens (Value Noise, Gradient Noise, Cell Noise) mussten wir unsere Landschaft lediglich mithilfe zweier simpler Codezeilen in eine Vielzahl von gleichgroßen quadratischen

    Gefällt Ihnen die Vorschau?
    Seite 1 von 1