4. Adventure gaming
In the early days of adventure gaming, classics such as King's Quest and Monkey Island defined the point & click adventure in which you wander the game world solving riddles by combining items, using them on characters or objects etc. Creating an adventure game is a good next step, as it requires the introduction of Items that the player can collect and use. It also requires the introduction of a new concept, Combinations, that StoryScript uses to create these kind of games. But we can cover both in one tutorial. We'll also throw in Features to flesh out our locations a bit more while we're at it 😃.
Working with combinations will require you to get your feet wet with some TypeScript programming. I’ve tried to make the StoryScript programming interface (Application Programming Interface, or API) as simple to use as possible. That said, all programming requires some getting used to. If you stick with it, though, it gives you a lot of power to create unique games.
For this tutorial, you can refer to the MyAdventureGame game in your StoryScript folder or the demo online. As in tutorial 1, create a new game, copy the GameContainer component and put in this template:
<template>
<div class="container-fluid body-content">
<div class="row">
<div v-if="game.state === 'Play'" id="party-container" :class="{ 'col-4': showCharacterPane }">
<div v-for="character of game.party.characters">
<backpack :character="character"></backpack>
</div>
</div>
<div id="location-container"
:class="{ 'col-8': game.state === 'Play' && showCharacterPane, 'col-12': game.state !== 'Play' || !showCharacterPane }">
<div v-if="game.state === 'Play'">
<location-text></location-text>
</div>
</div>
</div>
<div v-if="game.state === 'Play'" class="row">
<div class="col-12">
<combinations :combinations="game.combinations"></combinations>
</div>
</div>
</div>
</template>
While you are at it, change the gameName in the customTexts.ts file to ‘My adventure game’. We have a nice clean UI to start with:

Now we can start defining our combinations. Defining what combinations should be available in the game is done in your rules.ts file in your game's folder. Let's define ‘Walk’, ‘Use’, ‘Touch’ and ‘Look at’ for this example. First, create a new combinations.ts file in your game's folder with these contents, so we define our combinations in one place:
export const enum Combinations {
WALK = 'Walk',
USE = 'Use',
TOUCH = 'Touch',
LOOKAT = 'Look'
}
We can use these definitions to create the combinations in our rules.ts file like this. You can use the ssCombinationAction snippet to add a new combination action:
getCombinationActions: (): ICombinationAction[] => {
return [
{
text: Combinations.WALK,
preposition: 'to',
requiresTool: false
},
{
text: Combinations.USE,
preposition: 'on'
},
{
text: Combinations.TOUCH,
requiresTool: false
},
{
text: Combinations.LOOKAT,
preposition: 'at',
requiresTool: false,
failText: (ame: IGame, target: ICombinable, tool: ICombinable): string => {
return 'You look at the ' + target.name + '. There is nothing special about it';
}
}
];
}
When you paste in the code above, TypeScript will complain that it cannot find Combinations by showing it in red. You can fix these errors by hovering over such a highlighted text and using CONTROL + ‘.’ to add the required imports automatically. Make sure to select the right imports, those that are part of your game (in this case, './combinations.ts')!
There are a few things to note here:
- You can see that the combinations have an action text (e.g. 'Use', 'Look') and an optional preposition ('on' or 'at'). In StoryScript, these will be used to create combinations such as ‘Use pen on paper’ or ‘Look at gate’.
- As some combinations require two parts (we’ll call these parts the tool and the target, e.g. Use requires both but Look does not (of course, you could also use binoculars to get a better look, but you get the idea)) you should specify when one does not require a tool.
- You can specify a default text displayed when a combination is tried that doesn’t work in the customTexts.ts file. There are two templates you can override, one for combinations requiring a tool and one for those who don’t. The numbers between the curly brackets are placeholders, and will be replaced at runtime:
- noCombination: "You {2} the {0} {3} the {1}. Nothing happens.". For example: “You use the pen on the paper”.
- 0: The ‘tool’ for the combination
- 1: The ‘target’ for the combination
- 2: The combination name (Use, Look)
- 3: The preposition (on, at)
- noCombinationNoTarget: "You {1} {2} the {0}. Nothing happens.". For example: “You look at the gate”.
- 0: The ‘target’ for the combination
- 1: The combination name (Use, Look)
- 2: The preposition (on, at)
- noCombination: "You {2} the {0} {3} the {1}. Nothing happens.". For example: “You use the pen on the paper”.
- If you want something more specific, you can specify a fail text per combination, as shown above. You can be even more specific when you know what combination is tried on what object, which will be discussed in a bit.
With your combinations defined, they should show up in your browser:

For our combinations to do anything, we need tools and targets for them. These can be any of the following StoryScript entities:
- Features
- Items
- Enemies and Persons
- Barriers
In this part of the tutorial, we’ll focus on features and items. Enemies, persons and barriers will be discussed later.
In StoryScript, you can work with combinations in two ways, which you can mix if you want to. The first is text-based, using text descriptions for your locations and features. You can also go picture-based, which means you use one or more pictures to make your world come to life. We’ll start with a text example in this tutorial, and show how you can build the example using pictures in the next.
To create a location players can interact with, we’ll create some features. Features are noteworthy elements of a location that a player can interact with. Let’s start with a fountain as an example, one found in a forest clearing. Go to your Start.html and change the description element as shown below. Add the feature element in between the text. You can use the ssFeature snippet to do so more quickly:
<description>
<p>
You stand at the edge of a forest clearing. Tall trees border it on all sides,
with thick undergrowth making the clearing hard to get to. In the center of the
clearing is <feature name="fountain">a fountain with a six-sided base</feature>.
</p>
</description>
Second, open the Start.ts file and change it like this. To add a feature in a location, you can use the ssFature-inline snippet (the idea of features is that they are specific to one location so you can declare them inline, unlike e.g. items that are not (usually) bound to one location. You can also create stand-alone features if you want to use them somewhere else as well or to organize your code. That will be shown later):
export function Start() {
return Location({
name: 'Start',
description: description,
features: [
{
name: 'Fountain',
combinations: {
combine: [
{
combinationType: Combinations.LOOKAT,
match: (game, target, tool): string => {
return 'You look at the fountain water. It is very clear and reflects the forest.';
}
}
]
}
}
]
});
}
Your browser should now show you the fountain feature. Press the Look combination button and then the fountain feature to see your match text displayed:

When instead of Look you use Touch, you should see the default fail text as you haven’t specified any combination for the fountain feature and Touch.
Ok, let’s make the Touch action do something as well. When the player walks towards the fountain and touches the water, he’ll hear a soft muttering coming from the undergrowth at the edge of the clearing. This should give him the opportunity to go and check out that spot. For this to work, we need to add a couple of things.
Start with adding a new location called Passage using the npm run scl passage command. Give it a name. This time, we’ll use a stand-alone feature to demonstrate how that works. Use the StoryScript Create Feature snippet to create an empty feature:
npm run scf Corridor
Add the code below to the new Corridor feature, using the ssCombine snippet to add the combine entry:
export function Corridor() {
return Feature({
name: 'Corridor',
description: 'A passage through the undergrowth',
combinations: {
combine: [
{
combinationType: Combinations.WALK,
match: (game, target, tool): string => {
game.changeLocation(Passage);
return 'You crawl through the passage.';
}
},
]
}
});
}
With the new location added, we can make the feature to travel to it available when the Touch combination on the fountain is triggered with a bit more code to the Start.ts file:
{
combinationType: Combinations.LOOKAT,
match: (game, target, tool): string => {
return 'You look at the fountain water. It is very clear and reflects the forest.';
}
},
{
combinationType: Combinations.TOUCH,
match: (game, target, tool): string => {
if (!game.currentLocation.features.get(Corridor)) {
game.currentLocation.features.add(Corridor);
return `You walk towards the fountain and touch the fountain water.
It is a little cold. When you pull back your hand, you hear a soft
muttering. It is coming from a small passage in the undergrowth.`;
}
else {
return 'The fountain water is pleasant to the touch.';
}
}
}
Note how I used the backtick (‘`’) to break up the long text across multiple lines. As you see, the fountain inline feature is becoming big, so in order to organize your code you can also make a stand-alone feature out of it. Do so and clean up te Start.ts file like this:
