Sunday, March 7, 2021

Procedural Buildings: AI and Player Interaction

I'm in the process of working on my game where the player steals items from houses and office buildings that are infested by zombies. The goal is to have both the player and the zombies interact with objects inside the buildings as part of gameplay. This includes entities such as room lights, doors, pickup objects, items on the floor, drawers, etc. Most of the items placed in rooms have been static up until now, but I want to make almost every object be interactive. That means the player must be able to pick up, drop, move, open, close, etc. the various room items. AI People inside buildings will also interact more with objects, even if that only means better object collisions and physics.

Here are some of the interactions I'm working on. Sorry, I don't have too many pictures for these. This is mostly a list of concepts and ideas I'm working on implementing for gameplay. I'll probably post some YouTube videos of early gameplay a bit later. At this point I have the high level gameplay system written, it's just a matter of filling in all the details.

Lights

Every room and hallway has one or more light placed somewhere on the ceiling. In addition, some bedrooms have lamps placed on nightstands and dressers. I recently added small lights to the ceilings of some of the walk-in type closets that the player can enter. Each of these lights can be toggled on and off by the player. This feature has been around for a few months now. It allows players to get a better view of a room by turning on the lights, while at the same time having many of the room lights off by default to improve framerate. (Lights are expensive for large buildings containing a thousand rooms.)

I've added the ability for AI people in buildings to turns lights on and off as well. AIs will turn the light on as they enter a room, and turn it back off again when they exit if no one else is in the room. This only applies to people in buildings near the player to avoid unnecessary toggling of distant room lights.

I also added logic to query whether or not the player is in a lit room that takes into account shadow casters. I plan to use this at some point to allow the player to hide from enemies in the darkness. The system is mostly implemented, but I haven't enabled it yet.

Doors

The player can open and close interior room and closet doors with a key press. Exterior building doors automatically open and close to admit the player. I've added logic for the AI to open doors, which is controlled by a config file parameter. If the AI can't open doors, the path finding algorithm will attempt to navigate around closed doors if possible. I had to rewrite the door drawing code to allow doors to have different open vs. closed states on each floor of the building, rather than having a single door that extends down between all floors. The rewrite allows doors to be drawn sparsely based on player location and view direction, which was required to handle millions of doors across all buildings in the scene.

I added the concept of locked doors a few days ago. The user defines the probability of doors being closed and/or locked in the config file. Locked doors can only be opened after the player finds a key, which are hidden around the building in drawers. The same key can be used for every door of that building, but won't work on doors in another building. AI people have similar flags for whether or not they have keys and can open locked doors. If the player has a key but the AI doesn't, it's possible to lock the AI in a room. The key + locked doors system will be important for later gameplay.

Pickup Items

I added a player inventory and made almost every placed item something that can be picked up. There's an inventory weight limit, so some of the larger items such as the couch and refrigerator are too heavy to pick up and carry. But the player can pick up most smaller furniture, fixtures, appliances, items on tables, etc. This includes fun things like toilets, sinks, stoves, nightstands, rugs, pictures, potted plants, TVs, chairs, ceiling lights, exit signs, men's/women's restroom signs, etc. Every item has a weight and a monetary value, and some items have special effects. The player's goal is to steal the maximum value of items and make it out of the building alive with their loot. Heavy items will slow the player down while they're being carried.

Some objects must be picked up in multiple parts. For example:

  • Beds: take pillows, then sheets, then mattress; bed frame is too heavy to pick up
  • Potted plants: take plant, then soil, then pot
  • Pictures: take picture, then frame
  • Tables: must take everything off the table before the table itself can be picked up
  • Wine Racks: must individually remove every bottle of wine before taking the wine rack

Bottles function as power-ups/special effects, depending on what type of drink they are. Empty bottles have no effect. Coke and water heal the player. Beer and wine make the player drunk, which enables some neat scree space effects like drunken wave, double vision, and blurry vision. [I would like to thank my daughter Kate for input on these effects!] This will all be important for later gameplay. I plan to add other drinks such as poisons that hurt the player in some way (damage, blindness, slow speed, etc.)

I had some difficulty handling objects on shelves and in closets because they're not actual C++ objects stored with the building. They're only drawn as part of the shelves and closets when they're visible to the player, so they're all temporaries created in the drawing code. It took quite a bit of work to allow the player to pick these objects up, and to have the system remember what was taken if the player walked away and came back later. I didn't want to allow the player to pick up a valuable item from the same shelf or closet more than once, even if they walked to the other side of the map and back. What I did was to "expand" these storage containers into real objects when the player attempted to use the "pick up" key on them so that they can be properly handled. This required quite a bit of code reorganization. It also has some runtime cost because expanding an object requires rebuilding the vertex data for the entire room detail geometry of that building.

Look at all these great items in here for me to steal. Unfortunately, I don't have the carrying capacity to take them all, and this guy isn't happy to see me here.

Drawers

I need a way to distract players from zombies so that they're caught off guard. What better way than encouraging players to spend time searching through desk, dresser, and nightstand drawers for keys and other valuables. Each drawer can be individually clicked on to open and close, and can contain a single item. The player can decide to pick up the item to add to their inventory. Some drawers are empty, some contain boxes, and on rare occasions a drawer contains a key. I plan to eventually add other items to drawers such as bottles, books, papers, pens, pencils, cell phones, wallets, and money. At some point I may also allow the player to open kitchen cabinet doors.

The room key can be found in a dresser, desk, or nightstand drawer. Other drawers are empty or contain boxes.

Dynamic Items

Okay, the player can now pick items up. What about dropping them? Simple spheres are the easiest type of shape for implementing collision detection and physics. I started by adding soccer balls and basketballs to some bedrooms. These can be picked up, carried around by the player, and dropped. I later decided that it was more fun for the player to throw balls rather than dropping them. The player can also kick them while on the floor, or in mid-air, if they get close enough. AI people walking around the building will also kick any balls they happen to run into.

I've implemented proper collision detection and physics for balls, including gravity, momentum, static and kinetic friction, and elasticity. Most of this was adapted from my other/existing gameplay framework, though it's all new code. Balls will roll around on the floor (including proper texture rotation), bounce off of room objects, bounce down the stairs, and even roll under beds and possibly get stuck. They cast and receive dynamic shadows from room lights.

Balls contribute to gameplay by emitting sound which alerts zombies. They can either be helpful when thrown as a distraction/diversion, or harmful when the player accidentally kicks one. I've scattered them around in the middle of floors to maximize the chance of the player accidentally running into a ball.

Soccer balls and basketballs can be picked up, kicked, and thrown by the player. The basketball has rolled under the bed.

Sounds

I've added lots of new sounds for building gameplay mode. Everything discussed here has sound: switching on and off lights (clicks), opening and closing doors and drawers, picking up objects, throwing and kicking balls, bouncing of balls, drinking from bottles, and even walking on the floor. I've included damage sound effects for the player and various zombie moans for people in buildings.

Sound is important for gameplay. Zombies can't see through walls, but they can hear noise made by the player. Louder or repetitive sounds can be heard from further distances and are more effective at attracting zombies from other parts of the building. It's important for the player to try to be as quiet as possible while searching the house for valuables.

Another strategy is to make a lot of noise in one part of a house to attract zombies, then sneak to the other end of the house to do your searching. Or you can place some balls near a doorway and hope a zombie runs into them, making noise and attracting other zombies (away from you). If you're feeling bold you can close some doors so that zombies must open them to enter rooms. This can either be used to distract other zombies, or alert you to a nearby zombie entering the room that you're in while you're searching. If you have a key you can even lock a zombie out of the room, assuming it doesn't have a key.

8 comments:

  1. Cool idea for having everything interactable! Are you going to add large item dragging? Like, barricading doors by dragging a fridge in front of it? That seems like a very zombie-movie kind of thing to do.

    ReplyDelete
    Replies
    1. I never thought about item dragging. I didn't add a way for the player to drop large items because I was worried it would be too easy to get stuck. I think it would be easier to support dragging items in one direction along a wall. I just need to figure out what key I can assign that to. All of the reachable keys are in use for other tasks.

      Delete
    2. I'm not sure what the UI should be for dragging. I can add a key for push and/or pull, but what about sliding something to the side? It seems like I need some sort of click and drag for objects. It's also going to be a huge amount of work to handle objects like doors and drawers that would intersect with the new object's location when they're opened. Dragging an object also requires rebuilding all of the collision structures and recomputing the AI navigation graph. I don't think I can do that every frame while the object is being dragged. I probably have to do it once post drag.

      Delete
  2. I'd think holding the "pick up item" key on large objects, and then having the objects move with the player, kind of like the "magnesis" power in Breath of the Wild, but just sliding along the ground.

    Yes, I'd imagine rebuilding the navigation graph once when the drag is started (to remove the dragged item), and again when the drag is completed, would be enough. Yeah, right now you're ensuring no drawer/door collisions by carefully placing items, right? The way I'd do it is add an "access volume" bounding box to anything with operable access, and then preventing access when there is something in the way. Could also be used to ensure the player is close enough to interact with the item. You can't open a door from across the room. If you require exclusive access to the volume for operation, then the player and a zombie on both sides of the door would prevent the door from opening. Could be a good way to avoid the player walking straight into a zombie on the other side of the door, and also automatically hold the door closed while you're standing next to it. Zombies hiding in drawers?

    ReplyDelete
    Replies
    1. I can try out the method of holding down the interact key. I'm not sure how that would work, in particular when the player is moving a large object and there are smaller objects int the way. Should the smaller objects stop the movement of the larger object, or should they be pushed as well? There's also the question of what happens when the player tries to use a held object to push zombies away or block them. It gets very complex. I never quite figured this out. I suppose as long as the player can repeatedly move an item without getting it stuck on something, they can never trap themselves in a room.

      I use various approaches to ensure no two objects can intersect. For doors I calculate the volume of the door's path from open to closed, and check that against objects that have already been placed. In some situations it's legal to move other (smaller) objects out of the way to make room for doors. In other cases, the door is restricted to only open partway. If it's too badly blocked then I remove the door entirely (for cabinets). In the case of two cabinet doors facing each other on an inside corner, the player can only have one of them open at a time to avoid having them intersect. The logic is very special cased and complex, but as far as I know it always works correctly.

      Some types of items such as the refrigerator have a "clearance" area in front of them to make sure the door can open. No other "fixed" objects can be placed here, but the player can drop objects there. In the case of moving furniture, it won't be legal to place a table directly against the refrigerator, but it would be legal for the player to move it there. That means the placement and movement logic need to use different rules.

      I think doors are easier because I can adjust the open_amount value of a door or flag it as locked when it's blocked. The AI understands these flags. The difficult case is when the door is already open. Maybe the player has to close the door before moving an item in front of it, otherwise the door acts as an unpushable collision object? But what happens if a zombie tries to open a door while the player is pushing an object in front of it? Updating the collision data at the end of the push operation isn't enough here, it has to be done dynamically as the player is pushing.

      I'm sure this is all solvable, but the special cases may make this a huge mess. I wonder if any existing games actually get this sort of mechanic 100% correct?

      Delete
    2. Well, "correct" is a slippery term. I know Deus Ex : Human Revolution had draggable large objects, though I forget exactly how they were implemented.
      I'd be happy if any conflict with doors resulted in the door breaking. If you want to barricade, do it in advance!

      Delete
    3. This comment has been removed by the author.

      Delete
    4. I made some large furniture and appliances pushable by the player. I added a 'push' key that adds an impulse to the object rather than having it be dragged by the mouse, which is easier and only requires updating the state for the final position. Then I realized it was possible for the player to block themself in a room, so I had to add a 'pull' operation as well. The AI seem to behave reasonably without having to update the navigation graph because they can already adapt to players locking a door on them. They don't know the door is blocked ahead of time and will walk up to it, then stop, then walk away. They seem to eventually find a path around it, if one exists.

      Delete