INTRODUCTION
I started this project out of newly acquired interests in historical strategy games such as Paradox Interactive's games and fantasy games with vast worldbuilding such as the Elder Scrolls series. The combination of both led to wanting to create a tool for players to simulate the history of civilization in a randomly-generated world.
As for programming, my professional aim was to practice systems more related to the first kind of game, that is, grand strategy games with complex map systems.
Therefore, this project is focused, among other things, on:
Random map generation with editable parameters
Mesh creation and rendering (the landmass is created through code)
History simulation, with countries expanding, disappearing, and appearing through the centuries
Saving, loading, and replaying maps and their histories
Additionally, it contains:​
A small random word generation algorithm
An ocean water shader (this one is done with Unity's Shader Graph, so I will not break it down here)
A simple top-down camera controller
Watch the video below and continue reading for more details.​
WORLD MAP GENERATION
Map generation is done through a grid system, similar to a chessboard, at the beginning. Some parameters are offered for the user to play around with, after which a grid of a certain height and width is generated in the code. Each tile or cell gets a random displacement to its corners to avoid an artificial look, and then an algorithm randomly distributes elevation (which I shortened to "depth" in the code) to every tile corner in the whole map and smooths everything out to make it reasonable.
After the grid variables are completely generated "in code form", the actual object is generated in the engine as 3D polygons. This involves a triangulation process that basically consisted of adapting Unity's standard method of doing it to work with the rest of my project. The code is in the files below because it is too long to fit here.
If you have a look at it, you will see the physical cells are saved into a new dictionary, which is used for coloring them in the next part of the process.
HISTORY SIMULATION
After the initial countries have been generated following the mode that the player has chosen (empires or single-cell countries), centuries of history begin to be simulated. The borders are reshaped by a conquest algorithm that causes a random number of invasions, as well as the appearance of newly independent states. Again, the code of the main file is too long and spread to be shown here in an understandable summary, so I will instead share a small function for generating country names. You can find all the code files in the link at the bottom of the page.
public string CreateName()
{
char[] consonants = { 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' };
char[] vowels = { 'a', 'e', 'i', 'o', 'u' };
string finalString = "";
int arrangement = Random.Range(1, 4);
switch (arrangement)
{
case 1: //CVCV
finalString += consonants[Random.Range(0, consonants.Length)];
finalString += vowels[Random.Range(0, vowels.Length)];
finalString += consonants[Random.Range(0, consonants.Length)];
finalString += vowels[Random.Range(0, vowels.Length)];
break;
case 2: //CVCVC
finalString += consonants[Random.Range(0, consonants.Length)];
finalString += vowels[Random.Range(0, vowels.Length)];
finalString += consonants[Random.Range(0, consonants.Length)];
finalString += vowels[Random.Range(0, vowels.Length)];
finalString += consonants[Random.Range(0, consonants.Length)];
break;
case 3: //CVVC
finalString += consonants[Random.Range(0, consonants.Length)];
finalString += vowels[Random.Range(0, vowels.Length)];
finalString += vowels[Random.Range(0, vowels.Length)];
finalString += consonants[Random.Range(0, consonants.Length)];
break;
default:
break;
}
return finalString;
}
SAVING AND LOADING MAPS
If you read the code, you will see in the main file that the saving system is intertwined with the rest of the code throughout the whole script, which leads to some unintuitive code. As with my first project, the save-and-load system is enabled by Unity's tools to parse to and from JSON files. Maps are converted to and from serializable (saveable) classes so that a file can be saved and read whenever I need it. Specifically, the saving function is called by other scripts during the map creation process, while loading is called on the history replay interface.
Information about each cell at every point of history, such as vertex positions, elevation, and belonging to a country, is stored through the whole generation process and recovered when replaying the history of the world.
The file, which is the only one relevant for this whole breakdown, is in the folder below as MapManager.cs. I recommend using a text editor or viewer that highlights C# sytax or similar since otherwise the functions and such will not appear clearly separated.
ROOM FOR IMPROVEMENT
It is certainly important to do a self-evaluation after each project. The things that could be improved in this project are:
Clearer code: as with my previous project, I always feel the code needs more comments and annotations to be clearer.
Optimization: some algorithms are pretty expensive in terms of processing power, which would slow down people who want to make bigger maps.
Polishing: less so than in my last project, but still, the UI and map shaders are very barebones, because the project is focused on code. This could definitely see an improvement if I continued the project.
More separation between the saving system and the rest of the code: some conversions to serializable variables and the saving and loading themselves are all done in the same script as the generation. If I had the chance to restart the project, I would probably divide the two.
I also like to make a list of features to add and how I would approach making them:​
Cities and more map elements in general: it would be easy to populate the map with cities, rivers, etc., and save their positions, to give the map more life.
More complex conquest algorithm: right now a small country has the same chances of expanding as the biggest country. While this is somewhat true in the long run in real history, I would probably add a size check within the expansion algorithm
Integrated map sharing: as with the last project, map sharing is technically already possible, but a dedicated interface would be nice for novelists or game masters who want to move their worlds around between different devices