Skip to content

Livre dont vous êtes le héros

Créer une histoire interactive où les choix du lecteur influencent la narration. Vous apprendrez à gérer des états narratifs, naviguer entre eux, et structurer une expérience non-linéaire.

Ce que vous allez construire : Une histoire courte (3 chemins, 5+ états) avec choix multiples et fins différentes.

  • Navigation entre états narratifs
  • Stores Svelte pour gérer l’état global
  • Architecture modulaire (séparer logique et contenu)
  • Choix qui impactent la narration

Avant de coder, pensez structure narrative.

Questions à se poser :

  • Combien d’états (scènes) ?
  • Quels choix à chaque état ?
  • Vers où mènent ces choix ?
  • Y a-t-il des états communs (convergence) ?
  • Combien de fins possibles ?

Exemple de structure :

DÉBUT
État 1: Vous êtes devant une porte
→ Choix A: Entrer → État 2
→ Choix B: Partir → État 3
État 2: Intérieur sombre
→ Choix A: Allumer → État 4 (fin heureuse)
→ Choix B: Avancer → État 5 (fin tragique)
État 3: Forêt
→ Choix A: Continuer → État 5 (convergence)
→ Choix B: Retour → État 1
src/
├── lib/
│ ├── stores/
│ │ └── storyStore.js // État global de l'histoire
│ ├── data/
│ │ └── storyData.js // Contenu narratif
│ └── components/
│ ├── Scene.svelte // Affichage d'une scène
│ └── Choice.svelte // Bouton de choix
└── routes/
└── +page.svelte // Page principale

Principe : Séparer données (contenu) et logique (code).

Créez src/lib/data/storyData.js :

// Chaque scène a :
// - id unique
// - texte narratif
// - choix possibles (texte + destination)
export const scenes = {
start: {
id: 'start',
text: "Vous êtes devant une vieille maison abandonnée. La porte grince dans le vent.",
choices: [
{ text: "Entrer dans la maison", next: 'inside' },
{ text: "Partir vers la forêt", next: 'forest' }
]
},
inside: {
id: 'inside',
text: "L'intérieur est plongé dans l'obscurité. Vous sentez une présence.",
choices: [
{ text: "Allumer votre lampe", next: 'light' },
{ text: "Avancer dans le noir", next: 'darkness' }
]
},
forest: {
id: 'forest',
text: "La forêt est dense. Un chemin sinueux s'enfonce dans les arbres.",
choices: [
{ text: "Suivre le chemin", next: 'darkness' },
{ text: "Retourner à la maison", next: 'start' }
]
},
light: {
id: 'light',
text: "La lumière révèle une pièce magnifique remplie de livres anciens. Vous avez trouvé un trésor.",
choices: [
{ text: "Recommencer", next: 'start' }
],
isEnding: true
},
darkness: {
id: 'darkness',
text: "Dans l'obscurité, vous trébuchez et tombez dans un puits sans fond.",
choices: [
{ text: "Recommencer", next: 'start' }
],
isEnding: true
}
};

Structure claire :

  • Chaque scène est un objet avec id, text, choices
  • Les fins ont un flag isEnding
  • Les choix pointent vers d’autres scènes via next

Créez src/lib/stores/storyStore.js :

import { writable } from 'svelte/store';
import { scenes } from '../data/storyData';
// État initial : on commence à la scène 'start'
const initialState = {
currentSceneId: 'start',
history: ['start'] // Historique des scènes visitées
};
function createStoryStore() {
const { subscribe, set, update } = writable(initialState);
return {
subscribe,
// Naviguer vers une nouvelle scène
goToScene: (sceneId) => {
update(state => ({
currentSceneId: sceneId,
history: [...state.history, sceneId]
}));
},
// Réinitialiser l'histoire
reset: () => {
set(initialState);
},
// Obtenir la scène actuelle
getCurrentScene: (state) => {
return scenes[state.currentSceneId];
}
};
}
export const storyStore = createStoryStore();

Ce que fait le store :

  • Garde l’état actuel de l’histoire
  • Permet de naviguer entre scènes
  • Garde un historique (optionnel, pour debug ou fonctionnalité “retour”)
  • Fournit une méthode pour réinitialiser

Créez src/lib/components/Choice.svelte :

<script>
import { storyStore } from '../stores/storyStore';
let { choice } = $props(); // { text: "...", next: "..." }
function handleClick() {
storyStore.goToScene(choice.next);
}
</script>
<button on:click={handleClick}>
{choice.text}
</button>
<style>
button {
display: block;
width: 100%;
padding: 1rem;
margin: 0.5rem 0;
background: #2a2a2a;
color: white;
border: 2px solid #444;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
button:hover {
background: #3a3a3a;
border-color: #666;
transform: translateX(4px);
}
</style>

4. Composant Scene (affichage d’une scène)

Section titled “4. Composant Scene (affichage d’une scène)”

Créez src/lib/components/Scene.svelte :

<script>
import Choice from './Choice.svelte';
let { scene } = $props(); // Objet scène complet
</script>
<article class="scene" class:ending={scene.isEnding}>
<p class="text">{scene.text}</p>
<div class="choices">
{#each scene.choices as choice}
<Choice {choice} />
{/each}
</div>
</article>
<style>
.scene {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
background: #1a1a1a;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.scene.ending {
border: 2px solid gold;
}
.text {
font-size: 1.2rem;
line-height: 1.6;
margin-bottom: 2rem;
color: #ddd;
}
.choices {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
</style>

Créez src/routes/+page.svelte :

<script>
import { storyStore } from '$lib/stores/storyStore';
import Scene from '$lib/components/Scene.svelte';
import { scenes } from '$lib/data/storyData';
// Réactivité Svelte : se met à jour automatiquement
let currentScene = $derived(scenes[$storyStore.currentSceneId]);
</script>
<main>
<h1>L'histoire dont vous êtes le héros</h1>
{#if currentScene}
<Scene scene={currentScene} />
{/if}
{#if $storyStore.history.length > 1}
<p class="history">
Scènes visitées : {$storyStore.history.length}
</p>
{/if}
</main>
<style>
main {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
padding: 2rem;
}
h1 {
text-align: center;
color: white;
font-size: 2rem;
margin-bottom: 2rem;
}
.history {
text-align: center;
color: #888;
margin-top: 2rem;
font-size: 0.9rem;
}
</style>
Terminal window
npm run dev

Ouvrez http://localhost:5173 et testez votre histoire.

Vérifications :

  • ✅ Les choix mènent aux bonnes scènes
  • ✅ Les fins affichent un style différent
  • ✅ Le compteur d’historique fonctionne
  • ✅ Le bouton “Recommencer” réinitialise

Transitions entre scènes :

<script>
import { fade } from 'svelte/transition';
</script>
{#key currentScene.id}
<div in:fade={{ duration: 300 }}>
<Scene scene={currentScene} />
</div>
{/key}

Sauvegarder la progression (LocalStorage) :

// Dans storyStore.js
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
const stored = browser ? localStorage.getItem('storyProgress') : null;
const initial = stored ? JSON.parse(stored) : initialState;
const store = writable(initial);
// Sauvegarder à chaque changement
store.subscribe(value => {
if (browser) {
localStorage.setItem('storyProgress', JSON.stringify(value));
}
});

Variables narratives (inventaire, stats) :

// Ajouter au store
const initialState = {
currentSceneId: 'start',
history: [],
inventory: [], // Objets trouvés
stats: {
courage: 0,
intelligence: 0
}
};

Conditions sur les choix :

// Dans storyData.js
choices: [
{
text: "Utiliser la clé",
next: 'door_opened',
condition: (state) => state.inventory.includes('key')
},
{
text: "Forcer la porte",
next: 'door_forced'
}
]
// Dans Scene.svelte
{#each scene.choices as choice}
{#if !choice.condition || choice.condition($storyStore)}
<Choice {choice} />
{/if}
{/each}
  • ✅ Architecturer une narration interactive
  • ✅ Utiliser un store Svelte pour l’état global
  • ✅ Séparer données (contenu) et logique (code)
  • ✅ Naviguer entre états avec réactivité automatique
  • ✅ Créer des composants réutilisables (Scene, Choice)

Créez votre propre histoire :

  • Minimum 5 scènes
  • Au moins 3 fins différentes
  • Un embranchement qui converge (deux chemins mènent au même état)
  • Un style visuel cohérent avec votre narration

Astuce : Dessinez d’abord un schéma sur papier avant de coder.