1 はじめに
こんにちは。ソーシャルゲーム事業部の額田です。 この記事はカヤックUnityアドベントカレンダー2018の21日目の記事です。 今回はTextコンポーネントでの自動改行についてお話ししていきたいと思います。
日本語のテキストを表示するときに文章の区切りとしては不自然な箇所で改行されてしまい、
TextViewのサイズに合わせて\n
を自前で入れた経験はないでしょうか?
変更しないテキストならよいですが、セリフなど動的に変更するテキストの場合は、自前で改行文字を入れるのは労力がかかります。
なので、GameObjectのサイズに合わせて、いい感じに改行してくれるTextコンポーネントを作ってみたいと思います。
// Before 我輩は猫である。名前はま だない。
// After 吾輩は猫である。名前は\n まだない。
プロジェクトによっては「このTextにはデザインの都合上200文字しか表示したくない」というケースもあります。 そのような場合に、意図しない改行で文字制限を超えてしまうという問題も生じるかと思います。 今回は自動改行をする1つのやり方を紹介するに留め、その辺りは考慮しないこととします。
2 UnityのTextコンポーネント
2.1 いい感じに改行ができない理由
UnityのTextコンポーネントでは、英語のように単語と単語の間に空白がある言語は空白を区切りにして改行が行われます。
しかし、日本語や中国語、韓国語など空白を使わない言語の場合は改行をする目印がありません。 そのため、1 はじめに で述べたように不自然な位置で改行が行われてしまいます。
2.2 改行をするためには?
わかち書き
わかち書きとは空白を用いて単語や文節を区切って記述する方法です。 昔の某RPGゲームなどを遊んだことがある人には懐かしい文章だと思います。 ひらがな文章の場合に使われたりしますが、通常は使われない記述方法です。
空白で明示的に区切り位置を指定することができ、英語文章と同じく空白区切りでの改行をすることが可能になります。 ですが、可読性はよくないためオススメしません。
あきらめて かわいさ ろせんを めざし この ほうほうで げーむを つくるのも ありかも しれません。
日本語に不慣れな外国人あるいは日本語学習者がひらがなを主体としたわかち書きの文章を用いる例があり[2]、そのたどたどしいさまが、けなげさ、可愛らしさにつながりより深い共感を生む場合がある[3]。
Wikipedia - わかち書き
形態素解析
空白を用いずに単語や文節の単位で区切って改行をするためには、文章の中でどこからどこまでが意味のまとまった語句なのかを知る必要があります。
「が」「を」「に」などを目印に意味のまとまりを抽出するという方法も考えられますが、 日本語は複雑であるためアドホックな方法では限界があります。
これを実現するには 形態素解析 を行う方法があります。 簡単に言うと、文章を形態素と呼ばれるその言語の最小単位に分解して、それぞれの品詞が何かというのを解析します。
形態素解析を行うことで、意味のある最小単位で区切ることができます。 また、品詞情報を使うことで文節単位で改行をするなどより柔軟な改行も可能になると思います。
文字列 読み 原形 品詞の種類 活用の種類 活用形 お待ち オマチ お待ち 名詞-サ変接続 し シ する 動詞-自立 サ変・スル 連用形 て テ て 助詞-接続助詞 おり オリ おる 動詞-非自立 五段・ラ行 連用形 ます マス ます 助動詞 特殊・マス 基本形 。 。 。 記号-句点 Wikipedia - わかち書き
実際にUnityで形態素解析をする方法については3 形態素解析で紹介し、 4 自動改行させようではTextコンポーネントを継承したクラスで簡易的に自動改行を行ってみたいと思います。
3 形態素解析
3.1 MeCabとは
形態素解析を行うオープンソースにMeCabがあります。 今回はUnity上で行うために、MeCabを.NETに移植したNMeCabを使用します。
導入と使い方について、本記事だけで読み進められるよう簡単な説明をしていきますが、 以下のサイトを参考にしていただくとよいかと思います。 - Unityで形態素解析をする方法 - Qiita - NMeCabで形態素解析をしてみよう - Qiita
3.1 導入
NMeCabの最新版(現時点ではNMeCab 0.07)を
ここからダウンロードしてきます。
必要なのは以下の2つです。
- NMeCab0.07/dic
- NMeCab0.07/bin/LibNMeCab.dll
適当にNMeCab
ディレクトリなどをUnity側に用意して、これらを入れておきましょう。
3.2 使い方
まずは簡単なサンプルコードを動かしてみましょう。
- MeCabParamに形態素解析で使用する辞書を設定します。
先ほど配置した
NMeCab/dic/ipadic
を指定します。 なお、今回はUnityEditor上での実行を想定しています。 必要に応じて適切なファイルの配置とパスの指定を行なってください
MeCabParam param = new MeCabParam(); param.DicDir = @"Assets/NMeCab/dic/ipadic";
- 文章を解析するための
MeCabTagger
を生成し、文章を解析して形態素に分解します。 各形態素は双方向リストになっており、node.Next
に次のノードの参照が入っています。node.Surface
には形態素の文字列が入っています。node.Feature
には解析結果として、素性(品詞や活用形などの解析情報)がCSV形式の文字列で入っています。
MeCabTagger tagger = MeCabTagger.Create(param); string text = "吾輩は猫である。名前はまだない。"; MeCabNode node = tagger.ParseToNode(text); while (node != null) { // 文頭と文末にIDのない空ノードがあるためそれ以外を対象とする if (node.PosId != 0) { Debug.Log(node.Surface + "," + node.Feature); } node = node.Next; }
以下が全ソースコードです。これを空のGameObjectにアタッチしてUnityを実行してみましょう。
using UnityEngine; using NMeCab; public class Sample : MonoBehaviour { void Start() { MeCabParam param = new MeCabParam(); // 導入で置いたdic以下の辞書ディレクトリを指定 param.DicDir = @"Assets/NMeCab/dic/ipadic"; MeCabTagger tagger = MeCabTagger.Create(param); string text = "吾輩は猫である。名前はまだない。"; MeCabNode node = tagger.ParseToNode(text); while (node != null) { // 文頭と文末にIDのない空ノードがあるためそれ以外を対象とする if (node.PosId != 0) { Debug.Log(node.Surface + "," + node.Feature); } node = node.Next; } } }
4 自動改行させよう
それでは実際にTextコンポーネントを継承して、新しいコンポーネントを作っていこうと思います。
4.1 サンプルコード
Textが継承しているGraphicクラスに ExecuteInEditMode 属性がついているため、Updateメソッドに改行処理を書けばEditorで動的に改行されるのが確認できます。
以下がソースコードです。Textコンポーネントの代わりにこのコンポーネントをアタッチして動かしてみてください。
using System.Text; using UnityEngine; using UnityEngine.UI; using UnityEditor; using NMeCab; using NMeCab.Extension.IpaDic; public class WordWrapText : Text { private MeCabTagger _tagger; private MeCabNode _node; private Rect _rect; /// <summary> /// Textの横幅 /// </summary> private float _rectWidth { get { return this.rectTransform.sizeDelta.x; } } /// <summary> /// テキストを更新したときに形態素解析する /// </summary> public override string text { set { base.text = value; _node = Parse(value); } } /// <summary> /// Inspectorからテキストを変更した際に形態素解析する /// </summary> private void OnValidate() { _node = Parse(this.text); } private void Awake() { _node = Parse(this.text); } private void SetTagger() { MeCabParam param = new MeCabParam(); param.DicDir = @"Assets/NMecab/dic/ipadic"; _tagger = MeCabTagger.Create(param); } private MeCabNode Parse(string text) { if (_tagger == null) { SetTagger(); } return _tagger.ParseToNode(text.Replace("\n", "")); } /// <summary> /// preferredWidthを計算する /// [参考URL] https://bitbucket.org/Unity-Technologies/ui/src/9f418c4767c47d0c71f1727eb42a9a9024e9ecc0/UnityEngine.UI/UI/Core/Text.cs?fileviewer=file-view-default#Text.cs-502:509 /// </summary> private float CalcPreferredWidth(string text) { var settings = GetGenerationSettings(Vector2.zero); return cachedTextGeneratorForLayout.GetPreferredWidth(text, settings) / pixelsPerUnit; } private void Update() { if (_node == null) { return; } // rectに変更がなければ更新を行わない if (rectTransform.rect.Equals(_rect)) { return; } _rect = rectTransform.rect; // 改行文字を入れて、形態素の文字列を連結していく MeCabNode node = _node; StringBuilder builder = new StringBuilder(); string accumulator = string.Empty; while (node != null) { if (node.PosId != 0) { // 改行する長さになるまでaccumulatorに蓄積していく string newText = accumulator + node.Surface; if (CalcPreferredWidth(newText) < _rectWidth) { accumulator = newText; } else { builder.AppendLine(accumulator); accumulator = node.Surface; } } node = node.Next; } builder.Append(accumulator); this.text = builder.ToString(); } }
比較として、以下が通常のTextコンポーネントです。
4.2 解説
横幅に文字列が収まるかどうかは
preferredWidth
とRectTransformの横幅を比較しています。
TextのpreferredWidthは自身のtextのサイズを計算するため、実際にテキストを更新するまで取得できません。
そこで、任意の文字列に対してpreferredWidthを計算するためのCalcPreferredWidth
を定義しました。
node.Surface
を追加していき、まだ収まるなら蓄積し、収まらなくなったら改行文字を入れています。
// 改行する長さになるまでaccumulatorに蓄積していく string newText = accumulator + node.Surface; if (CalcPreferredWidth(newText) < _rectWidth) { accumulator = newText; } else { builder.AppendLine(accumulator); accumulator = node.Surface; }
4.3 発展
4 自動改行させようのコンポーネントでは句読点も改行の区切り対象になっています。 そのため、「。」だけであっても改行されてしまっています。 日本語の文書では、このように行頭に句読点が位置するのは避けるべきとされています。 そのために字詰などをすることを 禁則処理 と言います。
形態素解析により文章を最小の単位に分解し、それらの品詞を取得することで禁則処理やより柔軟な改行を行うことができます。 今回紹介したコンポーネントを改良して禁則処理を追加してみても面白いかもしれません! 興味のある方はぜひやってみてください!
(MeCabでは「。」や「、」は品詞が記号
となっています。
Tipsでは、形態素の品詞の取得の仕方について簡単に補足しています。)
Tips
◆NMeCabの品詞の取得の仕方
形態素の品詞はnode.Feature
にCSV形式で入っています。
それを","
で分割して品詞の入っているカラムを取り出してもいいですが、
NMeCabに拡張が入っているのでそれを使ってみましょう。
拡張のソースコードは3.1 導入でダウンロードした
NMeCab0.07/src/LibNMeCab/Extension
に入っているので、UnityのAssets/NMeCab
に入れます。
拡張のソースコードは#if EXT ~ #endif
で囲われているので、#defineディレクティブにEXT
を追加しましょう。
ここ
を参考にPlayerSettingsのOtherSettingsパネルから追加してください。
NMeCabNodeにGetPartsOfSpeech
という拡張メソッドが追加され、簡単に品詞を取得することができるようになりました。
using NMeCab.Extension.IpaDic; string sentence = "今日はいい天気です。"; var node = _tagger.ParseToNode(sentence); while (node != null) { if (node.PosId != 0) { var surface = node.Surface; var partsOfSpeech = node.GetPartsOfSpeech(); Debug.Log(surface + ":" + partsOfSpeech); } node = node.Next; }
おわりに
Unityで日本語の自動改行をMeCabを使ってやってみました。 今回はHowToということで確認用にUpdateで書いていたり、モバイルでの動作は考慮していないので実用化するにはもう一手間かかると思いますが、興味を持っていただければ幸いです。
明日は藤井さんの「簡単に水に近い表現を実現したい」です!