Calico

Quand vous écrivez une fiction interactive avec ink, vous avez principalement deux options pour obtenir un jeu jouable : soit votre histoire est intégrée dans un moteur de jeu comme Unity, Godot ou binksi, soit vous faites une exportation web. Calico vient enrichir cette deuxième option.

Dessin d'une bouteille d'encre renversée avec les traces de pattes d'un chat qui a marché dedans.
Logo de Calico.

L’exportation web d’Inky utilise un template relativement simple, qui se contente d’afficher votre histoire avec une apparence minimaliste, des boutons « restart/save/load » et la prise en charge d’une poignée de tags : # AUDIO, # AUDIOLOOP, # IMAGE, # LINK, # LINKOPEN, # BACKGROUND, # CLASS, # CLEAR et # RESTART. Si vous voulez aller plus loin, il faut mettre dans les mains dans le cambouis JavaScript.

Calico (GitHub, itch.io) est un template web alternatif créé par Elliot Herriman qui propose de nombreuses fonctionnalités supplémentaires, intégrées de base ou sous forme d’extensions (que Calico appelle des patches) faciles à activer.

Pour les développeurs JavaScript, plus qu’un template, Calico est aussi un framework. Il permet d’écrire ses propres patches, de définir de nouveaux tags, de nouveaux effets de texte et même d’intercepter tout le texte pour le transformer ligne par ligne. Nous aborderons cette utilisation avancée dans un prochain article.

Quelles fonctionnalités alors ?

De base, Calico gère une partie des tags géré par le template d’Inky. On retrouve # class, # image, # background, # clear et # restart. On perd # audio et # audioloop mais on verra qu’ils sont remplacés par des patches, ainsi que # link et # linkopen qui, à mon avis, ne sont pas très utiles. Si vous en avez besoin, ils sont faciles à réimplanter, on le verra dans l’article sur la création de patches.
En nouveaux tags, nous trouvons # delay, qui sert à insérer une pause entre deux lignes, ainsi que # linebyline, qui permet de s’arrêter à chaque ligne et d’attendre une action du joueur pour continuer, comme dans un visual novel.

En dehors des tags, les autres fonctionnalités de base que j’ai identifiées :

  • Un thème visuel différent, très simple, sans boutons « restart/load/save » ;
  • La possibilité de choisir l’effet de transition des textes (fondu ou aucun effet, mais on peut en créer d’autres soi-même) ;
  • Non nécessité de passer par Inky ou inklecate (le compilateur en ligne de commande), Calico est capable de compiler directement le fichier .ink. C’est très appréciable pendant la rédaction de l’histoire et si vous ne voulez pas que le .ink se retrouve en ligne, vous pouvez basculer facilement sur le JSON avant de publier.

Pour ne pas vous perdre avec vingt projets de démonstration, je les ai regroupés en 3 démos. Vous pouvez voir ces tags en action sur la première de ces démos intégrée juste en dessous ou disponible ici.

Pour l’instant, ça ne fait pas énormément de différences avec l’exportation web d’Inky, mais nous allons maintenant aborder les patches.

Patches

Je vais classer les patches dans deux catégories : ceux qui sont utilisables directement par l’auteur, et ceux qui sont là pour faciliter le développement de nouvelles fonctionnalités.

Patches utilisables tels quels

Commençons par ceux que vous pouvez utiliser tels quels, je pense que ce sont ceux qui vous intéresseront le plus.

Vous pouvez tester ces patches sur la deuxième démo.

audioplayer

Voilà le patch qu’il vous faut pour gérer le son, avec les tags :

# play # playonce # pause # resume # stop

autosave

Ce patch sauvegarde automatiquement la partie après chaque choix.

Attention : par défaut, il n’y a pas de bouton « restart », donc pour pleinement profiter de ce patch, il faudra probablement écrire un peu de code. Par défaut, la sauvegarde ne dure que le temps d’une session (elle est conservée lors d’un rechargement de la page mais est perdue dès qu’on ferme l’onglet).

dragtoscroll

Ce patch ajoute la possibilité de faire défiler la page en faisant un cliquer-glisser avec la souris.

forcetagsbeforeline

L’exportation web d’Inky diffère de Calico sur la gestion des tags :

Ligne 1 # tag1
# tag2
Ligne 2 # tag3

Dans ce cas, l’exportation web standard associe # tag2 à « Ligne 2 » tandis que Calico l’associe à « Ligne 1 ». Si vous souhaitez retrouver le comportement par défaut, utilisez ce patch.

markdowntohtml

Ce patch permet d’utiliser le format Markdown dans ink. Bien sûr, il faudra échapper avec \ les caractères qui entrent en conflit avec la syntaxe d’ink.

Comme ink travaille ligne par ligne, on ne peut pas utiliser les styles multilignes comme les listes.

minwordsperline

Un patch bien spécifique : il évite les mots orphelins sur une ligne en fin de paragraphe. Par défaut, il s’arrange pour qu’il y ait au minimum deux mots sur une ligne.

parallaxframes

Ce patch permet de créer un effet de parallaxe avec plusieurs images.

Si vous ne connaissez pas l’effet de parallaxe, le plus simple est de tester Winter, la FI pour laquelle a été initialement créé Calico. L’effet est utilisé dès l’écran titre et régulièrement dans le reste de l’histoire.

preload

Si vous utilisez des sons ou des images, ce patch est très utile : il les précharge avant de lancer l’histoire. Une barre de chargement personnalisable avec du CSS affiche la progression du préchargement.

scrollafterchoice

Par défaut, quand on fait un choix, le texte apparaît en dessous mais ne défile pas. Pour avoir un défilement automatique jusqu’au début du nouveau texte, il suffit d’inclure ce patch.

shorthandclasstags

Un petit patch de confort, il permet d’écrire # pouet au lieu de # class: pouet, utile si vous avez beaucoup de classes dans votre texte.

storage

Celui-ci sert surtout pour ceux qui veulent coder des fonctionnalités de sauvegarde, mais il expose également côté ink deux fonctions externes utiles : get(nomDeVariable) et set(nomDeVariable, valeur). Ça pourrait servir à un jeu de type Die & Retry pour mémoriser le nombre d’essais par exemple.

storylets

Ce patch est un théoriquement un gros morceau : il ajoute la fonctionnalité des narramiettes (storylets) à ink !

Je précise « théoriquement » parce que pour l’instant, ce patch ne peut afficher qu’une narramiette à la fois, ce qui limite beaucoup l’intérêt. À la date de publication de cet article, je travaille sur une version plus complète de ce patch, qui sera peut-être intégrée à Calico dans quelques temps. ?

Rentrer dans le détail serait un peu long pour cet article, mais dans les grandes lignes, voici comment ça marche :

  • Une narramiette est représentée par un nœud avec un tag # storylet (ou # storylet: category pour regrouper les narramiettes).
  • Dans ce nœud, des mailles permettent de définir les conditions d’activation de la narramiette, son urgence, son exclusivité et bien sûr son contenu.
  • On affiche ensuite les narramiettes disponibles avec un tunnel -> openStorylets -> (ou -> filteredStorylets(category) ->)

Patches requérant un peu de code

Passons maintenant aux patches qui nécessitent que vous écriviez un peu de JavaScript pour en faire quelque chose. Vous pouvez suivre la troisième démo en parallèle.

autosave

Et oui, je place celui-ci dans les deux catégories. Il fonctionne tout seul mais pour vraiment en tirer partie, il faut travailler un peu. Au minimum, ajouter dans le HTML un bouton « recommencer » pour ne pas être coincé dans une sauvegarde sans possibilité de revenir à zéro. Vous voudrez probablement aussi changer la méthode de stockage. Par défaut, c’est du session storage, ce qui veut dire que la sauvegarde est conservée si on recharge la page mais est perdue dès qu’on ferme l’onglet. autosave utilise le patch memorycard pour la sauvegarde et c’est l’option memorycard_format qui permet de changer de format. Vous avez trois options : cookies, session, local.

eval

L’usage de ce patch n’est pas recommandé par la développeuse de Calico mais je pense qu’il peut être utile pour faire des choses simples rapidement.

En activant ce patch, on peut exécuter du JavaScript directement depuis le code ink avec le tag # eval.
Écrire son propre patch est la meilleure façon d’ajouter une fonctionnalité, mais dans le rush d’une gamejam, on n’a pas toujours le temps de faire les choses bien.

shortcuts/choices

Ce patch permet d’associer des touches du clavier aux choix ink. Par exemple la touche 1 pour le choix 1, la 2 pour le choix 2… Ou A, B, C… Ou la barre d’espace quand qu’il n’y a qu’un seul choix possible.

stepback

stepback ajoute la possibilité de revenir en arrière (dans la pratique, il redémarre l’histoire et rejoue tous les choix qu’il a mémorisé, sauf le dernier). Sauf que cette fonctionnalité n’est pas directement accessible au joueur. C’est à vous d’ajouter un bouton ou autre mécanisme pour déclencher ce retour en arrière. Ce patch permet aussi de faire un retour en avant.

storage, history, memorycard

Je regroupe ces trois-là ensemble car ils sont surtout utiles si vous voulez créer vos propres patches.
memorycard et storage sont utilisés par autosave, mais vous pourriez vous en servir pour ajouter des boutons manuels « sauvegarder/charger ».

history est utilisé par autosave et stepback. Vous pourriez l’utiliser pour afficher l’ensemble des choix précédents et permettre de revenir à n’importe quel choix précédent.

Comment utiliser Calico

Ces fonctionnalités vous inspirent et vous voulez les exploiter dans votre projet ?
Je vais vous expliquer comment utiliser le template de base et comment le modifier à votre guise.

Désagréable prérequis

Il y a malheureusement un détail technique un peu pénible à traiter avant de démarrer. Calico est, grosso modo, un fichier HTML que vous allez télécharger sur votre ordinateur et ouvrir. Les navigateurs ne traitent pas de la même façon un fichier HTML s’il vient d’un site web ou s’il vient de votre ordinateur et certaines fonctionnalités sont ainsi bloquées. Calico a besoin de ces fonctionnalités. Il va donc falloir faire « comme si » votre fichier venait d’un serveur web.

C’est contraignant mais il n’y a pas le choix. D’ailleurs, c’est pareil pour Vorple et nous avons déjà un article qui explique comment avoir un serveur local.

Vous pouvez donc soit suivre ces instructions (identiques pour Vorple ou Calico), soit utiliser Catmint, un logiciel créé par la développeuse de Calico spécialement pour les jeux ink.

Le principe est très simple : dans Catmint, vous ouvrez votre HTML et voilà. Catmint va vous afficher le résultat. Si vous modifiez votre projet, il suffit de le recharger dans Catmint avec ctrl + R (ou cmd + R).

Bonus : Catmint compile automatiquement en JSON tous les .ink qu’il trouvera dans votre projet. C’est moins utile depuis la version 2 de Calico qui peut gérer directement les .ink, mais si vous travaillez sur un projet ink sans Calico, ça peut être utile.

Le template de base

Il vous faut maintenant récupérer le template de base. Pour avoir la dernière version, allez sur la page des dernières sorties de Calico et cherchez le lien « Calico.template.zip » le plus récent. Au moment où j’écris, il s’agit de la version 2.0.3 : Calico.Template.zip.

Décompressez ce fichier zip et vous obtiendrez ces fichiers :

# Les plus intéressants pour nous

index.html -- Le fichier à ouvrir dans Catmint ou sur un site web.
project.js -- C'est ici que vous configurez Calico et importez des patches.
story.ink -- Votre histoire ink.
style.css -- Le CSS de Calico auquel vous pouvez ajouter le vôtre.
patches/ -- Le dossier des patches que vous pouvez importer.

# Les autres

calico.js -- Le moteur Calico, on n'y touche pas.
ink.js -- Le moteur et compilateur Iink, on n'y touche pas non plus.
story.json -- Votre histoire ink compilée en JSON, inutile depuis la v2.
README.txt -- Contient un peu (très peu) d'infos sur Calico.

Dans la majorité des cas, vous ne toucherez qu’aux fichiers story.ink et project.js. Si vous ne connaissez pas le JavaScript, aucune inquiétude, vous n’allez pas vraiment coder, juste importer des patches et configurer des options.

Avant de s’attaquer à ces patches et à leurs options, lançons notre jeu !

Soit vous ouvrez index.html dans Catmint, soit vous y accédez via votre serveur web local (il faut probablement ouvrir http://localhost:8000/ dans votre navigateur mais ça peut varier suivant la méthode que vous avez choisie).

Et voici le résultat :

Capture d'écran d'une fenêtre. Le titre de l'application est « Catmint ». Le texte est : « Once upon a time… There were two choices. There were four lines of content. »
Capture d’écran de Catmint montrant l’histoire ink de base (Once upon a time…).

Dans la suite de cet article, nous allons modifier plusieurs fichiers (story.ink, project.js…). Vous pouvez éditer ces fichiers dans n’importe quel éditeur de texte brut (comme le Bloc-notes, TextEdit, Notepad++, VS Code…) mais pas dans un traitement de texte (comme Word ou LibreOffice).

Le project.js fourni n’est pas tout à fait minimaliste, il inclut déjà quatre patches dont un qui nécessite un peu de code. Puisque vous ne voulez pas forcément ces patches, je vous propose de démarrer avec un fichier beaucoup plus simple :

// Création du jeu
var story = new Story("story.ink");

Et oui, c’est plus simple, c’est minimal mais ça suffit. Si vous ne voulez pas que votre fichier ink s’appelle story.ink, c’est ici que vous pouvez mettre le bon nom.

Changer les options

Les fonctionnalités de base de Calico proposent une dizaine d’options que vous pouvez modifier facilement. L’objectif de cet article n’étant pas d’être une documentation complète, je vous renvoie à la documentation officielle pour le détail, mais je vais quand même vous faire un résumé :

  • passagedelay, linedelay, showlength, hidelength, suppresschoice sont toutes des options qui correspondent à des délais d’animation. Si vous voulez changer la vitesse à laquelle sont animés les textes, c’est avec ces options qu’il faut jouer. Par exemple, en les mettant toutes à 0, vous supprimez toutes les animations (mais utilisez plutôt textanimation pour ça).
  • defaultimageformat, defaultaudioformat, defaultimagelocation, defaultaudiolocation servent à trouver vos images et sons. Quand vous écrivez # image: boticelli, Calico le transforme en images/boticelli.png, par exemple.
  • textanimation permet de choisir l’animation des textes, mais vous n’avez le choix qu’entre « fade » et « none », c’est-à-dire « fondu » et « aucune ».

Voyons maintenant le code pour changer ces options. Dans project.js, il faut ajouter une ligne par option avant la création de story :

// Options
options.linedelay = 100;
options.passagedelay = 100;

// Création du jeu
var story = new Story("story.ink");

Importer un patch

C’est à peine plus compliqué, il faut également ajouter une ligne par patch, avant les options.
La ligne à ajouter est de la forme import "./chemin/vers/le/patch.js"
Le chemin doit être relatif au fichier project.js et doit commencer par “./”. Comme les patches sont dans le dossier patches, ça donne, par exemple, pour le patch minwordsperline :

// Importation des patches
import "./patches/minwordsperline.js";

// Options
options.linedelay = 100;
options.passagedelay = 100;

// Création du jeu
var story = new Story("story.ink");

Configurer un patch

Pour donner ses options à un patch c’est exactement comme pour les options de base :

// Importation des patches
import "./patches/minwordsperline.js";

// Options
options.linedelay = 100;
options.passagedelay = 100;
// Options des patches
options.minwordsperline_length = 3;

// Création du jeu
var story = new Story("story.ink");

Il n’y a pas d’ordre dans les options, il faut juste qu’elles soient toutes entre les import et le new Story.
Pour connaître les options d’un patch, ouvrez son fichier dans un éditeur de texte et cherchez la ligne var options = {. Les lignes qui suivent sont les options.
Par exemple :

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,
};

Si l’accolade ouvrante { est immédiatement suivie d’une accolade fermante }, comme ceci var options = {}, c’est qu’il n’y a pas d’options.

Cas particuliers

Il y a quelques rares exceptions à cette simplicité.

Parfois les options sont plus complexes qu’un simple nombre ou un simple texte. Par exemple, le patch preload permet de définir pour quels tags il faut essayer de détecter les fichiers à précharger et dans quelle catégorie (audio, image, other).
Si vous utilisez audioplayer, il faut dire à preload d’analyser les tags # play et # playonce pour la catégorie audio.

options.preload_tags.audio.push("play", "playonce");

L’autre cas complexe que vous pouvez rencontrer concerne les patches avancés qui nécessitent du code. Il y a toutes les chances que si vous utilisez ces patches, vous n’ayez pas besoin de cette explication mais allons-y quand même.

Il est possible que vous ayez besoin d’accéder aux fonctions et variables exposées par le patch. Il faut dans ce cas modifier l’importation :

// On importe choices.js et on nomme le résultat `choices`.
import choices from "./patches/shortcuts/choices.js";

// Sur ce 'choices', on trouve la fonction add()
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);

Un exemple complet pour conclure

Terminons cet article en réalisant un exemple complet.

Nous allons écrire une histoire ink qui utilise des tags Calico et du CSS. Nous allons créer une interface de style « messagerie instantanée » avec deux interlocuteurs : le joueur, identifié par des bulles bleues alignées à droite, et un mystérieux inconnu, identifié par des bulles blanches alignées à gauche.

Commençons par regarder le résultat.

L’histoire n’est pas palpitante mais elle utilise quelques unes des fonctionnalités présentées et c’est tout ce qu’on lui demande. Pour réaliser cette histoire, nous allons modifier trois fichiers : story.ink, style.css, project.js.

story.ink

+ [Ouvrir l'application Messagerie] # clear
-

Salut, t'es prêt pour vendredi ? # class: inconnu 
+ Je ne reconnais pas ce numéro, qui êtes vous ? [] # class: joueur # playonce: envoi
    Fais pas l'andouille, c'est @smwhr. Bon, il est prêt ton article ? # class: inconnu # delay: 1000
+ Vendredi ? Il se passe quoi vendredi ? [] # class: joueur # playonce: envoi
    Ton article pour Fiction-interactive.fr ? # class: inconnu # delay: 1000
    Au fait, c'est @smwhr, je sais pas si t'avais mon numéro. # class: inconnu # delay: 500
+ De ouf que je suis prêt ! Mais t'es qui ? [] # class: joueur # playonce: envoi
    C'est @smwhr et on attend ton article. Tu m'as supprimé de tes contacts ou quoi ? # class: inconnu # delay: 1000
-
+ Ouais, ouais, mon article est prêt [] # class: joueur # playonce: envoi
    Enfin ! On peut publier alors ! # class: inconnu # delay: 1000
+ Juste une petite démo à ajouter [] # class: joueur # playonce: envoi
    Magne-toi, t'as déjà loupé le créneau de vendredi dernier… # class: inconnu # delay: 1000
-
Fin de cette histoire épique
+ [Recommencer] # restart

style.css

Cet article n’étant pas un tutoriel sur la création de bulles de chat en CSS, je ne m’étends pas sur ce code. Il faut ajouter à la fin du style.css existant (et non tout remplacer) :

/* Tout les paragraphes dans une colonne, par défaut à gauche (start). */
#story {
  display: flex;
  flex-direction: column;
  align-items: start;
}

/* Un style "bulle de chat" pour inconnu et joueur */
.inconnu,
.joueur {
  position: relative;
  padding: 16px;
  border-radius: 8px;
}
.inconnu:before,
.joueur:before {
  content: "";
  position: absolute;
  bottom: 0;
  right: 100%;
  width: 0;
  border-top: 12px solid transparent;
}

/* Inconnu à gauche, en noir sur blanc */
.inconnu {
  background-color: white;
  color: black;
  align-self: start;
  border-bottom-left-radius: 0;
}
.inconnu:before {
  right: 100%;
  border-right: 15px solid white;
}

/* Joueur à droite, en blanc sur bleu */
.joueur {
  background-color: rgb(72, 120, 250);
  color: white;
  align-self: end;
  border-bottom-right-radius: 0;
}
.joueur:before {
  left: 100%;
  border-left: 15px solid rgb(72, 120, 250);
}

/* Et un peu de style pour les choix */
.choice {
  border: 1px solid grey;
  padding: 8px;
  border-radius: 16px;
}

project.js

Et enfin le fichier qui importe et configure tout ça :

import "./patches/audioplayer.js";
import "./patches/preload.js";
import "./patches/minwordsperline.js";
import "./patches/stepback.js";
import choices from "./patches/shortcuts/choices.js";
// Premier bonus : 
// Ce patch n'est pas encore dispo sur la v2.0.3 mais devrait l'être dans la prochaine version.
// Il est facile d'aller le récupérer sur le GitHub du projet Calico.
import "./patches/fadeafterchoice.js"

// On veut précharger les sons.
options.preload_tags.audio.push("playonce");
// On n'affiche la barre de chargement que si ça prend plus de 500ms.
options.preload_timeout = 500;

// On supprime le fondu par défaut
options.audioplayer_fadein = 0;
options.audioplayer_fadeout = 0;

// Les anciens paragraphes sont semi-transparents pour mieux visualiser les nouveaux.
options.fadeafterchoice_fadelevel = 50;

// On connecte les boutons 1 à 3 aux 3 premiers choix ink.
choices.add("1", 0, true);
choices.add("2", 1, true);
choices.add("3", 2, true);

// On crée la story
var story = new Story("story.ink");

// En second bonus, un aperçu d'une fonctionnalité avancée dont je n'ai pas parlé !
// On peut facilement détecter n'importe quelle touche et déclencher une action.
// Ici, les flèches gauche et droite permettent de revenir en arrière ou en avant.
Shortcuts.add("ArrowLeft", () => story.stepBack());
Shortcuts.add("ArrowRight", () => story.stepForwards());

Si vous avez lu attentivement ce code, vous avez vu que j’ai ajouté deux petits bonus pour vous donner envie de creuser (et aussi pour vous donner envie de lire mon prochain article ?) :

  • J’ai inclus un patch que je n’ai pas présenté ! Et pour cause, il vient tout juste d’être ajouté au code de Calico et n’a pas encore été publié officiellement. Sa fonction ? Rendre partiellement transparents les anciens passages pour mettre en avant les nouveaux.
  • J’utilise stepback que j’ai présenté mais je le combine à une fonctionnalité avancée : les shortcuts, qui permettent de déclencher du code quand le joueur appuie sur une touche spécifique.

Avec tout ça, vous êtes équipé pour faire vos premiers pas avec Calico. N’hésitez pas à passer sur le Discord pour nous montrer vos réalisations ou demander de l’aide !