今回のリポジトリ
https://github.com/kamahir0/TextMask
序論
皆さんペルソナ5、ご存じですか?
おそらく ご存じかと思います。
そう、ご存じなんです。
特にあのオシャレなUIは有名ですね。
今回はそんなペルソナ5のUI表現の中でも、個人的に印象に残っている項目選択のUI表現をUnityで再現してみた、という内容になっております。
今回再現するもの
ペルソナ5に限りませんが...
ゲームで「何かを選ぶ」というシーンにおいて、フォーカスしている項目だけ文字色を変えるという表現はよく見られます。
Unityで実装するのであれば....(仮にカーソルの色を黒として)
並べて表示するTextMeshPro全ての参照を持っておいて、現在フォーカスしているインデックス番目のものだけ文字色を白色に設定する(他は黒色に設定する)みたいな感じでもいけそうです。
しかし、ペルソナ5をやっていて気付いたことがあります。
例によってこちらもフォーカスしている項目(「行こう」)の文字色が反転しているのですが、よく見るとその1個下の文字も部分的に反転していることが分かります。(「仕方ない」の上端部)
これは先ほど述べたような、フォーカスしている項目だけ異なる文字色に設定するアプローチでは不可能です。
今回はシェーダーのステンシル機能を用いてこの「テキストによるマスク」を再現してみたいと思います。
作戦
という訳でここからはシェーダーを書いていきます!!!
...と思いきや、Unityに標準で搭載されているUI-Defaultシェーダーで十分行けそうなので普通にソレを使います。
TextMeshPro用のシェーダーは既存のものを改造します(たったの2行ですが...)
なお、今回はフォーカスしていない項目の文字色を白色、している項目の文字色を黒色、カーソルを白色としています。
カーソル側
Canvas下に2つのImageオブジェクトA, Bを配置します。
Aは普通にカーソルらしく働いてもらう方で、色は白にします。
もう一方のBはフォント形状にマスクされる方で、色は黒にします。
B用にマテリアルを新規作成します。
シェーダーはUI-Default
。作成したらプロパティをいじります。
今回は以下のようにしました。
プロパティ名 | 値 |
---|---|
Stencil Comparison | 3 |
Stencil ID | 1 |
Stencil Operation | 0 |
Stencil Comparison
はステンシルテストで適用する比較関数に対応している数値で、3
はEqual
です。
Stencil Operation
はステンシルテスト通過時の処理に対応している数値で、0
はKeep
です。
Stencil ID
は適当です。大事なのはテキスト側と値を合わせておくこと(比較関数Greater
Less
等が絡むなら値は考える必要アリ)。
テキスト側
Canvas下にTextMeshProオブジェクトを配置します。こいつがマスクする側となるワケです。
ここでHierarchyにおいての配置順は、
上から
↓ Image A
↓ 任意の数のTextMeshPro
↓ ImageB
...となるようにします。
ここでシェーダーを新規作成します。
適当なフォルダに...と言いたいところですが、どうやらTextMeshPro用のシェーダーはAssets > Text Mesh Pro > Shaders
フォルダにないと使えないっぽいので、そこに。
また今回は上述した通り、既存のものをたった2行程度改造するだけです。なんなら同フォルダにあるTMP_SDF-Mobile.shader
あたりをコピペしちゃいましょう。
で、具体的にはどう改造するのかというと...
// PIXEL SHADER
fixed4 PixShader(pixel_t input) : SV_Target
{
~~~~~~~~~~~~~~~~
//#if UNITY_UI_ALPHACLIP
clip(c.a - 0.001);
//#endif
return c;
}
まさかのコメントアウト
本当にこれだけ。(理由は後述)
で、こちらもマテリアルを新たに用意します。
TextMeshProのマテリアル追加はややややこしいのですが(や4連)、
簡単に手順を述べると
- TextMeshProコンポーネントの
FontAsset
をクリック - ProjectウィンドウでFontAssetがピクピクするので、ソレの「▷」マークをクリック
- 開いた中にマテリアルがあるのでクリック
- Inspectorの左上のアイコンから
右クリック>Create Material Asset
- マテリアルが作成されるのでシェーダーを先ほど作ったものに変更
※TextMeshPro用マテリアルのファイル名にはFontAssetの名前を含む必要があることに注意
で、さっきと同様にプロパティをいじる。ただしTextMeshProのマテリアルのプロパティはそのままじゃ一部表示されないので(なんで?)、Inspectorの右上のボタンからDebugモードにしてください。
今回は以下のようにしました。
プロパティ名 | 値 |
---|---|
_StencilComp | 8 |
_Stencil | 1 |
_StencilOp | 2 |
_StencilComp
はステンシルテストで適用する比較関数に対応している数値で、8
はAlways
です。
_StencilOp
はステンシルテスト通過時の処理に対応している数値で、2
はReplace
です。
_Stencil
は適当です。大事なのはカーソル側と値を合わせておくこと(比較関数Greater
Less
等が絡むなら値は考える必要アリ)。
で。
完成!
完成形
カーソルをC#スクリプトで適当に動かしてみました。
(サンプルプロジェクトではマウスホイールで選択できます)
ちなみにシェーダーコードの改変の必要性についてですが、早い話がこうなります。
あくまで自分の考察ですが、
これは文字の描画において透明になっている部分も実はピクセルシェーダーの処理が破棄されてなくて、ステンシル書き込みしちゃってる感じなのかなあ~と。
#if UNITY_UI_ALPHACLIP
というのがMaskコンポーネントを使うときなんかに有効になる?んですかね。
エディタでプロパティから変更できたりもしなさげだったので、今回はif部分を削除したという。
以上です!