I recently added a city environment to Software Inc. You used to just start out in a forest with nothing more than a road crossing through and some trees. The city changes things up a bit, since a road is laid out when you start a new game and you slowly take it over by expanding your plot.
Laying down the road system
So let's start off with the algorithm for generating the road system. I basically divide the map somewhere around the middle, from the right to the left edge. I then end up with two sections, i divide these up somewhere around the middle, from the top to the bottom edge. I continue this process until I end up with sections that I think shouldn't become any smaller and, finally, I fill the final sections with a skyscraper, a parking lot or some trees. And that is it!
I've added some pseudo code at the end of the article.
Skyscrapers are generated procedurally by placing each window individually along the outside of the building. The picture below shows how a finished skyscraper looks in the Unity editor.
I have four quads(rectangles/planes), which map to each corner of the texture pictured below in their UV coordinates. The bottom right gray quad is used for the roof and the top and bottom row of the building. The bottom left quad is the door, placed in the bottom center of each side of the building. The top quads are windows. The top right quad is lit in a separate emission map, so only windows that are made from the top right quad light up at night.
Since I'm using one texture for the entire building, I gain a lot of performance by merging all the quads. This is done in Unity by using a CombineInstance object, which contains a mesh, coordinates, scale and orientation. If you have a list of combine instances, Unity can merge them into one mesh for you, which saves a lot of overhead for both the GPU and CPU. I do the same with trees.
I'm faced with a problem however. I want the lights to slowly turn on during the evening, but with the current system, my only option is turning on all lights at once, by enabling the emission texture. I then remembered that Unity 5 introduced the option of having several UV maps, so I decided to take all window quads through a function, which generates a random UV coordinate, and saves it in the window quad's second UV map, for all vertices. I wrote a custom shader which uses the randomly generated x coordinate to decide when to enable emission and the y coordinate to tint the emission color by sampling the gradient texture pictured below. Randomizing the tint a bit adds some life to the windows. It is very important that all of this is controlled by a shader, since having this logic for each window on the CPU is way too slow.
Now I just need a threshold to decide when windows should light up. You can picture this as if each window had it's own horizontal line crossing through the curve below, and every time the curve is above this line, the windows light up. In the curve pictured below, 0 is 00:00 and 1 is 23:59. You can see how all the lights turn on during the evening, then they fall down quickly and stop at 10%, which represents the night owls. Finally you can see a small jump in the morning, at around 0.25, when the early birds arrive.
Pseudo code for generating the roads
The algorithm for splitting an area into two areas, with a dividing road, is recursive.
It takes as input whether the split should be vertical and the horizontal/vertical range of the area to divide. The Rectangle object is initialized with x, y, width and height parameters.
DivideArea( vertical, rangeMin, rangeMax, borderMin, borderMax ) //Choose a dividing line range = ( rangeMax - rangeMin ) / 4 range = Random number between -range and range mid = ( rangeMin + rangeMax ) / 2 + range //Generate a rectangle that defines where the road is, always 1 wide if vertical r = Rectangle( mid, borderMin, 1, borderMax - borderMin ) else r = Rectangle( borderMin, mid, borderMax - borderMin, 1 ) Place a road along the rectangle r //If there is enough room in this area, split it further if ( borderMax - borderMin > 5 ) DivideArea( inverse vertical, borderMin, borderMax, rangeMin, mid ) DivideArea( inverse vertical, borderMin, borderMax, mid + 1, rangeMax ) else //If there is not enough room, save the areas that have been generated //These areas can then be filled with skyscrapers and parking lots if ( vertical ) save Rectangle( rangeMin, borderMin, mid - rangeMin, borderMax - borderMin ) save Rectangle( mid + 1, borderMin, rangeMax - mid - 1, borderMax - borderMin ) else save Rectangle( borderMin, rangeMin, borderMax - borderMin, mid - rangeMin ) save Rectangle( borderMin, mid + 1, borderMax - borderMin, rangeMax - mid - 1 )