親方!空から絵文字が!
2021/05/26 加筆修正
本稿は TextMeshPro 2.0系を前提とした記事です。TextMeshPro 2.1系は考慮していませんので、ご了承ください。
TextMeshPro 2.1系への更新に伴い、Full Emoji Support Apiが大幅に改修されました。 そのため、本稿で紹介するFull Emoji Support Apiの課題の解決方法は使えなくなってしまいました。 現在、Full Emoji Support ApiはUPMでプロジェクトにインポートできるようになったので、そちらを利用することをおすすめします。
はじめに
こんにちは。クライアント開発部のちょろめです。
近々アプデで絵文字が降ってくるようになります。お楽しみに。
普段SNSやチャットなどで何気なく使っている絵文字ですが、これをバーチャルキャストで表示しようとすると結構頑張らないといけませんでした。今回はバーチャルキャストで絵文字を降らせるまでに頑張ったことについての話です。
この記事は主にUnityを使っているエンジニア向けです。絵文字に焦点を当てた内容になるので、UnicodeにおけるEmojiの仕様についてある程度の事前知識が必要となります。 p>
Unity、C#における絵文字
まずどのような環境で絵文字を取り扱う必要があるのかについてです。バーチャルキャストは開発にUnityとC#を使用しているのですが、それぞれに絵文字を取り扱う上での辛さがあります。その辛さについて最初に紹介したいと思います。
Unityにおける絵文字
実はUnityには絵文字を表示する機能が標準で搭載されています。その機能はTextMeshPro(以降、TMP)。TMPは1.2.2からスプライト文字にUnicodeのコードポイントを割り当てることができるようになりました。サンプルが用意されているのでデフォルトの状態でも15個の絵文字(😀😁😂🤣😃😄😅😆😉😊😋😎😍😘☹)が表示できるようになっています。 Assets/TextMeshPro/Resources/Sprite Assets/EmojiOne を見ると、どのコードポイントにどのSpriteが割り当てられているかを確認することができます。
この SpriteAsset はタブの Window/TextMeshPro/Sprite Importer から作成することができます。しかし、 SpriteAsset を作成するには TexturePacker 形式の Json とテクスチャアトラスが必要になります。
このコードポイント割当機能によって TMP で絵文字を表示ができるのですが、この機能には限界があります。例えば「👩🏻🚀」。つまり合成絵文字です。
長くなるので詳細は省きますが「👩🏻🚀」はUnicode的には「👩🏻」+「🚀」のように表現されています(参考)。また、「👩🏻」も「👩」+「🏻」のように表現されています(参考)。これらのような合成絵文字はTMPのコードポイント割当機能では表示することが出来ません。
現行の仕様で「👩🏻🚀」や「👩🏻」を表示しようと思ったらスプライトタグを使用する必要がありあそうです。
C#における絵文字
C#において「😀」は1文字であっても文字列長は2になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System; namespace EmojiTest { class Program { static void Main(string[] args) { var emoji = "😀"; Console.WriteLine($"文字数:{emoji.Length}"); } } } // 出力結果 // 文字数:2 |
これは「😀」がサロゲートペアで表現されているためです。
C#には System.Globalization.StringInfo (以降、StringInfo)というクラスが用意されており、サロゲートペアのみで表現される絵文字であれば StringInfo で絵文字を1文字として処理することが可能です。しかし、 StringInfo は合成絵文字をサポートしていません。そのため「👩🏻🚀」を1文字として扱うことはできず、「👩, 🏻, ZWJ, 🚀」に分解されてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using System; using System.Globalization; namespace EmojiTest { class Program { static void Main(string[] args) { var faceEmoji = new StringInfo("😀"); Console.WriteLine($"faceEmoji:{faceEmoji.LengthInTextElements}"); var womanAstronaut = new StringInfo("👩🏻🚀"); Console.WriteLine($"womanAstronaut:{womanAstronaut.LengthInTextElements}"); var emojis = StringInfo.GetTextElementEnumerator("😀👩🏻🚀"); while(emojis.MoveNext()) { Console.WriteLine(ToUnicode(emojis.GetTextElement())); } } } } // 出力結果 // faceEmoji:1 // womanAstronaut:4 // 😀 // 👩 // 🏻 // // 🚀 |
バーチャルキャストにおける絵文字
さて、上述のような環境で絵文字をよしなにしていかないといけないわけですが、コメントカードやコメント文字で絵文字を使用するには主に以下のハードルをこえないといけません。
- TMPで合成絵文字も表示できるようにする
- 合成絵文字も考慮した文字列分割
こうして列挙してみると合成絵文字の曲者具合がよく分かりますね。次はこれらのハードルを頑張ってこえていく話です🤸♂️
絵文字辛い問題を解決してくれるライブラリたち
頑張った話と言いながらライブラリを利用することで前述の辛い部分を解決しました。しかし、ライブラリにも少し課題が残されており、修正した部分もあるのでその点についても紹介したいと思います。
Full Emoji Support Api
Full Emoji Support Api は Refael_CS さんが提供する Unity 絵文字サポートライブラリです。上述したUnityにおける絵文字の扱いづらさを解決してくれます。
機能
機能は主に2つあります。それぞれの概要と使い方について書きます。
機能1:emoji-data から TMP の Sprite Asset を生成するための TexturePacker 形式 Json の生成
概要
emoji-dataは絵文字のテクスチャアトラスとパース用のJsonを提供したプロジェクトです。emoji-data自体はweb向けに作成されたものですが、Full Emoji Support Api はこれをUnityで使用できるようにします。この機能を用いて生成されたJsonを使って SpriteAsset を作るとUnicodeとnameにコードポイントが設定されたものができます。
「😀」なら
「👩🏻🚀」なら
となります。
使い方
TexturePacker形式Jsonの生成だけでなくTMPのSpriteAsset生成・適用までの手順を書きます。また、今回導入する絵文字はgoogleの絵文字とします。twitterの絵文字を使いたいなどといった場合は sheet_google_64.png の部分を sheet_twitter_64.png に読み換えてください。
- 絵文字データの用意
-
- emoji-dataの最新版をダウンロード
- sheet_google_64.pngのサイズが4096x4096になるように画像の右側と下側に空白を作る(イラレとか使ってやってください)
- 例
-
- emoji.json と sheet_google_64.png をプロジェクト内にコピペ
- Unity上のインスペクタで sheet_google_64.png のパラメータを修正
- Texture Type: Default
- Alpha Is Transparency: True
- Max Size: 4096
- TMPのSpriteAsset生成用のJsonファイルの生成
- Window/TextMeshPro/Convert EmojiData TexturePacker JSON ウィンドウを開く
- EmojiData to Convert にemoji-dataからコピーしてきた emoji.json を置く。Grid SizeをX, Y共に64に変更する。
- Convert To TexturePacker JSONをクリック
- texturepacker_emoji.json が生成されたことを確認する
- SpriteAssetの生成
- Window/TextMeshPro/Sprite Importer ウィンドウを開く
- SpriteDataSource に先ほど生成した texturepacker_emoji.jsonを置く
- ImportFormat は TexturePacker にする
- SpriteTextureAtlas に emoji-data からコピーしてきた sheet_google_64.png を置く
- Save Sprite Asset をクリック
- SpriteAsset が生成されたのを確認する
- SpriteAssetの適用
- Resources/StyleSheets/TMP Setiings にて DefaultSpriteAsset に生成した SpriteAsset を置く
機能2:Unicode合成絵文字を検出してスプライトタグに置換
概要
標準のTMPが表示することのできない合成絵文字を表示可能にする機能です。合成絵文字のシーケンスを検出してスプライトタグに置き換えてくれます。
使い方
TMP_EmojiTextUGUI という TextMeshProUGUI を継承したコンポーネントが用意されているので TextMeshProUGUI の代わりに TMP_EmojiTextUGUI を使用するだけです。使い勝手は何も変わりません。違いは絵文字が表示されるか否かだけです。
課題
合成絵文字を表示可能にしてくれる素晴らしい Full Emoji Support Api ですが、少し課題も残されています。 https://getemoji.com からEmoji12.0までの全絵文字をコピペしたものをみてみましょう。
結構な数の絵文字が□(以降、豆腐)になっていることが分かります。実は同じ豆腐でも異なる原因で発生している豆腐があります。あと、やたら「©️」がたくさんありますね。
いくつかある問題をそれぞれ説明すると以下のようになります。
- サポートするEmojiのバージョンが低い
- サンプルの SpriteAsset を生成したとき以降に emoji-data のバージョンが更新されたのでしょう。ぱっと見た感じだと Emoji 5.0 までしかサポートされていません。最新の emoji-data は Emoji 11.0 までサポートしているので SpriteAsset を生成しなおす必要がありそうです。
- 入力文字の異体字セレクタが欠けると一部の絵文字が豆腐になる
- 例えば画像左上の方で🎓と👑の間にある豆腐ですが、これは入力された「⛑」には異体字セレクタがついてないため 豆腐になってしまっています。この現象はUnicodeの「その他の記号」に分類される絵文字で発生しがちです。
emoji-dataでは「⛑」は異体字セレクタありの状態で記述されており、異体字セレクタなしの「⛑」が入力されるとそれはTMPのUnicodeコードポイント検索に引っかかりません(「⛑」と「⛑️」が別物ってマジ??)
異体字セレクタなしで入力された場合でもフォントがサポートしている絵文字であれば白黒の絵文字が表示されますが、今回使用しているフォントは「⛑」をサポートしていないので豆腐が表示されています。
動作として正しいといえば正しいのですが、フォントがサポートしてないとはいえせっかくスプライトがあるのだから絵文字を表示してほしいところです。豆腐は避けたい。
- 例えば画像左上の方で🎓と👑の間にある豆腐ですが、これは入力された「⛑」には異体字セレクタがついてないため 豆腐になってしまっています。この現象はUnicodeの「その他の記号」に分類される絵文字で発生しがちです。
- 入力文字の異体字セレクタが余計についてると「©️」がついてくる
- これは Full Emoji Support Api のかかえる課題というよりは emoji-data が抱える課題のような気もするのですが、 emoji-data に同梱されている emoji.json において、「その他の記号」に分類される絵文字は異体字セレクタがついてたりついてなかったりします。
異体字セレクタなしで記述されている絵文字に異体字セレクタありの入力が来ると余分な異体字セレクタが表示されてしまいます。
現状の Full Emoji Support Api だと異体字セレクタのコードポイントに一番最初に割当られているのが「©️」だったというわけで、「©️」がやたらたくさん表示されているようです(コードポイントの割り当てが重複すると一番最初の絵文字が優先されるっぽい)。
そもそも異体字セレクタに文字を割り当てないで欲しい…。
- これは Full Emoji Support Api のかかえる課題というよりは emoji-data が抱える課題のような気もするのですが、 emoji-data に同梱されている emoji.json において、「その他の記号」に分類される絵文字は異体字セレクタがついてたりついてなかったりします。
対応
まず最初に今回やる対応をすべて適用した結果の画像が↓です。
一番下の方にまだ豆腐が残ってるけど許して…(Emoji 12.0の絵文字たち)。❓が一部並んでますがこれはemoji-dataがそうなってるので許して…。それらを除けば概ねよさそうに見えます。それでは何をしてこうなったのかについてです。
- 最新の emoji-data から SpriteAsset を生成する
- これによって Emoji 11.0 までの絵文字を表示できるようになりました。「使い方」節の手順通りにやりました。しかし、この後の対応によって SpriteAsset の生成はやり直す必要があるのでそっちをやってから生成することをおすすめします。
- SpriteAsset を生成するときに異体字セレクタをすべて削除する
- これによって入力された文字の異体字セレクタが欠けると一部の絵文字が豆腐になる問題を解決できました。
異体字セレクタがありの状態でスプライトが登録されているのに、異体字セレクタなしの絵文字が入力されると豆腐になるのであれば、いっそのことスプライトを登録するときに異体字セレクタをすべて削除しまえばいいのです🔪
ConvertEmojiDataToTexturePackerJsonEditor の101行目に以下の処理を追加すれば自動で異体字セレクタをすべて削除できます。 -
12p_json = p_json.Replace("-FE0F\"", "\"");p_json = p_json.Replace(@"-fe0f.png", ".png");
- 勘のいい人は気づいてしまったかもしれませんが、この対応によって異体字セレクタありの「その他の記号」に分類される絵文字が入力されたときに余分な「©️」が表示される確率が100%になってしまいました。
しかし、この問題は次の対応で解決されます。
- これによって入力された文字の異体字セレクタが欠けると一部の絵文字が豆腐になる問題を解決できました。
- 文字を表示するときに異体字セレクタをすべて削除する
- 異体字セレクタは文字の字体をより詳細に指定するためのセレクタなので文字ではありません。なので、表示するものは何もありません。であれば異体字セレクタを削除してしまっても問題ないでしょう(たぶん)
本当はよくないのですが、今回は異体字セレクタを犠牲により多くの絵文字を表示することを優先しました。
具体的の修正点としては、 TMP_EmojiSearchEngine の75行目を以下のように変更しました。 -
1234567var c = p_text[i];if (c == '\ufe0f' || c == '\ufe0e'){v_changed = true;continue;}sb.Append(c);
- 異体字セレクタは文字の字体をより詳細に指定するためのセレクタなので文字ではありません。なので、表示するものは何もありません。であれば異体字セレクタを削除してしまっても問題ないでしょう(たぶん)
今回の対応の問題点
今回の対応の問題点、それはフォントがサポートしてる「その他の記号」に分類される絵文字がカラーにならないことです。異体字セレクタを処した弊害がさっそく出てますね😇
これはTMPのfallbackの検索順の問題で SpriteAsset が検索されるのは後半なので、その前の font 検索で引っかかると絵文字はカラーになりません。
異体字セレクタをすべて削除してしまったのが原因なので、逆に SpriteAsset を生成するときに異体字セレクタをすべてつければよいのではと思って試したのですが、異体字セレクタのない絵文字が入力されたときフォントがサポートしていない絵文字だと豆腐になってしまうのでやめました。
豆腐より白黒絵文字の方が目立たないし何が投下されたのかは分かるのでこっちの方がいいかなという判断です。
理想としては、
- 入力が異体字セレクタありならカラー絵文字を表示
- 入力が異体字セレクタなしでフォントがサポートしてるなら白黒文字を表示
- 入力が異体字セレクタなしでフォントがサポートしてない絵文字でもスプライトがサポートしてるならカラー絵文字を表示
なのですが、未踏の状態です。解決策 募集しております。
GraphemeSplitter
GraphemeSplitterは彼の有名なufcppさんが提供する書記素判定ライブラリです。上述したC#における絵文字の扱いづらさを解決してくれます。具体的には合成絵文字も考慮して文字列を分割することができます。
Readmeに書いてある以上のことはしなかったので使い方などは割愛します。
開発の背景などは「絵文字の連結と、書記素クラスター判定」をご覧ください。
まとめ
バーチャルキャストで絵文字を降らせるにあたっての困りごとと対応について紹介しました。また、今回 解説した改変 Full Emoji Support Api のソースコードはGitHubで公開しています。
Unityで絵文字を扱う方法のベストプラクティスが何なのかは正直まだよく分かりません。ですが、この記事が絵文字に苦しむUnityエンジニアの助けになればなと思います🙏
バーチャルキャストで絵文字が降ってくるようになったらコメントでどんどん投下してね🧝🤦🧝🙍🎅😥😯😥🤨🤨🤨💁😆😆😯😥😥😥😃😥😥😥😃😃😃😃😃🤨🤨🤨😁😎😊😎😉😍😊😍😂😎😊😎😂😎😂😍😊🤗😉😍😁😍😁😍😊🤗😊🤗😉😍
- Tag
- Unity