Monday, July 31, 2023

Stacked and Multi-Family Houses

I've recently put a lot of effort into improving building interiors by adding new types of objects and other details. Now it's time to revisit exterior geometry and room assignment, in particular for houses. I wanted more variety in house geometry, so I added a new type of house that has a smaller cube stacked on top of a larger cube. Then I made some of the larger houses multi-family homes split by floor. I'll describe these two additions below.


Stacked Houses

Houses initially came in two general floorplans, a rectangle and an L-shape. The L-shaped houses were formed from two rectangles placed next to each other with possibly different heights. This formed either an L-shaped floorplan, an L-shaped exterior vertical profile in cases where heights were different, or both. Each floor had the same floorplan so that a single set of vertical walls extended through all floors, and a set of stairs connecting them. Some houses also had rectangular basements, and some of those had a network of extended basement hallways as explained in this blog post. The combination of different heights, aspect ratios, roof styles, garages, sheds, porches, chimneys, etc. added a lot of variety to these otherwise simple shapes. However, I wanted to add even more house variety.

I've already created some styles of office buildings that have vertically stacked sections, where the upper stacks usually had a smaller footprint. There are some overhangs, but these are rare. This same stacking approach can be applied to houses, allowing me to place a smaller cube on top of a larger cube. Both the upper and lower cubes can have multiple floors. I added a 50% chance that each of the four sides of the upper cube is shifted inward to create a smaller footprint. If none of the four sides are moved, we simply end up with a single cube that has two different interior wall floorplans for the upper vs. lower levels. This is fine too as it adds variety to house interiors.

Here are some examples of houses created with this approach. These next three screenshots show houses with a combination of one, two, and three inset sides.

Two stacked part houses: Foreground house has a tall and narrow upper part; Background house has a small sloped roof section over one side of the lower part.

Two more stacked part houses next to each other, viewed through the power lines. Note that all houses in the same block use consistent exterior materials and styles.

A house with a wide bottom floor with garage, and a narrower upper part.

This was quite a bit of work to implement. I can't remember everything I changed, but it must have been over a dozen blocks of code. There are some obvious additions such as the custom sloped and overhanging roof quads on the lower cubes of the houses.

Note that the original version of these houses had steeper roofs on the lower parts, but I couldn't get the windows to look right. Removing the windows from the inset walls on the floor above the wider bottom parts created windowless rooms and caused other problems. While I did prefer the exterior look of theses houses, I just couldn't get the interior to be correct in enough cases.

The attic code had to be modified to only add attics to the upper parts of these houses. That change was relatively easy. What really got messy was adding stairs. Keep in mind that stairs are placed after adding walls and interior doorways. In some situations it was possible to add a single stairwell that extended all the way from the ground floor to the top floor without hitting any obstacles or blocking any doors. In other cases there was simply no valid location to add stairs, so stairs had to be split into two or three sections that were placed in different rooms. For example, the house in the foreground of that first screenshot has three sets of stairs: one connecting the bottom two floors, a second connecting the top three floors, and a third connecting the bottom part to the top part. Oh, and there's also a fourth set of stairs connecting the ground floor to the basement. The logic to place all of these stairs is quite complex and was difficult to implement. Updating the building AI (zombie) logic to navigate using these stairs also took some effort to get right. I actually had code in place to handle multiple sets of stairs, but it was broken in a way that wasn't triggered by the multiple sets of stairs that already existed in office buildings.


Multi-Family Houses

3DWorld's generated houses are typically smaller than office buildings, both in height and in their terrain footprint. Of course there can still be large houses. This usually happens with single cube houses rather than L-shaped houses, because the cube can fill the entire lot with no space cut out of it. Sometimes a house is generated at the max height of five floors and also near the max width in both X and Y. These are unrealistically large for a normal single family home.

For example, we have this monster house shown below. It has five floors with 17 rooms per floor, for 85 total rooms. There are 38 bedrooms and 8 bathrooms. My entire living family tree can stay in this house!

This house is too large for a single family, so I split it by floor into a five family apartment building. You can see the vents from all of the kitchens on the roof.

The solution is to split houses that are both wide and tall into multi-family homes. These can be anywhere from duplexes to small apartment buildings. The simplest solution was to assign one family to each floor and keep the same floorplan for each level. Here I mean the same walls and doors. The room assignments can be different, and they generally should be different, at least for the ground floor entry area.

This required significant changes. The most obvious change is that each individual apartment should have at least one bedroom and one bathroom, plus a living room, dining room, and kitchen when possible. This means that living rooms, dining rooms, and kitchens are no longer constrained to the first floor. Of course only the ground floor resident will have access to the basement (if there is one), but that may be okay for now. Room assignment is quite different, with some new constraints.

The second change was related to the front door placement. Regular houses usually have the front door connecting to either a small entryway room, or to the living room. However, it doesn't make sense in multi-family houses that someone should walk through the ground floor resident's living room to get to their upper floor apartment. No, we want the front door to connect to the room with stairs instead. If this isn't possible because the stairs room has no exterior wall, then the next best room for the front door is a hallway connected to the stairs room. Since hallways cross the entire part, and cube-shaped buildings only have a single part, we can place the door at either end of the hallway. Most of these larger houses already have a hallway, and most hallways connect to the room with the stairs. It should be rare to have stairs in a room that's neither along an exterior wall nor connected to a hallway. So far I haven't seen a house where this approach failed.

The above door assignment algorithm still has some flaws. In some cases the room with the stairs is assigned a function such as a living room, or it's not possible to reach every room in the house without crossing through the public stairs area. In fact the stairs room often has multiple doors, rather than the single apartment front door we would expect. I think I would have to go back to the floorplanning step to fix this. What we would really need to solve this is a dedicated entry + stairs room that connects to the front door and has exactly one door leading out to the rooms on each floor. Or we could possibly have an exterior stairwell with front doors above the ground floor. That could work, but would require significant code changes.

The third change I had to make is related to family names shown when the player enters the house. I kept the main generated last time for the ground floor tenant. This is what's shown on the sign above the front door (if present) and what's printed when the player enters the house from an exterior door. I used the hash of this name as a random number seed to generate different family names for the other floors above the ground floor. This is shown when the player uses the stairs to move to a different floor. The building's address and other string properties remain unchanged.

This system isn't perfect. Sometimes it's not possible to assign the required room types to each floor. Sometimes an exterior door can't lead to the room with stairs or a hallway connected to stairs. There are other ways of creating multi-family houses that avoid these problems, at the cost of introducing a different set of problems. For example, another approach I considered was to create duplexes by placing either two mirrored houses next to each other, or two symmetric parts (cubes) of the same house next to each other. This would make it easier to ensure the room assignment and connectivity requirements were met. Each part of the house could have its own front door without needing to connect through stairs.

This solution is more difficult to implement because it requires changing the exterior geometry and wall floorplan to work with duplexes. I would need to add logic to handle shared walls between units that doesn't assign windows or doors to these walls. There's also no easy way to guarantee symmetry for the two houses in the current procedural generation system. It's unclear how much better this would work and what other problems might arise. I decided it's too much effort to experiment with this approach since it's not an obvious improvement. I may change my mind about this later, so there may be a future post on this topic.

Saturday, July 22, 2023

Adding Building Interior Items

This past month I've been busy with summer related tasks and I haven't had as much time to work on 3DWorld. Much of my 3DWorld development time was spent fixing bugs in preparation for, and as a result of, demos I made to various relatives who visited me.

The new features I've added for July are fire extinguishers, pizza boxes, and shirts. I'll describe each of these in the sections below, along with showing some screenshots.

Fire Extinguishers

Interactive objects are the most fun to add. I don't remember how I came up with the idea of adding fire extinguishers, but I'm glad I did. They were both relatively easy to add and serve a gameplay purpose. They can be found in two places: hanging on the walls on each floor in office building hallways, and inside some kitchen cabinets in houses. Fire extinguishers are placed as three separate objects in office buildings: The sign, the holder, and the extinguisher itself. The player can take the sign and extinguisher but not the holder.

Fire extinguishers can be sprayed for up to a few seconds until they're empty. Empty extinguishers serve no purpose, but they can still be picked up and put down on the floor or the top of another building object. (Now that I think of it, I should have them squish spiders and cockroaches like books.) Spraying them produces a stream of smoke-like particles affected by low gravity that spread out until they collide with a floor, wall, or other object. These particles have two effects. First, they cause nearby zombies to temporarily retreat, similar to hitting them with basketballs and soccer balls. Fire extinguishers are more effective at keeping zombies away, but they have a limited number of uses. I think they can be used about five times on a zombie before they're empty.

The second use is to put out fires that were started on rugs by sparks from broken basement lights. This isn't very helpful for the player at the moment since these fires will go out on their own after a few seconds. However, if I decide to add fire propagation and increased fire damage later, this could be an important gameplay mechanic.

Fire extinguishers are drawn as 3D models and oriented so that the spray nozzle is facing away from the player when being held. They're put down with the same orientation/rotation the player was most recently holding them in.

Fire extinguisher attached to a holder hanging on the wall of an office building hallway. Notice the sign pointing to it near the top of the image.

Fire extinguisher removed from the holder by the player an in the process of being sprayed.

Fire extinguisher placed on a table by the player.

Pizza Boxes

My mother came up with this idea, as well as the idea to add shirts. I found a pizza box lid texture and slapped it onto the top of a flat white cube to create a pizza box. Pizza boxes are procedurally placed on surface such as tables, desks, and counters. They start out closed. The player can use the action key to open and close the box.

When the box is open the pizza is visible and can be taken by the player. If the player's health is low, pizza is eaten with a special chewing sound and adds +50 health. Otherwise it goes into the player's inventory, and is added to both their accumulated money and carry weight. Pizza boxes are therefore the first food/non-drink consumable in the game. I plan to add more types of food in the future, and assign them different gameplay effects.

Closed pizza box, ready to be picked up or opened by the player.

Opened pizza box showing the jalapeno pepperoni pizza inside.

Shirts

I had already added a T-shirt image that was originally used for shirts hanging in closets. Then I replaced the shirts with 3D models, and the image went unused. Why waste an image that's already in the git repo (online version control system); let's reuse it for non-closet objects. Shirts now can be found in two places: on the floors of bedrooms, and in the larger sized dresser drawers. (I don't add them to smaller drawers because they're always spread out rather then being folded.)

Shirts don't have a gameplay use, they're simply low-value, low-weight collectable inventory items. I did have to put some extra effort into making spiders, rats, and cockroaches walk over shirts rather than around or through them. I also had to add a special case so that rolling soccer balls and basketballs don't collide with and get stuck on "floor shirts".

A T-shirt left on the bedroom floor by some lazy person.
The two top dresser drawers have been opened, showing the T-shirts inside. The player's reflection can be seen in the mirror.

Bonus Image

I'll end this post with a bug I found in the algorithm that places stairs to connect different floors of houses. Well, technically it may be more of a closet placement bug. In any case, the problem was that I never wrote the code to handle intersections between stairs and closets. Closets are technically room objects, but they have their own walls and doors and are therefore somewhat special to place in a building.

A closet with stairs going to an upper floor hidden inside it. Definitely not a bug in the stairs placement code!
This bug was quickly fixed. I'm not sure how long it has been around, but this is certainly the first time I've noticed it. I think it's because I recently added the ability for stairs placement to cut through walls of houses when connecting multiple floors with different floorplans. Ah, I don't think I've written a post on the new house floorplans yet. That will have to wait until next time.