LoginSignup
43
37

Unityでペルソナ5風のテキストによるマスク

Posted at

今回のリポジトリ
https://github.com/kamahir0/TextMask

序論

皆さんペルソナ5、ご存じですか?

おそらく ご存じかと思います。

そう、ご存じなんです。


特にあのオシャレなUIは有名ですね。
今回はそんなペルソナ5のUI表現の中でも、個人的に印象に残っている項目選択のUI表現をUnityで再現してみた、という内容になっております。

今回再現するもの

ペルソナ5に限りませんが...
ゲームで「何かを選ぶ」というシーンにおいて、フォーカスしている項目だけ文字色を変えるという表現はよく見られます。

(例)ポケットモンスター ソード・シールド
SwordShield FocusUI.JPG

Unityで実装するのであれば....(仮にカーソルの色を黒として)
並べて表示するTextMeshPro全ての参照を持っておいて、現在フォーカスしているインデックス番目のものだけ文字色を白色に設定する(他は黒色に設定する)みたいな感じでもいけそうです。


しかし、ペルソナ5をやっていて気付いたことがあります。


Persona5MaskedTextSample.gif

例によってこちらもフォーカスしている項目(「行こう」)の文字色が反転しているのですが、よく見るとその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はステンシルテストで適用する比較関数に対応している数値で、3Equalです。
Stencil Operationはステンシルテスト通過時の処理に対応している数値で、0Keepです。
Stencil IDは適当です。大事なのはテキスト側と値を合わせておくこと(比較関数Greater Less等が絡むなら値は考える必要アリ)。

テキスト側

Canvas下にTextMeshProオブジェクトを配置します。こいつがマスクする側となるワケです。

ここでHierarchyにおいての配置順は、
上から
↓ Image A
↓ 任意の数のTextMeshPro
↓ ImageB
...となるようにします。

ここでシェーダーを新規作成します。

適当なフォルダに...と言いたいところですが、どうやらTextMeshPro用のシェーダーはAssets > Text Mesh Pro > Shadersフォルダにないと使えないっぽいので、そこに。

また今回は上述した通り、既存のものをたった2行程度改造するだけです。なんなら同フォルダにあるTMP_SDF-Mobile.shaderあたりをコピペしちゃいましょう。


で、具体的にはどう改造するのかというと...

TMP_SDF ImageMask.shader
// PIXEL SHADER
fixed4 PixShader(pixel_t input) : SV_Target
{
    ~~~~~~~~~~~~~~~~
    
    //#if UNITY_UI_ALPHACLIP
    clip(c.a - 0.001);
    //#endif
    
    return c;
}

まさかのコメントアウト

本当にこれだけ。(理由は後述)


で、こちらもマテリアルを新たに用意します。
TextMeshProのマテリアル追加はややややこしいのですが(や4連)、
簡単に手順を述べると

  1. TextMeshProコンポーネントのFontAssetをクリック
  2. ProjectウィンドウでFontAssetがピクピクするので、ソレの「▷」マークをクリック
  3. 開いた中にマテリアルがあるのでクリック
  4. Inspectorの左上のアイコンから右クリック>Create Material Asset
  5. マテリアルが作成されるのでシェーダーを先ほど作ったものに変更

※TextMeshPro用マテリアルのファイル名にはFontAssetの名前を含む必要があることに注意


で、さっきと同様にプロパティをいじる。ただしTextMeshProのマテリアルのプロパティはそのままじゃ一部表示されないので(なんで?)、Inspectorの右上のボタンからDebugモードにしてください。
今回は以下のようにしました。

プロパティ名
_StencilComp 8
_Stencil 1
_StencilOp 2

_StencilCompはステンシルテストで適用する比較関数に対応している数値で、8Alwaysです。
_StencilOpはステンシルテスト通過時の処理に対応している数値で、2Replaceです。
_Stencilは適当です。大事なのはカーソル側と値を合わせておくこと(比較関数Greater Less等が絡むなら値は考える必要アリ)。


で。


完成!

完成形

カーソルをC#スクリプトで適当に動かしてみました。
(サンプルプロジェクトではマウスホイールで選択できます)
TextMask_forQiita.gif

ちなみにシェーダーコードの改変の必要性についてですが、早い話がこうなります。

スクリーンショット 2024-04-29 213231.png

あくまで自分の考察ですが、
これは文字の描画において透明になっている部分も実はピクセルシェーダーの処理が破棄されてなくて、ステンシル書き込みしちゃってる感じなのかなあ~と。

#if UNITY_UI_ALPHACLIPというのがMaskコンポーネントを使うときなんかに有効になる?んですかね。
エディタでプロパティから変更できたりもしなさげだったので、今回はif部分を削除したという。

以上です!

43
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
37