Story of a Mansion
Last Hallowe'en, it was my pleasure to help launch the Azure Mystery Mansion, a text-based game built using Twine. Users explore the various rooms of an old house, picking up keys that allow final access to the attic where she or he can claim the deeds to the house. In the process, rooms are unlocked by means of discovering solutions to small puzzles and gathering clues from Microsoft Learn.
I learned a great deal on the making of this type of game from Em Lazer-Walker, who led the development of the original Mystery Mansion and enhanced Twine to work better for multiple developers. She also perfected its integration with PlayFab, the game's backend.
The game was a hit. A big hit. So big that it caught the imagination of many folks at Microsoft. We were soon making plans for a V2 of the Mystery Mansion, which I envisioned as a continuation of the old house motif. But the Mansion storyline ends so nicely, with the user gaining the keys to get the house deeds in the attic, that I couldn't seem to find a good way to continue the trope. Do we continue with a tour of outbuildings and old antiques stuck in a shed? Somehow the "Mystery Outhouse" doesn't have quite the same panache. Clearly, a new tale had to be told. In addition, we wanted to somehow localize the experience to make it more pertinent to a regional audience.
From Mansion to Mystery
Enter my work with my brother, an Art Historian at Cal State Chico, Matthew Looper. Inspired by his work, it struck me that a game centered around ancient Maya culture would resonate with our pan-Americas group with Cloud Advocates and Project Managers who cover Canada, US, and LATAM who were particularly interested in continuing the project. We gathered a team of regional PMs and marketing experts to make this idea a reality. Foremost in my mind was to not spin up an 'Indiana Jones' type mish-mash of exploitative cultural tropes, but to create something genuinely respectful of ancient Maya culture that was both educational and fun.
And so our path became plain. We would partner with experts to create a game that taught about Maya glyphs while exploring a mysterious pyramid, encountering surprises along the way. Working with an illustrator who is an expert in creating architectural drawings and reproducing glyphs would ensure its cultural appropriateness, so we contracted with illustrator Dana Moot II to create accurate depictions of a fictional Maya pyramid.
In addition, our experience in December with the success of our 25DaysOfServerless event, also a large group effort centered on exploring aspects of Microsoft Azure, provided a supplemental road map for the game. By building a scavenger hunt that also pushed users to solve puzzles by actually shipping code, we would showcase product launches progressively over a three-month three part release. The first one would be Azure Static Web Apps, and the user would be given a challenge to discover the meaning of one of the three glyphs that makes up the temple's name. Not to reveal too much, but didn't you always want to chat with a goddess? You'll deploy a chat interface built with React and use Azure Static Web Apps to view it live, learning via chat part of the name of the mysterious pyramid.
Tech Challenges
While Twine worked brilliantly for the Mystery Mansion I wanted to try a tool that gave me a little more design freedom and was a JavaScript-native tool that would foster collaboration. I wanted something that would enable many people to write parts of a game or to localize it, and that would be easier to scale and maintain. And of course, I reached for a tool that was most familiar to me (my blog is built using it): VuePress.
The Tech Stack
As a Vue.js developer I immediately gravitated towards VuePress, a static site generator that would work well for storytelling using files written in markdown. By using this toolset, which allows both styling via Tailwind.css and importation of standard Vue.js single-file components, we were able to get better control over the interface and more flexibility in the way we designed it, while keeping the 'vintage' look of the text-based game intact.
An example of the storytelling-friendly format of the game can be seen by perusing the markdown files that tell the tale of the pyramid exploration:
---
backdrop: images/1-nostairs-closest.png
---
# The West Wall
Walking west, you encounter thick underbrush. Vegetation has grown in close to the wall, and dangling vines whack your face as you push through.
As you move vines aside to pass, your hand brushes a carving embedded in the wall. It's another glyph.
<Item id="7" />
<Page url="398" instructions="Another puzzler. Your guidebook provides another clue: '3: Machine Learning can help you predict the prevalence of this kind of species.'" action="Walk south" condition="7" />
Child components display a glyph to collect, and once that condition is solved, the matching page navigation is displayed. Props are sent from the parent page to its child, to display instructions and actions to take. Item clues are contained in a localized json file:
"gameItem": "snake glyph",
"filename": "snake",
"initialHide": true,
"clues": {
"es": {
"clue": "4: El lenguaje que lleva el nombre de esta criatura convierte el código fuente en código de bytes que se puede ejecutar en cualquier plataforma compatible."
},
"en": {
"clue": "4: The language named after this creature turns source code into byte code which can be run on any supported platform."
},
"fr": {
"clue": "4: Le langage nommé d'après cette créature transforme le code source en code octet qui peut être exécuté sur n'importe quelle plate-forme prise en charge."
},
"pt": {
"clue": "4: A linguagem com o nome desta criatura transforma o código-fonte em código de bytes que pode ser executado em qualquer plataforma suportada."
}
},
The Game Engine
Jen: "Let's use VuePress! How do we make a game in VuePress?"
Chris Noring: "Hold my Glögg"
Within a ridiculously short amount of time, Chris had spun up a basic VuePress-based game engine using scoped slots, local storage, and a basic storyline written in markdown. I took this kernel of a game and built it into a journey to explore a pyramid, taking pictures and solving puzzles to restore glyphs vandalized by looters.
A camera interface displays the ids of images stored in local storage:
showCameraItems() {
var ids = getItems();
this.polaroids = ids.map(id => items.find(item => item.id == id));
},
Localization
Since we knew, given that the game was for the Regional team, that we would need to localize it, providing translations in French, Spanish, and Portuguese as well as English, we had to create an interestingly hybrid approach to translate both the markdown files supported by VuePress as well as the localizable strings used in the Vue.js files for the more complicated game play.
While VuePress has a built-in way of handling translations using routing (/zh
vs. /fr
routes, for example, will display appropriately translated content), there needed to be a way to propagate those changes to nested Vue.js files, such as puzzles embedded as child components. Enter the EventBus, a good way to signal to child components that changes to translations have occurred, and to pick up different translated strings.
When the UI is told to switch locales, the EventBus emits a command:
EventBus.$emit("lang_changed", lang);
And this command is acted upon, informing the child components that the language has changed and that the locale controlled by the i18n plugin must be switched
EventBus.$on("lang_changed", lang => (this.$i18n.locale = lang));
The Backend
Since this site is deployed on Azure Static Web Apps, the game is contained in /app
and any API calls are contained in /api
. Using the PlayFab SDK for Node, we are able to consolidate all API calls to the backend in this separate area and use Azure functions to invoke them. A login request can thus occupy only 20 lines:
const { PlayFabClient } = require('playfab-sdk');
module.exports = function (context, req) {
var request = {
Email: req.body.email,
Password: req.body.password,
RequireBothUsernameAndEmail: false,
};
PlayFabClient.settings.titleId = <This is the game's title id, set in PlayFab console>;
PlayFabClient.LoginWithEmailAddress(request, function (error, result) {
if (error == null) {
context.res = { body: result.data };
context.done();
} else {
context.res = { body: error };
context.done();
}
});
};
Deployment
Dogfooding is a great exercise, and the Azure Maya Mystery lives in the very product showcased by its embedded challenge, Azure Static Web Apps. It was shockingly easy to deploy the app; the challenge for us occurred when we were obliged to move the game into a subfolder so that it could be deployed on the Microsoft.com subdomain. This requirement forced some reshuffling of assets and the addition of a postinstall script after the app is built by VuePress, to copy images to an internal folder, but the deployment process was quite smooth.
Learn how to Publish an Angular, React, Svelte, or Vue JavaScript app and API with Azure Static Web Apps.
And also check out:
- API support in Azure Static Web Apps
- How to add an API to an Azure Static Web App
- Authentication and authorization
- Routes
- How to create a pre-production environment using Azure Static Web Apps
- Azure Free Trial
Playtesting
When developing this type of game, especially one with an educational component, it was really important to have playtesting by a diverse set of users. Interestingly, but not surprisingly, it was the 10 year old son of a Microsoft employee in the UK who gave us particularly solid feedback. He first noted that there weren't enough spiders (stay tuned, Tommy) and that we needed more puzzles (I added two!). Thanks, intrepid testers!
So, with a bit of imagination, some useful QA from our creative director, Em, and a lot of rethinking, rewriting, and translating, we were able to build the first part of a three part adventure that should satisfy the hardiest of explorers. It was exciting to see folks who had never tried to deploy a website on Azure suddenly able to solve the coding challenge and complete the code challenge, gaining entrance into the mysterious pyramid. In future months, we will allow users to continue their progress, exploring surprises that appear underneath the pyramid and working up to the summit, where the name of the temple and its owner will be revealed. Get ready for a true adventure, explorers!
What's Next? Mysteries-As-A-Service
Playtesting revealed another interesting possibility for our mysteries. We have talked to Museums who were curious about their potential application as they allow for online walk-throughs of their collections. Tommy and his Dad recommended that we create a stripped-down version of the game engine and release it for students to fork and alter for their own needs, to learn how to manage a project in GitHub and how to handle building and deploying a web site. So, I did! You can use this repo to build your own game.
This engine has unlimited potential for helping build both gamified experiences and for helping teach concepts such as basic CS and programming curriculum. As students write their storyline, they learn how the parts of the site fit together and how to gradually alter a codebase to suit their needs. I'm encouraging folks to tell me about their games in the Issues tab on GitHub.
What will our next stop be? For the kids, a trip through space? For the Azure Maya Mystery, a trip to the depths of the pyramid...hint, I hope you know how to swim!
Join us in our Adventure