UMGでルビを振ってみようと思って試してみました。
途中諦めようかと思ったけど、なんとか形にすることができたので、メモっておきます。
今回日本語フォントとして、たぬき油性マジックという書体を使ってます。
仕組みとしては、まずルビ付きの文字をまず括弧的なもので分けたテキストを用意。
ここは人力。
それを分解してテキストブロックにしてUMGで表示します。
そのあと、ルビを置く場所を計算しながら置いていきます。
例えば、
といった具合です。
で、UMGでTextBlockを並べるのですが、入れ物として2種類のパネルを使います。
CanvasPanel の方はルビ専用で、自由な場所に置くためです。一方の HorizontalBox は楽して左詰めするためです。
Size To Contentのチェックを付けていると、上図のようにバウンディングボックス(緑の枠)が小さくなりますが、ルビと本文の距離はここで調節します。
ブループリントからいじるので、 Is Variable のチェックを付けておきます。
キャンバスは以上です。
まず文字列を解析する関数を用意します。
この関数には、String型の入力ピンを一つと、本文とルビを配列で返すピンを追加します。
Local変数も追加します。下図は、関数のピン設定とローカル変数。
そして今回のキモとなる文字列分割ノード。
まず 区切り文字=デリミタに設定した半角の { で配列に分解します。
{ から始まる文章が来るかもしれないので、 Cull Empty Srings のチェックは外しておきます。
このノードを通った分割後の文字列はこんな状態。デリミタは消滅します。
ブランチノードで、デリミタが存在するか確認して分岐します。配列の数を調べれば分割されたかどうか判明します。
デリミタが無い=ルビが無い 場合、ルビに関する処理はしないので以下のようにします。
分割があった場合は、 } が含まれるので、今度は Splitノードを使ってルビを分離します。
わりとややこしい流れになったので、文字列が処理されていくイメージを図で説明するとこんな感じになります。
Index に +1 して set Array Elem しているのは、 Insert ノードを使った直後はこうなっているからです。
まだForEachLoopの Index は 1 のままなので、+1 しているという訳です。
で、最終的に2つの配列が整います。
関数は以上です。
EventGraphに戻って、変数を一通り準備します。
一番下の Stringは、Expose on Spawn にチェックを付けておきます。
書体設定用の変数は、Slate Font Infoという変数タイプです。
一度コンパイルしてやるとDefaultValueからフォントやサイズが設定できるようになります。
フォントの設定は、後からノードでも指定できるんですが、あらかじめ書体やサイズなんかが決まっていて途中で変更することが無い場合は、こうして変数の初期値として持たせておくとノードをつなぐときスッキリできるのでおススメです。
変数が用意できたら、文字を並べていくところを作っていきます。
用意しておいた関数から出てきた配列型の文字列を、準備した配列変数に保存します。
それを基にForEachLoopで順番に処理します。
上の方にあるConstruct Text というノードはそのまま探しても出てきません。
探すときはコレ。
たいていのものはこのノードで実体化できるので、大変便利です。
Outerのところは、パネルの子供にすることが決まっているので、親になるHorizontalBoxをつないでいます。特に親がいない場合はSelfノードで大丈夫みたいです。
Classは、TextBlock をセットします。するとReturn Valueピンからは UMGの生TexBlockが生成されてくるので、いろいろ設定をしてやります。
これでルビを除いた本文だけが並べられて表示できるようになります。
ループ処理が終わったら、 Force Layout Prepass ノードを入れます。
これは、TextBockの表示サイズを正確に取得するためです。
サイズが固定されてなくて、Size to Content にチェックがついているようなTextBlockやCanvasなどのWidgetパーツは、WidgetがレンダリングされるタイミングとWidgetブループリントが処理されるタイミングがかみ合わないことがあって、サイズ取得できないことが多いです。
Widgetブループリントが、描画担当にテキストを渡して
Wブ「このテキスト表示してよ」
描画担当「分かった~」
Wブ「で、サイズは?」
描画担当「これくらい?」
Wブ「ほんとに? ちょちょ も1回 Pre-passで確認してよ」
ってことだろうと勝手に想像してます。
つぎにルビの表示処理です。
そのまえに、本文のTextBlockのサイズをチェックして加算するマクロを用意します。
単純にTextBlockの幅を自身に加算してRubyPosition変数を更新していきますが、ルビで必要なのは、ルビを振る対象のTextBlockの幅を1/2したサイズだけが必要です。OffsetCenter変数にはそのまま入れるので常に上書きすることになります。
グラフに戻って続きを作ります。
今度は、ルビ用の配列変数をForEachLoopで処理していきます。
マクロで、本文のTextBlockの幅を調べて加算しつつ、ルビがあるかどうかをチェックします。
ルビ用の配列に空(Null)を入れていたのはこのためです。ルビがあろうとなかろうと本文は並べられます。どのTextBlockがルビ振り対象だったかを覚えておくためには単にに別の配列を用意して同じ順番にルビを入れておけばいいよね?っていう発想です。
常に座標だけは計算しておきたいので、マクロをブランチノードの前に置いています。
ルビが見つかったら、本文と同じ要領でTextBlockを生成して、フォントの設定をします。
文字のレイアウト設定なんかもあるのでちょっと大きくなってます。
最後のSet Positionノードで、ルビの位置がセットされます。
仕上げに、EventTickに、あのForceLayoutPrepassノードをつないでおきます。
ちょっとしつこい感じですが、いろいろ試してみてTickでもやった方がいい結果になったのでつなぐことにしました。毎フレーム処理されるのは負荷が高いので、本文の表示が終わったタイミングで処理が走り始めるようにしています。
なぜか、ルビの表示処理が終わって、isActive を false にすると、ルビの位置がずれるのです。ずっとTickが走り続けるのが気になるのですが、今のところ他の解決策が思いついていないので、情報をお持ちの方はネットに公開していただけると助かります。
これでルビ付きテキスト表示のWidgetは完成です。
複数行を処理してルビ付きテキスト表示するためのWidgetを用意します。
キャンバスの適当な位置に VerticalBoxパネルを配置します。
Size To Content と Is Variable にチェックを付けています。
配列変数を一つだけ用意します。受け取った文字列を配列にして保持するためのものです。
String型なのにTextという名前を付けるのためらったのですが、大本の「文章」が渡されてくることを考えて、あえてTextArrayとしました。アイコンの色と形で分かるのがビジュアルスクリプトの素晴らしいとこだと思うので、混乱は無い、と信じたい・・・
で、受け取ったテキストは以下のような状態で受け取ります。
改行位置は赤い矢印 ↓ の箇所。
うしろの方のしずかな空で、いきなり音がしましたので↓
シグナレスは{急|いそ}いでそっちをふり{向|む}きました。↓
ずうっと{積|つ}まれた黒い{枕木|まくらぎ}の向こうに、あの{立派|りっぱ}な{本線|ほんせん}のシグナル{柱|ばしら}が、↓
今はるかの南から、かがやく白けむりをあげてやって来る{列車|れっしゃ}を{迎|むか}えるために、↓
その上の{硬|かた}い{腕|うで}を下げたところでした。↓
宮沢賢治の「シグナルとシグナレス」より。
青空文庫からコピペさせていただきました。
この改行を分解して配列にします。
その関数が以下。再び Parse into Arrayノードの出番です。
最後のSet Padding は、行間の調整用です。
下は Construct Object from Class ノードです。作っておいたルビ付きテキストWidgetをセットします。アセットも生成できるので、Create Widget とほぼ同じ。
以上でWidgetは完成です。
テスト表示用にレベルブループリントを編集します。
表示してみると、冒頭のキャプチャになりました。
いろいろ試してみました。
フォントにいいのが無かったので、まったく迫力出ないですね。
再び青空文庫より。
もうノベルゲームが完成した感があります。
なんとかルビが付くようにできました。
もっといい方法があるぜよ、という方がおられればぜひご教授いただければ嬉しいです。
ではでは 今回はこの辺で
ステキなルビ振りライフを!