会話システムのテキスト管理: 翻訳できる形で書き出す設計
自作の会話システムは、ゲームの中で最初に「自分のもの」だと感じられる部分かもしれません。ノード、分岐、条件、自分で作った小さなエディタ。動きますし、シナリオも書けます。そして1言語でリリースまでたどり着きます。ところがある日「翻訳したいのでテキストをください」と言われて、その一言に答えるのが想像以上に難しいことに気づきます。セリフはノードの中に条件や話者情報と絡み合って入っていて、プレイヤーが聞く順番に読む方法はゲームを実際に遊ぶことしかありません。
問題は翻訳そのものではなく、データの形です。会話テキストは、通常のUI文字列にはない三つの要素を抱えているため、ゲーム内で最も書き出しにくいテキストになります。分岐構造、話者、そして実行時にしか決まらない値の三つです。書き出しでこのどれかが落ちると、翻訳者は手がかりのないまま作業することになり、その結果は数か月後に画面上で発覚します。
この記事では、その三つが往復しても壊れないように、書き出しと取り込みを設計する方法を扱います。
翻訳者が必要とする形は、実行時のデータ構造とは違う
実行時に必要なのはグラフです。ノードIDでの高速な参照、評価できる条件、次に進む先へのポインタ。しかしそのどれも、別の言語で自然な会話を書く人の役には立ちません。翻訳者が必要とするのは、直線的で読める文書です。誰が誰に向かって話しているのか、直前に何が語られたのか、プレイヤーが何を選んでここに来たのか、そして画面上でどれくらいの分量が入るのか。
どちらの見え方も正当で、間違いは「1つのファイルで両方を兼ねさせようとすること」です。書き出しは、オーサリング用データのコピーではなく射影(必要な面だけを取り出したもの)だと考えてください。エディタ側の形式はエディタに都合のよいままでかまいません。書き出しはそこから生成される平坦な表であり、取り込みはキーを頼りに翻訳を書き戻します。この分離ができれば、あとは「射影に何を含めるか」という問題だけになります。
射影が十分かどうかは、次の問いで判定できます。あなたのゲームを一度も遊んだことがない人が、書き出したファイルだけを見てこれらに答えられるでしょうか。
- このセリフを誰が、誰に向かって話しているのか
- どの場面・どの章のもので、その前後で何が起きているのか
- セリフなのか、プレイヤーの選択肢なのか、環境音的な一言なのか
- 直前に何が話されたのか、プレイヤーは何を選んでここに来たのか
- 各プレースホルダーに何が入るのか(実際にありうる例つきで)
- 表示枠が固定なら、その行に使える分量はどれくらいか
表示される1行を1キーに、それ以外は列に持つ
翻訳の単位は、プレイヤーがある瞬間に画面で読む1かたまりです。ノード単位でも会話単位でもありません。連続する5行を持つノードをひとかたまりのテキストとして書き出すと、内部の改行位置と段落数を保つ責任が翻訳者側に移ります。1つ結合されたり落ちたりするだけで以降の会話がずれますし、ファイルを見てもそれが起きたことは分かりません。
表示される各行には、グラフ上の位置に依存しない安定したIDを与えてください。conversation03_node07_line2 のように位置を含んだキーは、場面を並べ替えた瞬間に壊れます。以降のキーがすべて変わり、既存の翻訳が一致しなくなり、レビュー履歴も失われます。行を書いた時点で割り当てて二度と使い回さない短いIDのほうがはるかに安全で、人間が読むための情報はキーではなく別の列に持たせます。
この設計は「翻訳後に行を分割・結合するのはテキスト編集ではなくデータ変更である」ことも意味します。長い1行を画面上2行に分けたいと判断したら、それは新しいキーであり、すべての言語で新しい翻訳が必要になります。シナリオがまだ動いている早い段階でこれを決めておくほうが、翻訳作業中に気づくよりずっと安く済みます。
会話の書き出しを台無しにする最も多い原因が、話者名を本文に焼き込むことです。翻訳対象の文字列が「名前+コロン+セリフ」という形になっていると、名前が翻訳可能な文字列の中に何百回も複製されます。翻訳者が400行目でうっかり別表記にしても、他は一貫しているので意図的な使い分けに見えてしまいます。名前だけ色やスタイルを変えることもできなくなり、後からの改名は、読めない言語のテキストに対する一括置換になります。
話者はIDとして専用の列に持ち、表示時に通常の文字列システムで解決してください。キャラクター名はもちろんローカライズ対象です。表記のゆれ、敬称の扱い、一貫性はどれも重要ですが、名前は1キーにつき一度だけ翻訳されるべきで、行ごとに書き直されるものではありません。
場面、章、立ち絵や表情のタグ、そして短い自由記述のメモ。これらの列が、文脈から切り離された1行を翻訳可能にします。出力コストはほぼゼロで、翻訳者があなたに質問して返事を待つ手間の大半を先回りして消せます。「この行は皮肉である」「話し手はまだ相手が嘘をついていることを知らない」といったメモは、どんなスタイルガイドよりも訳文を変えます。
key,type,conversation,order,speaker_id,text,note,max_chars,placeholders,voice_clip
分岐を平坦なファイルで読めるようにする
分岐は、平坦なファイルにした時点で失われる文脈の代表です。10行が並んでいて、そのうち4行が互いに排他的な選択肢の結果であることが分からなければ、翻訳者はいずれ「プレイヤーが尋ねていない質問への返事」を書きます。そしてそれは画面上ではバグにしか見えません。
これは三つの列でほぼ解決できます。会話ID(1つの木全体をまとめて読めるようにする)、順序またはパスの列(上から下に読んで意味が通る並びにする)、そして「どうやってここに来たか」を平易な言葉で書いた短いメモです。
特に注意が必要なのはプレイヤーの選択肢です。選択肢の文言と、それに答えるセリフは必ず隣り合って見えるようにしてください。答えは選択肢に文法的に続くことが多く、日本語では語尾や助詞の受け方まで影響します。行の種類を示す列(地の文・セリフ・選択肢・掛け声)を用意し、選択肢がその先の分岐の直前に並ぶように並べ替えます。
条件を論理式のまま書き出さないでください。「信頼度フラグが3以上」というセルは翻訳者にとって雑音です。同じ内容を「プレイヤーがすでに2回断っているときに言う」という文にすれば、訳文に反映できる文脈になります。エディタ側が判断できるなら書き出し時に自動生成し、できないところは手で書きます。
変数・固有名と、連結して作ってはいけない文
実行時の値が最も多く登場するのが会話です。プレイヤー名、いま拾ったアイテム、残り回数。各行に含まれるプレースホルダーの一覧を専用の列として書き出し、翻訳後も同じ集合が残っているかを機械的に検証できるようにしてください。トークンが静かに1つ落ちれば単語の抜けた文になり、存在しないトークンが増えれば画面にプレースホルダーがそのまま表示されます。
より根が深いのは、文字列連結で文を組み立てる設計です。「前半+変数+後半」という作り方は、書いた言語では動いても多くの言語で破綻します。語順、助詞、冠詞、性、複数形が、挿入される値によって変わるからです。文は丸ごと1つの文字列にし、その中にプレースホルダーを置いて、翻訳者が必要な位置に動かせるようにします。数や性で文の形が変わる必要があるなら、その変化は会話ランナーのif文ではなく、複数形や選択の仕組みとして文字列側に持たせます。
プレイヤーが入力する名前は特別扱いで、メモ列に明記する価値があります。活用させられず、フォントに含まれない文字が来る可能性があり、長さも予測できません。その言語なら本来は名前を格変化させるところで、翻訳者は名前を避けた言い回しに書き換える必要があります。そしてそれは「このプレースホルダーはプレイヤーが打った自由入力である」と伝えられて初めて判断できます。
// 翻訳で壊れる書き方
Show(Localize(found_prefix) + itemName + Localize(found_suffix));
// 翻訳に耐える書き方
Show(Localize(item_found, { item: itemName }));
// item_found (ja): {item} を手に入れた!
// item_found (en): You found {item}!取り込みと、正しさの確かめ方
取り込みは必ずキーで行い、行の順番に依存させないでください。表計算ファイルは並べ替えられ、絞り込まれ、二人で分担して結合され、行が消されて足されます。3行目がいつまでも3行目だと仮定していると、話者名でソートしただけで台本全体が1行ずれ、しかも結果はもっともらしく見えてしまいます。キー列を必須にし、位置は完全に無視し、キーが欠けていたり重複していたらファイルごと弾きます。
そのうえで、ビルドに入る前に機械的なチェックを回します。実害を捕まえられて、自動化コストがほぼゼロなのは次のあたりです。
- 書き出したキーがすべて戻ってきていて、知らないキーが増えていないこと
- 各行のプレースホルダーの集合が元と完全に一致すること
- 話者・会話ID・順序の列が書き出した内容から変わっていないこと
- 表示枠に対して宣言した最大文字数を超えた行がないこと
- リッチテキストや装飾タグの対応が取れていて、個数も変わっていないこと
- 空のセルがなく、原文のままで残っているセルもないこと
最後の確認はファイルではなくゲームの中で
機械的なチェックが保証してくれるのは、データが壊れていないことだけです。枠には収まっているが別人の口調になっている、冗談のつもりが侮辱になっている、といった問題は検出できません。それを見つけるには実際の画面で読むしかありませんが、分岐のあるゲームを全言語で通しプレイするのは現実的ではありません。
代わりに会話の再生モードを用意してください。すべての会話とすべての分岐を順番にたどり、話者とセリフを現在の言語で表示していくデバッグ画面です。条件は無視して全部通します。半日で作れて、通常プレイでは到達しにくい分岐を確認する唯一の現実的な手段であり、レビューを「通しプレイ」から「読み合わせ」に変えてくれます。あわせて、言語ごとに全セリフをテキストファイルへ書き出す機能も付けておくと、ビルドを持っていないレビュアーでも台本を順番に読めます。
ここまでの設計が目指しているのは、たった一つの性質です。言語を1つ増やす作業が、ノードグラフを掘り返す発掘作業ではなく、データの操作で済むこと。書き出し・翻訳・取り込み・検証を、会話エディタを開かずに完了できるなら、その設計は正しくできています。