Godot Engine上で振り仮名(ルビ)を実現する


Godotでもルビを振りたい!

皆さんはゲームを作るとき、何のエンジンを使いますか? ……なになに、「既製エンジンを使うとは限らないじゃないか」ですって? いやいや、今時、フルスクラッチで自作する硬派な人は希少でしょう1。エンジンっぽいものを作るにしても、既製のツールの上にオリジナルのものを実装するケースが多いのではないかと思います。車輪の再開発はできるだけ避けたいでしょうから。
既製エンジンで言えば、UnityやUnreal EngineのようなAll-in-Oneなエンジンを使う方も多いでしょうし、あるいはGameMakerやRen’PyやPhaser (≒Pixi.js)のように強み重視なエンジンを使う方もいらっしゃると思います。どれもそれぞれ特徴があって面白いですよね。
有名なゲームエンジン間の比較や各エンジンの強み・弱みを解説しているgamesindustry.bizの記事を読んでみると、いろいろあるんだなあと改めて実感します。

ところで最近、私はGodot Engine2というFLOSSのゲームエンジンを触り始めています。シングルバイナリ(60MBほど)で動作し軽量、マルチプラットフォーム(Linuxでも動きます)に対応、おまけにFLOSS(MITライセンス)なのでソースコードも読み放題! という個人的な理想が詰まったエンジンなのです。控えめに言って神、というのはこのことでしょう。
そして、私は事あるごとにノベルゲームのような何かを作る習性3があるため、今回もGodot Engine上でノベルゲームっぽいものを作ることにしました。

海外ではそれなりに人気もあるエンジンということもあり、大体の機能は揃っています。テキスト表示も詰まることなくオーケー。各種画像表示4や音源再生もオーケー。基本的なノベルゲームっぽいものは難なく完成。
しかし、ちょろちょろとスクリプトを入れ込んでいるうちに、あることに気が付きます。そう、振り仮名、所謂ルビを振る機能がないのです。Godotは海外産である上に日本でもマイナーな存在ということもあって、ルビのようなレア機能は当然実装されていないのでした。

調べてみると、Unityでもデフォルトでルビはサポートされていないようですが、流石有名なUnity、いくつか実装例が存在しているようです5。しかし、Godotでの実装例は見当たらず……。
Godotに心を奪われてしまった一ファンとして、これは見過ごせません。というかファン云々以前に、とにかくGodot上でルビを振りたいのです。


……で、結論から申し上げると、いろいろと試行錯誤の結果、Godot上で振り仮名、ルビを振るスクリプトの作成に成功しました。以下のリポジトリで公開をしています(両者とも内容は同じです)。

これを使うと

  • "ただ、所々%ruby{丹塗,にぬり}の%ruby{剥,は}げた、\n大きな%ruby{円柱,まるばしら}に、\n%ruby{蟋蟀,きりぎりす}が一匹とまっている"

という文字列を

ルビ表示サンプル

ルビ表示サンプル

のように表示可能になります。ちゃんと振れていますね!

簡単な説明

後はスクリプトのソースコード読んでください……でもいいんですが、味気ないので、ちょっとだけ何をやっているか説明します。

処理ステップは大まかに二つに分かれていて、

  1. 与えられた文字列M1から、正規表現を利用してルビ対象文字列・ルビ文字列を取得し、ルビ文字列を除いたM2と、ルビ文字列の情報を含んだ配列R[]を生成する(parse)
    • 例 : 今日は%ruby{銀行,ぎんこう}へ行こう (M1) から 今日は銀行へ行こう (M2) と [{t_idx: 3, t_len: 2, text: "ぎんこう"}] (R[]) を生成
  2. M2R[]から、それぞれ文字列をフォントを用いて描画した際のサイズや位置を算出し、表示する(apply)
    • M2とルビは別のLabelになっている
    • ルビのLabelの位置が微妙にズレるため、SPACINGで調整をしている

という感じです。コードで言うと、label_container.gd

# ルビ指定を含むテキストのパース
# ルビ指定はrubies配列に格納し、ルビを除くテキストを返す
func parse() -> String:

がparseで、

# メッセージにルビを適用する
func apply():

がapplyです。そのまんまですね。

ルビの指定は、%ruby{ルビ対象文字列,ルビ文字列}で行います。%rubyが長ったらしいという方は、ソースをちょっと変えるだけで任意のプレフィックスに変更可能です。

コード数はさほど多くない(合わせても100行ないです)ので、細かい処理についてはソースを読んでいただけると。

既知の問題・懸念点

  • 現状、Autowrapには対応していません。複数行の文字列に対してルビを振りたい場合は、適宜\nで明示的に改行する必要があります
    • 必要性が出てきたら、改めて実装します
  • フォントによっては、正常に機能しないかもしれません
    • 現状、Noto Sans JPくらいでしかテストしていません
  • 中央寄せ、右寄せに対応できていません
    • 単純にAlignを見てxの調整をすればいいんですが、コードがややこしくなるため、一旦見送っています
    • 必要になったら対応するかもしれません。対応した場合、こちらでもお知らせします
    • alignのFILLを除き仮対応しました(alignのFILLは挙動がいまいち分からず……)

そんな感じで、ルビが振れて私は少しハッピーです6。バグや改善案等あればご連絡ください。PR・MRも(あれば)お待ちしています。


  1. それこそ、大企業雇われのエンジニアさんか、よほどの趣味人くらいではないかと。 ↩︎

  2. https://godotengine.org/ ↩︎

  3. ゲーム自体より、その下で動くシステムとかコード書いてた方が楽しい性分なんですよね。分かりませんか? ↩︎

  4. WebP対応しているのが地味に嬉しい。 ↩︎

  5. 検索したところ、Unityでの実装例としては https://github.com/ina-amagami/TextMeshProRubyhttps://github.com/jp-netsis/RubyTextMeshPro 等が出てきました。Unityは日本語圏でもエコシステムが強いですね。 ↩︎

  6. このような、小さいけど地味に必要な機能が実現できた時が一番嬉しい気がします。 ↩︎