【ScenarioFlow】SFTextの書き方

ScenarioFlow
ツールのバージョン
  • Unity: 2022.3.35f1
  • UniTask: 2.5.4
  • ScenarioFlow: 1.1.0
  • SFText Extension Pack: 1.1.0
    • Syntax: v1.1.0
    • Formatter: v1.0.0
    • Utility: v1.0.0

ScenarioFlowでは、会話シーンの内容をシナリオスクリプトに記述し、それを会話システムで実行することでその会話シーンが再生されます。SFTextは、ScenairoFlowが提供する標準的なシナリオスクリプトです。

今回の記事では、SFTextの文法と、スクリプトの快適な編集を可能にするVisual Studio Code (VSCode) の拡張機能について学びます。

はじめに

SFTextとは

SFTextは、「読みやすく書きやすい」をコンセプトにした、ScenarioFlowで使用できるシナリオスクリプトの形式の一つです。本物の台本のような見た目を持ち、シンプルな文法を持つことを特徴としています。また、Visual Studio Code (VSCode) の拡張機能で提供される、シンタックスハイライトや入力補完といった機能により、快適にスクリプトを編集することができます。

VSCodeの拡張機能の導入

Visual Studio Code (VSCode)は、Microsoftが提供するオープンソースのソースコードエディタです。VSCodeで使用できる、SFTextを快適に編集するための拡張機能が公開されています。

まずはVSCodeをインストールし、次にSFText Extension Packを導入しましょう。

VSCode:

Visual Studio Code - Code Editing. Redefined
Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications...

SFText Extension Pack:

SFText Extension Pack - Visual Studio Marketplace
Extension for Visual Studio Code - Extensions for SFText

また、オプションですが、VSCodeのカラーテーマとしてAyuを使用することをお勧めします。

Ayu - Visual Studio Marketplace
Extension for Visual Studio Code - A simple theme with bright colors and comes in three versions — dark, light and mirag...

プログラムの準備

サンプルプロジェクトのインポート

以下のUnity Packageファイルに、今回使用するプログラムやオブジェクトが含まれています。ファイルをダウンロードして、Unityへインポートしてください。また、ScenarioFlowとUniTaskのインポートも必要なことに注意してください。

Unity Packageファイルに含まれているhow-to-write-SFText シーンを開き、プレイモードを開始します。すると、SFTextスクリプト、Story.sftxt が実行され、会話シーンが再生されます。ここで再生される会話シーンは、キャラクターのセリフの表示とキャラクター画像の変更、2択の選択肢によるシナリオ分岐を含む、シンプルなものです。Nextボタンを押して、会話シーンを進めます。

Story.sftxtの実行結果
Story.sftxt

サンプルプロジェクトの概要

サンプルプロジェクトに含まれるクラスと、それらの役割を以下に示します。

クラス役割
ScenarioManager他のクラスを統括するマネージャクラス
ButtonNotifierNextボタンを押して会話シーンを進める機能を実装する
CancellationTokenDecoderCancellationToken 型用のデコーダを実装する。
PrimitiveDecoderstring型用のデコーダを実装する
SpriteProviderSprite 型用のデコーダを実装する
CharacterAnimatorキャラ画像の変更処理を実装する
DialogueWriterセリフ表示の処理を実装する
ScenarioBranchMakerシナリオ分岐処理を実装する
サンプルプロジェクトに含まれるクラスとそれぞれの役割

これらのクラスで構築される会話システムでは、次のコマンドを使用できます。

コマンド実行される演出実装クラス
write dialogue asyncセリフを表示するDialogueWriter
write dialogue and image asyncセリフを表示すると同時にキャラクター画像を徐々に変更するDialogueWriter
change character image asyncキャラクター画像を徐々に変更するCharacterAnimator
change character imageキャラクター画像を即座に変更するCharacterAnimator
show two selections async二つの選択肢を表示し、回答によってシナリオを2方向に分岐させるScenarioBranchMaker
jump to label指定したラベルへシナリオを分岐させるScenarioBranchMaker
会話システムに含まれるコマンドとその演出

これを踏まえて、Story.sftxt と、実行される会話シーンの対応関係を確認してみてください。

SFTextの文法

基本ルール

SFText書くにあたり、まずは2つの原則を確認しましょう。

  • すべての行は、縦線「|」により3つの領域に分割される
  • SFTextは、複数の「スコープ」から構成される

まず、外見上の大きな特徴として、SFTextはすべての行が縦線によって区切られ、以下の3つの領域に分割されます。

領域何を記述するか
スコープ宣言部スコープを開始するためのシンボルを記述する
コンテンツ記述部パラメータなど、スコープの内容を記述する
コメント記述部コメントを記述する(会話シーンに影響しない)
縦線で区切られる3つの領域とその役割
SFText
スコープ宣言部 | コンテンツ記述部 | コメント記述部
SFText

次に、SFTextは「スコープ」と呼ばれる基本ブロックで構成され、記述されたスコープが上から順に読み込まれます。

スコープには、以下の4つの種類があります。

スコープ役割
コマンドスコープコマンド呼び出しを引き起こす
セリフスコープセリフを表示するためのコマンド呼び出しを引き起こす
マクロスコープコマンド・セリフスコープの補助をする
コメントスコープコメントを記述する(会話シーンに影響しない)
SFTextにおけるスコープとその役割

Story.sftxtにおけるスコープの分類を示します。

Story.sftxtにおけるスコープの分類

上の表に示されている通り、SFTextでは、コマンドスコープとセリフスコープがコマンド呼び出しを引き起こします。ScenarioFlowではコマンド呼び出しの繰り返しによって会話シーンが再生されるということを踏まえると、SFTextで会話シーンを記述するためのするべきことは、コマンドスコープとセリフスコープを適切に並べ、必要に応じてマクロスコープを添えることであるといえます。

SFTextを書くためには、最低限、各スコープについて理解する必要があります。そこで次のセクションからは、順番に各スコープの役割と書き方を詳しく見ていきます。

コマンドスコープ

コマンドスコープは、任意のコマンドを呼び出すスコープで、SFTextの核となるスコープです。以下の規則に従って記述します。

  • スコープ宣言部に$(トークンコード) を記述してコマンドスコープを開始する
  • スコープを開始した行のコンテンツ記述部に、呼び出すコマンド名を記述する
  • スコープを開始した行の次の行から、コンテンツ記述部にトークンコード以外のパラメータを記述する
  • パラメータ記述では、{} で囲まれた部分のみがパラメータとして認識され、それ以外のテキストはコメントとして無視される
SFText
$tokencode | command name                          | 
           | comment {arg1} comment {arg2} comment | 
           | {arg3} {arg4} {arg5} ...              | 
SFText

以下の例では、change character image async というコマンドにトークンコードparallel を指定し、他にパラメータとしてsmile を与えて呼び出しています。

SFText
$parallel   | change character image async                          | 
            | Change the icon to {smile}                            | 
SFText

また、非同期コマンドに対しては必要なトークンコードを指定しますが、同期コマンドに対しては$sync としなければならないことに注意してください。

SFText
$tokencode | asynchronous command             | 
           | {arg1} {arg2} ...                | 
$sync      | synchronous command              | 
           | {arg1} {arg2} ...                | 
SFText
トークンコードと非同期コマンド

コマンドには、処理が即座に完了する同期コマンドと、処理の完了に時間がかかる非同期コマンドがあります。セリフの表示やキャラクターのアニメーションなど、会話シーンでは非同期コマンドが多く使用されますが、プログラム内の設定値の変更を素早く切り替えたいときなどは、同期コマンドが便利に使用できます。

トークンコードは、非同期コマンドの実行方法を切り替えるためのパラメータです。例えば、非同期コマンドはその実行途中に処理をキャンセルすることができますが、standard をトークンコードとして指定するとそのようなキャンセルが許可されるという性質が、promised を指定すると一切のキャンセルが禁止されるという性質が、非同期コマンドに付与されます。これらは、コマンドの仕様、もしくはコマンドによって引き起こされる演出の重要度によって使い分けます。例えば、プレイヤーに選択肢を表示するコマンドの場合、選択肢は必ず選択されなければならないのでキャンセルは必ず禁止しなければなりませんが、セリフを表示するコマンドの場合、キャンセルを許可してセリフの読み飛ばしを許可するべきか、キャンセルを禁止して必ず全文を読ませるべきかはそのセリフの重要度次第です。

トークンコードの活用方法については、別の記事で取り扱います。ここでは、以下の2点を覚えておいてください。

  • トークンコードで非同期コマンドの振る舞いを変更できる
  • 同期コマンドにはトークンコードが必要ないので、SFTextで同期コマンドを呼び出す場合はsyncを指定する必要がある

セリフスコープ

ScenarioFlowでは、コマンド呼び出しの繰り返しによって会話シーンが再生されるので、セリフの表示も、それに対応するコマンド呼び出しによって実現する必要があります。このようなコマンドの呼び出しはコマンドスコープによって記述することもできますが、SFTextでは見やすさとセリフ表示の頻度の高さを考慮して、セリフを表示するコマンド呼び出し専用の構文を用意しています。それがセリフスコープです。

セリフスコープを記述する際は、以下の規則に従います。

  • スコープ宣言部にキャラクター名を記述してセリフスコープを開始する
  • スコープを開始したその行から、コンテンツ記述部にセリフの内容を記述する
  • 呼び出すコマンドと与えるトークンコードは、マクロスコープで指定する
SFText
キャラ名 | セリフの内容 |
SFText

重要なのは、「セリフスコープは、コマンドスコープの省略記法に過ぎない」ということです。記述されたコマンドスコープは、マクロスコープ、#command#token でそれぞれ指定されたコマンド名とトークンコードをもとに、等価なコマンドスコープに置き換えられます。以下に例を示します。

SFText
#command  | {write dialogue async}           | <-- コマンドを指定
#token    | {$standard}                      | <-- トークンコードを指定
Sheena    | Hello, I'm Sheena.               | <-- セリフスコープ
          |                                  | 
$standard | write dialogue async             | <-- 等価なコマンドスコープ
          | {Sheena} {Hello, I'm Sheena.}    | 
SFText

上の例からわかる通り、セリフスコープで使用されるコマンドは、第一引数として話者名を受け取り、第二引数としてセリフ内容を受け取ることを想定したものでなければなりません。

さて、最も単純なセリフスコープは、すでに紹介された3つの規則に従って書くことができますが、セリフスコープをより効率的に使用するため、次の2つの規則も覚えておきましょう。

  • 複数行を横断してセリフを記述でき、その場合は、各行のテキストは<bk> によって結合される
  • コンテンツ記述部を--> で始めることで、追加のパラメータを与えることができる。パラメータの記述方法はコマンドスコープと同じ

順に、詳しく見ていきます。

複数行に記述されたセリフの結合

まず、セリフスコープにはセリフを複数行にまたがって記述することができ、その場合、記述されたテキストは<bk> (line breakを表す)によって結合されます。以下の例を確認しましょう。

SFText
#command  | {write dialogue async}                                         | <-- コマンドを指定
#token    | {$standard}                                                    | <-- トークンコードを指定
Sheena    | I'm a ScenarioFlow instructor.                                     | <-- セリフスコープ
          | Nice to meet you!                                                  | 
          |                                                                | 
$standard | write dialogue async                                           | <-- 等価なコマンドスコープ
          | {Sheena} {I'm a ScenarioFlow instructor.<bk>Nice to meet you!} | 
SFText

結合されたテキストは引数としてコマンド呼び出し時にデコーダに渡され、その後にコマンドに渡されるので、デコーダ内やコマンド内で<bk> を好きな文字に置き換えることができます。置き換える文字は、要求に応じて空白でもいいですし、改行文字でもいいでしょう。以下のコードは、複数行に書かれたセリフは改行とみなすデコーダの例です。ScenarioFlow.Scripts.SFText 名前空間に定義されるSFText.LineBreakSymbol にシンボル<bk> が保持されており、それを使用できます。

C#
using ScenarioFlow.Scripts.SFText;

[DecoderMethod]
public string ConvertToString(string input)
{
    return input.Replace(SFText.LineBreakSymbol, "\n");
}
C#

追加パラメータ付きセリフスコープ

セリフスコープで使用されるもっとも単純なコマンドは、話者名、セリフの内容、トークンコードに対応する3つの引数を受け取ります。しかし、実践的には、それら以外のパラメータをセリフとともに指定したいことがあります。例えば、キャラクターの画像をセリフとともに指定して、セリフの表示とともに画面上のキャラクター画像を変更したい場合です。

セリフスコープでは、コンテンツ記述部を--> で始めることで追加のパラメータを渡すことができます。パラメータの記法は、コマンドスコープの場合と同じで、{}で囲まれたものだけがパラメータとして認識され、またパラメータは複数行に記述できます(パラメータを記述するすべての行は、--> で始まる必要があります)。また、追加のパラメータを指定したセリフスコープは追加のパラメータを持たない通常のセリフスコープとは異なり、呼び出すコマンドは#xcommand マクロスコープで指定します(#token は共通)。

以下に、例を示します。--> が一つの右矢印「→」のように表示されているかもしれませんが、実際は半角ハイフン「-」二つに大なり記号「>」がついています。

SFText
#xcommand | {write dialogue and image async}           | <-- コマンドを指定
#token    | {$standard}                                | <-- トークンコードを指定
Sheena    | So, let's learn about SFText today.            | 
          | --> Change the icon to {normal}                |  
          |                                            | 
$standard | write dialogue and image async             | <-- 等価なコマンドスコープ
          | {Sheena} {So, let's learn about SFText today.} | 
          | {Smile}                                    | 
SFText

追加パラメータを持たないセリフスコープと持つセリフスコープを区別するとき、前者を「標準セリフスコープ」、後者を「拡張セリフスコープ」と呼びます。標準セリフスコープと拡張セリフスコープは混在可能なので、状況によって使い分けると良いでしょう。

SFText
#command  | {write dialogue async}                 | 
#xcommand | {write dialogue and image async}       | 
#token    | {$standard}                            | 
Sheena    | Don't say that!                            | 
Sheena    | You will find it helpful.                  | 
          | --> Change the icon to {smile}             | 
SFText

マクロスコープ

マクロスコープは、コマンドスコープとセリフスコープを補助するスコープです。マクロスコープには5つの種類があり、いずれも以下の規則に従って記述します。

  • #(マクロ名)をスコープ宣言部に記述して、マクロスコープを開始する
  • スコープが開始した行から、コンテンツ記述部にパラメータを記述する。パラメータの書き方は、コマンドスコープと同じで{}に囲まれたテキストのみがパラメータとして認識される
SFText
#(macro-name) | comment {Arg1} comment {Arg2} comment .... |
SFText

順に、各マクロスコープの役割と使い方を見ていきましょう。

#commandマクロスコープ

#command マクロスコープでは、追加引数なしの標準セリフスコープで使用するコマンドを指定します。

SFText
#command | {command name} |
SFText

一度宣言した#command は、新たな#command で上書きされるまで有効です。

SFText
#command   | {write dialogue async}           | 
#token     | {$standard}                      | 
Sheena     | Hello, I'm Sheena.               | <-- 'write dialogue async' を使用
Sheena     | I'm a ScenarioFlow instructor.   | <-- 'write dialogue async' を使用
           | Nice to meet you!                |
#command   | {update diaogue async}           | 
Sheena     | Are you interested in SFText?    | <-- 'update dialogue async' を使用
SFText

#xcommandマクロスコープ

#xcommand マクロスコープは、追加引数ありの拡張セリフスコープで使用するコマンドを指定します。

SFText
#xcommand | {command name} |
SFText

一度宣言された#xcommand は、新たな#xcommand に上書きされるまで有効です。

#tokenマクロスコープ

#token マクロスコープは、セリフスコープで使用するトークンコードを指定します。このマクロスコープで指定したトークンコードは、標準・拡張セリフスコープの両方で使用されます。

SFText
#token | {token code} |
SFText

一度宣言された#token は、新たな#token に上書きされるまで有効です。

#labelマクロスコープ

#label マクロスコープは、シナリオ分岐を実装するためのマクロスコープです。パラメータには、ラベル名を指定します。

SFText
#label | {label name} |
SFText

#label マクロスコープを宣言すると、その下にあるコマンドスコープもしくはセリフスコープにラベル名が付与されます。すると、C#スクリプト側からそのラベルを参照し、シナリオ分岐を実装することができるようになります。このC#でのシナリオ分岐の実装方法については、別の記事で取り扱います。

SFText
$f-promised | show two selections async                             | 
            | {Yes}                                                 | 
            | --- Jump to {Ans1}                                    | 
            | {No}                                                  | 
            | --- Jump to {Ans2}                                    | 
#label      | //============ {Ans1} ============//                  | If 'Yes' is selected
Sheena      | I'm happy to hear that.                               | 
            | --> Change the icon to {smile}                        | 
$sync       | jump to label                                         | 
            | Jump to {Confluence}                                  | 
#label      | //============ {Ans2} ============//                  | If 'No' is selected
Sheena      | Don't say that!                                       | 
SFText

一つのSFText内で、ラベル名の重複は許されないことに注意してください。

#defineマクロスコープ

#define マクロスコープは、パラメータとして与える定数を定義するためのマクロスコープです。パラメータとして、シンボルと置き換え先の値を渡します。

SFText
#define | {symbol} {value} |
SFText

#define を宣言すると、SFTextがUnityにインポートされるとき、指定されたシンボルが同じく指定した別の値で置き換えられます。

SFText
$serial     | change character image async       | 
            | Change {X}'s image to {Normal}     | 
X           | Hello, I'm Sheena.                 | 
#define     | {X} {Sheena}.                      | 
$serial     | change character image async       | 
            | Change {X}'s image to {Smile}      | 
X           | I'm a ScenarioFlow instructor.     | 
            | Nice to meet you!                  |
//          | ---------------------------------- | 以下のように置き換わる
$serial     | change character image async       | 
            | Change {X}'s image to {Normal}     | 
X           | Hello, I'm Sheena.                 | 
$serial     | change character image async       | 
            | Change {Sheena}'s image to {Smile} | 
Sheena      | I'm a ScenarioFlow instructor.     | 
            | Nice to meet you!                  |
SFText

#defineマクロスコープは、それを宣言した次の行から有効です。#defineは上書き可能であり、同じシンボルに対して複数の#define が宣言されていた場合、シンボルが書かれた行より上の行で、最も新しい#define の値が使用されます。

また、#define がシンボルを置き換える対象は、以下のパラメータのみです。

  • コマンドスコープのパラメータ(トークンコードを除く)
  • セリフスコープの話者名
  • 拡張セリフスコープの追加パラメータ
マクロスコープの有効範囲

1つのマクロスコープは1つのSFTextスクリプト全体に影響を及ぼしますが、他のSFTextスクリプトには影響を与えません。

例えば、#define を宣言したとき、それが宣言されたSFTextとは別のSFText内にあるシンボルが置き換えられるようなことはありません。あくまで、#define が宣言されたSFText内にのみ効力を発揮します。

また、#label についてラベル名の重複は許されないと説明しましたが、それは1つのSFText内での場合で、2つの異なるSFText間でなら同名のラベルがあっても問題ありません。加えて別の観点で考えると、あるSFTextから別のSFTextのラベルを直接参照してシナリオ分岐を行うことはできないということです。

コメントスコープ

コメントスコープは、実際の会話シーンに影響しない、コメント文を記述するためのスコープです。以下の規則に従って記述します。

  • スコープ宣言部を// で開始することで、コメントスコープを開始する
  • スコープ内では、コンテンツ記述部とコメント記述部の両方にコメントを記述できる
SFText
// (any text) | comment | commnet
SFText

スクリプトに書いた内容の意図を別の人あるいは後の自分自身に伝えるため、または一時的に不要なスコープをコメントアウトするために使用します。

SFText
#command    | {write dialogue async}             | 
#token      | {$standard}                        | 
// Sheena   | Hello, I'm Sheena.                 | 
Sheena      | I'm a ScenarioFlow instructor.     | 
            | Nice to meet you!                  |
SFText

SFTextの編集

SFTextは既に述べられた通り、Visual Studio Code (VSCode) とその拡張機能を使用して快適に編集を行うことができます。ここまではSFTextの文法について学習してきましたが、ここからは実際にSFTextを書き上げる環境、そして効率の良い書き方に焦点を当てます。VSCodeの拡張機能でどのような機能が提供されているのか、どのようにそれらを使うのかを学んでいきましょう。

シンタックスハイライト

VSCodeの拡張機能を導入すると、SFTextに対してシンタックスハイライトが有効になります。

シンタックスハイライトが有効になった状態

上の画像では、カラーテーマとしてAyu Dark Borderedが選択されています。VSCodeのカラーテーマは好きなものを使用できますが、SFTextのシンタックスハイライトはカラーテーマ「Ayu」向けに最適化されているので、SFTextの編集時はAyuのテーマのどれかを選択することをお勧めします。

Ayu - Visual Studio Marketplace
Extension for Visual Studio Code - A simple theme with bright colors and comes in three versions — dark, light and mirag...

フォーマッター

拡張機能では、テキストを整列させるフォーマッターが提供されています。

フォーマッターは、コマンドパレットからFormat Document を選択するか、Shift+Alt+F (Windowsの場合)で呼び出すことができます。ただし、SFTextの編集においてフォーマッターのみを直接的に呼び出すことはなく、その他の頻繁に使う機能を呼び出すときに、自動的に呼び出されるようになっています(VSCodeの機能で、ファイルのセーブ時にフォーマッターを呼び出すようにもできます)。

フォーマッターの使用

オートコンプリートとショートカットキーを使用した素早い編集

拡張機能が提供するオートコンプリート(入力補完)とショートカットキーを駆使することによって、SFTextを効率的に、素早く編集することができます。ここでは、その機能を使用するために必要な事前準備と、具体的な使い方について学びます。

コマンド情報の登録

オートコンプリートとショートカットキーをフルに活用するためには、C#側で定義しているコマンドの情報を拡張機能に渡す必要があります。そのために必要な手順は、次の通りです。

  1. コマンドとしてエクスポートするC#メソッドに、必要な属性を付加する
  2. SFText Snippets Builderによって、JSONファイルを生成する
  3. VSCode側で、JSONファイルをSelect JSON Path コマンドにより選択し、Load JSON File コマンドでロードする

順番に、これらの手順を見ていきましょう。

1. 属性の付加

まず、コマンドとしてエクスポートするC#メソッドに、いくつかの属性を付加します。例として、以下にshow two selections async コマンドに対応するShowTwoSelectionsAsync メソッド、そしてwrite dialogue and image async コマンドに対応するWriteDialogueAndChangeImageAsync メソッドを示します。

ScenarioBranchMaker.cs & DialogueWriter.cs
using ScenarioFlow;
using ScenarioFlow.Scripts.SFText;

[CommandMethod("show two selections async")]
[Category("Branch")]
[Description("Show two selections two the player.")]
[Description("Move on to one of specified labels that correspondes to the answer.")]
[Snippet("{${1:selection1}}")]
[Snippet("--- Jump to {${2:label1}}")]
[Snippet("{${3:selection2}}")]
[Snippet("--- Jump to {${4:label2}}")]
public async UniTask ShowTwoSelectionsAsync(string selection1, string label1, string selection2, string label2, CancellationToken cancellationToken)
{
    // 省略
}

[CommandMethod("write dialogue and image async")]
[Category("Dialogue")]
[Description("Show a dialogue on the screren.")]
[Description("Also, change the character icon.")]
[DialogueSnippet("Change the icon to {${1:image}}")]
[Snippet("{${1:speaker}}")]
[Snippet("{${2:dialogue}}")]
[Snippet("Change the icon to {${3:image}}")]
public async UniTask WriteDialogueAndChangeImageAsync(string speaker, string dialogue, Sprite sprite, CancellationToken cancellationToken)
{
    // 省略
}
C#

各属性の概要を、以下に示します。CommandMethod 属性以外は、ScenarioFlow.Scripts.SFText 名前空間で定義されています。

属性概要
CommandMethodコマンド名を指定する
Categoryコマンドのカテゴリを指定する
Descriptionコマンドの説明を記述する。複数個に分けて記述できる。
Snippetコマンドスコープで挿入されるスニペットを記述する。複数個付与でき、その場合スニペットは複数行に分かれる。
DialogueSnippet拡張セリフスコープで挿入されるスニペットを記述する。複数個付与でき、その場合スニペットは複数行に分かれる

CommandMethod 以外の属性はオプションですが、すべて、VSCodeでの入力支援のために使用されます。特に、編集の効率を上げるため、少なくともSnippetDialogueSnippet は付加しておいた方が良いでしょう。DialogueSnippet は、拡張セリフスコープで使用されるコマンドにのみ付加することに注意してください。

Snippet 属性とDialogueSnippet 属性には、SFTextを見返したときに、そのコマンドあるいはセリフスコープで何が起きるのかを簡単に想像できるようなテキストを渡します。そして、そのテキストには、コマンドに渡すパラメータを{${n:name}}の形式で埋め込みます。n はパラメータ番号を、name にはパラメータ名を書きます。すると、コマンドや拡張セリフスコープのパラメータをスニペット(テキストのテンプレート)により効率的に入力することができ、それらの振る舞いも分かりやすいものになります。

トークンコードはパラメータとして埋め込まないこと、DialogueSnippet 属性については、話者名とセリフ内容に対応するパラメータを抜くことに注意してください。また、Snippet 属性とDialogueSnippet 属性は共存可能です(セリフスコープはコマンドスコープの省略表記なので、セリフスコープで使用するコマンドは普通のコマンドスコープでも呼び出せる)。

2. JSONファイルの生成

Unityエディタの上部メニューのWindow/ScenarioFlow/SFTextSnippetsBuilder から、SFText Snippets Builderを開きます。そして、以下の手順でJSONファイルを生成します。

  1. Projectウィンドウで右クリックし、Create/ScenarioFlow/SFText Snippets から、空のJSONファイルを作成する
  2. SFText Snippets BuilderのEditボタンを押し、Add JSON Text ボタンを押してスロットを増やしたら、そこへ作成したJSONファイルをドラッグ&ドロップで紐づけ、Save ボタンで保存する
  3. Update JSON Files ボタンを押して、登録したJSONファイルにコマンド情報を書き込む
空のJSONファイルの作成
完全なJSONファイルの生成

ちなみに、JSONファイルにはCommand Listにおいてチェックボックスが有効になっているコマンドのみが出力されます。また、Copy JSON to Clipboard ボタンにより、手動でJSONテキストをコピーすることもできます。

3. JSONファイルの登録

生成したJSONファイルを、次の手順でVSCodeへ登録します。

  1. コマンドパレットからSelect JSON Path を呼び出し、生成したJSONファイルを選択
  2. コマンドパレットからLoad JSON File を呼び出し、選択したJSONファイルを読み込む
生成したJSONファイルの登録

これでコマンドの情報がVSCodeに登録され、拡張機能による入力支援が完全に有効になります。後にC#側で新たなコマンドを定義したり、属性に変更を加えたりした場合は、JSONファイルのパスが変わらない限りは、(1)SFText Snippets BuilderでJSONファイルを更新、(2)VSCodeのLoad JSON File で更新されたJSONファイルを読み込み、の2ステップでその変更を反映させることができます。ちなみに、登録したJSONファイルの内容を、更新することなく単に削除したい場合は、VSCodeでClear JSON Data コマンドを呼び出します。

SFText Command List

上部メニューからWindow/ScenarioFlow/SFText Command List を選択してSFText Command Listを開き、定義されているコマンドとデコーダの情報を、一覧で確認することができます。

コマンドの一覧
デコーダの一覧

二番目の画像が示す通り、実は、コマンドに付加できる属性の中でCategory 属性とDescription 属性は、デコーダにも付与することができます。

SpriteProvider.cs
using ScenarioFlow;
using ScenarioFlow.Scripts.SFText;

[DecoderMethod]
[Category("Resource")]
[Description("Decoder for 'Sprite'.")]
[Description("Sprites need to be registered so that they will be available.")]
public Sprite GetSprite(string name)
{
    if (spriteDictionary.TryGetValue(name, out var sprite))
    {
        return sprite;
    }
    else
    {
        throw new ArgumentException($"Sprite '{name}' does not exist.");
    }
}
C#

オートコンプリート

以下の要素に対して、サジェストによるオートコンプリートを使用できます。特に、コマンドのオートコンプリートには、JSONファイルで登録したコマンド情報が反映されていることに注意してください。

  • マクロスコープのマクロ名
  • コマンドスコープのトークンコード
  • コマンドスコープのコマンド名
コマンド名のオートコンプリート

ショートカットキー

オートコンプリートに加えて、以下の3つのコマンドを使用することで、効率的にSFTextを記述することができます。コマンドはコマンドパレットから呼び出すこともできますが、普通はショートカットキーにより素早く呼び出します。また、すべてのコマンドはフォーマッターを呼び出し、スクリプトを整形することも覚えておいてください。

コマンドショートカットキー(Windows)役割
Move CursorShift+Enterカーソルをスコープ宣言部、コンテンツ記述部、コメント記述部の最後尾へ、順番に移動させる
Insert ArgumentsAlt+Enterカーソルの置かれているスコープに必要なパラメータのスニペットを挿入する
Insert Line BelowCtrl+Enterカーソルのある行の下に新しい行を挿入する

各スコープの記述例

各スコープについて、オートコンプリートとショートカットキーを駆使した効率的な記述方法を見ていきます。

マクロスコープ

マクロスコープの記述は最も簡単です。マクロ名をオートコンプリートで入力して、Alt+Enter でスニペットを挿入してパラメータを埋めるだけです。

マクロスコープの記述
コマンドスコープ
  1. トークンコードを入力し、Shift+Enter
  2. コマンド名を入力し、Alt+Enter
  3. スニペットでパラメータを入力し、Ctrl+Enter

スニペットのパラメータ入力では、Tabキーによりカーソルを次のパラメータ位置へ移動させられます。

コマンドスコープの記述
セリフスコープ
  1. 話者名を入力し、Shift+Enter
  2. セリフ内容を入力してCtrl+Enter
  3. セリフがまだあるなら何も書かずにShift+Enterし、手順2へ。ないなら次のスコープを開始
セリフスコープの記述

追加パラメータを与える拡張セリフスコープの場合は、セリフを書き終わったらそのスコープ上でAlt+Enter を押し、スニペットを挿入します。

拡張セリフスコープの記述

拡張セリフスコープ上でAlt+Enter を押したときに挿入されるスニペットは、その拡張セリフスコープの上で宣言されている中で、最も新しい#xcommand マクロスコープで指定されているコマンドに付加されたDialogueCommand 属性の値に対応しています。また、ターゲットとなる拡張セリフスコープの上側で#xcommand マクロスコープが一つも宣言されていない場合はスニペットが挿入できないので注意してください。

SFText
Sheena      | Good morning!         | ここではスニペットを挿入できない
#xcommand   | {Command A}           |
Sheena      | Hello!                | ここではCommand Aのスニペットが挿入される
#xcommand   | {Command B}           | 
Sheena      | Hi!                   | ここではCommand Bのスニペットが挿入される
SFText

拡張機能の設定項目

半角文字の登録

拡張機能のフォーマッターは、SFText内の縦線の位置がそろうようにテキストを整形してくれますが、その処理の中で、フォーマッターは各文字が半角文字か全角文字かを判定しています。一般的な半角アルファベットや半角記号は正しく半角文字であると判定されるのですが、一部の特殊な半角文字は正しく半角文字であると認識されません。例えば、「…」は半角文字ですが、初期状態ではフォーマッターに全角文字であると判定され、テキストの整列が正しく行われません。

「…」を含むテキストが正しく整形されない

このように、使用しようとした半角文字が正しく半角文字であると認識されなかったとき、設定を変更することでその問題を解決することができます。

VSCodeの設定を開き、検索バーに「sftext」と入力します。するとHalf Width Character Listという項目が現れるので、そこに半角文字と認識してほしい文字のセットを、正規表現で入力します。デフォルトでは\x01-\x7E\uFF65-\uFF9F が入力されており、これで一般的な半角文字はカバーされています。初期値は広範囲の文字をカバーするためにUTF-8の文字コードを使用していますが、実際にこの設定を変更するときは、半角文字として認識させたい新たな文字をそのまま付け足せばよいです。

今回の例では、「…」を設定値に付け足します。

Half Width Character List に「…」を付け足す

すると、「…」を含むテキストが正しく整形されるようになります。

「…」を含むテキストが正しく整形される

マクロスコープに対するスニペットのカスタマイズ

コマンドスコープのスニペットはC#側で属性を付加することによって設定できましたが、マクロスコープのスニペットは、VSCodeの設定で変更できます。設定を開いて検索バーに”sftext”と入力すると、Half Width Character Listの下に、各マクロスコープに対するスニペットの設定項目が並んでいるのが確認できます。

これらの項目に、Snippet 属性と同様の形式でスニペットとして挿入されるテキストを入力することで、好きなスニペットを各マクロスコープに対して登録できます。

VSCodeの設定からマクロスコープ用のスニペットを設定できる

まとめ

今回の記事では、SFTextの文法、そしてVSCodeの拡張機能を使用した効率的な編集について学習しました。

SFTextでは、各行は縦線「|」により、スコープ宣言部、コンテンツ記述部、コメント記述部の3つの領域に分割されます。

領域何を記述するか
スコープ宣言部スコープを開始するためのシンボルを記述する
コンテンツ記述部パラメータなど、スコープの内容を記述する
コメント記述部コメントを記述する(会話シーンに影響しない)
縦線で区切られる3つの領域とその役割
SFText
スコープ宣言部 | コンテンツ記述部 | コメント記述部
SFText

SFTextは複数の「スコープ」から構成され、スコープには以下の4種類があります。

スコープ役割
コマンドスコープコマンド呼び出しを引き起こす
セリフスコープセリフを表示するためのコマンド呼び出しを引き起こす
マクロスコープコマンド・セリフスコープの補助をする
コメントスコープコメントを記述する(会話シーンに影響しない)
SFTextにおけるスコープとその役割
Story.sftxtにおけるスコープの分類

コマンドスコープとセリフスコープがコマンド呼び出しを引き起こすので、SFTextでは会話シーンを記述するために、適切な順でコマンドスコープとセリフスコープを並べ、マクロスコープでそれらの補助をすることになります。サンプルプロジェクトの会話システムで上記のStory.sftxtを実行すると、以下のような会話シーンが再生されます。

Story.sftxtの実行結果

SFTextの編集時には、VSCodeの拡張機能が提供するオートコンプリート、ショートカットキーを駆使しして効率的にスクリプトを記述できます。重要なショートカットキーは以下の三つです。

コマンドショートカットキー(Windows)役割
Move CursorShift+Enterカーソルをスコープ宣言部、コンテンツ記述部、コメント記述部の最後尾へ、順番に移動させる
Insert ArgumentsAlt+Enterカーソルの置かれているスコープに必要なパラメータのスニペットを挿入する
Insert Line BelowCtrl+Enterカーソルのある行の下に新しい行を挿入する

これらを使用して、例えばコマンドスコープは次のように、素早く記述できます。

コマンドスコープの記述

なお、オートコンプリートとショートカットキーをフルに活用するためには、C#側でコマンドとしてエクスポートされるメソッドに対し、適切に属性を与えなければならないことに注意してください。そして、それらをもとに生成したJSONファイルをVSCodeへ登録する必要があります。

会話シーンを効率的に記述するため、まずはSFTextの文法を覚え、VSCodeにおける操作に慣れることから始めましょう。今回扱ったサンプルプロジェクトを使用して、SFTextを書く練習をしてみてください。

コメント