🚿

ScenarioFlow覚え書き

2024/07/04に公開

初めに

自前で会話ダイアログを実装したい!という動機でネットを漁っていたところ、ScenarioFlowというライブラリを発見!
基本的な機能が実装されているかつカスタマイズ性が高いということで、これをベースに会話イベントを作れると開発効率が上がりそう。というモチベーションからGithubのREADMEを読み込んだので、理解した内容を覚え書きとして記載します。

ScenarioFlowとは

ScenarioFlowは、ゲームやインタラクティブなアプリケーションのシナリオ管理を容易にするためのフレームワークです。このフレームワークは、シナリオの記述、実行、制御を簡単に行うためのツールと機能を提供します。

ScenarioFlowが持つ機能

シナリオスクリプトの管理

  • シナリオスクリプトは、ストーリーやイベントの進行を記述するためのスクリプトです。
  • これらのスクリプトをパースし、実行する機能をScenarioFlowは提供しています。

タスク実行の制御

  • シナリオスクリプト内に記載したひとつのイベント単位を[タスク]と呼びます。
  • ScenarioFlowはタスクの順序やタイミングを制御することができます。

コマンドとデコーダの登録

  • シナリオ内で使用するカスタムコマンドやデコーダを登録することができます。
  • 特定のアクションやデータの変換をシナリオ内で実装できます。

スキップモードとキャンセル機能

  • ユーザーがシナリオの特定部分をスキップ/キャンセルする機能を提供します。

非同期処理のサポート

  • 非同期的なシナリオ進行(タイプライター風のテキスト演出など)を実装することができます
  • ScenarioFlowはUniTaskを使って非同期処理を実装しています。

ScenarioFlowのコンポーネント

ScenarioScript

  • シナリオの内容を記述するスクリプトファイルをScenarioScriptと呼びます。
  • 独自のフォーマットが実装されていて、視覚的にわかりやすいテキストになっています。

ScenarioBookPublisher

  • シナリオスクリプトをScenarioBookに変換するためのクラスです。
  • コマンドやデコーダを登録して利用します。

ScenarioBook

  • ScenarioScriptを元にして生成されたデータ構造です。
  • ScenarioBookPublisherによって生成され、ScenarioBookReaderが読み込みます。

ScenarioBookReader

  • ScenarioBookを読み込み、会話処理をアウトプットするクラスです。
  • ScenarioTaskExecutorを呼び出してシナリオを実行します。

ScenarioTaskExecutor

  • シナリオに書かれたタスクを管理するクラスです。
  • ScenarioBookReaderから参照され、以下を管理/実行します。
    • 次のイベントへの通知
    • キャンセル/スキップ処理
    • 各タスクの順序制御
    • タスクの実行
    • 非同期タスクの制御

各コンポーネントの関係性

  • ScenarioScriptを参照し、シナリオを読み込みます。
  • ScenarioTaskExecutorで通知/キャンセルのインターフェイスを設定します。
  • ScenarioBookPublisherから登録されたコマンドやデコーダを読み込みます。
  • ScenarioBookPublisherScenarioScriptScenarioBookに変換します。
  • ScenarioBookReaderScenarioBookを読み込んで会話を実行します。
  • ScenarioBookReaderScenarioTaskExecutorで定義した内容を元に、会話中の通知/キャンセル処理を実行します。

各クラスで定義される機能

ScenarioTaskExecutor

  • INextNotifier
    • ユーザ入力や他イベントによってシナリオを進めるための定義です。
  • ICancellationNotifier
    • タスクのキャンセル機能を定義します。
    • シナリオ実行中にタスクをキャンセルするための機能です。
  • ScenarioTaskExecutor
    • 上記で定義したインターフェイスを引数にして初期化します。
    • 後続でインターフェイス化(IScenarioTaskExecutor)した上でscenarioBookReaderに渡します。
ex.) ScenarioManager.cs からの呼び出し
       // ScenarioTaskExecutorの設定
        EnterKeyNotifier enterKeyNotifier = new EnterKeyNotifier();
        INextNotifier nextNotifier = enterKeyNotifier;
        ICancellationNotifier cancellationNotifier = enterKeyNotifier;
        ScenarioTaskExecutor scenarioTaskExecutor = new ScenarioTaskExecutor(nextNotifier, cancellationNotifier);
  • ISkipActivator
    • シナリオ自体のスキップ機能を定義するために使われます。
    • ScenarioManager.cs内で条件を定義してUniTaskのキャンセル機能(GetCancellationTokenOnDestroy)を呼び出します。
ex.) ScenarioManager.cs からの呼び出し
        // シナリオタスクのキャンセル処理の設定
        IDisposable disposable = scenarioTaskExecutor;
        disposable.AddTo(this.GetCancellationTokenOnDestroy());

        // スキップモードの管理
        ISkipActivator skipActivator = scenarioTaskExecutor;
        skipActivator.Duration = 0.05f;
        UniTaskAsyncEnumerable.EveryUpdate()
            .Select(_ => Input.GetKeyDown(KeyCode.S))
            .Where(x => x)
            .ForEachAsync(_ =>
            {
                skipActivator.IsActive = !skipActivator.IsActive;
                Debug.Log($"Skip Mode: {skipActivator.IsActive}");
            }, cancellationToken: this.GetCancellationTokenOnDestroy()).Forget();

ScenarioBookPublisher

  • ILabelOpener
    • シナリオスクリプト内の特定のラベルを処理するためのインターフェースです。
    • シナリオ中で定義されたラベルに対応する処理を行うために使用されます。
  • ICancellationTokenDecoder
    • シナリオの実行中にキャンセルに関する情報を解析するためのインターフェースです。
    • ScenarioScript中に含まれたキャンセル処理を解読してキャンセル処理を実行します。
  • IReflectable
    • 定義したコマンドやデコーダを纏めたインターフェイスです。
    • ScenarioBookPublisherを初期化する際に引数として渡します。
  • IScenarioBookPublisher
    • 上記で初期化したScenarioBookPublisherをインターフェイス化した上でScenarioScriptScenarioBookに変換します。
        // ScenarioBookPublisherの設定
        ILabelOpener labelOpener = scenarioBookReader;
        ICancellationTokenDecoder cancellationTokenDecoder = scenarioTaskExecutor;
        ScenarioBookPublisher scenarioBookPublisher = new ScenarioBookPublisher(
            new IReflectable[]
            {
                new BranchMaker(labelOpener),
                new CancellationTokenDecoder(cancellationTokenDecoder),
                new ColorDecoder(),
                new ConsoleDialogueWriter(),
                new DelayMaker(),
                new PrimitiveDecoder(),
                new MessageLogger(),
                new BoolDecoder(),
            });

        // ScenarioBookの生成
        IScenarioBookPublisher scenarioBookPublisherInterface = scenarioBookPublisher;
        IScenarioScript scenarioScriptInterface = scenarioScript;
        ScenarioBook scenarioBook = scenarioBookPublisherInterface.Publish(scenarioScriptInterface);

ScenarioBookReader

  • IScenarioTaskExecutor
    • 上記で定義したScenarioTaskExecutorをインターフェイス化し、引数として渡します。
  • IScenarioBookReader
    • 初期化したscenarioBookReaderをインターフェイス化します。
    • ReadAsyncコマンドで引数に渡したScenarioBookを実行します
ex.) ScenarioManager.cs からの呼び出し
        // ScenarioBookReaderにIScenarioTaskExecutorを設定
        IScenarioTaskExecutor scenarioTaskExecutorInterface = scenarioTaskExecutor;
        ScenarioBookReader scenarioBookReader = new ScenarioBookReader(scenarioTaskExecutorInterface);

        // ScenarioBookReaderによるscenarioBookの読み込みと実行
        IScenarioBookReader scenarioBookReaderInterface = scenarioBookReader;
        scenarioBookReaderInterface.ReadAsync(scenarioBook, this.GetCancellationTokenOnDestroy()).Forget();

サンプルを使わずに実装するには?

  • INextNotifier,ICancellationNotifierを継承したクラスの定義
  • scenarioTaskExecutorに対してキャンセル条件(IDisposable)を実装
  • IReflectableに含めるコマンド、デコーダーの実装
  • スキップモード(GetCancellationTokenOnDestroyを呼び出す処理)の実装
  • [各クラスで定義される機能]に従って各クラスを呼び出し、ScenarioScriptを実行する

(でよさそう?実装してみて気づきがあれば都度修正していきます)

Discussion