Procedural City Generation

04/02/2011 - Jackson Fields

Haste is set in a city with incredibly tall buildings. Because of the height of these buildings, the alleys between them are permanently blocked in shadow and have seen neglect and decay over the years. To get a large playable environment like this without overloading the artists, we focused on procedurally generating the city, particularly looking at the different playable aspects of the city:

  • Building Placement
  • Slums Between the Buildings
  • Inside the Buildings (In Progress)

Building Placement

We wanted the city to have a very sterile and inorganic feel to it so we partitioned it in grids. The City itself is split through the middle by a river, with a north and south side, then split again by the main roadways dividing the north side in half parallel to the river, and dividing both sides perpendicular to the river. The roadways converge, framing the tallest building in Haste: the ultimate objective.

These roadways and the bounds of the north and south sides of the city are mostly hardcoded to be in similar locations for gameplay purposes, but the rest of the city is partitioned procedurally by recursively subdividing the regions along the x and z axes on a random number of subdivisions until the region is small enough to justify a building. When the building is placed, a random height is assigned with some buildings biased to be very high and others biased to be a mid-height (while all continuing to tower over the terrain and not exceed the height of the tallest building).

Slums

In contrast to the city, we wanted the slums to have an organic feel - growing between the alleys and taking up an irregularly partitioned mess in all axes. Using the same bounds as the city plots, we filled the regions with small rectangular blocks representing individual houses. To give an organic feel, we displaced each block a small random value on their x and z axes, added a small random height disparity to each block, and a small rotation around the vertical axis.

Applying the same logic to a second layer (with fewer buildings) gave a nice undulating feeling to the slums. We wanted a third layer, with a different building type, to be on top of this second layer. Instead of applying the same logic (which would invariably result in floating buildings) we randomly selected buildings on the second layer to act as supports for this third layer and added these buildings on top with the same rotation as the supporting building.

The large number of buildings required for the slum layer had performance issues when rendering. To counter this, we first cut out any building that was entirely occluded by the city buildings by testing for intersections on the slum vertices with the building plots. Further performance measures are described in our post on Level of Detail.



To keep the map consistent, we make sure we generate and store a seed at loadtime. When portions of the map are disposed and regenerated, they are regenerated based on the preliminary seed.