Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

@pasta_sakura_script

クローディア(目閉じ)

句点でひと呼吸、読点で小さな間――台詞の「間」を自動で刻んでくれるのが @pasta_sakura_script ですわ。長い台詞を読みやすい位置で折り返す力まで、備えておりますのよ。

アンソニー(素)

では私も、……読点ごとに、……間を置いて、……お話しいたしましょうか。

クローディア(驚き)

まあ、それでは間の取りすぎですわ! 仕組みを知って、ほどよい話し方に整えてくださいまし。


@pasta_sakura_script は、台詞のテキストにウェイトタグ(\_w[ミリ秒])を挿入してさくらスクリプトへ変換するモジュールである。budoux による日本語の分かち書きを使った自動改行(\n または \n[N] の挿入)も提供する。

local SAKURA_SCRIPT = require "@pasta_sakura_script"

モジュールのメタデータ: _VERSION = "1.0.0"、_DESCRIPTION = "Sakura Script wait insertion module for natural conversation tempo"

act:talk との関係

アクション行の台詞や、シーン関数で act:talk(...) に渡したテキストは、さくらスクリプトを組み立てるときに、発言したアクターの表を引数にしてこのモジュールの talk_to_script で変換される。アクターの表は pasta.toml の [actor."名前"] に書いた値を持つ(CONFIG.actor["名前"] と同じ表)。アクション行に登録していないアクター名を書いた行は、名前だけを持つ表で変換されるため、[talk] の値と実装の既定値を使い、自動改行はしない(登録していないアクター名)。

ふつうのゴーストでは、このモジュールを直接 require する必要はない。ウェイトの長さや自動改行は、pasta.toml の [talk] セクションと [actor."名前"] のキーで調整する(pasta.toml での設定・pasta.toml での budoux 設定)。

talk_to_script(actor, talk, column)

テキストにウェイトタグを挿入して返す。アクターの表に budoux があれば、続けて自動改行も行う。

SAKURA_SCRIPT.talk_to_script(actor, talk) -> string
SAKURA_SCRIPT.talk_to_script(actor, talk, column) -> string
パラメータ型必須説明
actortable | nil✅アクターの表。ウェイトの設定(アクター表のウェイト設定)と budoux・budoux_newlines・budoux_jump をこの表の直下から読む。表でない値(nil など)を渡すと、[talk] の値と実装の既定値を使い、自動改行はしない
talkstring | nil✅変換する台詞のテキスト
columntable | nil—自動改行の行の状態の表(行の状態の表)。渡すと、始めの状態として読み、変換の後に終わりの状態へ書き換える。渡さない(nil)ときは、最初の行の頭から数える。表でも nil でもない値を渡すと Lua のエラーになる

戻り値: 変換後の文字列(column を渡しても、戻り値は文字列 1 つである)。

  • talk が nil または "" のときは "" を返す。column は書き換えない。
  • talk に数値を渡すと文字列にしてから変換する。表など文字列にできない値を渡すと Lua のエラーになる。

行の状態の表

column に渡す表は、次のフィールドを持つ。無いフィールドは、表の「無いときの値」として読む(空の表 {} は、最初の行の頭である)。縦書きのバルーンでは、行が列に当たる。

フィールド型無いときの値説明
lineinteger1行の番号。最初の行が 1
widthinteger0その行に積んだ字数(半角 1 文字を 1、全角 1 文字を 2 と数える。1 文字は 文字の分類 の見た目の 1 文字。タグは数えない)
jumpbooleanfalsebudoux_jump の配列を使っているか(行を動かすタグでの数え直し)
  • 変換が成功した後にだけ、line・width・jump を書き換える。変換がエラーになったときは、表は渡したときのままである。
  • アクター表に budoux が無い(空の配列、表でない actor を含む)ときも、改行は入れずに、テキストの字数と行を動かすタグで状態を進める。
  • 同じ表を続けて渡すと、前の呼び出しの終わりの行と字数を引き継いで数える。引き継いだ字数のままでは最初の語が行に収まらないときは、その語の前に改行を入れる。字数が 0 なら、最初の語の前に改行を入れない。
  • 応答を組み立てるとき、pasta は 1 回の出力の中で、スポットごとにこの表を持って台詞のたびに渡す(1 回の出力の中の持ち越し)。
local col = { line = 2, width = 10 }
SAKURA_SCRIPT.talk_to_script({ budoux = {40, 12} }, "今日は", col)
-- → \n今日は(2 行目の上限 12 に、字数 10 から「今日は」の 6 を足すと超えるため、前で改行する)
-- col.line → 3、col.width → 6、col.jump → false

アクター表のウェイト設定

ウェイトの長さ(ミリ秒)は、アクター表の直下にある次のキーで決まる。キーごとに「アクター表の値 → pasta.toml の [talk] の値 → 実装の既定値」の順で最初に見つかった値を使う。

キー対象の文字既定値
script_wait_normal一般の文字(下の分類に当たらない文字)50
script_wait_period句点1000
script_wait_comma読点500
script_wait_strong感嘆符・疑問符500
script_wait_leaderリーダー(… など)200
  • 既定値は実装の既定値である。
  • アクター表の値が数値(数値に変換できる文字列を含む)でないキーは、無いものとして扱う。小数は整数に切り捨てる。
  • 実際に挿入されるウェイトは、設定値から 50 を引いた値(値 - 50)である。計算した値が 0 以下のときは何も挿入しない。既定の script_wait_normal = 50 では、一般の文字の後ろにウェイトは入らない。
  • どの文字を句点・読点などとみなすかは、アクター表では変えられない。pasta.toml の [talk] で変える(pasta.toml での設定)。

動作仕様

文字の分類

このページの「1 文字」は、見た目の 1 文字(拡張書記素クラスタ)を指す。組み合わせの絵文字(🙆‍♀️・👍🏽・🇯🇵)や結合文字の付いた文字(e に結合用のアクセントを付けた é)は、いくつかのコードポイントでできていても 1 文字である。ウェイトの挿入・文字の分類・budoux の幅は、この 1 文字を単位にする。1 文字の途中には、ウェイトも改行も入らない。

半角カナと半角の濁点・半濁点の並び(ガ・パ など)も 1 文字になる。以前の版では カ と ゙ の 2 文字として数えていたが、今はウェイトが並びの後ろにだけ入り、カ と ゙ の間では改行しない。

テキストを先頭から読み、次の順で分類する。

  1. さくらスクリプトの単位: 次のタグ・エスケープ・囲みを、それぞれ 1 つの単位として読む。そのまま出力し、ウェイトは入れない。読み方は文法の章の コマンドの字句構造 と同じである。
    • タグ: \ に、_ を 0〜2 個付けた名前の 1 文字(ASCII 英数字・!・+・*・?・&・-)が続く形(\h・\n・\_w・\-)。名前が s・p・b のときは直後の数字 1 桁(0〜9)を、w のときは直後の数字 1 桁(1〜9)を名前に含め、この形は引数を取らない(\s12 は \s1 と 2、\w10 は \w1 と 0)。名前(数字付きの形を除く)の直後に [ があれば、引数を閉じる ] までをタグに含める(\s[5]・\_w[100]・\![open,inputbox])。引数の中の \ と続く 1 文字は組で、\] では引数は閉じない。引数の先頭の " から次の " までは引用で("" は中の " 1 文字)、中の ]・, では引数は閉じない(角括弧内のエスケープと引用)。引数が閉じないときは名前までがタグになり、[ を 2 の文字として読み、その後ろは続けて読む。
    • エスケープ: \\ と \%。2 文字で 1 つの単位として読み、後ろの文字とつなげない(\\n は \\ と n で、改行タグ \n にはならない)。アクション行の \\・\% はこの形で出力される(インライン要素)。
    • 囲み: \_? から次の \_? まで、\_! から次の \_! までを、全体で 1 つの単位として読む(タグをそのまま表示する囲み)。囲みは 1 回の呼び出しに渡したテキスト(talk_to_script の talk、break_lines の text)の中でだけ読まれる。同じテキストの中に閉じが無い \_?(\_!)は、ただのタグになる。アクション行の台詞と act:talk の台詞は、同じアクターの続けての台詞が 1 つにつながったテキストで 1 回の呼び出しになる。act:sakura_script で積んだテキストは他とつながらない(sakura_script(actor, text))。
  2. それ以外の文字は 1 文字ずつ、次の文字の集合に含まれるかを上から順に調べる(複数の集合に含まれる文字は、先に当たった分類になる)。タグの名前の直後に続く文字もここで分類する(\nHello の Hello は一般の文字、\n!? の !? は感嘆符・疑問符)。\ の後ろがタグにもエスケープにも囲みにもならないとき(\あ、テキスト末尾の \ など)は、その \ も 1 文字として分類する。ただし、その \ の直後にはウェイトを入れず、続く字の後ろに入れる(直後に入れると \\ になり、エスケープとして読まれるため)。いくつかのコードポイントでできた文字は、まず 1 文字の全体で集合を調べ、どれにも当たらなければ先頭のコードポイント(基底の文字)で調べる。2 つ目以降のコードポイントでは調べない(ガ は先頭の カ で一般の文字になる)。集合に書いた文字の当たり方は pasta.toml の chars_* を参照する。
分類文字の集合([talk] のキー)既定の文字
句点chars_period。。..
読点chars_comma、,,
感嘆符・疑問符chars_strong?!!?
リーダーchars_leader・・‥…
行頭禁則文字chars_line_start_prohibited゛゜ヽヾゝゞ々ー)]}」』):;]}」、・ー゙゚
行末禁則文字chars_line_end_prohibited([{「『([{「
一般の文字(上のどれにも当たらない文字)—

ウェイトの挿入

  • 一般の文字: 1 文字ごとに、その後ろへ script_wait_normal - 50 のウェイトを入れる。組み合わせの絵文字(🙆‍♀️ など)も 1 文字で、ウェイトは後ろに 1 つだけ入る。
  • リーダー: 1 文字ごとに、その後ろへ script_wait_leader - 50 のウェイトを入れる。
  • 句点・読点・感嘆符・疑問符・行頭禁則文字: 連続する並び(」!?。 など)をひとまとめにし、並びの最後に 1 回だけウェイトを入れる。値は並びの中の文字の設定値の最大値から 50 を引いた値である。行頭禁則文字は並びを延ばすだけで、自分の値を持たない(行頭禁則文字だけの並びにはウェイトが入らない)。
  • 行末禁則文字: ウェイトを入れない。直前の並びはそこで終わる。
  • さくらスクリプトの単位(タグ・エスケープ・囲み): ウェイトを入れない。単位の途中(エスケープの 2 文字の間、囲みの中)にも何も入れない。直前の並びはそこで終わる。

使用例

local SAKURA_SCRIPT = require "@pasta_sakura_script"

-- 既定値(一般の文字 50 → ウェイトなし、句点 1000 → 950)
SAKURA_SCRIPT.talk_to_script(nil, "こんにちは。")
-- → こんにちは。\_w[950]

-- アクター表の直下でウェイトを変える
local actor = { script_wait_normal = 100 }
SAKURA_SCRIPT.talk_to_script(actor, "こんにちは。")
-- → こ\_w[50]ん\_w[50]に\_w[50]ち\_w[50]は\_w[50]。\_w[950]

local actor2 = { script_wait_normal = 80, script_wait_period = 200 }
SAKURA_SCRIPT.talk_to_script(actor2, "やあ。")
-- → や\_w[30]あ\_w[30]。\_w[150]

-- さくらスクリプトタグはそのまま残る
SAKURA_SCRIPT.talk_to_script(actor, "こんにちは\\s[5]元気?")
-- → こ\_w[50]ん\_w[50]に\_w[50]ち\_w[50]は\_w[50]\s[5]元\_w[50]気\_w[50]?\_w[450]

-- 囲みの中にはウェイトが入らない
SAKURA_SCRIPT.talk_to_script(actor, "あ\\_?\\s[1]abc\\_?い")
-- → あ\_w[50]\_?\s[1]abc\_?い\_w[50]

-- タグの名前の直後の字は通常の文字として扱う
SAKURA_SCRIPT.talk_to_script(nil, "\\n!?")
-- → \n!?\_w[450]

-- 組み合わせの絵文字は 1 文字(途中にウェイトは入らない)
SAKURA_SCRIPT.talk_to_script(actor, "🙆‍♀️OK")
-- → 🙆‍♀️\_w[50]O\_w[50]K\_w[50]

-- 連続する句読点は最大値(感嘆符・疑問符 500 → 450)
SAKURA_SCRIPT.talk_to_script(nil, "え!?")
-- → え!?\_w[450]

-- 読点・句点・行頭禁則文字の並び(句点 1000 が最大 → 950)
SAKURA_SCRIPT.talk_to_script(nil, "あ、」。")
-- → あ、」。\_w[950]

-- リーダーは 1 文字ごと(200 → 150)
SAKURA_SCRIPT.talk_to_script(nil, "そう…なの?」")
-- → そう…\_w[150]なの?」\_w[450]

pasta.toml での設定

[talk] セクションで、全アクター共通のウェイトの長さと文字の集合を設定する。書かなかったキーは実装の既定値になる。アクター表([actor."名前"])に同じ名前のウェイトのキーを書くと、そのアクターではアクター表の値が優先される。

[talk]
# ウェイト(ミリ秒)
script_wait_normal = 50
script_wait_period = 1000
script_wait_comma = 500
script_wait_strong = 500
script_wait_leader = 200

# 文字の集合
chars_period = "。。.."
chars_comma = "、,,"
chars_strong = "?!!?"
chars_leader = "・・‥…"
chars_line_start_prohibited = "゛゜ヽヾゝゞ々ー)]}」』):;]}」、・ー゙゚"
chars_line_end_prohibited = "([{「『([{「"

上の値はすべて実装の既定値である。アクターごとに句点の間だけ変える例:

[talk]
script_wait_normal = 100

[actor."さくら"]
spot = 0
script_wait_period = 300

[actor."うにゅう"]
spot = 1

この設定で、さくらの「やあ。」は や\_w[50]あ\_w[50]。\_w[250]、うにゅうの「やあ。」は や\_w[50]あ\_w[50]。\_w[950] になる。

break_lines(text, widths)

budoux の日本語分割モデルで求めた語の区切り位置に、さくらスクリプトの改行タグ \n を挿入する。

SAKURA_SCRIPT.break_lines(text, widths) -> string
パラメータ型必須説明
textstring | nil✅改行を入れるテキスト。nil または "" のときは "" を返す
widthstable | nil✅行ごとの幅の上限の配列。nil・表でない値・空の表のときは text をそのまま返す

widths の仕様:

  • widths[1] が 1 行目、widths[2] が 2 行目の幅の上限である。配列の最後の値が、それ以降の行すべてに使われる({10, 12} なら 1 行目は 10 まで、2 行目以降は 12 まで)。
  • 幅は半角 1 文字を 1、全角 1 文字を 2 と数える(東アジアの文字幅で幅が曖昧な文字も 2)。10 は全角 5 文字分である。1 文字は 文字の分類 の見た目の 1 文字で、いくつかのコードポイントでできた文字は文字全体の幅で数える(🙆‍♀️ は 2)。
  • 改行は 1 文字の途中には入らない。
  • 要素は数値で書く。小数は整数に切り捨てる。数値でない要素があると Lua のエラーになる。

改行の入れ方:

  • 語を先頭から順に行へ積み、次の語を足すと幅の上限を超えるときに、その語の前へ \n を入れて次の行へ移る。
  • 1 語だけで上限を超える場合は、語の途中で分割しない(その行は上限を超える)。
  • さくらスクリプトの単位(タグ・エスケープ・囲み。\_w[50]・\\・\%・\_?…\_? など)は幅に数えず、途中に \n を入れず、元の文字との位置関係を保ったまま出力する。文字の直後にある単位はその文字の側に残り、\n は単位の後ろに入る。単位の読み方は 文字の分類 と同じである(タグの名前の直後の字は通常の文字として幅に数える)。
  • テキストの中に行を動かすタグ(\n・\n[…]・引数の無い \c・\_l[…])があると、そのタグの後ろの字を数え直す(行を動かすタグでの数え直し)。
  • break_lines は、毎回最初の行の頭から数え、入れる改行は \n である。アクター表の budoux_newlines・budoux_jump に当たる指定は無いため、\_l[…] の所では字数だけを 0 に戻す。

通常は talk_to_script と組み合わせて呼ぶ必要はない。アクター表に budoux があれば、talk_to_script がウェイトの挿入後に自動で break_lines を適用する。break_lines を直接呼ぶのは、独自の変換手順を組む場合である。

行を動かすタグでの数え直し

自動改行(talk_to_script と break_lines)は、テキストの中の次の単位の所で、その後ろの字を数え直す。作者が台詞に書いたタグも、単語や関数が返して台詞につながったタグも、act:sakura_script で積んだテキストの中のタグも同じに扱う。縦書きのバルーンでは、行が列に当たる。

単位後ろの字の数え方
\n・\n[…](\n[half] を含む)次の行の頭から字数 0 で数え、配列の次の値を使う
引数の無い \c最初の行の頭から字数 0 で数え、budoux の配列の先頭の値を使う(budoux_jump の配列を使っていたら budoux へ戻る)
\_l[…](相対の \_l[@…] を含む)字数 0 から数える。アクター表に budoux_jump があれば、その配列の先頭の値から使い(もう一度 \_l[…] があれば先頭から使い直す)、続く \n・\n[…] のたびに次の値を使う。budoux_jump が無いか空の配列なら、行の番号は変えず、同じ値を使い続ける
  • 上の表に無い単位では数え直さない。\x・\x[noclear]・\_q・引数つきの \c[…]・ウェイト(\_w[…] など)・表情の切り替え(\s[…])・スコープの切り替え(\0・\1・\p[…] など)がこれに当たる。
  • 単位の読み方は 文字の分類 と同じである。エスケープ \\ の後ろの n(\\n)と、囲み(\_?…\_?・\_!…\_!)の中の \n・\c・\_l は、行を動かすタグとして読まない。
  • 行を動かすタグの直後に続く語の前には、改行を入れない(行の頭に空の行を作らない)。
  • 行を動かすタグが budoux の 1 語の途中にあると、タグの前と後ろを別の行として数え、タグの後ろを新しい行の頭として扱う。
  • 改行を入れる位置の直前が単位にならない \(文字の分類)のときは、その \ の前で改行する(\ の直後に入れると \\ のエスケープとして読まれるため。\ が行の頭にあるときは改行しない)。
  • 相対の \_l[@…] でも字数は 0 に戻る。横書きで \_l[@…] を使って表示の位置を少しずらしているときも、そこから数え直す。

以前の版では、テキストに元からある \n などを数え直さず、前の字数を引き継いで数えていた。この直しで、budoux を使うゴーストでは改行の位置が変わることがある。

SAKURA_SCRIPT.break_lines("今日は\\n天気", {8})
-- → 今日は\n天気(\n の後ろは字数 0 から数えるので、「天気」の前に改行は入らない)

pasta.toml での budoux 設定

アクター表に budoux を書くと、そのアクターの台詞に自動改行が入る。値は break_lines の widths と同じ配列である。

[actor."女の子"]
spot = 0
budoux = [10, 12]
# 1 行目は幅 10(全角 5 文字)まで、2 行目以降は幅 12(全角 6 文字)まで

budoux を書かないアクター、または空の配列を書いたアクターには自動改行は入らない。

同じアクター表に、次の 2 つのキーも書ける。どちらも budoux が空でない配列のときだけ読む(budoux が無いアクターでは、書き間違いがあってもエラーにならない)。

キー型説明
budoux_newlines数値自動改行で入れる改行の幅の倍率。100 倍して小数点以下を切り捨てた N が 100 なら \n、それ以外は \n[N] を入れる。書かなければ \n。数値に変換できる文字列も数値として読み、数値にできない値を書くとその台詞の変換がエラーになる(budoux_newlines)
budoux_jump整数の配列\_l[…] の後ろの行に使う幅の配列(行を動かすタグでの数え直し)。書き方と、整数にできない要素の扱いは budoux と同じ。表でない値は無視する(budoux_jump)
[actor."ぱすた"]
spot = 0
budoux = [40]            # 20 字ずつ
budoux_jump = [40, 28]   # \_l[…] の後: 1 行目は 20 字、2 行目以降は 14 字
budoux_newlines = 1.8    # 自動改行を \n[180] にする

budoux_newlines が効くのは自動改行で入れる改行だけで、台詞に書いた改行や pasta が出す区切りの幅は変わらない。break_lines は、この 2 つのキーを使わない。

使用例(直接呼び出し)

local SAKURA_SCRIPT = require "@pasta_sakura_script"

SAKURA_SCRIPT.break_lines("今日はいい天気ですね", {6})
-- → 今日は\nいい\n天気ですね

SAKURA_SCRIPT.break_lines("今日はいい天気ですね", {10})
-- → 今日はいい\n天気ですね

-- さくらスクリプトタグは幅に数えず、出力に残る
SAKURA_SCRIPT.break_lines("こ\\_w[50]れ\\_w[50]は\\_w[50]テ\\_w[50]ス\\_w[50]ト", {6})
-- → こ\_w[50]れ\_w[50]は\_w[50]\nテ\_w[50]ス\_w[50]ト

-- nil・空の表は安全
SAKURA_SCRIPT.break_lines(nil, {10})    -- → ""
SAKURA_SCRIPT.break_lines("テスト", {})  -- → "テスト"(変更なし)
SAKURA_SCRIPT.break_lines("テスト", nil) -- → "テスト"(変更なし)

-- talk_to_script 経由の自動適用(アクター表に budoux がある場合)
local ok, CONFIG = pcall(require, "@pasta_config")
local actor = ok and CONFIG.actor and CONFIG.actor["女の子"]  -- budoux = [10, 12] を書いたアクター
SAKURA_SCRIPT.talk_to_script(actor, "今日はいい天気ですね。")
-- → 今日はいい\n天気ですね。\_w[950](ウェイトを挿入した後で改行が入る)

改行の位置は budoux の分割結果に依存する。上の出力は現行の分割モデルでの結果である。


クローディア(素)

これで台詞の「間」も折り返しも、あなたの思うままですわね。早口すぎるゴーストになっていたら、まず script_wait_normal を疑いなさいまし。

アンソニー(素)

既定の一般文字は 50 ミリ秒、差し引き 0 でウェイトは入らない、でございましたね。

クローディア(高笑い)

おほほほ! さきほどのあなたの話しぶりとは、大違いですわね!