Dev blog for November 2018
This is the first blog for our newest game project, Buzzo and the Clong Factory. Since we are both interested in stories and characters, we decided to produce an RPG (role playing game). We considered a 2D format but since we had some success with the 3D aspect of Sarah In The Sky, we made the decision to continue with 3D. It's more work, both the art and the technical nature, but creates a more immersive environment which adds weight to the story and overall experience.
So the next step was creating all the hidden core components that an RPG demands: characters, conversations, items, quests, schedules, inventories and a way to store all that information. After looking at some RPG frameworks available for Unity (the game engine we use), I decided to make my own. This is potentially a longer path but I would get exactly the features I need, and some of the development time would otherwise have been time spent learning someone else's tool, methods and code. (I really looked hard at the ORK framework, but after a day I had already hit limitations and differences from my target that I gave up on it.)
I considered using a real database like Sqlite, and in hindsight it would probably have worked well, but instead I took a table-based approach that used separate files per table. Most of my tables would have low numbers of entries (< 1000) and I could get away with linear searches to extract data. If I had a large table or really needed fast access, I could always wrap it in a hash table when the table is loaded.
I used arbitrary id numbers for indexing all the table entries. To generate the id, I used the handy C# DateTime method ToBinary() which generates a 64-bit number that is the current time, potentially accurate to the millisecond. I only wanted a 32-bit integer, so I only kept the lower 32 bits. To this I added an ever-increasing serial number. The serial number prevents any two id's from being the same if, say, the generator function was called from a loop that executed in less than a millisecond.
I started with conversations thinking they would be the toughest to deal with. I tend to go after the riskiest parts of a project first, that way I find out quickly if my estimate of the amount of work was accurate or not. The current dialogue system stores conversations by entity (typically a unique character in the game).
Conversations have to change according to the progress of the game, of course, so every conversation's availability at any time is dependent on a set of conditions. There can be multiple conditions for a single conversation and all of them must evaluate to true before the conversation can be considered for use. So say you want to congratulate the player on a side quest boss victory. You can make the conversation that does this dependent on the quest completion. Or if the player obtained a special item from the boss, the conversation could depend on the player having the item in his inventory.
Conversations are always two-way so there needs to be a reply system. I opted to have the player speak first, then after the NPC replies, there can be up to three choices for the player to continue the conversation. Three may seem small, but you can quickly build a complex tree of dialogue with three branches.
I created a dialogue table to store each exchange of conversation: the player's inquiry and the NPC's reply. Each conversation references an opening dialogue object, and each dialogue can reference other dialogues. Each dialogue can emit an event (yes, there's a table of generic events you can define). You can also mark a dialogue object as a one-shot (once enacted, never available again) and as part of the Delayed Group: a set of dialogues that become available one at a time, but only after a time period has elapsed since the last delayed dialogue was presented. The intent here is to keep the exchanges fresh so that the NPC can have something new to say almost every time they are approached, even if it's not quest related. I figure I can fill them up with lore and character history that the player can explore if interested.
Of course, while I was designing the data, I was also working on the tool to create and edit them. These images are from that tool. It runs as an EditorWindow in Unity and manages the entire database.
After getting the dialogue scheme designed I realized I would need a tool to test not only the conversations but their dependencies: the entities that talk, the quests completions states, the contents of characters inventories, and events that get triggered. The result was a simple interface scene that allows me to essentially play the RPG portion of the game. I can edit the players and NPC's inventories, trigger events and hold conversations--all the things needed to complete the quests.
Of course, a game is pretty boring played like this, but hopefully it will save time during testing. I won't, for example, have to walk across the world nor fight enemies to obtain resources in order to see if the one-eyed electric banshee will finally sell me a grotogator to complete my hot oil shower.