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 fase di sviluppo.
Crea un nuovo progetto
Il primo passo è creare un nuovo progetto. You can find more information on how to create a new project starting from a template here. 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
Markup
Tutti i template supportano Markdown e Tailwind CSS, quindi li utilizzeremo per la nostra 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.
Questo è l'esempio:
=== 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
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 narrazione.
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:
=== 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:
=== 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.
Dopo aver ottenuto il valore di input, puoi impostare il nome del personaggio utilizzando il valore ottenuto.
Questo è l'esempio:
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 utilizzare i nomi dei personaggi nei dialoghi.
Questo è l'esempio:
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:
=== 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 gli assets e caricali
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.
One of the first steps is choosing where to save your visual novel assets. In questo caso salveremo gli assets nello storage Firebase (un servizio di hosting). Puoi utilizzare qualsiasi servizio di hosting tu preferisca o addirittura salvare le risorse localmente nel progetto; per saperne di più, 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
corrente. 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 { 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://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fmain-menu.webp?alt=media",
},
],
},
// labels
{
name: "start",
assets: [
{
alias: "bg01-hallway",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fbg01-hallway.webp?alt=media",
},
],
},
{
name: "second_part",
assets: [
{
alias: "bg02-dorm",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fbg02-dorm.webp?alt=media",
},
],
},
// characters
{
name: "fm01",
assets: [
{
alias: "fm01-body",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-body.webp?alt=media",
},
{
alias: "fm01-eyes-grin",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-eyes-grin.webp?alt=media",
},
{
alias: "fm01-eyes-smile",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-eyes-smile.webp?alt=media",
},
{
alias: "fm01-eyes-soft",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-eyes-soft.webp?alt=media",
},
{
alias: "fm01-eyes-upset",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-eyes-upset.webp?alt=media",
},
{
alias: "fm01-eyes-wow",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-eyes-wow.webp?alt=media",
},
{
alias: "fm01-mouth-grin00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-grin00.webp?alt=media",
},
{
alias: "fm01-mouth-serious00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-serious00.webp?alt=media",
},
{
alias: "fm01-mouth-serious01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-serious01.webp?alt=media",
},
{
alias: "fm01-mouth-smile00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-smile00.webp?alt=media",
},
{
alias: "fm01-mouth-smile01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-smile01.webp?alt=media",
},
{
alias: "fm01-mouth-soft00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-soft00.webp?alt=media",
},
{
alias: "fm01-mouth-soft01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-soft01.webp?alt=media",
},
{
alias: "fm01-mouth-upset00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-upset00.webp?alt=media",
},
{
alias: "fm01-mouth-upset01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-upset01.webp?alt=media",
},
{
alias: "fm01-mouth-wow01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm01%2Ffm01-mouth-wow01.webp?alt=media",
},
],
},
{
name: "fm02",
assets: [
{
alias: "fm02-body",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-body.webp?alt=media",
},
{
alias: "fm02-eyes-bawl",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-eyes-bawl.webp?alt=media",
},
{
alias: "fm02-eyes-joy",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-eyes-joy.webp?alt=media",
},
{
alias: "fm02-eyes-nervous",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-eyes-nervous.webp?alt=media",
},
{
alias: "fm02-eyes-smile",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-eyes-smile.webp?alt=media",
},
{
alias: "fm02-eyes-upset",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-eyes-upset.webp?alt=media",
},
{
alias: "fm02-eyes-wow",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-eyes-wow.webp?alt=media",
},
{
alias: "fm02-mouth-cry01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-cry01.webp?alt=media",
},
{
alias: "fm02-mouth-nervous00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-nervous00.webp?alt=media",
},
{
alias: "fm02-mouth-nervous01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-nervous01.webp?alt=media",
},
{
alias: "fm02-mouth-smile00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-smile00.webp?alt=media",
},
{
alias: "fm02-mouth-smile01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-smile01.webp?alt=media",
},
{
alias: "fm02-mouth-upset00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-upset00.webp?alt=media",
},
{
alias: "fm02-mouth-upset01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-upset01.webp?alt=media",
},
{
alias: "fm02-mouth-wow01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Ffm02%2Ffm02-mouth-wow01.webp?alt=media",
},
],
},
{
name: "m01",
assets: [
{
alias: "m01-body",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-body.webp?alt=media",
},
{
alias: "m01-eyes-annoy",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-eyes-annoy.webp?alt=media",
},
{
alias: "m01-eyes-concern",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-eyes-concern.webp?alt=media",
},
{
alias: "m01-eyes-cry",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-eyes-cry.webp?alt=media",
},
{
alias: "m01-eyes-grin",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-eyes-grin.webp?alt=media",
},
{
alias: "m01-eyes-smile",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-eyes-smile.webp?alt=media",
},
{
alias: "m01-eyes-wow",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-eyes-wow.webp?alt=media",
},
{
alias: "m01-mouth-annoy00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-annoy00.webp?alt=media",
},
{
alias: "m01-mouth-annoy01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-annoy01.webp?alt=media",
},
{
alias: "m01-mouth-concern00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-concern00.webp?alt=media",
},
{
alias: "m01-mouth-concern01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-concern01.webp?alt=media",
},
{
alias: "m01-mouth-cry00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-cry00.webp?alt=media",
},
{
alias: "m01-mouth-cry01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-cry01.webp?alt=media",
},
{
alias: "m01-mouth-grin00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-grin00.webp?alt=media",
},
{
alias: "m01-mouth-neutral00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-neutral00.webp?alt=media",
},
{
alias: "m01-mouth-neutral01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-neutral01.webp?alt=media",
},
{
alias: "m01-mouth-smile00",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-smile00.webp?alt=media",
},
{
alias: "m01-mouth-smile01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-smile01.webp?alt=media",
},
{
alias: "m01-mouth-wow01",
src: "https://firebasestorage.googleapis.com/v0/b/pixi-vn.appspot.com/o/public%2Fbreakdown%2Fm01%2Fm01-mouth-wow01.webp?alt=media",
},
],
},
],
};
export default manifest;
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.
Questo è l'esempio:
=== 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:
=== 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
Utilizzare le transizioni
Per rendere più dinamica la visual novel, è possibile utilizzare le transizioni per mostrare le immagini. Puoi trovare maggiori informazioni sull'utilizzo delle transizioni qui.
Questo è l'esempio:
=== 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. Per maggiori informazioni su come utilizzare le animazioni, vedere qui.
Se devi impostare molte proprietà, ti consiglio di usare TypeScript, perché ti offre più controllo, più funzionalità e feedback sul tipo.
In questo esempio, la mia animazione rimuoverà steph
dalla scena e la reinserirà nel step
successivo. La specchierò anche sull'asse x per assicurarmi che sia rivolta nella direzione giusta.
Per rimuovere/inserire steph
, utilizzerò le funzioni moveOut
e moveIn
. Per l'effetto specchio, utilizzerò la funzione canvas.animate
.
Per prima cosa, utilizzo la funzione canvas.animate
per creare un'animazione che imposta la proprietà scaleX
da -1
(specchiata) a 1
, con autoplay: false
in modo che non inizi immediatamente.
Quindi, utilizzo la funzione moveIn
per spostare steph
nella scena, passando il tickerId
dell'animazione in modo che possa essere ripresa una volta completata la transizione.
Utilizzo l'opzione forceCompleteBeforeNext: true
per garantire che l'animazione venga completata prima che venga eseguito lo step
successivo. Poiché moveIn
è una transizione, forceCompleteBeforeNext
è true per impostazione predefinita.
Poiché per questa animazione utilizzo TypeScript, ho creato una label
per essa, in modo che possa essere richiamata anche da altri linguaggi.
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
As explained here, from ink you can call labels
written in ts and vice versa.
Ora puoi chiamare questa label
(animation_01
) dalla label
principale (start
).
=== 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).