The Unholy Society is a game inspired by 80s and 90s action movies as well as comic book series such as Preacher and Hellblazer. It presents a world enslaved by the united forces of demons and monsters.
- Genre: adventure, action
- Premiere: Q2 2018
- Platforms: PC, Switch
One half of our game, The Unholy Society, is an adventure game. You walk around, talk with people, make choices and explore the storyline. We have developed a system, a simple scripting language, to better define adventure games like ours.
Even though our “game designer” is also a programmer, he wanted to separate the game logic from the game’s core source code. This way, the logic, as in, “you cannot enter the church before you speak with aunt” is specified in a different place and a different language, than for example, the logic of how to load Spine models and animate them in the game.
So, we’ve made a script system, designed with “conversations”, as a primary use-case. We call it “The Unholy Conversations”.
As the name suggests, the first use-case is for when a player talks with one or more NPCs. However, the system is also useful to define cut-scenes: NPCs can talk (with or without the player) and animations in the environment and the NPCs can be run and so on. The scripts are also run when you enter a location or a particular area of a location. When the player is inside a “sensor” (a simple rectangular box), a script decides whether the player can interact with something and what happens when (s)he does.
It’s a set of files, named something like “.conv.txt”. The decides when the script is run. For example, the file “aunt.conv.txt” says what happens when you start a conversation with the “aunt”, one of the NPCs in the first act of our game. The file may look as simple as this:
aunt: Hello dear.
hero: Hello my dear auntie!
aunt: You’re late.
Simple enough, right? If a line starts with “xxx:” then it’s just someone speaking. “xxx” can be the name of an NPC (it can also be empty to indicate this NPC, which is implicitly “aunt” within “aunt.conv.txt” file) or just “hero”.
A central part of the conversation is choices. They look like this:
aunt: Go to Edward.
-> Why are you so suspicious when it comes to the Vladinsky family?
aunt: I’m not sure are they a good influence on our Susan.
-> Can I talk with Susan?
aunt: She’s getting dressed. There’s no time for chit-chat now.
-> Bye then.
aunt: See you soon.
As you can see, each choice is just a line that starts with “->”. They all must have the same indentation (hi, Python lovers!) and they must end with an empty choice. This way you can have nested choices too. Note that the choices are (right now) always spoken by a hero and this is why, in the text above, we do not clarify who says, “Bye then”.
To enable simple loops within a conversation, one line may start with a label like “label start” and another line can then jump to it, like “go-label start”. This allows the creation of conversations where you can inquire with an NPC about something, like an investigation, and only exit the loop once you are satisfied (or you have exhausted all the options). The label mechanism does seem like the unpopular “goto” mechanism, abolished from most high-level programming languages, yet here it works perfectly. It’s simple (the conversation files are always short and jumps are only within a single conversation, of course) and flexible, as it allows jumps to different locations.
A game logic remembers the state of the storyline.
Take for example, “Have you already spoken with aunt?”. This particular information (if you have already visited someone) is actually very often useful. All the conversations (and thus, all the NPCs and all locations) have an automatically remembered state of “visited”. It’s a simple boolean flag (it can be true or false). It is initially false and then automatically changes to true once you have spoken with someone.
You use a variable in simple boolean expressions together with the “if” instruction:
if not visited
aunt: Finally! There you are!
hero: Hello my dear auntie!
aunt: You’re late.
aunt: Go to Edward.
Here we use the automatic variable “visited”. Like in every good programming language, variables are placed in namespaces, with each conversation being a natural namespace. This means that another NPC can say different things depending on whether you have spoken with aunt:
if not aunt.visited
uncle: Your aunt is waiting for you in front of the church!
uncle: Go on, boy.
Another automatic variable is “gate”. Some NPCs simply won’t let you through until you have done something. For instance, you cannot talk with Susan too early in the game, as you have seen in the above snippets. Once an NPC decides that you can pass, it can “change” the gate status doing “set gate false”.
Finally, every conversation can have it’s own custom variables. This can be used to store any game state. For example, the aunt could have a variable like “is_angry”. You can declare such a variable within a conversation file and use it (read or write) from this or other conversations. For instance, a conversation with aunt could end badly for you:
declare is_angry false
aunt: Go to Edward.
-> Right away!
-> Come on, do I really have to?
aunt: Yes, you insolent brat.
aunt: I remember when you were young and…
hero: OK, OK, I’m going…
set is_angry true
Now, another NPC or the aunt could use this flag: “if aunt.is_angry”.
As you can see, all the variables must be declared and the declaration must specify their initial value. In this example, the aunt is initially not angry (but watch out, you insolent brat!) That said, it’s a secure and reliable programming language. Only special “automatic” variables, like “visited” or “gate”, do not need to be declared.
Naturally, all the conversation variables are saved to the save-game. This allowed the save-game code to be quite simple: just write all the variables to the save-game.
The conversations can cause a variety of actions. They can teleport the player or an NPC. They can make some NPC or asset appear or disappear. They can play a particular animation. All of this is smartly saved to the save-game, so the game designer does not have to worry about these technical details when designing the game logic.
All the scripts are automatically validated (they use existing variables, are connected to the actual data, etc.). This makes modifying these files safe and testing is easier when the computer automatically detects some of your mistakes. Right now the testing is done each time the game loads (it takes a fraction of a second now – less than 1/10 of a second), but it may be moved to our continuous delivery process (using Jenkins) later.
We have designed a conversation system once before, for our not-yet-released game, The Venice. It had some good ideas and we have borrowed some of them. But it was also not comfortable enough to write a large amount of logic… what we have now, The Unholy Conversations, is both simpler and more flexible to use.
We have looked at other conversation systems as well. One in particular was from an excellent indie game, Night In The Woods, which is public and documented on https://github.com/thesecretlab/YarnSpinner/ . We have borrowed various ideas from it, e.g. that the “talk” lines should be simple to write, the “visited” state should be tracked automatically and the “if” and choices should be easy.
The “Yarn” language was, in turn, inspired by Twine, a simple text format for telling non-linear stories with choices.
Twine is also renowned for its simple-to-use graphic editor for stories, although we quickly decided that a graphic editor was not as useful as it sounds for things like our game conversations. Conversations in games need to depend on various variables and they need to be written easily. Wrapping them in a graphic editor felt unnecessarily limiting. We’re programmers, we work fast with a text editor and a clean programming language with simple syntax — so that was our focus on The Unholy Conversations.
After the development of The Unholy Society and once we catch our breath, we plan to make this system independent from the game and release it as an open-source component. We’re huge fans of open-source here (we use our own open-source Castle Game Engine for the entire game development), so in doing so, we will give back to the community too.
If you appreciate our dedication and want to know more about the game, please add The Unholy Society to your wishlist to stay up to date with all further announcements.