Mechanic #167 - PGC: Interior Design Regions |
| Posted: Mar 20, 2011
A technique for defining rooms in terms of spaces rather than the things that go in those spaces. |
This entry is a reprint of material published in the Blind Mapmaker on March 20, 2011.
There's basically two approaches to procedural design. The first is that you build something first and decide what it is later (i.e. Minecraft uses 3D Perlin noise to carve the world. Mountains are basically a natural occurrence of a specific noise phenomenon). Or you can decide what you want and build towards it (i.e. I want a mountain, this is how you build it, it will never be anything but a mountain). The bottom up approach works well for natural formations, like terrain, because such formations are based on a very similar principle of rule-based change over time. In fact, many things which change over time are a good fit for bottom up, such as a town which grows from a village to a metropolis or a baby which grows into a man. As such, bottom up design is well represented in the procedural game field.
However, the bottom up approach can not easily emulate a process which was never bottom up to begin with. The way a town grows over time happens very naturally, as new buildings are added to meet the increased needs of the city. However, a house doesn't change over time. It is designed by an architect. The only process a house features over time is entropy. An architect designs a house from the top down. You might be able to design a random process that could potentially end up designing a house, but in a way, you'd be designing backwards.
Interior design is largely a conscious decision. Granted, for a lot of people, it's as much entropy and laziness over time, but the basic principles are specific design choices. For instance, a room is generally designated from a high level. A bedroom, for example, is probably going to have a bed. You might have your 27" iMac in your room or in a home office, but you aren't going to have it in the bathroom. A fridge might be in a kitchen, while you might have a mini-fridge somewhere else.
Designing a map, that's the level we want to work at. We want to say that this room is a kitchen, and that should be the last thought we give it. We don't care what kind of microwave is going to be in this kitchen. We shouldn't even care if there is a microwave. We want to concern ourselves only with the high level room type. This is a chapel. This is a treasure room. This is a barracks. But at some point, we need to create the rules which tell the game the difference between a kitchen and a chapel.
Single Object Regions - a Bed |
Interior design can be thought of as a complex system of interacting parts that we manipulate through the set of objects and how they relate to each other. When an interior designer is trying to lay out a room, he is thinking of the furniture he has available and how it will work, functionally and aesthetically, with each other and with the floor plan of the room. While we could come up with a ruleset that could emulate that process, what we're going to do instead is to break it into two separate parts, and let a person design the hard part.
In roguelikes, they are called "vaults". They are basically prefabricated rooms that contain all the interesting stuff, as created by a designer. They are little islands of non-randomness in a sea of empty rooms and random enemy placement. I believe that prefabs are the answer. I think procedural design from the top level is less about creating things and more about combining things.
Fig 167.1 - Prefabs: Nifty, but not that reusable.
But prefabs have a pretty serious drawback. Prefabs can not change easily. However, I think I have a fairly simple solution to this that will allow us to rotate, scale, and randomize prefabricated room layouts easily and effectively. Once you've design a basic bedroom, for example, you can change it from a teenager's bedroom to a giant posh bachelor pad to tiny hole in the wall squatted in by a heroine addict. Design a church layout and you can use it for everything from tiny chapels to grand cathedrals.
Put simply, rather than laying out furniture, we are going to lay out space.
Fig 167.2 - A couple beds with different footprints.
I'm going to start with the most basic example. Say you have a place reserved for a bed. There are several beds that you can choose from. How big this space is will determine which beds you can use. A space that is 1x2 cells will only allow the twin bed and the bunk bed. The queen and the four poster bed are too large for that space. However, if you have a 2x2 cell space, all four beds will comfortable fit in that space - but the two large beds will fit more appropriately to the space allotted. Using sets, those lovable bastards, we can create a collection of all the possible BED-type furniture that can occupy this space. And this set changes based on the size of the space.
I've effectively move the problem from placing a particular bed with a particular footprint to placing a footprint from which I select a bed. It's a small, but important distinction. The footprint can change size.
Fig 167.3 - A 'sleeping' region with interior 'bed' and 'nightstand' regions.
Where this becomes interesting is that you can create regions with subregions. A bed region has a very simple purpose (to place a bed) and has constraints of how large or how small this footprint can be. Similarly, a nightstand region (to place a nightstand) might never grow or shrink at all, if all the nightstands had the same footprint. But you can create a "sleeping" region which encapsulates both the bed and nightstand regions. You can stretch or shrink this region, causing it's internal regions to grow or shrink likewise. The same region can be used for both a twin bed and a four poster bed.
Similarly, you can create a 'bedroom' region which contains the 'sleeping' region, along with 'storage' (for bookcases), 'working' (for computer desks), and 'clothing' (for dressers). This bedroom can be rotated, stretched, and squished and should produce a workable bedroom environment every time. Even better, by controlling the set of possible furniture, we can have a lot of control over what goes into each of these regions. So you can create a posh bedroom or an abandoned house bedroom simply by providing a different set of objects when the regions are turned into maps. You don't even need to declare regions as separate objects - just include the binary notation for picking objects based on keywords (i.e. FURNITURE & BED & NOT-BUNKBED would pick all bed furniture that isn't a bunk bed).
So the trick, now, is to figure out how to intelligently resize these regions.
Hey! Someone Already Solved This Problem! |
As it turns out, this is exactly the same problem that GUIs face when dealing with windows that can resize. Even more, the hierarchal region make up is exactly the same as the view/control hierarchy used by GUIs. Not only can we use a similar interface builder to design our prefabs, we could potentially even use one already made for making GUIs.
So how do the GUI makers solve this problem? Well, there's traditionally been a couple widely used approaches. At the most basic, all regions are resizable by default and have properties that define the minimum and maximum sizes, along with a preferred size that the GUI layout manager will attempt to adhere to.
Unfortunately, my experience in this area is not particularly detailed but I know that Java manages its layouts through the use of specific layout managers. A grid layout, for example, would assign each control view one or more cells on a grid. Resizing the window would resize the grid. A flow layout manager would place objects like text with word wrap, placing as many objects as it can along a horizontal line before moving down to the next line. A border layout would place objects against the four sides or in the center. In many cases, you would create a complex layout by combining layout managers in a particular hierarchy. For instance, the top level layout manager might be a border layout, but on the left side would be a grid layout manager that handled those controls.
Fig 167.4 - Apple's 'Springs and Struts' solution.
Apple does it a little bit differently. They have a program called Interface Builder in which you graphically lay out the components of your window. Then, for each view component, you specify the "springs and struts". That is, whether a particular width is variable in size or static. So, you can make the width of a view static, but the distance between it and the neighboring view variable and it would create a view that stays the same size horizontally, but is centered between its neighbors. Do the opposite, and the view would grow and shrink, maintaining the exact same distance from its neighbors.
I like Apple's approach, but I think games - especially ones that use tiles - have a smaller set of possible values. A bed's region might only grow one tile in only one direction, meaning that the springs and struts would be arguing over a single tile (or less). I think in cases like that, we might want to use the idea of size-optional regions. For instance, a second nightstand on the left of a bed will only appear if the region is at least 4 tiles wide. Similarly, we can use optional regions to add, for example, additional aisles to a chapel when it gets so large the pews become unwieldily.
Another possibility is to swap out regions at specific size ranges - a sort of mipmapping for room regions. Or, just to be really random, we can build multiple regions for the same space and then randomly choose one during the build phase.
In this musing, I basically did for interior design what I did when I created my Blueprint system. I basically broke the process into two parts. There's the generator which takes a set of input and produces a particular output. And then there is the sort of blueprint, which is a high level, abstract design which creates those inputs. So a blueprint is mastered to create a unique set of inputs that a generator works with. Similarly, this region system has an abstract representation of a physical space that can produce multiple unique physical spaces. Heck, it is a blueprint for creating blueprints.
As such, I think procedural generation works in these two steps. The generator, which does all the heavy lifting, is a machine which goes through a very specific process. The same inputs yield the same outputs. All the actual randomization happens to that input. So a preliminary stage is add to the pipeline to give the designers as much control (or as little) that they want or need to produce a desired outcome. It's becoming increasingly obvious to me that procedural design is going to be the product of three unique aspects: the set of objects (the domain), the high level description of possible inputs (the blueprint), and the process which takes those inputs and creates an in game object (the generator).
I was having a lot of difficult organizing my thoughts for this musing. The idea of using a GUI-like approach to interior layouts was something I'd been thinking about for a long time now, but every time I tried to write it down, I'd end up rambling on and on. As such, this musing represents only about half of the idea and I still need some time and consideration before I'm ready to write down the other half. However, I think the key realization I got from this is that I was essentially creation a sort of visual blueprint, something which doesn't just operate on sets and values but also on spatial relationships. With that, I think I can take what's left and make it into something even stronger than the initial half considered thoughts I had before.
|