自作ゲーム「O'BLOCK」(スニーカー転売で10億ドルを目指すローグライト)を日本語・中国語・韓国語に対応させました。問題は、ゲームが自作の Rust エンジン(Proof Engine)で動いていて、フォントアトラスに ASCII と記号しか入っていなかったこと。そしてゲーム本体には英語の文字列が約3万9千行のあちこちに散らばっていました。
やったことを短くまとめます。
1. 全角文字を2マス扱いにする
等幅グリッドでは CJK の文字は半角2つ分の幅です。エンジンに is_wide(char) と text_cols(&str)(全角を2列として数える)を足して、文字送り・幅計測・右寄せ・折り返しをすべて「文字数」ではなく「列数」で計算するようにしました。英語の描画位置が1ピクセルも変わらないことはテストで確認しています。
折り返しも変えています。日本語と中国語はどの文字の間でも改行できますが、「。」「」」の前では改行しない(禁則処理)。韓国語は分かち書きがあるので英語と同じくスペースで折り返します。
2. 実行中に作り直せるフォントアトラス
アトラスは起動時に TTF から作る SDF(符号付き距離場)です。CJK は Windows に最初から入っているフォント(中国語: Microsoft YaHei、日本語: 游ゴシック、韓国語: Malgun Gothic)から、その言語の翻訳ファイルで実際に使う文字だけを2マス幅のセルに焼きます。日本語は約1,150文字で、生成は約150ms。設定画面で言語を切り替えると、再起動なしで次のフレームまでにアトラスを作り直します。
3. 3万9千行を書き換えずに翻訳する
すべての文字列を tr!() で包むと数千か所の修正になります。でもこのゲームは、画面上の文字のほとんどを render.rs の数個のヘルパー関数経由で描いていました(箱に収めるための関数)。なのでそこで翻訳します。
- スクリプトでソースを走査し、
format!("SOLD {} PAIRS FOR {}", n, cash)をSOLD {0} PAIRS FOR {1}というテンプレートに変換(合計3,323個) - 描画時に
tr()がテンプレートと照合して値を取り出し、翻訳側のテンプレートに好きな順番で戻す - 結果はキャッシュするので、1フレームあたりのコストはハッシュ1回
- 合わなかったものは英語のまま。表示が壊れることはありません
人名(パロディのラッパーたち)、スニーカーのモデル名、カラーウェイ、ゲーム内のメカニクス名はあえて英語のままです。
4. ハマったところ
英語の複数形の "s" をプレースホルダーで渡しているテンプレートが十数個あって、「3日s」と表示されました。英語以外では複数形の部分だけを捨てるようにして解決。
おわりに
O'BLOCK は Steam で無料、10月20日リリースです。相手の「言い値」を読む交渉ゲームが好きな人は、ウィッシュリストに入れてもらえるとすごく助かります。
エンジンは MIT ライセンスで公開しています: https://github.com/Mattbusel/proof-engine
