LogoPixi’VN
Créez votre premier projet

Créer votre premier visual novel

Guide étape par étape pour créer un visual novel avec Pixi’VN, couvrant la configuration du projet, la narration, les ressources et l’interactivité.

Ce tutoriel vous guidera tout au long du processus de création de votre premier visual novel.

Qu’est-ce qu’un visual novel ? Un visual novel (VN) est une forme de fiction interactive numérique. Les visual novels sont souvent associés au média du jeu vidéo, mais ne sont pas toujours classés comme tels. Ils combinent une narration textuelle avec des illustrations statiques ou animées, ainsi qu’un degré variable d’interactivité. Ce format est parfois appelé « novel game », une retranscription du terme wasei-eigo noberu gēmu (ノベルゲーム), qui est plus couramment utilisé au Japon.

À des fins de test, dans ce guide, nous recréerons le visual novel Breakdown en utilisant Pixi’VN. Breakdown est une courte histoire qui possède toutes les caractéristiques qu’un visual novel devrait avoir. Josh Powlison, le créateur de Breakdown, nous a autorisés à utiliser sa narration à des fins pédagogiques ❤️.

Puisque Pixi’VN vous permet d’écrire votre propre narration en choisissant une ou plusieurs langues narratives disponibles, des exemples seront fournis pour chaque langue actuellement disponible à chaque étape du développement.

Créer un nouveau projet

La première étape consiste à créer un nouveau projet. Vous pouvez trouver plus d’informations sur la création d’un projet à partir d’un modèle ici. Dans ce guide, nous utiliserons le modèle « Visual Novel - React ».

Visual Novel -> React

Une fois la création terminée, il est très important de lire le fichier README.md situé à la racine du projet. Ce fichier contient des informations essentielles sur le projet et sur son utilisation.

Dans notre cas, pour démarrer le projet, il suffira d’exécuter les commandes indiquées:

npm install
npm start

Création des personnages

Nous allons maintenant définir les personnages de cette histoire. Pour cela, les personnages utilisés seront définis dans le fichier /values/characters.ts. Pour plus d’informations sur la création et l’utilisation des personnages, vous pouvez consulter : Characters

Que signifie mc? mc est une abréviation courante de « Main Character ». Il est courant, dans les visual novels, d’utiliser mc comme nom du personnage principal.

import { RegisteredCharacters } from "@drincs/pixi-vn";
import Character from "../models/Character";

export const mc = new Character('mc', {
    name: 'Me',
});

export const james = new Character('james', {
    name: 'James',
    color: "#0084ac"
});

export const steph = new Character('steph', {
    name: 'Steph',
    color: "#ac5900"
});

export const sly = new Character('sly', {
    name: 'Sly',
    color: "#6d00ac"
});

RegisteredCharacters.add([mc, james, steph, sly]);

Première ébauche de la narration

Markup

Tous les modèles prennent en charge Markdown et Tailwind CSS, nous les utiliserons donc pour la narration.

Nous pouvons maintenant commencer à écrire la « première ébauche » de la narration du visual novel. Nous créerons le premier label appelé start, qui correspondra au début du jeu. Ensuite, nous pourrons écrire les dialogues qui se dérouleront dans le visual novel.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
james: You're my roommate's replacement, huh?
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you' fine!
mc: ...

He thrusts out his hand.

james: James!
mc: ...Peter.

I take his hand and shake.

james: Ooh, Peter! Nice, firm handshake! The last quy always gave me the dead fish. I already think we'r gonna get along fine.
james: Come on in and... 
james: ...
james: I know you're both watching, come on out already!

sly: I just wanted to see what the new guy was like. Hey, you, Peter- be nice to our little brother, or you'll have to deal with *us*.
mc: ...
james: Peter, this is Sly. Yes, that is her real name.

I put out my hand.

sly: I'm not shakin' your hand until I decide you're an all-right dude. Sorry, policy.
mc: Fair enough, I'm a pretty scary guy, or so l've been told.
james: The redhead behind her is Stephanie.
// Example of using Tailwind CSS
steph: <span class="inline-block motion-translate-y-loop-25">Hey</span>! Everyone calls me Steph. I'll shake your hand.

// ...
-> DONE

Diviser la narration en labels

Il n’est pas recommandé de créer des labels très longs, même pour les visual novels linéaires. Il est préférable de créer plusieurs petits labels et de les appeler lorsque nécessaire à l’aide des fonctionnalités de contrôle du flux narratif.

Pour cette raison, même si l’histoire est linéaire dans ce cas, elle sera divisée en plusieurs labels, en utilisant start et second_part.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
james: You're my roommate's replacement, huh?
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you' fine!
mc: ...

He thrusts out his hand.

james: James!
mc: ...Peter.

// ...
-> second_part

=== second_part ===

She enters my room before I'VE even had a chance to. \\n\\n...I could've just come back and gotten the platter later...
She sets it on a desk. I throw my two paper bags down beside the empty bed.

steph: They got you a new mattress, right? That last guy was a druggie, did James tell you that?
sly: *We're* the reason he got expelled!
steph: Sly! If word gets out about that... well, actually, it wouldn't matter, *he's* the one who shot himself up.

I'm fumbling for a new subject.

// ...
-> DONE

Nous allons maintenant demander au joueur s’il souhaite continuer la suite du visual novel.

Pour cela, nous utiliserons le menu de choix.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
// ...

You want continue to the next part?
* Yes, I want to continue
-> second_part
* No, I want to stop here
-> END

=== second_part ===

// ...
-> DONE

Modifier les informations du personnage et les utiliser comme variable

Nous allons maintenant donner au joueur la possibilité de modifier le nom de mc.

Pour ce faire, nous demanderons au joueur de remplir un champ de saisie à l’aide des fonctionnalités de Pixi’VN.

Après avoir récupéré la valeur saisie, il sera possible de définir le nom du personnage à l’aide de cette valeur.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
// ...

He thrusts out his hand.
# request input type string default Peter
What is your name?
# rename mc { _input_value_ }

// ...
-> DONE

Il sera ensuite possible d’utiliser les noms des personnages dans les dialogues.

Voici un exemple:

file_type_ink
ink/start.ink
VAR steph_fullname = "Stephanie"

=== start ===
// ...

sly: I just wanted to see what the new guy was like. Hey, you, [mc]- be nice to our little brother, or you'll have to deal with *us*.
mc: ...
james: [mc], this is [sly]. Yes, that is her real name.

I put out my hand.

sly: I'm not shakin' your hand until I decide you're an all-right dude. Sorry, policy.
mc: Fair enough, I'm a pretty scary guy, or so l've been told.
james: The redhead behind her is [steph_fullname].
steph: Hey! Everyone calls me [steph]. I'll shake your hand.

She puts out her hand, and I take it.

mc: Thanks, good to meet you, [steph_fullname].
steph: WOW, that is, like, the most perfect handshake I've ever had! Firm, but also gentle. [sly], you *gotta* shake his hand!

// ...
-> DONE

Utiliser la fonctionnalité « glue » des dialogues

Dans les visual novels, il est souvent utile de coller du texte dans le dialogue en cours. Par exemple, pour mettre une conversation en pause et la reprendre dans un step suivant. Pour ce faire, nous pouvons utiliser la fonctionnalité glue.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
// ...

james: Ooh, [mc]! Nice, firm handshake!
<>The last guy always gave me the dead fish.
<>I already think we're gonna get along fine.
james: Come on in and...

// ...
-> DONE

Définir et charger les assets

Pour charger et manipuler des assets (images, Gif, vidéos, etc.), vous devez utiliser Assets. Assets est une classe riche en fonctionnalités provenant de la bibliothèque PixiJS. Pour plus d’informations, consultez ici.

L’une des premières étapes consiste à choisir où enregistrer les assets de votre visual novel. Dans cet exemple, nous enregistrerons les assets dans Firebase Storage (un service d’hébergement). Vous pouvez utiliser le service d’hébergement de votre choix ou même enregistrer les assets localement dans le projet. Pour en savoir plus, consultez ici.

Avant d’utiliser un asset, il est fortement recommandé d’initialiser la matrice d’assets.

Par défaut, comme vous pouvez le voir dans le fichier assets/manifest.ts, tous les templates dans onLoadingLabel essaient de charger en arrière-plan les « bundle assets » avec un alias égal à l’identifiant du label courant. Il est donc recommandé d’ajouter, dans le manifest, un « bundle assets » pour chaque label avec un alias égal à l’identifiant du label et contenant les images utilisées dans ce label.

Voici un exemple:

import { AssetsManifest } from "@drincs/pixi-vn";
import { MAIN_MENU_ROUTE } from "../constans";

/**
 * Manifest for the assets used in the game.
 * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets
 */
const manifest: AssetsManifest = {
    bundles: [
        // screens
        {
            name: MAIN_MENU_ROUTE,
            assets: [
                {
                    alias: "background_main_menu",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/main-menu.png",
                },
            ],
        },
        // labels
        {
            name: "start",
            assets: [
                {
                    alias: "bg01-hallway",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg01-hallway.webp",
                },
            ],
        },
        {
            name: "second_part",
            assets: [
                {
                    alias: "bg02-dorm",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg02-dorm.webp",
                },
            ],
        },
        // characters
        {
            name: "fm01",
            assets: [
                {
                    alias: "fm01-body",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-body.webp",
                },
                {
                    alias: "fm01-eyes-grin",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-grin.webp",
                },
                {
                    alias: "fm01-eyes-smile",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-smile.webp",
                },
                {
                    alias: "fm01-eyes-soft",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-soft.webp",
                },
                {
                    alias: "fm01-eyes-upset",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-upset.webp",
                },
                {
                    alias: "fm01-eyes-wow",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-eyes-wow.webp",
                },
                {
                    alias: "fm01-mouth-grin00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-grin00.webp",
                },
                {
                    alias: "fm01-mouth-serious00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-serious00.webp",
                },
                {
                    alias: "fm01-mouth-serious01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-serious01.webp",
                },
                {
                    alias: "fm01-mouth-smile00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-smile00.webp",
                },
                {
                    alias: "fm01-mouth-smile01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-smile01.webp",
                },
                {
                    alias: "fm01-mouth-soft00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-soft00.webp",
                },
                {
                    alias: "fm01-mouth-soft01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-soft01.webp",
                },
                {
                    alias: "fm01-mouth-upset00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-upset00.webp",
                },
                {
                    alias: "fm01-mouth-upset01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-upset01.webp",
                },
                {
                    alias: "fm01-mouth-wow01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm01/fm01-mouth-wow01.webp",
                },
            ],
        },
        {
            name: "fm02",
            assets: [
                {
                    alias: "fm02-body",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-body.webp",
                },
                {
                    alias: "fm02-eyes-bawl",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-bawl.webp",
                },
                {
                    alias: "fm02-eyes-joy",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-joy.webp",
                },
                {
                    alias: "fm02-eyes-nervous",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-nervous.webp",
                },
                {
                    alias: "fm02-eyes-smile",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-smile.webp",
                },
                {
                    alias: "fm02-eyes-upset",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-upset.webp",
                },
                {
                    alias: "fm02-eyes-wow",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-eyes-wow.webp",
                },
                {
                    alias: "fm02-mouth-cry01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-cry01.webp",
                },
                {
                    alias: "fm02-mouth-nervous00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-nervous00.webp",
                },
                {
                    alias: "fm02-mouth-nervous01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-nervous01.webp",
                },
                {
                    alias: "fm02-mouth-smile00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-smile00.webp",
                },
                {
                    alias: "fm02-mouth-smile01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-smile01.webp",
                },
                {
                    alias: "fm02-mouth-upset00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-upset00.webp",
                },
                {
                    alias: "fm02-mouth-upset01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-upset01.webp",
                },
                {
                    alias: "fm02-mouth-wow01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/fm02/fm02-mouth-wow01.webp",
                },
            ],
        },
        {
            name: "m01",
            assets: [
                {
                    alias: "m01-body",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp",
                },
                {
                    alias: "m01-eyes-annoy",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-annoy.webp",
                },
                {
                    alias: "m01-eyes-concern",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-concern.webp",
                },
                {
                    alias: "m01-eyes-cry",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-cry.webp",
                },
                {
                    alias: "m01-eyes-grin",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-grin.webp",
                },
                {
                    alias: "m01-eyes-smile",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp",
                },
                {
                    alias: "m01-eyes-wow",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-wow.webp",
                },
                {
                    alias: "m01-mouth-annoy00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-annoy00.webp",
                },
                {
                    alias: "m01-mouth-annoy01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-annoy01.webp",
                },
                {
                    alias: "m01-mouth-concern00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-concern00.webp",
                },
                {
                    alias: "m01-mouth-concern01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-concern01.webp",
                },
                {
                    alias: "m01-mouth-cry00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-cry00.webp",
                },
                {
                    alias: "m01-mouth-cry01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-cry01.webp",
                },
                {
                    alias: "m01-mouth-grin00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-grin00.webp",
                },
                {
                    alias: "m01-mouth-neutral00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-neutral00.webp",
                },
                {
                    alias: "m01-mouth-neutral01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-neutral01.webp",
                },
                {
                    alias: "m01-mouth-smile00",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp",
                },
                {
                    alias: "m01-mouth-smile01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile01.webp",
                },
                {
                    alias: "m01-mouth-wow01",
                    src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-wow01.webp",
                },
            ],
        },
    ],
};
export default manifest;

Ajouter des images de fond et de personnages

Il est maintenant temps de penser également à la partie visuelle. Nous allons ajouter les arrière-plans et les sprites des personnages sur le canvas du visual novel.

Qu’est-ce qu’un sprite ? En infographie, un sprite est une image bitmap bidimensionnelle intégrée dans une scène plus large, le plus souvent dans un jeu vidéo 2D.

Dans notre cas, les sprites des personnages sont composés de 3 images : le corps, les yeux et la bouche. Nous utilisons ensuite ImageContainer pour composer le personnage. Vous trouverez plus d’informations sur l’ajout de composants canvas dans cette documentation.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
# show image bg bg01-hallway
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1
james: You're my roommate's replacement, huh?
# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-smile01]
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you'll be fine!
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-grin00]
mc: ...

// ...
-> DONE

Chargement intelligent des assets

Dans notre cas, nous avons enregistré les images du jeu sur un service d’hébergement (Firebase). Pour cette raison, le chargement des assets n’est pas instantané.

Afin que le joueur ne perçoive pas trop de chargements, nous devons les regrouper dans certaines phases du jeu. Dans cet exemple, je chargerai les images les plus utilisées au début du label.

Vous pouvez trouver plus d’informations sur la gestion des chargements ici.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
# lazyload bundle m01 fm01 fm02

# show image bg bg01-hallway
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1 with movein direction right speed 300
james: You're my roommate's replacement, huh?
# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-smile01]
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you'll be fine!
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-grin00]
mc: ...

// ...
-> DONE

Utiliser des transitions

Pour rendre le visual novel plus dynamique, vous pouvez utiliser des transitions pour afficher les images. Vous trouverez plus d’informations sur l’utilisation des transitions ici.

Voici un exemple:

file_type_ink
ink/start.ink
=== start ===
// ...

# show image bg bg01-hallway
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1 with movein direction right speed 300
james: You're my roommate's replacement, huh?
# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-smile01]
james: Don't worry, you don't have much to live up to. Just don't use heroin like the last guy, and you'll be fine!
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-grin00]
mc: ...

// ...
-> DONE

Construire une animation

Pour rendre le visual novel plus dynamique, vous pouvez utiliser des animations. Pour plus d’informations sur l’utilisation des animations, consultez ici.

Je recommande d’utiliser TypeScript si vous devez définir de nombreuses propriétés, car cela offre plus de contrôle, plus de fonctionnalités et un retour de type.

Dans cet exemple, mon animation retirera steph de la scène et la réinsérera au step suivant. Je la retournerai également sur l’axe X pour m’assurer qu’elle regarde dans la bonne direction.

Pour faire sortir/entrer steph, j’utiliserai les fonctions moveOut et moveIn. Pour l’effet miroir, j’utiliserai la fonction canvas.animate.

Tout d’abord, j’utilise la fonction canvas.animate pour créer une animation qui définit la propriété scaleX de -1 (miroir) à 1, avec autoplay: false afin qu’elle ne démarre pas immédiatement.

Ensuite, j’utilise la fonction moveIn pour déplacer steph dans la scène, en passant le tickerId de l’animation afin qu’elle puisse reprendre après la fin de la transition.

J’utilise l’option forceCompleteBeforeNext: true pour m’assurer que l’animation se termine avant l’exécution du step suivant. Pour moveIn, étant une transition, forceCompleteBeforeNext est défini sur true par défaut.

Comme j’utilise TypeScript pour cette animation, j’ai créé un label dédié afin qu’il puisse être appelé depuis d’autres langages également.

labels/animation01.ts
import { canvas, ImageContainer, moveIn, newLabel } from "@drincs/pixi-vn";

export const animation01 = newLabel("animation_01", [
    async () => {
        let tickerId = canvas.animate<ImageContainer>(
            "steph",
            {
                scaleX: 1,
            },
            { autoplay: false, forceCompleteBeforeNext: true }
        );

        await moveIn(
            "steph",
            {
                value: ["fm02-body", "fm02-eyes-joy", "fm02-mouth-smile01"],
                options: { xAlign: 0.8, yAlign: 1, scale: { y: 1, x: -1 }, anchor: 0.5 },
            },
            { direction: "right", ease: "easeInOut", tickerIdToResume: tickerId }
        );
    },
]);

ink

Comme expliqué ici, depuis ink vous pouvez appeler des labels écrits en JS/TS et inversement.

Vous pouvez maintenant appeler ce label (animation_01) depuis le label principal (start).

file_type_ink
ink/start.ink
=== start ===
// ...

# show imagecontainer james [m01-body m01-eyes-grin m01-mouth-grin00]
# show imagecontainer sly [fm01-body fm01-eyes-smile fm01-mouth-smile00]
# show imagecontainer steph [fm02-body fm02-eyes-upset fm02-mouth-nervous00]
# remove image steph with moveout direction left speed 300
[steph_fullname] goes through the opposite door,
# call animation_01
<> and returns with a HUGE tinfoil-covered platter.

// ...
-> DONE

Conclusion

Vous savez maintenant comment créer un visual novel avec Pixi’VN. Un grand pouvoir implique de grandes responsabilités : utilisez-le judicieusement et créez une excellente histoire ! 🚀

Voici un exemple interactif avec une interface minimale (HTML). En faisant défiler vers le bas, vous pouvez voir le même résultat en utilisant une interface complète (template React).