最初のビジュアルノベルを作ろう
Pixi’VNでビジュアルノベルを作成するためのステップバイステップガイド。プロジェクトのセットアップ、ナラティブ、アセット、インタラクティブ性について解説します。
このチュートリアルでは、最初のビジュアルノベルを作成するプロセスをご案内します。
テスト目的として、このガイドではPixi’VNを使ってビジュアルノベル Breakdown を再現します。 Breakdownは、ビジュアルノベルが備えるべきすべての機能を持つ短編ストーリーです。 Breakdownの作者であるJosh Powlison氏は、教育目的でそのナラティブを使用することを許可してくれました❤️。
Pixi’VNでは、1つ以上の利用可能なナラティブ言語を選んで独自のナラティブを書くことができるため、開発の各ステップで、現在利用可能な各言語ごとに例を示します。
新しいプロジェクトを作成する
始める前に、前提条件をインストール済みであることを確認してください。
最初のステップは、新しいプロジェクトを作成することです。 以下を実行することで作成できます。
npm create pixi-vn@latestそして Visual Novel テンプレートを選択します。
VS Code
VS Codeでプロジェクトを開くと、ワークスペースタスクの実行に同意するかどうか、およびプロジェクトの推奨拡張機能をインストールするかどうかを尋ねられます。 両方とも承諾することを強くお勧めします。
テンプレート、タスク、拡張機能についての詳細はこちらをご覧ください。
キャラクターの作成
ここでは、このストーリーのキャラクターを定義します。 そのために、使用するキャラクターを /content/characters.ts ファイルで定義します。
キャラクターの作成と使用方法についての詳細は次を参照してください: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]);ナラティブの最初の草案
Markup
すべてのテンプレートはMarkdownとTailwind CSSをサポートしているため、ナラティブにはこれらを使用します。
それでは、ビジュアルノベルのナラティブの「最初の草案」を書き始めましょう。
まず、ゲームの始まりとなる start という名前の最初のナラティブノード(label)を作成します。 その後、ビジュアルノベルで続くダイアログを書いていきます。
以下がその例です。
=== 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ナラティブをlabelsに分割する
(線形のビジュアルノベルであっても)非常に長いナラティブノード(labels)を作成することは推奨されません。むしろ、複数の小さなナラティブノード(labels)を作成し、必要に応じてナラティブフロー制御機能で「呼び出す」ことが推奨されます。
そのため、今回のストーリーは線形ですが、2つのナラティブノード(labels)に分割します。最初のものは先ほど作成したもの(start)で、2つ目は second_part という名前になります。
以下がその例です。
=== 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選択肢メニュー
ここでは、プレイヤーにビジュアルノベルの第2部に進むかどうかを尋ねます。
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).
選択肢メニューについての詳細はこちらをご覧ください。
以下がその例です。
=== 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キャラクター情報を編集し変数として使用する
ここでは、プレイヤーに mc の名前を変更できるようにします。
そのために、プレイヤーにPixi’VNの機能を使って入力ボックスに入力してもらいます。
入力値を取得した後、その値を使ってキャラクター名を設定できます。
ink では、_input_value_ はプレイヤーが入力した値を保持する特別な変数です。 詳細はこちらをご覧ください。
以下がその例です。
=== start ===
// ...
He thrusts out his hand.
# request input type string default Peter
What is your name?
# rename mc { _input_value_ }
// ...
-> DONEこれで、ダイアログ内でキャラクター名を使用できるようになりました。
ink では、角括弧(例:[sly])を使うことでテキスト置換の仕組みを利用できます。デフォルトでは、テンプレートは角括弧の間のテキストがキャラクターの id と一致するかどうかを確認し、一致する場合はそのキャラクターの名前に置き換えます。
JS/TSでは、${} を使ったテンプレートリテラル(例:`${sly.name}`)を使うことで同じ結果を得られます。
以下がその例です。
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アセットを定義して読み込む
アセット(画像、gif、動画など) を読み込んで操作するには、Assets を使用する必要があります。 Assets は多くの機能を持つクラスで、PixiJSライブラリに由来します。詳細を知りたい場合はこちらをお読みください。
最初のステップの一つは、ビジュアルノベルのアセットをどこに保存するかを選ぶことです。オンラインのホスティングサービスを使うか、プロジェクト内にローカルで保存するかのいずれかです。 両方の選択肢についての詳細はこちらをご覧ください。
アセットをローカルに保存することを選ぶ場合は、src/assets の中に配置するだけで済みます。テンプレートは自動的にAssetPackを使ってファイルを解析し、src/assets/manifest.gen.json に定義します。 一方、オンラインで管理することを選ぶ場合は、assets/index.ts を手動で編集して登録する必要があります。
どちらの場合も、各アセットには1つ以上のエイリアス(alias)が割り当てられ、コード内でそのアセットを参照する際に使用できます。
{
"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": {}
}
}
]
}
]
}背景とキャラクター画像を追加する
さあ、ビジュアル面についても考える時が来ました。 ビジュアルノベルのキャンバスに背景とキャラクターの画像を追加していきます。
今回の場合、各キャラクターは体・目・口の3つのスプライトで構成されています。 これらをまとめてキャラクターを構成するために、Sprite の子要素をグループ化する Container であるImageContainerを使用します。 キャンバスコンポーネントの追加方法についての詳細はこちらのドキュメントをご覧ください。
以下がその例です。
=== 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サウンドと音楽
ビジュアルノベルにサウンドと音楽を追加するには、sound ユーティリティを使用できます。
サウンドチャンネルを定義することで、異なる種類のサウンド(例:BGM、SFX)を管理し、その音量、一時停止、再開などを制御できます。 チャンネルはゲームの開始時、例えば Game.init 関数の then コールバック内で定義することをお勧めします。
この例では、バックグラウンドミュージック(BGM)用とサウンドエフェクト(SFX)用の2つのチャンネルを定義します。 また、defaultChannelAlias をSFXチャンネルに設定するので、サウンドを再生する際にチャンネルを指定しなければ、デフォルトでSFXチャンネルで再生されます。 バックグラウンドチャンネルを定義するために、background プロパティを true に設定します。これにより、他のチャンネルとは異なり、各ナラティブステップ(step)の終わりでサウンドが停止されることはなく、一時停止または停止されるまで継続します。
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;
});最後に、ナラティブノード(labels)内で sound.play 関数を使ってサウンドや音楽を再生できます。 また、チャンネルを使用して異なる種類のサウンド(例:BGM、SFX)を管理し、その音量、一時停止、再開などを制御することもできます。
使用方法についての詳細はこちらをご覧ください。
=== 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
まとめ
さて、これでPixi’VNを使ってビジュアルノベルを作成する方法が分かりました。 大いなる力には大いなる責任が伴います。賢く使って、素晴らしいストーリーを作り上げましょう! 🚀