Version 5.0.0 released with new authoring syntax and debug tools
January 23, 2020I’ve been busy the last few months working on a game that I entered into last year’s IntroComp. This game is built with the Elm Narrative Engine, and I’ve been adding new tooling to make it easier to write and debug. The tooling has now stabilized, so I’ve moved it all over into the engine and released it as version 5.0.
Most notably, I’ve added an authoring syntax for describing the world model and for building queries and rules. I’ve also added syntax for dynamic narrative text. This new syntax is much nicer and intuitive to use than the Elm code equivalent that it gets parsed into, and also makes it possible to import all game content, rules, and narrative from an external source, like a spreadsheet.
I’ve added some more advanced queries for comparing entities, and a debug bar that uses the new syntax to easily search your world model see which rules are getting triggered as you test play your game.
You can play around with these new features in a live sandbox example, or clone the starter repo, or review the full API docs.
Details of the new features are below.
New Authoring Syntax
The authoring syntax is meant to be quick and intuitive to use. The engine exposes parsers for each syntax type, as well as a debug view for any parsing errors.
Entity and World Model Syntax
You can define an entity by specifying an ID and a series of properties separated by periods, like this:
CAVE.location.dark
TORCH.item.illumination=5.current_location=PLAYER
PLAYER.chapter=1.fear=1.current_location=CAVE
This sets up a cave with the “dark” and “location” tags, and a torch with an “item” tag, an “illumination” stat, and a “current_location” link to the player (ie, in the player’s inventory), and the player with a few stats and a current location link.
Rules Syntax
Rules are made up of queries and changes.
Here is an example query to get all of the items in inventory:
*.item.current_location=PLAYER
Here is another one to check if the player is too scared:
PLAYER.fear>10
And one more that checks if the player is in a dark location. Note that parenthesis are needed to mark the “sub-query” for the location:
PLAYER.current_location=(*.location.dark)
This is a change to increase the player’s fear by 2:
PLAYER.fear+2
And another to move all items a chest to the player’s inventory (again, parenthesis are needed for the sub-query to find all of the entities to update):
(*.item.current_location=CHEST).current_location=PLAYER
Rules are made up of a query trigger, a list of query conditions, and a list of changes with this syntax:
ON: *.location.dark
IF: *.illumination>3.current_location=PLAYER
PLAYER.fear<5
DO: PLAYER.current_location=$.fear+1
GOBLIN.-sleeing
This rule will trigger when interacting with any entity with the “dark” and “location” tags, and will check if any entities that have an “illumination” stat greater than 3 are in the player’s inventory, and if the player’s fear is less than 5, and if so, will move the PLAYER to to the trigger’s ID (“$” gets replaced with the actual trigger ID in generic rules like this), increment the fear level, and remove the “sleeping” tag from the goblin entity.
The “IF” and “DO” lines can have as many queries and changes (respectively) as you want, or can be completely omitted.
Note how this format would work in a spreadsheet where the first column can have the rule’s ID, the second can have the rule syntax, and additional columns with any additional fields, like narrative or sound effects. With this setup, an entire row could be imported.
Dynamic Narrative Syntax
Although narrative is something totally up to the client and not handled by the engine, it is such a common aspect of interactive stories that I included some useful parsers based on the ink syntax. It supports cycling text ({a|b|c}
and variants for looping cycles or random cycles), injected text ({GOBLN.name}
) and conditional text (You feel {PLAYER.fear>1? scared | ok}.
).
For example, given the following text:
You shout at {$.name}{| again}.
"{?What do you want?|Why do you keep bothering me?|Leave me alone!}"
{$.suspicious>3 & WARRANT.current_location=PLAYER?"You're under arrest!"|"Never mind."}
On the first line, this will inject the name of the entity that triggered this narrative. If you trigger this narrative multiple times, “again” will be shown every time but the first (note that empty cycle segments are allowed). The second line will cycle repeated through three options in a random order when triggered multiple times (since it uses {?..|..}
). The third line will choose which response to display based on the two queries before the “?” (multiple queries are separated by “&”).
Note that you need to provide a config with relevant data to make it all work, but this can be a very powerful way to add dynamic content without adding extra rules.
Debug Bar
Finally, the debug bar uses the entity syntax above as a concise way to textually display the current state of the world model. You can search the world model to filter just the results you care about. For example, you can search by ID to see the specified entity as well as any links to it. You can search by tag to see all entities with that tag (or stat or link). You can search .
to see all entities. When the search is empty, the list of results hides to say out of the way of your actual game content.
Also, the debug bar shows the last trigger ID and the last matching rule, making it easy to tell if the rule you expected to match actually did, and if not, you can search the world model to find out if something isn’t as you expect.
Here is an example of the debug bar in use in the game I am currently working on. You can see that my search is location=PLAYER
, and as expected, the list of results matches the items in the inventory displayed in the UI below:
Next steps
I feel this release makes working with the Elm Narrative Engine much nicer and easier. I’ve been using the syntax in my code files, but I am excited for the option to import it from external sources. Adding new content is so much easier now than typing out the Elm data structures that it gets parsed into. The debug bar has been very handy for me in many cases already.
I do not have any new features planned for the engine now. My future plans are for the ecosystem, for example, adding a JavaScript wrapper around the engine so that you can use this without having to use or know Elm, as well as working on a browser-based editor/IDE to allow you to author, build, test, and publish Elm Narrative Engine stories with minimal effort or technical requirements. I also plan on building a more comprehensive documentation site with recipes to help people learn to use this tool.
I hope that as the Elm Narrative Engine is becoming more stable and approachable, that more people will reach for it when they want to make interactive stories. I encourage you to take a look at the live sandbox example if you haven’t already.