Procedural Content Generation in Unity
Procedural content generation, or PCG, is a method for algorithmically creating complex or copious data from a set of predefined rules. Implementations of PCG range from the rendering of fractal patterns, to the synthesis of text to speech, to its varied employment in the video games industry. As a hobbyist game developer, I was especially interested in the application of PCG to the latter. The technique enables a developer to generate expansive and novel content that would otherwise be time-prohibitive for a team to design manually. My first inquiry was into using PCG to craft infinitely explorable worlds through 3D terrain generation. I also prototyped a flag construction algorithm to help identify generated factions such as those that might inhabit an infinite world. For both projects I used Unity3D, a publicly available game engine that can be given instructions through C# or its engine-specific languages UnityScript and Boo. C# was the most widely used and documented of the three and given my prior experience with Java it was my natural tool of choice.
My first plunge into procedural content was infinite world generation based on the terrain developed by Sebastian Lague. The algorithm starts by selecting a random number or “seed” that can be used to recreate the same world. Using that seed and Perlin noise, a 2D coordinate can be mapped to a continuous height-map that will determine the height of that 2D coordinate in 3D space. The output is continuous but smooth, so to provide a more naturalistic and varied appearance multiple layers of noise with different frequencies and amplitudes can be combined. This results in a landscape that has large mountainous features with rugged detail when viewed up close. To construct the actual 3D object, those height values are converted into vertices inside of the game environment that are then connected to form the faces that are rendered.
Of course, generating an infinite landscape would require infinite time, so the terrain is split up into a grid with each cell or “chunk” capable of independent construction. The algorithm only loads the chunks nearest to the player, so when the player changes position chunks can be loaded in or out of the game and stored for later use. This provides the shared benefit of only rendering relevant chunks and allowing each chunk to be constructed in parallel which hastens world generation. Further optimizations can be achieved by generating lower level of detail chunks and swapping them in for the detailed chunks that are more distant, but still rendered. This has little effect on visual fidelity since the chunks are too far away for detail to be visible but can greatly improve performance since the number of chunks is proportional to the visible distance squared. The final algorithm is one that loads concentric circles of decreasingly detailed chunks in and out of the world relative to the player’s changing position.
The next prototype was for a flag texture generator built for the purpose of identifying procedurally generated factions. In the context of a game engine, textures refer to the image that is placed over a 3D object or plane. The flags are constructed from a set of shape components that are assigned rules for how they can be combined. A component’s shape is defined by a sequence of points which are then filled by a color so that they image can scale to high resolutions, much like scalable vector graphics. For more complicated shapes that have concave corners, the shapes are split up into subcomponents of convex shapes. For instance, if the flag contains an n-pointed star, the star is subdivided into n triangular arms and the central polygon.
The flag is assembled by randomly selecting from types of components and colors in sequence. First it picks a backdrop such as solid or striped colors. It then selects decorations like stars, cantons and borders. With the list of components the flag can then be rendered one layer at a time with later layers overwriting shared pixels. To optimize the rasterization, the components know their location and bounds, allowing the flag painter to only loop through the subsection of the flag that contain the component’s pixels rather than the whole flag each time. This particularly improves performance for complicated shapes comprised of many subcomponents.
As much as the optimizations improved the performance of the flag generation, some of its features were eventually superseded by the introduction of true scalable vector graphics into Unity. Even with this new addition, components such as the n-pointed stars and polygons might still be useful to generate decorations not known at compile-time. Further optimizations to the assembly could be achieved by rendering sections of the flag or sets of layers in parallel then combining the results. Ultimately the flag project was an effective prototype and for a final implementation it would be faster to render the flag on the GPU by using a shader.