Sunday, September 26, 2010

Break down the doors

This week I had to deal with kicking in doors and “swapping sectors”. Ever played Resident Evil 4 or 5? Then you know there are two ways to open a door; doing it gently or by force. Pressing the “open” button twice will let you kick or slam a door open. I was in the need of something similar. Normally you would open the doors as a gentlemen. Not just because the game isn’t that rushed, the noise will also attract enemies. But since the movie contains a chase scene, it would look quite stupid when the hero carefully opens a door after running for his life for 60 meters.

Doors in the engine are objects like any other, but with a couple of special features. The model itself could be a door of course, but also any other model. Having Anne Frank's closet as a door is perfectly possible. But on top of that, you define how the door is opened/closed with properties such as “angle closed”, “angle opened”, “position closed”, “position opened”, the squeak and slam sounds, and the speed of operation. Oh yes, and the kind of required access(keys, itemX in inventory) of course.

This allows to make simple rotation / slide animations by interpolating with an “elapsed time” between the opened and closed status. Kicking in a door simply boosts the deltaTime speed, and causes another sound effect plus animation for the player (kick, push, pull, …). Hi-tec Star Trek doors that curl up or something can use animated objects.

Last but not least, when placing a door it figures out at which portal and A* path connection it is. When closing a door, the path and portal will eventually get blocked, preventing enemies trying to take that route or rendering the sectors behind that door.

Police! Please wipe your bottom and get off the toilet with your pants up before we kick in the door!

Second issue, swapping sectors. Say what? Although a few (modern) games have total destruction and chaos abilities, it is still a common technique to replace a piece of map when something happened. Example. You wonder what happens when pushing that button labeled with “Detonator”. Bang. When going back, you notice uncle Buck’s chicken barn is completely gone.

In real life stuff like this only requires some TNT. In games you’ll need to adjust the visual mesh, reevaluate the collision mesh, alter path-finding routes, and eventually regenerate static lighting or reflection information. Which is why replacing a piece of map with a second pre-modeled variant is a common way to fix this little problem.

In my case I didn’t need to blow up barns. Nah, but nightmares tend to spawn complete new locations in all of a sudden. One second you were walking in your house, the next second you are suddenly in a metro station or cornfield. With this technique you could make dynamic “mazes” for example.

You have seen this chamber in a few hundred ways already. But now it's also possible to replace it with a complete different map (also the dimensions can be different).

Replacing a sector had a few difficulties though. As you may have read previous week, the world is one huge map. All sectors connect with each other, which means their absolute positions in 3D are important. Throwing one out can cause some problems. First of all, the world has 1 big global octree that helps you sorting out where you are
(function whereTheHellAmI( vector position ) : sector ).
Second, pathfinding may require a rerouting. Third, lights from neighbour sectors have to rebuild their “visible sectors” listing. Fourth, old info such as collisionMesh and local objects need to get removed / replaced. And last, if there were any global objects / characters inside that sector, they need to get transferred somewhere. There is a realistic chance this happens, since the enemies are free to walk everywhere.

I did it as follow. Both (or even more) sectors are placed at the same position, meaning they can overlap each other. But only one should be activated. By default all sectors are active, but sleeping sectors that pop-up later can be disabled by hand. When replacing:

- “Active flags” from both sectors are switched. Disabled sectors cannot be rendered, loaded or picked when requesting the sector at position XYZ.
- Unload previous sector completely. Just like the background roaming thread would remove sectors that were left behind / too far away. This also removes its physical mesh. Don’t want to stick behind invisible walls.
- For each global object inside the old sector, call a script callback that decides what to do with it. Enemies could get moved to a default location. Stuff like weapons or puzzle objects can get moved to a temporary “vault” sector somewhere outside the world. When loading back the previous sector, a custom script can ask these objects back from the vault.
- Load the new replacement sector
- For each neighbor sector of the old one (trace via its portals); adjust portals and force to reevaluate the lights to make sure the shadow depthMaps are correct. Each portal has a property that tells which sector can be seen behind it. In case this was the old sector, replace it with a reference to the new one.
- Path-finding… The game basically has one huge navigation mesh, also for unloaded sectors. When two sectors overlap, then just also model 2 pieces of nav-mesh at that point. But block all the connections that belong to a disabled sector so that the player cannot use that route.

You can guess the scripting was extended with a few new functions for swapping sectors.

Crikey, that was the detonator button

No comments:

Post a Comment