Dev Diary #1: Dialogue System

This month, we deep-dive into the tech powering the custom dialogue system we built for Creatures Like Us

DEV DIARY

Trinity

8/21/20266 min read

Screenshot of a dialogue decision in-game with two options for Sumitra.
Screenshot of a dialogue decision in-game with two options for Sumitra.

Building a Bespoke Dialogue System

Creatures Like Us is full of character dialogue and casual decision making. However, unlike a conventional narrative where you embody a single individual, you are instead frequently shifted between the (metaphorical) shoes of each crew member. United in solidarity, your squat crew can achieve common goals; but the needs and perspectives of individuals in the group are not static, nor are they guaranteed to align with eachother. Making choices in real life requires energy, especially for tough decisions and circumstances where your time & effort is divided - in Creatures Like Us, your crew are no different.

Rather than implementing a skill-based decision system for characters, we wanted to better reflect real-world activism & organising in a way that is challenging but rewarding. Your crew have individual needs like hunger, group objectives like preparing the squat for a gig, and personal preferences - not to mention a limited amount of energy to spend on tasks & decision making. On top of that, accessibility for our players is an important consideration that cannot be overlooked. We needed a versatile dialogue system for this that we could tweak and alter throughout development.

Dialogue Scripts as Code

Dialogue systems in games can be incredibly complex - just taking a look at big narrative games like Disco Elysium. Sprawling branched narratives criss-crossing eachother, with many edge cases to handle. There are a number of tools for game developers looking to make complex dialogue trees such as Articy. As a studio however we wanted game mechanics embedded into our dialogue, and those mechanics are prone to changing as we iterate & refine our design. (Also, as the lone programmer on the project, I know I will be required to fix things when something goes wrong in some dialogue logic). Plus, we only have one writer at present, so we knew we couldn't overstretch ourselves with vast quantities of complex branches and infinite endings. As a result, we opted to roll our own dialogue engine in Unity; but rather than building a data-driven system that could break and bloat every time we introduced a change, I chose to use the power of the C# programming language to our advantage.

Every dialogue script in the game is actually a regular C# code script. You're probably thinking "Why", "This is bonkers", or "Oh no, not Visual Studio". Hardcoding data is usually a big no-no for a variety of reasons. However, I posit that dialogue scripts with branching decisions are not plain-old-data. Rather, every branching narrative you see in a game is actually a procedural flow of logic, i.e. one dialogue decision happens, followed by another, and another, producing many possible routes - a lot like code.

Critically in our case, whenever game mechanics have changed, it has been a simple matter to update the dialogue system to support a new feature without impacting existing dialogue. Not only that, but we get free syntax highlighting, intellisense and other great features bundled in with Visual Studio. This way, I have been able to keep pace with rapid design changes while also ensuring the system is still user-friendly for our writer with little C# programming experience. And it works - every conversation you see in the game has been directly written in C# scripts, using our custom API.

How did you make this even usable!?

Be warned: Tech ramble ahead. Nerd recommended.

Like many programmers, I have a strong dislike for unnecessary duplication. Writing boilerplate code, while sometimes necessary, is often an off-putting and rather time-wasting endeavour, so much so that some people have decided they'd rather use generative AI to code than learn new systems. As a sidenote: We do not and will never use generative AI at Circle-A Studios. Learning experiences are an essential part of game development that using AI can undermine, and that's before we even consider all of the ethical & environmental problems with generative AI.

To mitigate glazed eyes and confusion then while writing in our dialogue engine, I had to reduce boilerplate as best as I could so our writer could make dialogue and decisions intuitively, without tripping over spaghetti code along the way. Fortunately, C# makes this straightforward.

Screenshot of a C# dialogue script showing 5 lines of dialogue & a character decision.
Screenshot of a C# dialogue script showing 5 lines of dialogue & a character decision.

Each dialogue script is a custom class which inherits from a special Conversation class. This provides members and methods for writing dialogue lines, decision branching, and direct access to objects, variables, functions & data assets in the game - another benefit of using C# as the medium is the unimpeded access to our game API without any need for fragile bridging code.

Each "segment" of dialogue is implemented as a method that typically contains a bunch of dialogue lines and ends in a decision, returning a branch function. If null is returned, the dialogue ends immediately; otherwise, the branch function handles the outcome of the decision, selecting the next segment. The underlying structure of a Conversation is actually a tree of these segments and branches, with individual segments built on-the-fly.

Character data, glossary data, item data and more assets are retrieved with direct references via a special generated class that maps Unity folder paths to nested classes, with asset files as properties in those nested classes. Whenever we add a new asset, this file can be regenerated at the click of a button, or in future this can be done automatically once I have made a custom Unity asset importer (we already do this in the build pipeline).

Screenshot in-game of some dialogue and a tooltip displaying a definition of the noun "foodshare".
Screenshot in-game of some dialogue and a tooltip displaying a definition of the noun "foodshare".

C# has simple yet powerful string formatting capabilities, and ToString() can be implemented for any object. Combined with TextMeshPro features, this enables us to write dialogue with direct references to assets and at the same time embed custom formatting, links, glyphs, localisation (something we may cover down the line when we can afford it, as this is also unconventional shall we say) - all without giving these a thought.

Was this a Good Idea?
Screenshot of a C# dialogue script showing some objective proposals for the meeting system.
Screenshot of a C# dialogue script showing some objective proposals for the meeting system.

Arguably, many of the game mechanics could be implemented with existing dialogue systems. But none of them would have adapted so easily to our changing requirements and designs, nor would they be guaranteed to do everything we needed of them. By building our own system, we haven't hit any limits - and if we do, we can update it ourselves, free of charge and utilising the full power of the C# programming language.

The main flaw of our dialogue engine is that it isn't (yet) a drop-in replacement for node-based dialogue systems. While just as powerful, it is ultimately harder to visualise branches in pure text than it is with a graphical node system, even with the plentiful features in Visual Studio. For Creatures Like Us, where we're trying to avoid having too much infinite "spaghetti" dialogue anyway, this suits us fine. I have built a rudimentary dialogue visualiser so we can check the logic flow without testing in-game if need be; and when the game mechanics are more stable, we have the capability to upgrade the engine with a graphical node editor.

All in all, I have been able to minimise the amount of C# knowledge required to build a dialogue script while retaining all the flexibility afforded by writing in C#. As an example, when we added the Meeting system this year, I was able to build it with some minimal variation on the existing decision system, despite it having very different mechanics to a "regular" character decision. Our writer has swiftly adopted the dialogue system, making good use of the built-in features. The same system is used for cutscene dialogue. It's greatest strength is it's versatility.

Thanks for reading our first dev log!

We hope you enjoyed a peek behind the curtain at our tech stack. Want to hear more about the game? Check out our Discord community where you can chat about it! We also have Kickstarter and Steam pages where you can follow & wishlist Creatures Like Us for updates.

Screenshot of a C# dialogue script showing 5 lines of dialogue & a character decision.
Screenshot of a C# dialogue script showing 5 lines of dialogue & a character decision.