The ProcessThis is the process that the interior generator goes through, in sequence:
1. The type of floorplan is based on an enum, and the size of the overall floorplan in width and height is encoded into the enum name for the sake of brevity. Note that these sizes are inflated compared to the actual size that the building appears to be on the outside -- common to most RPG games. That gives us much more interesting interiors, and exteriors that we can actually see the whole of.
2. A random seed is passed to the FillMap method, along with the InteriorGenerationStyle enum. Given those same two inputs, and the same desired floor (Z Index), the result will always give you the same map, which is helpful.
3. The first mapgen step is to set all of the InteriorGenerationStyle-related variables, which includes defining the broad profile of the building's outer walls to match the exterior. This also places the exterior doors or ladders. For side doors, since you can't see those in the exterior graphics in the game, those can be placed at any Y offset along each wall.
4. There are a couple of different room templates that I have defined in a similar style to what LaPier was doing. Which are used varies by InteriorGenerationStyle as well, and I have four different possible definition types. W is "provisional wall," C is "provisional blocking ceiling," b is "provisional non-blocking ceiling," period is "generic floor," and blank space is "hallway." At this stage in the process, that's all we need. I'm not defining room types, and I'm also not defining possible door-points.
5. For buildings that have more than one floor, it decides how many floors they will have between min/max values for both the upper and lower bounds. It then clamps the requested floor to the actual floor bounds -- so if you request floor 0 and the max floor is -1, you get floor -1 as your topmost floor (so, this would be an underground building, like the ice age hatch).
6. Now we actually cut staircases between the floors. Starting at Clamp(0) floor, it starts deciding where to place floors, and then it recursively moves toward the floor you actually requested, collision-detecting and stripping out two-floors-away staircases as it goes. The result is that you wind up with staircases that are always lined up perfectly and never sitting on top of one another. Beyond that it's a little hard to explain, so I'll leave the code to speak for itself on how exactly I made that work, when the code is released.
7. Now that we have the outer shape of the building in place, the doors or ladders to the outside, and the interior staircases, it's time to actually start filling in walls and rooms and hallways. To do this, I use LaPier's method with a few modifications: I don't intentionally leave space between each room, and when there is leftover space I distribute it completely randomly in the X and Y bounds of each row. I also randomize the list of rooms per row after all of them have been picked, so that there isn't a trend of having smaller rooms to the right of the building.
8. Step #7 happens in isolation, in a completely different code structure from the actual building I've been building so far. What I wind up with is an identically-sized LaPier-method overlay of rooms with no external shaping that I can plop down on the staircases, the doors, and the exterior walls that I've defined so far in steps 1-6. During this step, I
never overwrite the actual existing building structure that we've defined in those first six steps. The staircases, the doors, and the exterior walls cause all sorts of disruptions to the LaPier-style rooms, and that's completely okay as that actually adds substantially to the variance once we combine the two.
9. Now I'm done with the LaPier method, and I have a perfect external shape, random staircases, doors to the outside, and some pretty interesting rooms layered about -- and filler hallways naturally develop based on how I designed the LaPier room templates, too. However, there are no internal doors between any of the rooms or hallways, and all sorts of things are inacessible and invalid. The perspective of the walls is munged up in many places, because the various room templates haven't been blended together, they've just been set next to one another. When working with rooms with perspective, that's deadly to the realism.
9.a. From here on out, we enter a master method called CleanUpInteriors, which is what I spent the bulk of this past week creating. It has 22 steps that it goes through with the "provisional" walls and floors and ceilings, and then it converts all the provisionals to actuals, and then it goes through a further 30 distinct cleanup steps.
9.b. The very first thing that we do in CleanUpInteriors is 8 cleanup steps that get rid of the most obvious problems: walls that are incredibly high or short, ceilings that are placed incorrectly, and so on.
9.c. Next, and optionally, the game looks at all of the defined rooms (any contiguous areas of GenericFloor), and it cuts random doorways between a room and anything that's not part of the room, in a random direction. Later we'll definitely be more precise about making sure everything is connected in a valid way, but this is a handy way to disrupt things early. Some InteriorGenerationStyles use it, others turn it off
9.d. Now we go through another 11 of those cleanup steps, some of them quite lengthy. The goal here is to get things prepped to the point that the overall structure of what is closed off and what is open is now pretty set. We need to know which tiles the player is allowed to traverse in some fashion in order for our next step to work right.
9.e. Having the data on traversable tiles in hand, now I use a pathfinding algorithm to find all the grouped traversable tiles that are unable to be traversed to one another. For buildings with two exterior doors, I actually use a pathfinding trick that allows for each door to have a separate set of rooms and hallways that does not connect to the other. That doesn't always happen, but it can, which is a neat variance. Sometimes one of the side doors just opens into what amounts to a coat closet, or sometimes it's just half the building, etc.
At any rate, once I have the data on where connections are missing, I add in randomly-sighted doors such that everything is connected. It makes sure that a valid connection will result from each door so that it doesn't put in excess doors, but where it places that door out of the pool of valid places per contiguous traversable tile set is completely randomized.
9.f. Now we have a fully-traversable interior floorplan, but there are still a lot of details that are messed up, and the cutting of the new doors (and in some cases, hallways) has caused some new minor disruptions. So it's time for one last cleanup step on the provisional tiles, then the conversion to the non-provisional tiles, and then the final 30 cleanup steps.
9.g. Some of these final 30 cleanup steps added in chasms (for rooms that are completely inaccessible by any means for some reason), and courtyards (for rooms that aren't directly accessible via an interior door or hallway, but instead require going under the edge of a ceiling to get into them. The chasms and courtyards are fanciful and just part of the AVWW theme; you could easily leave them as hallways or GenericFloor, at your preference, if you're using this algorithm in another game.
10. Now the game has a fully-defined, polished set of interior rooms, halls, chasms, and courtyards, inside an outer structure, with staircases up and down and exits to the outside on a single floor out of potentially many. FUTURE STEP: All of the rooms are now nicely defined, distinct from the hallways, based on being either GenericFloor groups or Hallway groups. These GenericFloor groups can instead be changed into more specific floor types, such as Bedroom, Kitchen, Bathroom, Office, etc, etc, etc.
This is actually a comparably straightforward step, although I'll have to be careful not to put bedrooms leading into the kitchen, or bathrooms without any doors. Anyway, I haven't don this step yet, which is one of the main reasons I'm not yet releasing the code (the other reason being that the numerous cleanup steps need more testing and tuning time, although at this stage they're looking pretty solid).
Post-ProcessOnce the above process is completed, you have a hard-won array of tiles that define an interior floorplan. It's devoid of any objects, but it lays out the rooms and doors and stairs and all that good stuff. It also doesn't really lay out anything specific about the walls or ceilings, etc.
So the game itself has to decide when to draw a front-left-corner ceiling tile, or things of that nature. These were already things I'd implemented in AVWW when I was creating floorplans by hand in xml, and it's a really straightforward bit of logic (if there is ceiling to your top and right, but not your bottom or left, draw the front-left-corner ceiling, etc).
Also, the game itself will have to go through and actually put in things like tables and beds, ovens and debris, and so on. But since the algorithm above (with the later addition of step 10) will determine the function of rooms (which tiles belong to the kitchen, etc), that makes for nicely encapsulated, relatively straightforward sub-algorithms.
This algorithm also doesn't do any setting of tilesets -- to choose what the walls, ceilings, or floors look like. That's yet another thing the game has to do, and something I'd already coded back in March. That stuff is trivial compared to actually making the floorplans, I can tell you. But the cool thing is that this represents nesting within nesting within nesting (actually deeper than that, in reality). So the floorplans are quite unique when just looked at on their own, but when you combine those with different entity-seeding logic in each room and hall, with different tilesets, with different enemy populations, and so on -- you get a pretty insane amount of variance.
(Continued Below)