UnityNovelProj Unity で VisualNovel を作成する

仕組みについて

Manager

Imgur

Manager コンポーネントがアタッチされた複数の X Manager が存在します。 たとえば DialogueSystemController であれば DialogueSystemController.cs がアタッチされており 参照すべき GameObject や ScriptableObject Configuration を参照しています。

Imgur

レンダリング

VN Contoller という配下にレンダー用の GameObject RenderGroups が存在します。 そこには

  • Main
    • 2D イラスト
    • 3D モデル
  • Live2D
    • Live2D 用の Camera と CanvasRoot が存在します。

別れている理由としては、Live2D は複数のレイヤーに分かれて各イラストが動くため、 z-index の管理上分けておいたほうがレンダリングしやすかったから、のはずです…(怪しい)

Imgur

LAYERS 配下は複数の %d-%w+ 描画レイヤーに別れています。 0 のほうがより下に描画され上書きされやすく、 6 のほうがより上に描画され上書きされづらいです。 Dialogue 配下の各 GameObject は TextMeshPro など、描画に必要なコンポーネントが付与されています。 ただし、専用の DialogueContainer.cs が直接アタッチされているわけではありません。

DialogueSystemController が DialogueContainer.cs の参照を持っており、CS ファイルを介して間接的に GameObject を操作しています。

Imgur

どうやって動作しているのか

どうやってテキストが描画されているのか

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 では取っています。

Imgur

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 がすべての文字を描画するまで待機する
    • Command の場合
      • CommandManager がコマンドを実行する
    • Dialogue, Command それぞれ処理が終了したら
      • すべてのコマンドを強制終了させる
        • コマンドには強制終了時点での処理を設定できる
          • キャラ移動において、スキップされたときに一気にその地点へ飛ばすため
      • ユーザ入力を待ち受ける
        • キー入力されたら UserPromptNextEventReceived 経由で SystemController から通知がきえて、 nextPrompt = true になり、次の処理に進む

テキストの表示については DisplayTextArchitect がすべて担当します。 ConversationManager から「Hello, World!」などの文字列を受け取ったら以下の処理を行います。

  • Display, AppendDisplay で処理を開始する
    • AppendDisplay であれば前描画していた文字列を消さない
  • 描画中は yield return null をおこない、かつ 描画の仕方はインターフェースで抽象化する
  • 描画中は isDisplaying=true となっており、それを ConversationManager が監視している