Calico
When you write an interactive fiction with ink, you have two main options to have a playable game: either your story is embedded in a game engine like Unity, Godot or binksi, or you use Inky’s « export for web » feature. Calico enhances that second option.
Inky’s web export uses a relatively simple template, which displays your story in a minimalist style, « restart/save/load » buttons, and support for a handful of tags: # AUDIO
, # AUDIOLOOP
, # IMAGE
, # LINK
, # LINKOPEN
, # BACKGROUND
, # CLASS
, # CLEAR
and # RESTART
. If you want to go further, you’ll have to get your hands dirty (with JavaScript).
Calico (GitHub, itch.io) is an alternative web template created by Elliot Herriman which provides many extra features, some included and some as easy to activate plugins (that Calico calls patches).
For JavaScript developers, more than a template, Calico is a framework. It allows you to write your own patches, to define new tags, new text effects and even to intercept all the text to transform it line by line. We’ll talk about this advanced usage in a future article (available in French).
So, which features?
Calico handles some of the tags handled by Inky’s template. There’s # class
, # image
, # background
, # clear
and # restart
. We lose # audio
and # audioloop
but we’ll see that they are replaced by patches, as well as # link
et # linkopen
which are, in my opinion, not very useful.
If you need them, they are easy to reimplement, as we’ll see in the article about writing patches.
There are new tags: # delay
, which inserts a pause between lines, and # linebyline
, which stops after each line and waits for an action from the player to continue, like in a visual novel.
In addition to tags, the other basic features I found:
- a different visual theme, very simple, without « restart/load/save » buttons
- the possibility of choosing the text transition effect (between fade or no effect, but one can create other effects)
- no need to use Inky or inklecate (the command line ink compiler), Calico is able to compile
.ink
by itself. It’s a great help when you’re writing your story and if you don’t want to publish the.ink
file online, you can easily switch to JSON before publishing.
I don’t want to confuse you with twenty demo projects so I grouped them in 3 demos. You can see the tags in action in the first demo, embedded here or on itch.io
At the moment, it isn’t that different from Inky’s web export, but we’ll now talk about the patches.
Patches
I’ll sort the patches in two categories: those that can be used as-is by the author, and those that are made to ease the development of new features.
Patches usable as-is
Let’s start with those you can use right now, as they might be the most interesting to you.
You can try these patches in the second demo.
audioplayer
That’s the patch you need to handle sound, with these tags:
# play # playonce # pause # resume # stop
autosave
This patch automatically saves the game after each choice.
Beware: by default, there’s no restart button, so you’ll probably need to write some code to take full advantage of this patch. By default, the save only last for the duration of a session (it’s preserved during a page reload but it’s lost when the tab is closed).
dragtoscroll
This patch adds the possibility to scroll the page by clicking and dragging with the mouse.
forcetagsbeforeline
Inky’s web export handles tags differently than Calico:
Line 1 # tag1
# tag2
Line 2 # tag3
In this example, the standard web export associates # tag2
to « Line 2 » whereas Calico associates it to « Line 1 ». If you prefer the standard behaviour, use this patch.
markdowntohtml
This patch allows you to use Markdown in ink. Of course, you’ll need to escape with a \
the characters conflicting with ink’s syntax.
Since ink works line by line, multiline Markdown features such as lists are not available.
minwordsperline
A very specific patch: it avoids orphan words on a line at the end of a paragraph. By default it ensures that there are at least two words on a line.
parallaxframes
This patch creates a parallax effect with several images.
If you’re not familiar with the parallax effect, just check out Winter, the IF for which Calico was created. The effect is used on the title screen and regularly in the rest of the story.
preload
If you’re using sounds or images, this patch is very useful: it preloads them before starting the story. A loading bar shows the progression and is customisable with CSS.
scrollafterchoice
By default, when a choice is made, the text appears below but doesn’t scroll. To automatically scroll to the start of the new text, you just need to include this patch.
shorthandclasstags
A small convenience patch: it allows writing # quack
instead of #class: quack
if you have a lot of classes in your text.
storage
This one is mostly useful if you’re willing to code some save features but it also expose two useful external functions to the Ink side: `get(variableName)` et `set(variableName, value)`. It could be used by a *Die & Retry* type game to remember the number of tries for instance.
storylets
This patch is theori
This patch is theoretically a big one : it adds storylets to ink!
I say « theoretically » because for now, ce patch only show one storylet at once, which is very limited. I have my own version of this patch which is a lot more powerful if you are interested: https://github.com/floriancargoet/ink-storylets
Going into the details would be a little too long for this article, but in a nutshell, here’s how it works (the Calico version, not mine):
- A storylet is represented by a knot with a
# storylet
tag (or# storylet: category
to group storylets). - In that knot, stitches define the activation condition of the storylet, its urgency, its exclusivity and of course its content.
- Available storylets are then shown with a tunnel:
-> openStorylets ->
(or-> filteredStorylets(category) ->
)
Patches requiring a bit of code
Now, let’s see patches which require that you write a little bit of JavaScript to be useful. You can follow the third demo in parallel.
autosave
Yes, I’m putting this one in both categories. It works alone but to really to advantage of this one, some work is required.
At the very least, adding a Restart button in the HTML to avoid being blocked in a save without the ability to reset the story. You’ll probably also want to change the storage method. By default, it uses session storage, which means the save is preserved when reloading but lost when closing the tab. autosave depends on the memorycard patch for saving and the memorycard_format option allows changing the method. You have three options: cookies, session, local.
eval
Calico’s developer discourages the use of this patch but I think it can be useful to make simple things quickly. By enabling this patch, you can execute JavaScript from the ink code with the #eval
tag.
Writing your own patch is the best way to add a feature, but in the rush of a game jam, you don’t always have the time to do things the right way.
shortcuts/choices
This patch allows associating keyboard keys to Ink choices. For instance, the key 1 for the first choice, the key 2 for the second choice … Or A, B, C … Or the spacebar when there’s only one choice.
stepback
stepback adds the possibility of going back (in pratice it restarts the story and replays every choice except the last one). However, this feature is not directly available to the player. You have to add a button or another mechanism to trigger that step back. This patch also allows going forward.
storage, history, memorycard
I’m grouping these three together because they are mostly useful if you want to create your own plugins.
memorycard and storage are used by autosave but you could use it to add Save/Reload buttons to trigger it manually.
history is used by autosave and stepback. You could use it to display all the previous choices and allow going back to any point.
How to use Calico
Are you inspired by these features and want to use them in your project?
I’ll explain how to use the default template and how to modify it as you wish.
Unpleasant prerequisite
Sadly, there one technical detail, a bit painful, that we have to deal with before we can start. Calico is, roughly, an HTML that you’ll download and open on your computer. Web browsers do not handle an HTML in the same way whether it comes directly from a website or if it comes from your computer. Some features are blocked. Calico needs these features. So we’ll have to pretend our file comes from a webserver.
It’s inconvenient, but there’s no choice. By the way, it’s the same for Vorple and we have an article explaining how to use a local server.
You can either follow these instructions (in French, identical for Vorple or Calico), or use Catmint, a software written by Calico’s developer just for ink games.
The way is works is very simple: in Catmint, you open your HTML and voilà. Catmint will display the result. If you edit your project, just reload it in Catmint with ctrl + R
(or cmd + R
).
Bonus: Catmint automatically compiles to JSON every .ink
file it finds in your project. It’s less useful since the version 2 of Calico which can directly handle .ink
files, but if you work on an ink project without Calico, it can be nice.
The base template
Now, you’ll need to get the base template. To get the latest version, go to Calico’s releases page and look for the most recent « Calico.template.zip » link. As I’m writing this article, it’s the 2.0.3 version: Calico.Template.zip.
Uncmpress that zip file and you’ll get these files:
# The most interesting for us
index.html -- The file to oopen in Catmint or on a web site.
project.js -- Here you'll configure Calico and import patches.
story.ink -- Your ink story.
style.css -- Calico's CSS, where you can add your own styles.
patches/ -- The folder of patches you can import.
# The others
calico.js -- The Calico engine, don't touch it.
ink.js -- The ink engine and compiler, don't touch it either.
story.json -- Your ink story compiled to JSON, useless since v2.
README.txt -- Contains a little (very little) infos about Calico.
In most cases, you’ll only edit story.ink
and project.js
. It you don’t know JavaScript, don’t fret, we won’t really code, we’ll only import patches and configure them.
Before diving in these patches and their options, let’s launch our game!
Either you open index.html
in Catmint, or you access it through your local web server (you probably need to open http://localhost:8000/
in your browser but it depends).
And here is the result:
For the rest of this article, we’ll modify several files (story.ink, project.js
…). You can edit them in any text editor (such as Notepad, TextEdit, Notepad++, VS Code…) but not in a word processor (such as Word or LibreOffice).
The provided project.js
file is not exactly minimalist, it already includes four patches, one of which requires a bit of code. Since you might not want these patches, I suggest you start with a much simpler file:
// Create the story
var story = new Story("story.ink");
Yes, that’s simpler, that’s minimal but that’s enough. If you don’t want your ink file to be called story.ink
, that’s were you can put the correct name.
Change the options
Calico’s basic features provide a dozen of options you can easily change. Being an exhaustive documentation is not an objective of this article, so I’ll redirect you to the official documentation for the details but I’ll still sum it up:
passagedelay
,linedelay
,showlength
,hidelength
,suppresschoice
are all options for animation timings. If you want to change the speed of text animations, play with these options. For instance, by setting all of them to 0, you effectively remove all animations (but you should instead usetextanimation
to that effect).defaultimageformat
,defaultaudioformat
,defaultimagelocation
,defaultaudiolocation
help find your images and sounds. When you write# image: boticelli
, Calico rewrites it toimages/boticelli.png
, for instance.textanimation
allows you to select the text animation effect, but you can only choose betwen « fade » and « none ».
Now let’s see how we can change these options. In project.js
, you need to add one line per option before creating the story object.
// Options
options.linedelay = 100;
options.passagedelay = 100;
// Create the story
var story = new Story("story.ink");
Import a patch
It’s hardly more complicated, you need to add one line per patch, before the options.
The line to add follow this shape: import "./path/to/the/patch.js"
The path must be relative to the project.js
file and must start with ./
. Since patches are in the patches
folder, the result is for instance for the minwordsperline patch:
// Import the patches
import "./patches/minwordsperline.js";
// Options
options.linedelay = 100;
options.passagedelay = 100;
// Create the story
var story = new Story("story.ink");
Configure a patch
Providing options for a patch works in exactly the same way as it does for base options:
// Import the patches
import "./patches/minwordsperline.js";
// Options
options.linedelay = 100;
options.passagedelay = 100;
// Patches options
options.minwordsperline_length = 3;
// Create the story
var story = new Story("story.ink");
Order of options is not important as long as they are between the import
s and the new Story
.
To know the options of a patch, open its file in a text editor and look for this line: var options = {
. The lines after it are the options.
For example :
var options = { // if false, will stop previous tracks when adding a new one audioplayer_allowmultipletracks: false, // how long it takes by default to fade in a track audioplayer_fadein: 2000, // how long it takes by default to fade out a track audioplayer_fadeout: 2000, };
If the opening brace {
is immediately followed by a closing one }
, like that: var options = {}
, it means there’s no option.
Special cases
There is a few exceptions to this simplicity.
Sometimes, options are more complex that a simple number or text. For instance, the preload patch allows to define
Parfois les options sont plus complexes qu’un simple nombre ou un simple texte. For example, the preload patch lets you define which tags to try to detect files for preloading, and in which category (audio, image, other).
If you’re using audioplayer, you need to tell preload to analyse the # play
et # playonce
tags for the audio category.
options.preload_tags.audio.push("play", "playonce");
The other complex case you may encounter is when you use advanced patches which require code. If you’re using them, I guess you don’t need this explanation but I’ll explain anyway.
It’s possible you’ll need to access functions and variables exposed by the patch. In that case, you’ll have to edit the import:
// We import choices.js and name the result `choices`.
import choices from "./patches/shortcuts/choices.js";
// On that 'choices' object, we'll find the add() function
choices.add("1", 0, true);
choices.add("2", 1, true);
choices.add("3", 2, true);
choices.add("4", 3, true);
choices.add(" ", 0, true, true);
A complete example to conclude
Let’s finish this article by writing a complete example.
We are going to write an ink story with tags and CSS. We’ll create an « instant messaging app » style with two characters: the player, identified by right-aligned blue bubbles, and a mysterious stranger, identified by left-aligned white bubbles.
Let’s take a look at the result first.
The story is not captivating but it uses some of the features we’ve talked about and that’s all we expect of this story. To create it, we’ll edit three files: story.ink
, style.css, project.js
.
story.ink
+ [Open the Messages application] #clear
-
Hi, are you ready for Friday? #class: unknown
+ I don't know this number, who are you? [] #class: player #playonce: send
Don't be stupid, it's @smwhr. So, is your article ready? #class: unknown #delay: 1000
+ Friday? What happens on Friday? [] #class: player #playonce: send
Your article for fiction-interactive.fr? #class: unknown #delay: 1000
By the way, it's @smwhr, I don't know if you had my number. #class: unknown #delay: 500
+ I'm SO ready! But who's this? [] #class: player #playonce: send
It's @smwhr and everyone is waiting for your article. Did you delete me from your contacts or something? #class: unknown #delay: 1000
-
+ Yeah, yeah, my article is ready [] #class: player #playonce: send
At last! Let's publish it then! #class: unknown #delay: 1000
+ Just one small demo to add [] #class: player #playonce: send
Hurry up, you already missed last Friday's slot … #class: unknown #delay: 1000
-
End of this epic story
+ [Restart] #restart
style.css
This article is not a tutorial on CSS chat bubbles so I won’t comment this part. It has to be added at the end of the existing style.css
(don’t delete the rest of the file):
/* All paragraphs in a column, to the left (start) by default. */
#story {
display: flex;
flex-direction: column;
align-items: start;
}
/* A chat bubble style for unknown and player */
.unknown,
.player {
position: relative;
padding: 16px;
border-radius: 8px;
}
.unknown:before,
.player:before {
content: "";
position: absolute;
bottom: 0;
right: 100%;
width: 0;
border-top: 12px solid transparent;
}
/* unknown to the left, black on white */
.unknown {
background-color: white;
color: black;
align-self: start;
border-bottom-left-radius: 0;
}
.unknown:before {
right: 100%;
border-right: 15px solid white;
}
/* player to the right, white on blue */
.player {
background-color: rgb(72, 120, 250);
color: white;
align-self: end;
border-bottom-right-radius: 0;
}
.player:before {
left: 100%;
border-left: 15px solid rgb(72, 120, 250);
}
/* And a but of style for the choices */
.choice {
border: 1px solid grey;
padding: 8px;
border-radius: 16px;
}
project.js
And at last, the file importing and configuring everything:
import "./patches/audioplayer.js";
import "./patches/preload.js";
import "./patches/minwordsperline.js";
import "./patches/stepback.js";
import choices from "./patches/shortcuts/choices.js";
// This patch is not yet available in version 2.0.3 but should be there in the next release.
// It's easy to retrieve in on Calico's GitHub repository.
import "./patches/fadeafterchoice.js";
// We want to preload the sounds.
options.preload_tags.audio.push("playonce");
// Only show the loading bar if it takes more than 500ms.
options.preload_timeout = 500;
// Disabled the default fade.
options.audioplayer_fadein = 0;
options.audioplayer_fadeout = 0;
// Old paragraphs are half-transparent to better see the new ones.
options.fadeafterchoice_fadelevel = 50;
// Link keys 1, 2, 3 to the 3 first Ink choices.
choices.add("1", 0, true);
choices.add("2", 1, true);
choices.add("3", 2, true);
// Instanciate the story
var story = new Story("story.ink");
// As a bonus, a preview of an advanced feature I didn't talk about!
// You can easily detect any key press and trigger an action.
// Here, the left and right arrows can be used to navigate the story backwards or forwards.
Shortcuts.add("ArrowLeft", () => story.stepBack());
Shortcuts.add("ArrowRight", () => story.stepForwards());
If you’ve read this code with attention, you’ve notived that I added two small extras to encourage you to dig deeper (and also to inspire you to read my next article? Available in French):
- I’ve included a patch I didn’t show before! J’ai inclus un patch que je n’ai pas présenté ! And for good reason, it’s just been added to Calico and not officially released yet. What does it do? It makes previous paragraphs partially transparent to highlight the new ones.
- I’m using stepback, which I’ve presented, but I’ve combined it with an advanced feature: shortcuts, which trigger code when the player presses a specific key.
With all this, I hope you’re ready to take your first steps with Calico!