DEV Community

Cover image for Building fantasy worlds with Go
Ben Overmyer
Ben Overmyer

Posted on

Building fantasy worlds with Go

For the last two years, I've been working on a system that generates fantasy worlds in a simulationist fashion. It is currently in two parts: a back end API written in Go, and a front end web application written in PHP with Laravel. You can see it in action at Iron Arachne.

For this article, I'll write exclusively about the back end part, as that's where all of the important functionality is contained.

How a World is Made, Step by Step

At the moment, the code necessary to generate an entire coherent planet is not complete. So, the system works with smaller parcels, each randomly constructed.

Each piece of the system is interrelated with others. However, there is a natural starting point for this. The first step in building a world is to create the terrain. This is done by creating "regions" of arbitrary size. Each region has an altitude, temperature, humidity, distance from the equator, distance from and direction to the nearest mountains, and distance from and direction to the nearest ocean. Those statistics are then used to generate a climate for the region. The climate is mostly about wind speed, wind direction, and precipitation. From there, a biome is generated for the region, based on both the region's statistics and the climate's statistics. The biome is something like "tropical rainforest" or "temperate deciduous forest." The biome determines what traits animals and plants will need to have to live there. Finally, the seasons for the region are generated based on all of the above. Distance from the equator is taken into account for how many and how varied the seasons are.

After that geographic information is generated, the next step is to populate the region. This starts with animals and plants generated based on the biome. After that, mineral and soil composition are generated.

The next step is to generate a culture for the region. First, a rough constructed language is generated. This will be used to name the culture as well as unique things found within its influence, and to feed other systems later. Next, things like music style, building style, clothing style, and other cultural accoutrements are generated. Finally, an originating fantasy race and a religion or philosophical outlook are generated.

After a culture is created, then a civilization is created. This consists of a ruler or rulers, towns, and organizations. The ruler is a character built from the race. Towns and organizations also have unique characters. Characters are detailed enough to warrant their own article, so I won't go into them more here. Organizations have specific goals and traits, as well as types - for example, wizarding schools, or mercenary companies. Towns are even more complex, as they have producers of various professions and a chain of goods that they can produce based on available resources from the environment and the skill of the producers.

Generators, Consumers, and Generated Objects

In order to avoid massive amounts of duplicate data being passed around to the various packages and their functions, I implemented a pattern of generators, consumers, and discrete generated objects. Generators are data structures that contain a lot of additional information required for creating the generated objects, but are not necessary for representing that object. For example, a geographic region, its climate, biome, and so forth is necessary for generating a culture. However, the culture data structure doesn't contain the geographic region. Whenever a consumer uses a culture to generate something that also needs geographic information, it also requires the geographic region as an argument.

Package Structure

The API is comprised first and foremost of a series of dozens of Go packages. Each of these packages is responsible for one aspect of the system. There is a package for generating geographic data. There is a package for working with languages. There is a package for generating music styles. There is a package for working with living species.

Each of these packages is designed to be as self-contained as possible. While the API itself relies on many of them, each package has only one or two dependencies on other packages within the project. Managing this dependency chain is an important part of the process, but it's only tangential to the real work.

Common Ancestors

Animals, plants, trees, insects, monsters, and races all make use of the same Species data structure. They expand upon it in different ways, but are ultimately treated as the same type of object. As a result, there are some interesting possibilities in place. For example, traits of one Species could be grafted onto another - say, a human magically altered with plant features. This is not widely used yet, but it's there.

Next Steps

I'm currently working on the system that generates a coherent planet of thousands of these arbitrary regions. This is one of the most important systems currently missing from the project, as it will fundamentally tie together everything else. Procedurally generated world maps are popular, but there is a lot of math involved. It's easy to get sucked into simulating a particular detail and ignore the wider picture.

I hope to write more articles like this in the future exploring this project and related disciplines.

Top comments (0)