Guide d’écriture Loreline

Ce guide vous donne toutes les clés pour écrire des histoires interactives avec Loreline. Aucune expérience en programmation n’est requise. Si vous savez écrire un scénario ou une histoire à choix, vous serez en terrain connu.

Commençons par un exemple simple :

Une bonne odeur de café flotte dans la salle.

barista: Salut ! Comment tu vas aujourd'hui ?

choice
  Je passe une excellente journée
    barista: Formidable ! Le café va la rendre encore meilleure.

  Besoin de caféine...
    barista: N'en dis pas plus ! Laisse-moi m'en occuper.

  Tu t'appelles Alex, non ?
    barista.name = "Alex"
    barista: Oh, je ne m'attendais pas à ce que tu t'en souviennes !

Même si ce type de script est nouveau pour vous, son fonctionnement est intuitif : il décrit une scène dans un café et propose des choix qui mènent à des résultats différents.

Concepts fondamentaux

Voyons comment Loreline vous aide à créer des histoires interactives. Nous commencerons par les éléments de base et progresserons graduellement vers des fonctionnalités plus complexes.

Structure narrative et beats

Vous pouvez écrire vos dialogues dès le début d'un script, mais à mesure que l'histoire s'étoffe, une meilleure organisation deviendra nécessaire. C’est ici qu’interviennent les « beats » : des sections regroupant des scènes ou des moments liés. Pensez aux beats comme aux chapitres ou scènes de votre histoire :

beat EntrerCafe
  Le soleil du matin filtre à travers les vitres du café tandis que tu entres.

  barista: <friendly> Bienvenue ! Je ne crois pas t'avoir déjà vu ici.

  choice
    Je regarde juste un peu
      barista: Prends ton temps ! Je suis là quand tu es prêt.
      -> ExplorerMenu

    En fait, j'aurais bien besoin d'un café
      barista: <happy> Tu es au bon endroit !
      -> PrendreCommande

beat ExplorerMenu
  À côté de toi, une cliente régulière sirote sa boisson avec contentement.

  sarah: Leurs lattés sont incroyables. Je viens ici chaque matin.

  barista: <cheerful> Sarah a raison ! Tu veux en essayer un ?

  choice
    Bien sûr, je prendrai la même chose qu'elle
      sarah: <pleased> Excellent choix !
      -> PrendreCommande

    Qu'est-ce que tu recommandes d'autre ?
      -> PrendreCommande

beat PrendreCommande
  barista: Alors, qu'est-ce que je te prépare ?

  choice
    Un latté, ce serait parfait
      barista: <excited>
        Ça arrive tout de suite !
        Je vais le préparer spécialement pour ta première visite.

      sarah: <smile> Tu ne le regretteras pas.
      -> FinVisite

    Juste un café classique pour aujourd'hui
      barista: Parfois, les classiques sont le meilleur choix !
      -> FinVisite

beat FinVisite
  Tu trouves un coin confortable pour déguster ta boisson.

  sarah: <friendly> J'espère te revoir bientôt !

La notation en flèche (->) permet de naviguer entre les beats et de créer une intrigue à embranchements. Chaque beat peut avoir son propre flux narratif, ses choix et ses conséquences.

Vous pouvez aussi terminer l’histoire entièrement en utilisant -> . (flèche vers un point) :

beat FinVisite
  barista: Merci d'être venu ! À la prochaine.
  -> .

Personnages et dialogues

Pour rédiger des dialogues, commencez par définir vos personnages et leurs propriétés :

character barista
  name: Alex
  amitie: 0  // Niveau d'amitié
  serviceCommence: true

character client
  name: Sam
  visits: 0
  boissonPreferee: null

Une fois définis, les personnages peuvent parler via une syntaxe simple : leur identifiant suivi de deux-points.

barista: Salut ! Qu'est-ce que je te sers ?
client: Juste un café normal, s'il te plaît.
barista: Ça arrive tout de suite !

Les dialogues peuvent aussi s’étendre sur plusieurs lignes en plaçant le texte sur des lignes indentées après les deux-points :

barista:
  Salut, bienvenue dans notre café !
  On a un mélange spécial pour toi aujourd'hui.
  Jette un œil à ces grains de café éthiopiens en édition limitée au comptoir.

Écrire du texte narratif

Dans Loreline, le texte narratif s'écrit tel quel, sans marqueur spécial :

Une bonne odeur de café flotte dans la salle. Le soleil filtre par les fenêtres, projetant de longues ombres sur le parquet.

Un doux murmure de conversation emplit l'espace.

Les balises entre chevrons (<balise>) peuvent être utilisées dans n'importe quel texte, qu'il s'agisse de dialogue ou de narration :

barista: <friendly> Content de te revoir ! Comme d'habitude ?
client: <tired> Oui s'il te plaît, j'en ai vraiment besoin aujourd'hui.

La machine <whirs>se met à ronronner</whirs> tandis que de la vapeur <hiss>s'échappe avec un sifflement</hiss>.

Ces balises permettent d’exprimer les émotions des personnages ou changer la façon dont le texte est affiché, selon les possibilités de votre jeu ou application.

Gérer l’état

Toute histoire interactive se doit de mémoriser les choix et de suivre la progression. Loreline utilise des déclarations d’état pour cela. Il existe deux types d’état : persistant et temporaire.

État persistant

L’état persistant est conservé durant toute la durée de l’histoire :

state
  grainsDeCafe: 100    // Suivre l’inventaire
  heureDePointe: false // Est-ce l’heure de pointe ?
  numeroJour: 1        // Quel jour de l’histoire
  meteo: ensoleillé    // Météo actuelle

Vous pouvez modifier ces valeurs au fur et à mesure que votre histoire progresse :

grainsDeCafe -= 10    // Utiliser des grains
heureDePointe = true  // Début de l’heure de pointe
numeroJour += 1       // Passer au jour suivant
meteo = "pluvieux"    // Changer la météo

Assignation vs. déclaration. Dans une assignation (=, +=, -=…), le côté droit est une expression. C’est pourquoi "pluvieux" a besoin de guillemets : sans eux, pluvieux serait interprété comme un nom de variable. Les nombres (100), booléens (true/false) et null sont aussi des expressions, donc pas besoin de guillemets.

C’est différent des déclarations state et character (avec :), où la valeur n’est pas une expression : meteo: ensoleillé assigne directement le texte « ensoleillé ». Pour y insérer une expression, utilisez l’interpolation : name: $nomJoueur ou total: ${a + b}, comme dans les dialogues ou les textes narratifs.

L’état peut aussi contenir des objets imbriqués et des tableaux :

state
  menu:
    espresso: 3
    latte: 5
    cappuccino: 4
  specialsDuJour: ["Torréfaction Éthiopienne", "Cold Brew Vanille"]

État local à un beat

Vous pouvez déclarer un état à l’intérieur d’un beat. Il persiste entre les visites du beat mais est limité à ce beat, afin d’éviter tout conflit avec une variable de premier niveau portant le même nom :

state
  compteur: 0  // Compteur de premier niveau

beat Cafeteria
  state
    compteur: 0  // Compteur séparé, local à ce beat

  compteur += 1
  barista: Tu as commandé $compteur cafés dans ce bistrot !

État temporaire

Parfois, vous aurez besoin d’un état temporaire, existant uniquement au sein d’un beat. Utilisez le mot-clé new pour créer un état temporaire qui se réinitialise à chaque fois que vous entrez dans le beat :

beat DegustationCafe
  // Ces valeurs se réinitialisent à chaque entrée dans DegustationCafe
  new state
    tassesGoutees: 0
    torrefactionActuelle: legère
    niveauPlaisir: 5

  choice
    Prendre une autre gorgée if tassesGoutees < 3
      tassesGoutees += 1
      Des notes intéressantes dans celui-ci...

    Terminer la dégustation
      -> CommanderBoisson

Dans cet exemple, tassesGoutees, torrefactionActuelle et niveauPlaisir reprennent leurs valeurs initiales à chaque entrée dans le beat DegustationCafe.

Rendre les choix interactifs

L’essence de la fiction interactive réside dans les choix offerts :

beat CommanderBoisson
  choice
    Commander un cappuccino
      grainsDeCafe -= 15
      barista: <happy> Un cappuccino, c'est parti !
      -> PreparerBoisson

    Se renseigner sur les thés
      barista: Nous avons une belle sélection de thés verts et d'infusions.
      -> MenuThe

    Consulter le menu tranquillement
      Tu prends ton temps pour lire la longue liste de boissons.
      -> MenuBoissons

Les choix peuvent être conditionnels, disponibles uniquement si certains critères sont remplis :

beat MenuSpecial
  choice
    Commander la torréfaction spéciale if grainsDeCafe >= 20
      grainsDeCafe -= 20
      barista: Excellent choix ! Notre mélange éthiopien est incroyable.
      -> PreparerBoisson

    Discuter avec le barista if barista.amitie > 2
      barista: <friendly> Tu veux que je te raconte comment je suis devenu barista ?
      -> DiscussionBarista

Lorsqu’un choix effectue simplement une transition vers un autre beat sans logique supplémentaire, vous pouvez l’écrire sur une seule ligne :

choice
  Rester au café -> Cafeteria

  Finir la journée -> FinJournee

  Rejoindre $sarah if sarah.present -> DiscussionSarah

Les choix peuvent aussi être imbriqués. Lorsqu’une branche de choix se termine sans transition ->, l’exécution reprend naturellement après le bloc de choix :

barista: Qu'est-ce qui te ferait plaisir ?

choice
  Une boisson chaude
    choice
      Espresso
        barista: Un espresso, c'est parti !
      Latté
        barista: Excellent choix ! Une préférence de lait ?
        choice
          Lait d'avoine
            barista: Notre option la plus populaire !
          Lait standard
            barista: Un classique. Ça arrive tout de suite.

  Une boisson froide
    choice
      Café glacé
        barista: Parfait par ce temps !
      Limonade
        barista: Pressée maison, ma préférée.

barista: Ce sera prêt dans un instant.

La dernière ligne est jouée quel que soit le choix de boisson, toutes les branches convergent naturellement après le bloc de choix extérieur.

Composer les choix avec des insertions

À mesure que votre histoire s'agrandit, vous pourriez vouloir réutiliser des groupes de choix dans différents beats. Les insertions de choix vous permettent d’intégrer les choix d’un autre beat en utilisant le préfixe + :

beat SceneCafe
  choice
    + BoissonsDeSaison
    + MenuHabituel
    Rien pour moi, merci
      barista: Pas de souci, fais-moi signe si tu changes d'avis.

beat BoissonsDeSaison
  barista: N'oublie pas nos spécialités de saison !
  choice
    Chocolat chaud épicé
      barista: Un choix parfait pour la saison !
    Thé aux agrumes
      barista: Excellent, c'est notre dernière nouveauté.

beat MenuHabituel
  choice
    Espresso
      barista: Un espresso, c'est parti !
    Latté
      barista: Excellent choix !

En atteignant le choix dans SceneCafe, on verra les options de BoissonsDeSaison et MenuHabituel fusionnées avec l’option « Rien pour moi ». Chaque beat inséré peut aussi inclure un dialogue qui se joue avant que ses choix ne soient affichés.

Utiliser des beats comme sous-routines

Vous pouvez appeler un beat comme une fonction en utilisant des parenthèses. Le beat appelé s’exécute, et quand il se termine, l’exécution reprend là où il a été appelé :

character perso
  name: null

beat Introspection
  if !perso.name
    Comment est-ce que je m'appelle ?
    ChoisirNom()
    -> Introspection
  else
    Ah oui, je me souviens, je m'appelle $perso.name !

beat ChoisirNom
  choice
    Alex
      perso.name = "Alex"
    Sam
      perso.name = "Sam"
    Jamie
      perso.name = "Jamie"

Ici, ChoisirNom() entre dans le beat ChoisirNom, permet de choisir un nom, puis renvoie à Introspection où l'exécution continue.

Varier le texte entre les visites

Quand les joueurs revisitent un même beat, il est souvent souhaitable que le texte change. Les blocs alternatifs permettent de définir des variantes et de contrôler leur sélection. Utilisez -- pour séparer les éléments à l'intérieur du bloc :

sequence
  Le café est encore calme à cette heure matinale.
--
  Quelques habitués se sont installés avec leurs journaux.
--
  L'affluence du matin commence à se faire sentir.

barista: Qu'est-ce que je te sers aujourd'hui ?

Le mot-clé sequence joue chaque élément dans l'ordre à chaque nouvelle visite, puis reste sur le dernier. Ici, la première visite affiche « Le café est encore calme… », la deuxième « Quelques habitués… », et à partir de la troisième, toujours « L'affluence du matin… ».

Cinq modes sont disponibles :

Mot-clé Comportement
sequence Éléments dans l'ordre, puis reste sur le dernier
cycle Éléments dans l'ordre, puis recommence au premier
once Éléments dans l'ordre, puis plus rien
pick Un élément au hasard à chaque fois
shuffle Tous les éléments dans un ordre aléatoire

Voici cycle pour un barista qui alterne ses salutations :

cycle
  barista: Bonjour ! Qu'est-ce que ce sera ?
--
  barista: Content de te revoir ! Comme d'habitude ?
--
  barista: Salut ! On essaie quelque chose de nouveau aujourd'hui ?

once est utile pour du contenu qui ne doit apparaître qu'un nombre limité de fois :

once
  Une clochette tinte quand tu pousses la porte pour la première fois.
--
  L'odeur familière du café t'accueille à nouveau.
--
  Tu salues d'un signe quelques habitués que tu as appris à connaître.

barista: Salut !

Après la troisième visite, le bloc once ne produit plus rien et seul « Salut ! » est joué.

Avec un seul élément et sans --, once permet simplement d'afficher quelque chose uniquement lors de la première visite :

once
  Une clochette tinte quand tu pousses la porte pour la première fois.

barista: Salut !

pick choisit un élément au hasard à chaque fois :

pick
  Un air de jazz s'échappe doucement d'une enceinte sur l'étagère.
--
  La machine à espresso siffle et crépite.
--
  Des rires fusent d'une table près de la fenêtre.

Et shuffle joue tous les éléments, mais dans un ordre aléatoire :

shuffle
  Tu remarques le menu à la craie sur le mur.
--
  Un chat dort sur le rebord de la fenêtre.
--
  Le barista astique des verres derrière le comptoir.

Chaque élément peut contenir plusieurs lignes de texte et de dialogue, comme n'importe quel autre bloc :

cycle
  barista: Le spécial du jour, c'est un macchiato caramel.
  L'odeur est tout simplement irrésistible.
--
  barista: On a un nouveau cold brew au menu.
  Une ardoise annonce le prix.
--
  barista: Essaie notre latté épicé de saison !
  Les épices chaudes embaument l'air.

Texte dynamique

Rendez votre texte dynamique en utilisant le symbole $ pour l’interpolation de variables :

barista: Il nous reste $grainsDeCafe grains en stock.
barista: Ça fera ${grainsDeCafe * 2} euros pour le lot !

Les personnages peuvent aussi être référencés par leur identifiant, ce qui affichera automatiquement leur propriété name :

beat FermerBoutique
  $barista commence à ranger pour la journée.  // Affichera "Alex commence à ranger pour la journée"
  $client salue d'un geste en partant.          // Affichera "Sam salue d'un geste en partant"

Échapper les caractères spéciaux

Comme $ et < ont une signification spéciale dans Loreline, vous pouvez les échapper lorsque vous avez besoin d’afficher ces caractères tels quels :

barista: Ce café rare va te coûter 9$$. Ça te convient ?
perso: Zut, il ne me reste que 5\$...

Les deux formes $$ et \$ produisent un $ littéral dans la sortie.

Vous pouvez aussi échapper les chevrons pour éviter qu’ils soient interprétés comme des balises :

Ce texte contient une \<balise> échappée.

Texte vs. conditions

La plupart du temps, Loreline est assez intelligent pour distinguer quand if fait partie de votre texte plutôt qu’une condition. Par exemple, ceci fonctionne très bien sans aucun échappement :

beat WeatherReport
  david: It's Friday once again, if you can believe it!

Loreline détecte que if you can believe it! n'est pas une condition valide, il conserve donc la ligne entière comme texte de dialogue. Cet exemple est en anglais car il illustre spécifiquement un cas propre à l'anglais, où if apparaît naturellement dans une phrase courante.

Cependant, lorsque le texte après if se trouve être une expression valide, il sera interprété comme une condition. Dans ce cas, entourez le texte de guillemets doubles si vous voulez qu’il soit traité sans ambiguïté comme du texte :

state
  tired: false

choice
  Go outside if tired           // choix affiché uniquement quand tired est vrai
  "Go outside if tired"         // le texte du choix est littéralement "Go outside if tired"

Utilisez \n pour insérer un saut de ligne dans une seule ligne de dialogue :

perso: Est-ce que je peux payer...\nle reste...\ndemain ?

Cela s’affiche en trois lignes distinctes :

Est-ce que je peux payer...
le reste...
demain ?

Si vous avez besoin d'un \n littéral dans la sortie, utilisez un antislash supplémentaire : \\n.

Fonctions

Loreline permet d'utiliser des fonctions. Elles peuvent être appelées depuis des expressions, de l’interpolation de texte ou comme instructions autonomes. Une fonction est appelée par son nom suivi de parenthèses. Par exemple, random est une fonction intégrée qui renvoie un nombre aléatoire entre deux valeurs :

barista: Ta commande sera prête dans $random(2, 5) minutes !
// Affichera un nombre aléatoire entre 2 et 5

Fonctions intégrées

Loreline inclut plus de 50 fonctions intégrées couvrant les mathématiques, l’aléatoire, les chaînes, les tableaux, les dictionnaires, et plus encore. En voici quelques exemples :

sante = clamp(sante + soin, 0, santeMax)

if chance(4)
  Tu trouves une gemme rare par terre !

if array_has(inventaire, "clé dorée")
  Tu ouvres la porte ancienne.

salutation = "  bonjour le monde  ".trim().upper()

De nombreuses fonctions supportent aussi la notation par point. Par exemple, string_upper(nom) peut s’écrire nom.upper().

Pour la liste complète, consultez la référence Fonctions intégrées.

Définir vos propres fonctions

Vous pouvez définir vos propres fonctions à l’extérieur des beats. Une fonction possède un nom, des paramètres optionnels, et un corps écrit dans une syntaxe de script standard :

function additionner(a, b)
  return a + b

state
  pommes: 7
  oranges: 3

Nous avons $pommes pommes et $oranges oranges, ce qui fait un total de $additionner(pommes, oranges) fruits !

Les fonctions peuvent accéder et modifier les variables d’état :

state
  fruits: 2

function obtenirFruit()
  fruits = fruits + 1

Tu as $fruits fruits.

obtenirFruit()

Tu as $fruits fruits.

Les fonctions peuvent utiliser des boucles pour construire des résultats :

function enumerer(nombre, mot)
  var resultat = ""
  for (i in 0...nombre)
    if i > 0
      resultat += ", "
    resultat += "$mot ${i + 1}"
  return resultat

Voici tous mes objets : $enumerer(3, "pomme")
// Sortie : Voici tous mes objets : pomme 1, pomme 2, pomme 3

Pour une référence complète de la syntaxe de script disponible dans les fonctions (variables, structures de contrôle, opérateurs et plus), consultez la page Scripting de fonctions.

Fonctions externes

Vous pouvez aussi déclarer des fonctions vides. Celles-ci servent de points d’ancrage (hooks) pour votre moteur de jeu ou application. Le script les déclare, et l’environnement hôte fournit l’implémentation réelle :

function jouerExplosion()

sarah: C'est quoi ce diamant vert ? Attends, laisse-moi le toucher...
james: Nooon ne le touche pas !

jouerExplosion()

james: Sarah ? Sarah !!

Importer des scripts

À mesure que votre histoire s'agrandit, vous pouvez la répartir sur plusieurs fichiers en utilisant des instructions d’import :

import objets
import personnages/barista
import "scenes/intro.lor"

Les imports chargent le contenu d’un autre fichier .lor dans le script courant. L’extension .lor et les guillemets sont optionnels. import personnages/barista cherchera personnages/barista.lor dans un sous-dossier personnages.

Syntaxe alternative : accolades

Tout au long de ce guide, tous les exemples utilisent l’indentation pour définir les blocs. Loreline supporte aussi les accolades comme alternative :

beat Cafeteria {
  choice {
    Commander un espresso {
      barista: Un espresso, c'est parti !
      -> TraiterCommande
    }

    Commander un latté if !heureDePointe {
      barista: Excellent choix ! Je vais le faire bien mousseux.
      -> TraiterCommande
    }

    Partir -> FinJournee
  }
}

Les deux styles fonctionnent partout où des blocs sont utilisés (beats, choix, déclarations d’état, if/else). Vous pouvez utiliser le style que vous préférez, bien que la syntaxe basée sur l’indentation tende à être plus lisible pour le contenu narratif.

Commentaires et organisation

Gardez votre script organisé avec des commentaires :

// Suivre la fidélité du client
client.visits += 1

/* Vérifier si on doit
   déclencher l'événement spécial */
if client.visits > 10
  -> RecompenseFidelite

Fonctionnalités avancées

Voici un exemple complexe combinant plusieurs fonctionnalités :

beat DegustationCafe

  state
    tassesGoutees: 0
    torrefactionPreferee: null
    derniereImpression: ""

  barista: <enthusiastic> Prêt à découvrir nos nouvelles torréfactions ?

  choice
    Essayer la torréfaction légère if tassesGoutees < 3
      tassesGoutees += 1
      derniereImpression = "vive et citronnée"

      Les notes vives et citronnées dansent sur ton palais.

      if chance(3) // 1 chance sur 3
        torrefactionPreferee = "legère"
        barista: <happy> Je vois cette étincelle dans tes yeux !
        -> DiscuterGout

    Essayer la torréfaction moyenne if tassesGoutees < 3
      tassesGoutees += 1
      derniereImpression = "noisette et équilibrée"

      Une agréable saveur de noisette emplit ta bouche.
      -> DiscuterGout

    Discuter des origines du café if barista.amitie > 1
      barista: <passionate> Laisse-moi te parler de nos producteurs...
      -> OriginesCafe

    Terminer la dégustation if tassesGoutees > 0
      if torrefactionPreferee != null
        -> CommanderFavori
      else
        -> CommandeHabituelle

beat DiscuterGout
  barista: Qu'est-ce que tu penses de ces notes $derniereImpression ?

  choice
    Exprimer son enthousiasme
      barista.amitie += 1
      -> DegustationCafe

    Hocher poliment la tête
      -> DegustationCafe

Ce guide a passé en revue les principales fonctionnalités de Loreline, mais il y a toujours plus à découvrir en écrivant vos propres histoires. Expérimentez avec différentes combinaisons de ces fonctionnalités pour créer des récits riches.

Bonne écriture !