Earlier this year I had the opportunity to experiment with the idea of creating a completely modular, procedurally built environment.
My goal was to implement procedurally generated elements in my level without sacrificing my design, aesthetic, and performance goals. With this in mind, I thought I would share a few various lessons that I learned over the duration of the project.
Even a perfect environment is filled with imperfection.
In the land of random numbers, variation is king. If you are already going to be placing your props based on randomly generated numbers, do yourself a favor and add some variation to your assets.
Being able to generate the kind of imperfection that you would find in a real-life location is one of the best ways to sell your environment.
Programming in variation parameters after you’ve already spent time setting up your existing logic is time-consuming. But, it is a vital step if you want to have any influence over the visual direction of your level. Failure to do this puts your environment at risk of feeling less like a lived-in space, and more like a room with random objects scattered around.
Think about the relationships between objects.
When you begin creating your scene, think about how each object is going to interact. Even if you are not going to be producing a bunch of unique objects, there is still a huge abundance of variation that can be expressed through each object and its relationship with its surrounding objects.
Something as basic as a coffee cup could be used to characterize an entire scene. Is it broken? Add some model variants! Is it from a souvenir shop in an interesting location? Add some texture variants!
Maybe it’s not even being used as a coffee cup, and it’s being used to hold up a screen or being used as an ashtray. Once you begin to look at objects as narrative building blocks instead of one-off props, you’ll make your life easier and hopefully make your environments better in the process.
Break down your environment’s construction early.
Regardless of your pipeline, generating an environment from scratch is complicated. But if you go through the process of breaking down your environment before you begin production, you’ll get a much better picture of what needs to be made. Figuring out what needs to be generated before you’ve spent hours UV-ing and Texturing unnecessary assets will save you time and headaches.
Think about all of the elements that an environment needs. Everything from the construction of the walls and floors down to the signage around the room or the props on the floor.
If you go through these thought processes early in your development, when it comes time to start assembling everything in your blueprint, you’ll be able to focus on tweaking your parameters to fit your overall mise-en-scene and less on making your environment believable.
So, now that I’ve given you some high-level tips about procedural world building, let’s look at specifically how I put together the tiles in my environment. All of the basic logic I implemented sits in a single blueprint tile and is controlled by a single data struct. All arcade cabinets, walls, props, lighting, and miscellaneous objects are spawned and placed based on a random seed dictated in the blueprint class.
All spawn offsets are stored in a struct which allows local manipulations to any generated asset. I ended up making two variants of my blueprint; One that propagates during runtime, and one that propagates during construction.
The tile that propagates during runtime is more expensive but allows for a ton of flexibility during gameplay. The construction tile propagates all its props at execution, and because of that has no effect on frame rate. Below you can see the tiles updating at runtime!
Below is a portion of the construction graph used for the procedural tile. Most of the graph on the left is defining all the values needed for generating each tile, while most of the graph on the right contains the logic for each element (ie: not spawning posters over windows or turning off lights if there are no fixtures).
As I began working on this environment, I came across a problem. In order to output the level of texture variation I wanted in my scene, I would have to spend an inordinate amount of time creating and assigning more material instances then I would have time to make.
The best solution I found was implementing functions into my materials that utilized the Object Position node.
The basic concept of the function just takes the object world position of whatever instanced mesh the material is assigned to and uses that value to randomly select a 1:1 UV square from a 5×5 tilesheet of textures. With the function made, I was able to add a huge amount of variance to my assets with a single material.
Above you can see the object variance function at work as I move around each object. On the right, you can see the screen texture and color of my arcade machine randomly generate. On the right, you can see the poster diffuse and edge wear change as it moves. With these two materials, I was able to populate my entire environment with just a handful of materials and textures.
Hopefully, this helped you put together your own procedurally generated environment!