LogoPixi’VN
Make your first

Crea la tua prima Visual Novel

Guida passo passo alla creazione di una visual novel con Pixi'VN, che copre l'impostazione del progetto, la narrazione, le risorse e l'interattività.

Questo tutorial ti guiderà attraverso il processo di creazione della tua prima Visual Novel.

Cos'è una visual novel? Una visual novel (VN) è una forma di narrativa interattiva digitale. Le visual novel sono spesso associate al medium dei videogiochi, ma non sempre vengono etichettate come tali. Combinano una narrazione testuale con illustrazioni statiche o animate e un grado variabile di interattività. Il formato è più raramente indicato come novel game, una ritrascrizione del termine wasei-eigo noberu gēmu (ノベルゲーム), che è più spesso usato in giapponese.

A scopo di test, in questa guida ricreeremo la visual novel Breakdown utilizzando Pixi'VN. Breakdown è un racconto breve che ha tutte le caratteristiche che una visual novel dovrebbe avere. Josh Powlison, il creatore di Breakdown, ci ha dato il permesso di utilizzare la sua narrazione per scopi didattici❤️.

Poiché Pixi’VN ti dà la possibilità di scrivere la tua narrazione scegliendo un o più linguaggio narrative disponibili, verranno realizzati esempi per ciascun linguaggio attualmente disponibile in ogni step di sviluppo.

Crea un nuovo progetto

Il primo step è creare un nuovo progetto. Puoi trovare maggiori informazioni su come creare un nuovo progetto partendo da un template qui. Utilizzeremo il template "Visual Novel - React".

Visual Novel -> React

Una volta completata la creazione, è molto importante leggere il file README.md che si trova nella cartella principale del progetto. Questo file contiene informazioni importanti sul progetto e su come utilizzarlo.

Nel nostro caso, per avviare il progetto dovremo semplicemente eseguire i seguenti comandi:

npm install
npm start

Creazione dei personaggi

Adesso definiremo i personaggi di questa storia. Per fare ciò, definiremo nel file /values/characters.ts i personaggi che utilizzeremo. Per maggiori informazioni su come creare e utilizzare i personaggi puoi consultare: Personaggi

Cosa significa mc? mc è un'abbreviazione comune per "Main Character". Nelle visual novel è prassi comune usare mc come nome del personaggio principale.

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]);

Prima bozza della narrazione

Ora possiamo iniziare a scrivere la "prima bozza" della narrazione della visual novel. Creeremo la prima label chiamata start, che sarà l'inizio del gioco.

Dopodiché potremo scrivere i dialoghi che seguiranno nella nostra visual novel. Il template che abbiamo scelto supporta il linguaggio di markup markdown (Linguaggio di markup in ink), quindi lo useremo per la nostra narrazione.

Questo è l'esempio:

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.
steph: Hey! Everyone calls me Steph. I'll shake your hand.

// ...
-> DONE

Dividi la narrazione in più label

Non è consigliabile creare labels molto lunghe (anche per visual novel lineari), ma è consigliabile creare più labels piccole e "chiamarle" quando necessario con le funzionalità di controllo del flusso di gioco (ink knot (o label)).

Per questo motivo, anche se nel nostro caso la nostra storia è lineare, verrà divisa in due labels, la prima sarà quella che abbiamo appena creato, mentre la seconda si chiamerà second_part.

Questo è l'esempio:

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

Menù a scelta

Ora chiederemo al giocatore se desidera continuare con la seconda parte della visual novel.

Per fare ciò, utilizzeremo il menu di scelta.

Questo è l'esempio:

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

Modifica le informazioni del personaggio e usalo come variabile

Ora darò al giocatore la possibilità di cambiare il nome del mc.

Per fare ciò, chiederò al giocatore di completare una casella di input utilizzando le funzionalità di Pixi’VN (Utilizzare il prompt di input in ink).

Dopo aver ottenuto il valore di input, puoi impostare il nome del personaggio utilizzando il valore ottenuto (Modifica il nome del personaggio in ink).

Questo è l'esempio:

ink/start.ink
VAR _input_value_ = ""

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

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

// ...
-> DONE

Ora potremmo usare i nomi dei personaggi nei dialoghi (Usa il nome del personaggio nei dialoghi in ink).

Questo è l'esempio:

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

Utilizzare la funzionalità "glue" dei dialoghi

Nelle visual novel è spesso utile incollare del testo nel dialogo corrente. Ad esempio, per mettere in pausa una conversazione e farla proseguire in uno step successivo. Per farlo, possiamo usare la funzionalità glue.

Questo è l'esempio:

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

Definisci le assets e caricale

Uno dei primi steps è scegliere dove salvare le risorse della tua visual novel. In questo caso salveremo gli assets nello storage Firebase (un servizio di hosting).

Per caricare e manipolare risorse (immagini, gif, video...) sarà necessario utilizzare Assets. Assets è una classe con molte funzionalità e proviene dalla libreria PixiJS, se vuoi maggiori informazioni leggi qui.

Prima di utilizzare un asset, si consiglia vivamente di inizializzare la matrice degli asset.

Per impostazione predefinita, come puoi vedere nel file assets/manifest.ts, tutti i template in onLoadingLabel tentano di caricare in background il "bundle asset" con l'alias uguale all'id della label. Si consiglia quindi di aggiungere nel manifest un bundle assets per ogni label con alias uguale all'id della label e contenente le immagini utilizzate in quella label.

Questo è l'esempio:

import { Assets } from "@drincs/pixi-vn";
import manifest from "../assets/manifest";

/**
 * Define all the assets that will be used in the game.
 * This function will be called before the game starts.
 * You can read more about assets management in the documentation: https://pixi-vn.web.app/start/assets-management.html
 */
export async function defineAssets() {
    Assets.init({ manifest });

    // The game will not start until these asserts are loaded.
    await Assets.loadBundle("main_menu");

    // The game will start immediately, but these asserts will be loaded in the background.
    // Assets.backgroundLoadBundle("main_menu");
    // Assets.backgroundLoad("background_main_menu");
}

Aggiungere lo sfondo e le immagini dei personaggi

Ora è il momento di pensare anche all'aspetto visivo. Aggiungeremo lo sfondo e gli sprite dei personaggi al canvas della visual novel.

Cos'è uno sprite? Nella computer grafica, uno sprite è una bitmap bidimensionale integrata in una scena più grande, il più delle volte in un videogioco 2D.

Nel nostro caso gli sprite dei personaggi sono composti da 3 immagini: il corpo, gli occhi e la bocca. Quindi utilizziamo ImageContainer per comporre il personaggio.

Puoi trovare maggiori informazioni su come aggiungere componenti canvas in questa documentazione (Utilizza componenti canvas in ink).

Questo è l'esempio:

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

Caricamento intelligente degli assets

Nel nostro caso abbiamo salvato le immagini del gioco su un servizio di hosting (Firebase). Per questo motivo il caricamento delle risorse non avviene tempestivamente.

Per evitare che il giocatore percepisca troppi caricamenti, dovremmo raggrupparli in determinate fasi del gioco. Nel mio caso caricherò le immagini più utilizzate all'inizio della label.

Puoi trovare maggiori informazioni su come gestire i caricamenti qui.

Questo è l'esempio:

ink/start.ink
=== start ===
# load assets bg01-hallway
# load assets m01-body m01-eyes-grin m01-eyes-smile m01-eyes-wow m01-mouth-grin00 m01-mouth-smile00 m01-mouth-smile01
# load assets fm01-body fm01-eyes-smile fm01-eyes-upset fm01-mouth-serious00 fm01-mouth-serious01 fm01-mouth-smile00
# load assets fm02-body fm02-eyes-joy fm02-eyes-nervous fm02-eyes-wow fm02-mouth-nervous00 fm02-mouth-smile00

# 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

Utilizzare le transizioni

Per rendere più dinamica la visual novel, è possibile utilizzare le transizioni per mostrare le immagini. Puoi trovare maggiori informazioni sull'uso delle transizioni qui (Uso delle transizioni in ink).

Questo è l'esempio:

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

Creare un'animazione

Per rendere più dinamica la visual novel, è possibile utilizzare le animazioni. Puoi trovare maggiori informazioni su come utilizzare le animazioni qui (Utilizzo delle animazioni in ink).

Consiglio di utilizzare Typescript se è necessario impostare molte proprietà, in questo modo si ha un maggiore controllo sull'animazione, più funzionalità e feedback sul tipo.

Nel mio caso l'animazione rimuoverà steph dalla scena e la reinserirà nel steph successivo. La specchierò anche sull'asse x per assicurarmi che sia rivolta nella direzione giusta.

Per far entrare/uscire steph userò le funzioni moveOut e moveIn. Per l'effetto specchio userò il ticker ZoomTicker.

Una caratteristica importante delle transizioni è che mettono momentaneamente in pausa tutte le animazioni collegate a quel componente e le riprendono al termine della transizione.

Quindi, nel mio caso, userò prima della funzione moveIn la funzione addTicker per aggiungere il ticker ZoomTicker. In questo modo steph verrà specchiato sull'asse x una volta completata la transizione.

Inoltre, poiché per questa animazione utilizzerò TypeScript, ho creato un'label per questa animazione. In modo che possa essere chiamato anche da altri linguaggi che non siano JS/TS.

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

export const animation01 = newLabel("animation_01", [
    async () => {
        canvas.addTicker(
            "steph",
            new ZoomTicker({ type: "zoom", limit: 1, speed: 70 })
        );

        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", speed: 300 }
        );
    },
]);

Ora posso chiamare questa label animation_01 dall'label principale start. (Come spiegato qui da ink è possibile chiamare labels scritte in ts e viceversa.)

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

Conclusione

Bene, ora sai come creare una visual novel con Pixi'VN. Da un grande potere derivano grandi responsabilità, quindi usalo saggiamente e crea una grande storia! 🚀

Ecco un esempio interattivo con un'interfaccia utente minimale (HTML). Scorrendo verso il basso è possibile vedere lo stesso risultato utilizzando un'interfaccia utente completa (template React).