Shadows of Doubt is a detective stealth game set in a fully-simulated sci-fi metropolis! There’s been a murder and it’s up to you to solve it by any means necessary, with the condition that you keep a low profile. A unique mix of procedural generation and hand-crafted design enables every room of every building to be explored. Be sure to wishlist on Steam, join our Discord or read previous dev blog entries here!
Hello everyone! Welcome to a fresh new edition of the Shadows of Doubt dev blog. This month I’ve put together a list of some of the most challenging aspects of creating the project so far. I should probably note this doesn’t include challenges for writing, voxel modelling or audio work, ie the stuff I largely don’t do. Perhaps we’ll visit some of those in future blogs with the relevant team members. This one is more about my role, which is mostly design, coding and project management. I’ll also say that just because these bits have been difficult, it doesn’t mean they haven’t been fun to work on; in fact, they’ve provided some of the best learning experiences of the project so far.
Shadows of Doubt is a procedurally generated game. That’s great and everything, except when it comes to the technical challenge of lighting. Most non-procedurally generated games feature baked lighting (and not just lighting actually, it means you can do all sorts of yummy baking). Baking essentially means precalculating tricky things into a bunch of data the game can read when you play. Much faster than trying to figure out all those calculations in real-time.
Lighting is a big one as it means you don’t have to ask the game engine to bounce light everywhere when playing. Although it’s common for a game to feature a mixture of both baked and real-time lights anyway, the nature of the levels where nothing is predetermined means Shadows has to use 100% real-time lighting: That’s a big performance hog.
So what are the solutions?
Simply use fewer lights. If calculating all this is expensive, then use less of them right? Fortunately, having fewer lights creates some nice heavy shadows which kind of lends quite well to the noir genre. There are a few cases where this is tricky, however; mostly the street scenes. In this scenario, however, I’ve come up with a way to ‘fake’ having a bunch of ambient lighting. Over every street is a large area light, which casts a colour down on the street. This is designed to mimic a mixture of all the ambient lights in the street. Every object that spawns in the street (for example a red paper lantern) can alter the ambient colour of the overhead area light slightly. It’s not perfect, but this method helps to light up the street with additional ambient light.
Limit lighting and shadow distance. Fortunately, in Unity you are able to set distances for lights to be active. This isn’t my favourite compromise as it means the lights essentially turn off as you reach a certain distance, but it is a good ‘catch all’ method for making sure we don’t have too many lights shining at once.
Another Unity thing is light layers. This allows you to make sure lights are only cast on certain layers (any object in the world can be part of a layer). Using this, I can make individual buildings have their own light layer. Therefore ensuring that lights within an interior largely only affect things inside a building and vice versa. I’m not actually sure how much this is helping performance, but I imagine I’m at least saving the engine some amount of calculation by cutting off a chunk of active lights this way.
So we have this world populated by literally 1000s of props, most of which can be moved, damaged or destroyed in some way. That’s not necessarily a problem for a modern game engine, but things get complicated when you think about how you want them to behave.
Lots of things might get moved around by the player (say the player trashes an apartment or a bar), we don’t want those objects to stay in their trashed positions forever; as this is a simulation we would probably want the AI to put things back how they were eventually, or at least imply that and have everything be reset at some point. That’s great then; tell things out-of-place to get reset to their original positions after a certain amount of time. Well, what about litter? What about things the AI have deliberately moved for good reason? Wallets? Purses etc? We actually don’t want everything to reset. In order to solve this problem I had to come up with a ‘relocation authority’ system; or a system dictating special cases on when we want items to reset and when we don’t. Generally, we have to look at the object’s owner; they have the ‘power’ to set the spawn locations of objects, so they can move them. There are plenty of special cases, however; such as if an NPC steals an item and positions it in their home or elsewhere. In short this is a bit of a special case minefield. Yay.
Then there’s save data. Due to the nature of the game, we can’t always dictate what objects are valuable sources of information for a case. Therefore when we save a game, we have to save everything. Not just objects the player is carrying or is in the immediate vicinity of, but every item in the world! Ouch.
In the next dev blog I’ll continue ranting about stuff! Stay tuned.