Generierung der Welt II

Autor: Adrian Richter

Grundlegende Terrain-Form

Sieht man sich das Ergebnis der Generierung mithilfe der 3D-Noise-Funktion an, so hat man bereits eine sehr beeindruckende Landschaftsform geschaffen. Diese ist nun jedoch überall sehr gleichmäßig zufällig aufgebaut; das wollen wir nicht! Wie man sich gut vorstellen kann, wollen wir verschiedene Bedingungen aus der realen Welt und die daraus resultierende Landschaft darstellen.

Für diesen Zweck haben wir zwei weitere 2D-Noise-Funktionen generiert, die als Temperatur- und Luftfeuchtigkeitswerte interpretiert werden. Diese liegen zwar nun nicht in realistischen Wertebereichen, sind aber für unsere Zwecke vollkommen ausreichend.

Das Bild zeigt die Temperaturwerte (je mehr rot, desto wärmer)

Da der Threshold, der bestimmt, ob ein Voxel gefüllt wird oder nicht, mithilfe einer Sigmoid-Funktion errechnet wird (siehe Generierung der Welt), ist deren steepness ("Steilheit") ein Maß dafür, wie bergig bzw. flach das Gelände wird. Um dies nun klimaabhängig zu machen, haben wir folgende Methode implementiert, die dafür sorgt, dass in es warmen Gegenden flach und in kalten steil und bergig ist:

// trying aproximate the
// steepvalues       10   20   60  200
// for temperature  0.0  0.2  0.4  1.0
fn steepness_from_temperature(temperature: f32) -> f32 {
    120.0 * temperature * temperature + 72.0 * temperature + 3.5
}

Die Auswirkung kann man bereits im Screenshot oben erkennen. Je dunkler, also kälter, ein Bereich ist, desto tiefer sind die Schluchten.

Klima und Biome

Bis jetzt haben wir zunächst nur die grundlegende Landschaftsform geschaffen. Das alleine macht allerdings noch keine realistische Welt aus. Nun sollen auch andere Teile des Spiels vom Klima abhängen. Um dieses problem möglichst zentral zu lösen haben wir das Enum Biome erstellt, welches einige nützliche Methoden implementiert.

fn from_climate(temperature: f32, humidity: f32) -> Biome {
    match (temperature, humidity) {
        (0.0...0.2, 0.0...0.2) => Biome::Stone,
        (0.0...0.2, 0.2...0.4) => Biome::Snow,
        (0.0...0.2, 0.4...1.0) => Biome::Snow,
        (0.2...0.4, 0.0...0.2) => Biome::GrassLand,
        (0.2...0.4, 0.2...0.4) => Biome::GrassLand,
        (0.2...0.4, 0.4...1.0) => Biome::Forest,
        (0.4...1.0, 0.0...0.2) => Biome::Desert,
        (0.4...1.0, 0.2...0.4) => Biome::Savanna,
        (0.4...1.0, 0.4...1.0) => Biome::RainForest,
        _ => Biome::Debug,
    }
}

Diese Konstruktor-Methode ermittelt aus den übergebenen Kilmawerten das passende Biome und gibt dieses entsprechend zurück. Hier kann man einmal sehen, in welchem Wertebereich sich die Werte befinden. An dieser Stelle kann bestimmt werden, wie oft welches Biome zu finden sein wird und bei welchen klimatischen Voraussetzungen. Da die Biome's aus den gleichen Noise-Maps wie die steepness bei der Terrainform generiert werden, passen diese automatisch zusammen.

fn material(&self) -> GroundMaterial

Die material-Methode führt lediglich ein einfaches match aller möglichen Biome's durch und gibt ein entsprechendes Bodenmaterial zurück.

fn plant_threshold(&self) -> f32

Hier, in der plant_threshold-Methode, befindet sich die Stelle, an der geregelt werden kann, wie stark die Vegetation in jedem Biome sein soll.

fn plant_distribution(&self) -> &'static [PlantType]

Mit plant_distribution erhält der Aufrufer ein Array, das in relativen Häufigkeiten aussagt, welche Pflanzen in einem spezifischen Biome stehen können. Befinden sich z.B. in der Wüste nur Kakteen, dann befindet sich im Array nur ein einziger Cactus-Eintrag. Eine 1:2-Verteilung ist in einem Array der Länge drei auszudrücken. So kann die Verteilung der Pflanzen beliebig genau angepasst werden.

Dieses Enum (und seine Methoden) kann nun von überall genutzt werden, weil an jedem HexPillar der Welt auch sein Biome hängt. Dies ist z.B. für die Wetter-Gruppe hilfreich, da diese nun sehr einfach ermitteln kann, in welchem Gebiet sie sich befindet und welches Wetter dort dementsprechend herrschen muss.

Grenzen zwischen Biomen

Hat man nun diese Features implementiert und bewegt sich durch die Welt, so fällt leider recht schnell auf, dass die Biomgrenzen sehr scharf verlaufen und nicht besonders schön anzusehen sind.

Wir würden uns eher wünschen, dass die Biome's etwas ineinander übergehen, sodass auch beispielsweise Wälder nicht einfach abgeschnitten werden.

Dieses Problem haben wir durch Überlagerung einer weiteren, aber wesentlich höherfrequenteren Noise-Map auf sowohl temperature_noise als auch humidity_noise gelöst. Sieht man genau hin, kann man das Resultat bereits in der Temperaturdarstellung weiter oben erkennen. Die Temperatur hat eine Art scheckiges Muster. Das bewirkt innerhalb der Biome's nichts, denn der Threshold ist an diesen Stellen ohnehin weit überschritten. An den Rändern jedoch kommt es dazu, dass sich die angrenzenden Biome's gegenseitig überlagern. Dies ist auf der folgenden Darstellung noch einmal veranschaulicht.

Grenze zweier Biome's links ohne, rechts mit Überlagerung weiterer Noise-Map

Das Ergebnis sieht dann wie folgt aus.

Pflanzenplatzierung

Nun haben wir schon viele Elemente in unserer Welt, die für eine möglichst natürliche und realistische Umgebung sorgen sollen. Aber was wäre ein Spiel mit dem Namen Plantex ohne viele verschiedene Pflanzen, die sich zudem noch anhand der Umgebung passend aufstellen? Auch hier beweist das Biome-Enum erneut seine Nützlichkeit. Nun schauen wir uns allerdings die Nutzung der Methoden plant_threshold und plant_distribution genauer an.

Um aber zunächst wieder eine grundlegende, natürlich wirkende Verteilung der Pflanzen zu bekommen, auf der wir dann aufbauen können, generieren wir uns eine 2D-Noise-Map, die aussagen soll, ob auf der gefragten Säule eine Pflanze wächst oder nicht. Nun können wir wir für jeden HexPillar, der generiert werden soll, mit der plant_threshold-Methode den Schwellwert abfragen und mit dem Wert aus der Noise-Map vergleichen. Soll nun eine Pflanze gestellt werden, rufen wir die Methode plant_distribution auf und erhalten ein Array, das, wie oben beschrieben, aussagt, welche Pflanzen mit welcher Wahrscheinlichkeit hier stehen können. Ein RNG nimmt nun einen zufälligen Pflanzentyp aus diesem Array, welcher dann dort generiert wird.

Verschiedene Pflanzen in verschiedenen Biome's

All diese Schritte zusammen ergeben eine Welt, die unendlich groß ist, zufällig generiert wird, verschiedene Klimaregionen in Biome's darstellt und diese mit passenden Pflanzen füllt. Keine zwei Spieler werden die gleiche Welt erhalten, außer sie wollen es. Denn gibt man den gleichen Seed an, so erhält man exakt die gleiche Welt noch einmal.

results matching ""

    No results matching ""