Make your first Visual Novel
Step-by-step guide to creating a visual novel with Pixi’VN, covering project setup, narrative, assets, and interactivity.
This tutorial will guide you through the process of creating your first Visual Novel.
For testing purposes, in this guide we will be recreating the visual novel Breakdown using Pixi’VN. Breakdown is a short story that has all the features that a visual novel should have. Josh Powlison, the creator of Breakdown, has given us permission to use his narration for educational purposes❤️.
Since Pixi’VN gives you the ability to write your own narration by choosing one or more available narrative languages, examples will be made for each currently available language at each development step.
Create a new project
Before starting, make sure you have installed the prerequisites.
The first step is to create a new project. You can do this by running:
npm create pixi-vn@latestand selecting the Visual Novel template.
VS Code
If you open the project with VS Code, you will be asked to consent to running some workspace tasks and to install the recommended extensions for the project. It is strongly advised to accept both.
You can find more information about templates, tasks, and extensions here.
Characters creation
Now we will define the characters of this story. To do this, we will define in the /content/characters.ts file the characters that we will be using.
For more information on how to create and use characters you can consult: Characters
import Character from "@/models/Character";
import { RegisteredCharacters } from "@drincs/pixi-vn";
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]);First draft of the narrative
Markup
All templates have Markdown and Tailwind CSS support, so we will use it for our narration.
Now we can start writing the "first draft" of the narration of the visual novel.
We will create the first label called start, which will be the beginning of the game. After that we can write the dialogues that will follow in our visual novel.
This is the example:
=== 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.
// ...
-> DONESplit the narrative into labels
It is not advisable to create very long labels (even for linear visual novels), but it is advisable to create multiple small labels and "call" them when needed with the narration flow control features.
For this reason, even if in our case our story is linear, it will be divided into two labels, the first will be the one we just created (start), while the second will be called second_part.
This is the example:
=== 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.
// ...
-> DONEChoice menus
Now we will ask the player if he wants to continue with the second part of the visual novel.
To do this, we will set narration.choices to an array of choice options created with newChoiceOption (to jump or call a label) and newCloseChoiceOption (to close the choice menu without navigating anywhere).
You can find more information about choice menus here.
This is the example:
=== start ===
// ...
You want continue to the next part?
* Yes, I want to continue
-> second_part
* No, I want to stop here
-> END
=== second_part ===
// ...
-> DONEEdit character information and use it as a variable
Now I will give the player the ability to change the name of the mc.
To do this, I will ask the player to complete an input box using Pixi’VN's features.
After getting the input value, you can set the character name using the obtained value.
In ink, _input_value_ is a special variable that holds the value entered by the player. You can read more about it here.
This is the example:
=== start ===
// ...
He thrusts out his hand.
# request input type string default Peter
What is your name?
# rename mc { _input_value_ }
// ...
-> DONENow we could use character names within dialogues.
In ink, you can use square brackets (e.g. [sly]) to take advantage of the text replacement mechanism: by default, the templates check whether the text between the brackets matches a character id, and if so, replace it with that character's name.
In JS/TS, you can achieve the same result using template literals with ${} (e.g. `${sly.name}`).
This is the example:
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!
// ...
-> DONEDefine assets and load them
To load and manipulate assets (images, gifs, videos...) you will need to use Assets. Assets is a class with many features and comes from the PixiJS library, if you want more information read here.
One of the first steps is choosing where to save your visual novel assets: you can use an online hosting service or save them locally in the project. You can find more information about both options here.
If you choose to save the assets locally, you just need to place them inside src/assets: the template automatically uses AssetPack to analyze the files and define them in src/assets/manifest.gen.json. If instead you choose to keep them online, you will need to manually edit assets/index.ts to register them.
In both cases, each asset is assigned one or more alias, which you can then use in your code to reference that asset.
{
"bundles": [
{
"name": "images",
"assets": [
{
"alias": [
"images_icon",
"icon"
],
"src": [
"images/[email protected]",
"images/icon-VA-NtA.webp",
"images/[email protected]",
"images/icon-E_ZC2g.png"
],
"data": {
"tags": {}
}
}
]
}
]
}Add background and character images
Now it's time to think about the visual part too. We will add the background and character images to the visual novel canvas.
In our case, each character is composed of 3 sprites: the body, the eyes and the mouth. We use ImageContainer, a Container that groups Sprite children, to group them together and compose the character. You can find more information on how to add canvas components in this documentation.
This is the example:
=== 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: ...
// ...
-> DONESounds and music
To add sounds and music to your visual novel, you can use the sound utility.
You can define sound channels to manage different types of sounds (e.g., BGM, SFX) and control their volume, pause, resume, etc. It is recommended to define the channels at the start of the game, for example in the then callback of the Game.init function.
Into this example, I define two channels: one for the background music (BGM) and one for the sound effects (SFX). I also set the defaultChannelAlias to the SFX channel, so if I don't specify a channel when playing a sound, it will be played in the SFX channel by default. To define the background channel, I set the background property to true, so unlike the other channels the sounds will not be stopped at the end of each narrative step, but they will continue until they are paused or stopped.
import { Game, sound } from "@drincs/pixi-vn";
import { BGM_CHANNEL_NAME, SFX_CHANNEL_NAME } from "@/constans";
Game.init(body, {
// ...
}).then(() => {
sound.channels.add(BGM_CHANNEL_NAME, { background: true });
sound.channels.add(SFX_CHANNEL_NAME);
sound.defaultChannelAlias = SFX_CHANNEL_NAME;
});Finally, you can play the sounds and music in the labels using the sound.play function. You can also use channels to manage different types of sounds (e.g., BGM, SFX) and control their volume, pause, resume, etc.
You can find more information about how to use it here.
=== start ===
# show image bg bg01-hallway
# play sound sfx_whoosh delay 0.1
# show imagecontainer james [m01-body m01-eyes-smile m01-mouth-neutral01] xAlign 0.5 yAlign 1 with movein direction right ease circInOut type spring
james: You're my roommate's replacement, huh?
# play sound sfx_whoosh channel bgm loop true
# 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: ...
// ...
# pause all sounds
# show imagecontainer steph [fm02-body fm02-eyes-smile fm02-mouth-smile00]
# play sound sfx_whoosh delay 0.1
# remove image james with moveout direction right ease circInOut type spring duration 0.5 delay 0.05
# remove image sly with moveout direction right ease anticipate duration 0.5
# remove image steph with moveout direction left ease easeInOut duration 0.5 delay 0.1
You want continue to the next part?<># continue
* Yes, I want to continue
-> second_part
* No, I want to stop here
-> END
=== second_part ===
# show text bg "(A few minutes later...)" style \{ fontFamily: "Arial", dropShadow: \{ alpha: 0.8, angle: 2.1, blur: 4, color: "0x111111", distance: 10, \}, fill: "\#ffffff", stroke: \{ color: "\#004620", width: 12, join: "round" \}, fontSize: 60, fontWeight: "lighter" \} with fade
# edit text bg align 0.5
# pause
# resume all sounds
# show image bg bg02-dorm align 0 with fade
# play sound sfx_whoosh delay 0.4
// ...
She enters my room before I'VE even had a chance to.
// ...
-> DONE
Conclusion
Well, now you know how to create a visual novel with Pixi’VN. With great power comes great responsibility, so use it wisely and create a great story! 🚀