Cozy Kingdoms Generation

Cozy Kingdoms is the marriage of a procedural map generation method, a really good tileset, and a name generation algorithm. It looks a bit like this:



Let's talk about how to generate this beauty. The generation code is 430 lines long, with very nicely logical steps in the beginning and more and more conjointed code as it goes along into the realm of choosing specific subimages for tiling purposes.

To give an overview of the whole process, here's a neat animation.



Well, that was a bit short. Let's go over it once more, step by step, and I'll put down a couple of words about each.


At first, a number of regions are created at random positions. Well, semi-random. They cannot be too close to each-other, for one thing, or too close to the border of the screen. The regions are what will define the landmass, while the border of the screen will make up the surrounding sea.



This is an intermediate screenshot as the regions propagate quite at random. The best way to get a feel for this is to see it animated above. I did also write a post about this organic Voronoi growth.

We can also see the battle between the land and the sea. Earlier I said the border of the screen was sea - well, that's only true of half of it, depending on Perlin noise. Here we see that most of the bottom and most of the left edge of the screen indeed are sea, while the top right instead joins the offscreen mainland.


While the above method makes interesting terrain, it does tend to lead to jittery borders - which are going to be a problem when this is to be translated into terrain. The smoothing is perfomed by having any tile surrounded by three "foreign" tiles change into that other tile.



Now, all regions which share more than three tiles as border are connected temporarily. The lines drawn above do not actually show the borders that are interesecting, just bear with that for a second.


As many connections as possible are removed as long as all nodes still connect through each-other in a single graph.


Now that we know whcih regions should be connected, we can build walls to seperate the regions disconnected from each-other in the earlier step. You might notice slightly different colours for the walls - the borders can either be forest (green), mountain (brown) or lake (blue), more visible in the next image. This is chosen at random, though with only vertical borders as mountains.


At this point, the centre of each region is relocated, taking into consideration the path length to other regions (pulling it closer to the border) as well as how big of an area around it consists of its own tiles (pulling it away from the border). It is all about striking a balance - honestly, though, the centre is just moved to a hundred random positions, and the "best" one is chosen.


Roads are made as inaccurately as possible. They move through the midpoint of the border to the other region. The road is rendered with tiny random steps that on average move in the right direction. The diagonals often break, which is kind of on purpose.



Hey, now we can actually translate the above into terrain. Compared to above, we are looking at almost the same thing - borders are made a bit wider, and random parts of the terrain take on a lighter colour - depending on Perlin noise.


The forests are expanded depending on three values:

  • Perlin noise
  • How much path there is nearby (less forest)
  • How much forest-border there is nearby (more forest)



A heightmap is generated (independent of land/sea) by using Perlin noise rouned to the nearest integer. Wherever there is a difference, a ledge is generated - but only where there is neither forest, path, mountain or water. Not pictured is that there will also be ledges along the coast - that is, if terrain is higher than zero.


However, the ledges should not be insurmountable. Using Perlin noise, the ledges are split into three types - heavy ledges, soft ledges and invisible ledges.


Lakes are added. They are shaped as several overlapping circles of random sizes and positions, creating round, noncircular shapes.


Mountains are added with the same methodology.


Beaches are added along the coastline at areas of zero height.


Rivers are added by picking random inland points and finding the closest point of water. Sorry, they can actually flow uphill. Not that anyone would notice...


A darker sea blue is added, depending on:

  • How much land there is within a certain radius
  • Perlin noise



Everything's changed! Roads and ledges are connected, and the borders of forests, beaches and lakes are rounded. Mountains are given a distinct, high-contrast look.

The tileset I am using is designed around the idea of Wang-tiles. That is, tiles either like the paths that can connect to 4-directional neighbours of the same type; or tiles like the forests that have smooth borders depending on which of their 8-directional neighbours are of a different type. Mountains are basically a much more complicated version of the latter type.

I had to wait until now to do it, since the tiles of course would have to change if a big lake was smacked down next to them.

Also, another round of singleton trees are added, especially if close to actual forests.


A Bit More About Tiling

To give a bit of insight into how to do this, I find it easiest to use two grids. All we have seen so far has been renderings of TG[#], which held the index of the terrain type to be used at any given point. Going through each tile of these two grids, a third grid, SG[#], is filled in. This specifies which specific tile image is to be drawn.



For instance, a TG[#] value of 11 denotes slightly lighter grass. For each of these tiles, a script aptly named wang_2c() looks to see which of the surrounding tiles do not have TG[#] = 11, and then chooses a border-subimage for these.

This actually creates a lot of problems, because the tileset assumes that all patches of terrain, such as the lighter grass, must be at least both two tiles wide and two tiles high at all points. You cannot have a singleton patch or a thin line of grass . Since Perlin noise is what is used to determine where the light grass is, often generated patches do not have an acceptable shape. Another script runs through, trims small patches or expands others. Repeat for grass, beach, forest, mountain, water, etc. With roads and ledges, the opposite is the problem, because at no point can a ledge be two tiles across in both directions.

This is also a bit of a simplification, since, on top of TG[#] terrain and SG[#] ground sprite, I also use BG[#] to denote borders of the three types, as well as paths, and OG[#] to specify objects drawn on top of the SG[#] ground type. This whole thing is a bit complicated, which is mostly my own fault.



Finally, we can now add cities and towns around the region centres that we once spent so long making, so that the paths actually connect different towns with each-other. The size of each town is random, though one tile will hold the capital with castle walls and all.


Palm trees and rocks are added with a small random chance at any unoccupied tile - though preferring the beaches (generally, by random chance not so much in the above).


Finally, here is an image of the full region in all its glory, now showcasing slight colouration of different areas, recognisable from the regions we looked at in the beginning. As well, the towns now show off random town/region names, like:

  • Fervefield
  • Flelhouse
  • Borney
  • Chongeskpool
  • Regovine
  • Slokene
  • Leskcastle
  • Biveingvine
  • Slerwold
  • Mosemills
  • Pomabridge
Check out this post to see how the town names are generated.



EVALUATION

The Cozy Kingdoms, as a project, is a bit weird. Usually, the world maps which this project emulates, are designed as a surface covering some elements of gameplay. Usually, these towns would mean something, the paths would mean something, and the forests, if not just obstacles, would mean something, too!

This is not a game. It just looks like one.

I have had an incredible amount of problems dealing with how the tiles are to fit together, and still in every single rendering, there are several glitches or errors or what you would call them, tiles that do not fit together. That's not the fault of the tileset, and only sort of the fault of my algorithm - the problem is that I am using them for something they were not meant for. They were meant to be drawn with. Not to be generated.


The tileset is built with an assumption: within the 3x3 area around this tile, only one transition is happening. Either a ledge, a coast, between beach/grass or light/dark, a forest, a mountain, a river OR a path. Not all of them at once. Humans are good at this - creating nice terrain with spaced-out features. In fact, it comes naturally. Not so, when it comes to terrain, whether natural or algorithmic.

Throughout the project, I have kept asking myself, what scale is this, anyway? Is one tree on the map one tree in the world? A town of two houses on the map, surely in the world would be a hamlet of ten or twenty houses. Maps are representative. As are the Cozy Kingdoms - they represent something. What it is, though, I cannot say.

I guess the point is, as with the Dungeon Generator, I have created a game system divorced from its game. At least, this one is much more technically impressive and leads to more interesting results.

After this, I did, however, go on to work some more with region-tiles and experiments with continent-sized land-masses.



The tileset is another one by Pita, and can be bought at itch.io. It's pretty nice, offering more possibilities than the ones showcased here. Though it is sort of difficult to make work with autotiling since there are so many layers (height, terrain, objects) to combine. Or maybe I'm just not clever enough.

Comments

  1. This is beautiful

    ReplyDelete
  2. Cool stuff. Can't wait to auto-generate more kingdom's histories after getting this random cool continent map. xD

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. wow this is cool!!! can you share this code???

    ReplyDelete

Post a Comment