I took a two-week break from my current game project (“Twin Towers”), to experiment with a few ideas for my submission to Adventure Game Jam 2023 on itch.io. Just barely made the deadline 😀
My ongoing adventure game project is eating up a ton of time, even though it isn’t even particularly large in scope. I am thinking a lot about ways to tell stories about Amy’s world in smaller increments. Another issue I’m facing with writing adventure games, or generally story-driven games, is – I already know all the answers! Don’t get me wrong, it’s fun to write and think up stuff, but there’s no surprise element left for me as the author. Especially after testing every turn over and over.
So, I had a few goals and questions for this jam:
- Generate a procedural world to explore
- Can I create stories in that world procedurally, too? Something that surprises me, when playing my own game?
- Exercise my existing lua script dialogue framework for Unreal Engine
In the end, I still don’t have an answer to automated story creation. But there is some groundwork now that I hope to expand on. My jam result in itself isn’t all that impressive, but there were so many lessons, I had to write it all down in this post mortem. Hopefully, you find it interesting, too!
Application structure and test setup
I was going for a 3D application in UE. Since my main story engine runs completely in Lua, my first step was to implement what I had in mind essentially as a text adventure.
The basic layered structure of the application looks like this:
- Lua story: interaction functions, some game state (did we talk to this character before, does the player know this piece of information already, etc.)
- Lua map generation: builds data structure for locations, paths, places items and characters
- Lua game loop: a simple console-based interface exercising the map generation and story functions
Actual 3D Game in UE
- Lua / UE interface (C++ and blueprint): a thin layer of functions to get game state from the Lua side to UE, and user actions back to Lua
- UE game: the 3D representation of the game, and handling of user input (with its own game loop)
This setup had a couple of advantages:
- rapid iterations – write and run, no build times
- conveniently debuggable Lua console application (in Visual Studio Code, for example)
- easy to scrap ideas and try out something new, without having to re-implement UI, 3D interaction logic, etc.
- running test scenarios for story logic would be really simple
- clean separation between story logic and UI
Early on, I imagined the map consisting of a series of connected “islands”, where getting from one island to the next would be fun and somewhat challenging. Sort of a generated stage, which works independently of the main story.
So, the majority of the two weeks went into implementing graph algorithms in Lua, keeping in mind that the layout of the connected islands in the 3D game should be very straightforward. No time for complex graph layout algorithms!
I settled for a center node surrounded by more nodes. Connections between them are randomized and intersection-free. “Bridges” connect the outer circle to the next island. Here’s what it looked like in the end:
(There are some hidden locations, too: single nodes attached to some other node. These can easily fit into the “gaps” between the main nodes).
To test and debug the map generation algorithm, I delved into learning graphviz. This proved tremendously useful! Completely disregarding beautiful visualization, I had an easy way to check the structure of my generated game graph:
The “tiers” are the islands, the player starts at the top and has to get to the bottom. Locations are assigned different types (house, bridge, etc.) and are populated with characters and items to interact with.
Characters and relations
One of my main ideas was to not just randomly distribute characters, but also set up relationships between them, such as love, hate, parent/child. Additionally, more relations to items on the map are added, such as ownership, current possession (a character can “have” an item that actually belongs to someone else), want or need, etc.
This network of character/character/item relations was to be a seed for interesting story constellations. This turned out to be way too complex for a game jam, so the relationships are there, but unused by the current game.
Story / quest structure
Since story generation was not going to go anywhere, I eventually set up a main story and a few quests explicitly. There is still a procedural element to this, as characters and items are appearing in different places on the map with every new game.
(This simple randomization has drawbacks: you may encounter a key much earlier in the game than the lock – solution before puzzle.)
With the code to place characters and items already implemented, adding this handcrafted story on top of the generic structure turned out to be quick and easy, though! So I see a possible path towards generating stories by authoring “building blocks” and combine them in some way on a given map.
Moving around the map
The basic game mechanic for getting around:
- as the player moves to a location, paths to adjacent locations are discovered
- the player collects crystals to pass the bridge to the next island
Visiting locations and interaction
When you enter a location in the game, the 3D visualization is created on the fly from the information in the corresponding graph node stored on the Lua side. Items and characters are “interactables”, which I had already implemented. Only very little additional coding was needed to support the “point and click” mouse interface for this game.
Here’s a screenshot showing one of the locations, with
- Amy (our protagonist)
- A character she is talking to
- two items: a trapdoor revealing a hidden room, and a crystal to use as bridge toll
- a door to exit the location
- the player’s inventory
- two of four map parts that the player needs to collect to solve the main quest (visualizing game progress)
Most obviously, the environment is pretty bland now. Generating a more interesting map and locations would be a great start. Compared to designing, modeling and set-dressing a whole map manually, I see this as the greatest potential time saver for getting a story-driven game out more easily.
- more types of places and buildings per island, with some structure depending on the type of area (village, open country, forest)
- more item types with specific properties and behavior
- more diversity in the look of places and items (there is a concept for “item roles”, so having many instances for “money”, e.g. coins, jewels, gold nuggets, … is possible)
- a more diverse cast of characters, with attributes such as profession, age, etc. – which can be used to generate visually distinct individuals
A next step towards story generation could be to utilize the character relationship concept to add some smaller side quests and stories.
- E.g. Alice has “lost” an item, and it turns out to be stolen by Bob. Get it back from Bob and return it to Alice to receive a reward. The reward might be something needed for the another automated quest, or the main story.
- Eventually this would lead to automatic generation of “puzzle dependency graphs”. At the very least, I could produce a puzzle structure that is guaranteed to be solvable, and enrich it with meaning and story manually.