UnityNovelProj Unity で VisualNovel を作成する
仕組みについて
Manager
Manager コンポーネントがアタッチされた複数の X Manager が存在します。 たとえば DialogueSystemController であれば DialogueSystemController.cs がアタッチされており 参照すべき GameObject や ScriptableObject Configuration を参照しています。
レンダリング
VN Contoller
という配下にレンダー用の GameObject RenderGroups が存在します。
そこには
- Main
- 2D イラスト
- 3D モデル
- Live2D
- Live2D 用の Camera と CanvasRoot が存在します。
別れている理由としては、Live2D は複数のレイヤーに分かれて各イラストが動くため、 z-index の管理上分けておいたほうがレンダリングしやすかったから、のはずです…(怪しい)
LAYERS 配下は複数の %d-%w+
描画レイヤーに別れています。
0 のほうがより下に描画され上書きされやすく、 6 のほうがより上に描画され上書きされづらいです。
Dialogue 配下の各 GameObject は TextMeshPro など、描画に必要なコンポーネントが付与されています。
ただし、専用の DialogueContainer.cs が直接アタッチされているわけではありません。
DialogueSystemController が DialogueContainer.cs の参照を持っており、CS ファイルを介して間接的に GameObject を操作しています。
どうやって動作しているのか
どうやってテキストが描画されているのか
DialogueSystemController.cs
がすべての入口になっています。
DialogueSystemController.cs が初期化されるとき
- DisplayTextArchitect
- ConversationManager
がインスタンス化されます。 これらは pure c# です。
ConversationManager のコンストラクタにおいて、 DialogueSystemController.UserPromptNextEvent Delegate に「userPromptNext を false にする」イベントを登録しておきます。 これによって、DialogueSystemController が次のイベントへ進もう!としたら、それを ConversationManager が暗黙的に受け取って次の会話へ進めるようになります。
さて、ここからどうやって会話が表示されているのか?について見ていきます。 もっとも重要な点として、 Chapter18 においては Test C# コードから会話が実行されています。 TestingConversationManager.cs の テストテキストファイル指定からシナリオを読み込んでいます。 このように開発段階においては複数の TestingXXX.cs を用意してそれを Testing GameObject にアタッチし、Run してテストする、という方法を StellarProject では取っています。
addressable からテストファイルを取得し、 string[]
を DialogueSystemController.Say から実行しています。
void Start()
{
StartCoroutine(Running());
StartConversation();
}
IEnumerator Running()
{
yield return null;
}
void StartConversation()
{
var lines = TextReader.ReadAddressableTextFileSync(textAddressableName);
DialogueSystemController.instance.Say(lines);
}
DialogueSystemController.Say が実行されると、ConversationManager に処理が移譲されます。 会話系の処理全般はすべて ConversationManager が担当します。
ConversationManager が行うことは以下です。
ConversationManager.StartConversation(List<string>)
が実行されると、ConversationManager- このタイミングの
List<string>
は本当にただの raw string
- このタイミングの
- DialogueParser で
1 行のみ
構文解析してSpeaker, Dialogue, Command
data に分けられる- はじめに DialogueParser で Speaker, Dialogue, Command 部分の各 string へ大まかに分けられる
- その後 DLD_Speaker, DLD_Dialogue, DLD_Command とそれぞれさらに適切に解析される
- 解析した 1 行の Command, Dialogue をそれぞれ存在すれば画面に描画したり実行する
- Dialogue の場合
- C, A, WA などシグナルコマンドがあれば一時停止させる
yield return
IEnumerator で実装しているため一時停止できる
- TagManager によって、
<date>
,<playerName>
といった動的に変更したいテキスト内容を置換する - TextArchitect へ会話文表示を依頼し、 TextArchitect がすべての文字を描画するまで待機する
- C, A, WA などシグナルコマンドがあれば一時停止させる
- Command の場合
- CommandManager がコマンドを実行する
- Dialogue, Command それぞれ処理が終了したら
- すべてのコマンドを強制終了させる
- コマンドには強制終了時点での処理を設定できる
- キャラ移動において、スキップされたときに一気にその地点へ飛ばすため
- コマンドには強制終了時点での処理を設定できる
- ユーザ入力を待ち受ける
- キー入力されたら UserPromptNextEventReceived 経由で SystemController から通知がきえて、 nextPrompt = true になり、次の処理に進む
- すべてのコマンドを強制終了させる
- Dialogue の場合
テキストの表示については DisplayTextArchitect がすべて担当します。 ConversationManager から「Hello, World!」などの文字列を受け取ったら以下の処理を行います。
- Display, AppendDisplay で処理を開始する
- AppendDisplay であれば前描画していた文字列を消さない
- 描画中は yield return null をおこない、かつ 描画の仕方はインターフェースで抽象化する
- 描画中は isDisplaying=true となっており、それを ConversationManager が監視している