いつの間にか夕暮れも早まり、ようやく夏の勢いも少し弱まってきたように感じていますが、ここしばらく夏バテか何かわからない不調を引きずりつつ過ごしています。
この企画も残すところ2本ということで、実装を意識した構成にしてみようと思います。
プロダクションごと、プロジェクトチームごとに文化やカスタム環境があると思いますが、あえて標準的なエディタ環境でできる範囲での個人的なおすすめということでご理解いただけると助かります。
シェーダーの理解と学習のために、意図的にシンプルなデザインで進めてきましたが、実際にリリースされているゲームはユニークなデザインばかりです。今回はそういったレベルに近づけるよう実践的な制作手法を、デザインからテクスチャアトラスを作ってUMGで構成するところまで紹介します。
仕様をデザインに起こして、テクスチャアトラスを作るためにPhotoshopを使用、大量のデザインパーツを実装するための汎用的なシェーダーを作るところもあるので結構なボリュームになってます。
最後のほうに、サイズの違うゲージを同期させる方法があります。
仕様
スキルゲージを作ります。
- 時間でチャージ
- 満タンになるとスキルを発動できることを教えたい
- 発動するとゲージはゼロに戻る
- 技名(未定)を一緒に配置したい
デザインする
まずは最低限必要な要素で構成しつつ画面のレイアウトとデザインを決めます。
レイアウトはゲームのジャンルによってだいたいスタンダードなパターンがあったりしますが、そこは縛られないよう疑問をぶつけながら挑みます。
この場所に置く理由は? キャラの向きやカメラの視線との相性は? 操作への集中度は? などなど。
デザインは、ぶっちゃけ世界観に合ってりゃいいのでは、と考えていますが、新規性やユニークさ、キャッチーさ、分かりやすさ、などなど 考えながらラフでデザインを作っていきます。
アイデアがひらめいたらこの時に取り込みます。
まずは カタチ と 色 を慎重に選ぶフェイズ。

想定される状況の表現を詰めます。

どうやら仕様を満たせそうです。
今回の仕様は『満タン』か『満タンじゃないか』の2つの状態が表現できれば条件は満たせるので、イベントとしては、満タンの時にトリガー(発火)されて、発動するまではループ。目立たなければこのループ中に演出を盛ればいい、という風に考えます。
デザインはこれで行くぜ!となったら、
次にゲームの仕様について精度を上げたい。
デザインではなく実装方法に影響するので関係者と握ります。
- 技名は全体でいくつになるのか
- ゲージの長さは固定?
- 発動禁止やチャージ不能または停止のような状況はあるのか
3つ目は見た目の話ですが、状況的に頻度が低くデザインを考え直すほどの要素ではないので、このタイミングの確認で大丈夫。
大事なのは大まかなデザインで雰囲気がつかめるものであれば、できるだけ速やかに実装するのが理想的です。
決まってなくても焦ってはダメです。急いで決めてもいいことはないです。
少し多めの余裕を意識して作業を進めておくと、急に決まっても慌てずに済みます。
むしろ、このゲームだったらこうなるよね、というの想定して先手を打つことができるようになりたいものです。
テクスチャアトラスを作る
デザインをパーツに分解します。
表示方法の違いによって分けます。
まずは単純に 動くか動かないか(動かないけど表示タイミングの違うものも含む)、次に描画のしくみが違うもの(テクスチャの種類やシェーダーの着色方法など)
今回のデザインはこのようになりそうです。

パーツの分解方針が決まれば、次はパーツのサイズを確定していきます。
デザインの上から半透明のレイヤーを重ねます。
これは UVの範囲をアタリとして視覚化する作業で、選択範囲で囲って分かりやすいカラーで塗りつぶします。
このとき、UVで切り取る範囲を想定して 4の倍数で範囲を決めます。
中のピクセルの周囲に1px以上の余白(アルファの透明部分)をあけます。

1px以上の余白を含めて4の倍数サイズということです。
この作業の目的は次の2つ
- 4の倍数はテクスチャ圧縮による劣化対策
- 1px以上の空きはバイリニアフィルタによる にじみピクセルの侵入を回避
この数値はあくまでもデザインの話ではなく、テクスチャをキレイに画面に描画するためのテクニックです。
将来的にグラフィックチップの性能や仕組みが変われば、変化していくと思います。
近年のコンソールやPCは BC7 とかの BCxシリーズ がほとんどで 4x4 のブロックサイズが多いと思います。
ちなみにモバイル系プラットフォームで ASTC圧縮を使っていると、ブロックサイズが 5x5 とか 6x6、10x10 などいろんなサイズが用意されているようです。状況によっては 5の倍数を検討することもあるかもしれません。劣化度合を考えると、UI表示で 大きなブロックサイズを選択することはないと思いますが・・・
ちょっと話が逸れましたが、だいたい下のようになりました。

また、可変するかしないかで、余白の大きさも検討します。
シェイプを使うと小数になりやすく誤差やピクセルのブレが出やすいので、できるだけ、 選択範囲→塗りつぶし を強くオススメします。
作業の能率化のためにレイヤー名をつけるのをおすすめします。

UVのアタリが確定したところで、アトラス用のテクスチャを用意していきます。
Ctrl キーを押しながら レイヤーパネルのサムネをクリックすると、レイヤーのピクセルのある所を選択範囲として検出してくれます。

情報パネルを確認すると、サイズがわかります。

ゲージの枠を含めた幅が 384px だったので、直近のべき乗サイズは 512px。
高さが 52px で 2本で 104px、直近のべき乗サイズは 128px。
ということで まずは 512 x 128 の長方形で作り始めます。
UIで扱うテクスチャは基本的に Mipmap を使わないので、べき乗じゃなくても大丈夫です。ただ中途半端なサイズのテクスチャは、勝手にピクセルを削られたり補充されたり、UVの計算で割り切れなくて誤差の原因になったり、と注意が必要です。べき乗と4の倍数は結構相性がいいのでリサイズの時でも安心感があります。
ゲームのUIは、メモリの割り当てが少ないことがほとんどなので、同時にメモリに常駐する者同士でアトラスを組むとキャッシュに無駄が出にくくなります。
場合によってもっと大きなテクスチャアトラスの一部に組み込むこともあります。
今回はスキルゲージだけの構成で進めようと思います。
技名の表示については、ゲージではなくゲームシステムに依存する存在で、コストや期間でキャラ数が減ったり、トレンドやローカライズで文字量が変化したりと内容について不確定感が強いので、別管理で調整できるよう別のテクスチャアトラスにします。
これもデザインをみて、一つにつき 256 x 48 の右詰めでよさそう。ということにします。
シェーダーで UVを切り出すときに、テクスチャサイズ(解像度)に基づいて計算します。なので、テクスチャサイズを気安く変更しづらいというというのが、テクスチャアトラスを使う上でので一番のリスクだと言っていいと思います。
作るべきサイズが決まれば、さっそく新規のドキュメントを作成。

ここに、デザインパーツをドラッグ&ドロップするのですが、UV領域を決めるときの半透明のレイヤーと一緒に移動させます。

ゲージの枠とゲージ本体をドロップしたら、

スキマを開けずにぴったりくっつけます。
近くまでマウスで動かして、残りはキーボードの矢印キーで ちょいちょいと動かすとストレスが少ないと思います。
残りのパーツは、ちょっとしたコツが必要です。
ぼやけていたり、細く尖っていたりするデザインは、ほとんど透明で視認しづらいピクセルが存在しています。UVのアタリをとるときに、うっかり削ってしまうことが多々あります。

不透明度が 50%以下のピクセルには 選択範囲の表示も省略されます。(Photoshopの仕様です。実際にはきちんと選択範囲に含まれているのでご安心を)
こういうときは レイヤー効果の『境界線』を利用します。外側に2ピクセルほど設定するとUVのアタリを確実に取れるようになります。

これもテクスチャアトラスのほうに持っていきます。

テクスチャアトラスは レイアウトはこんな感じでOK。
DirectX系のグラフィックでは左上が原点(0, 0)なので、左上準拠で詰めますが、ゲージのようなデザインは若干長さや形状にゆとりを持たせておいたほうが将来的に保険となってくれます。デザインが完全に確定するまでは、種類の違うパーツはぴっちぴちに詰めないのがコツです。
次にアルファチャンネルを作ります。
その前にレイヤー整理しておきます。
ドラッグ&ドロップした UVのアタリが散らばってしまっている状態。
UV範囲のレイヤーのみを Ctrl キーを押しながらクリックして複数選択しておいて、

Ctrl + G でグループ化します。
ついでにレイヤーのカラーとUVのアタリで塗りつぶしたカラーをなんとなく合わせておきました。
この小さな手間は、そのうち効いてくれる日がやってきます。たぶん。

グループ化しておくと、テクスチャファイルとして書き出すときに、ワンクリックで非表示にできるからです。
ということで、UVを非表示にして、新規レイヤーを一つ追加します。

このレイヤーを選択した状態で、Ctrl + Shift + Alt + E キーを押します。
表示レイヤーの状態をラスタライズできるショートカットで、他のレイヤーには影響がないのがウリ。

新規レイヤーを作らなくても自動で作ってくれるのですが、フォーカスしているものやレイヤーの種類によって作ってくれない時があるので、確実な方法を紹介しました。
このレイヤーのサムネをクリックして選択範囲を読み込んで、

これをアルファチャンネルとして「保存」します。
コツは、最初だけ Ctrl 押しながらクリック。2つ目以降は Ctrl + Shift でクリックです。
下は チャンネルのパネルを使った「保存」。

メニューからは 選択範囲 > 選択範囲を保存(V)
ダイアログが出るので、OKすると作成されます。
アルファチャンネルを作るために一時的に作ったレイヤーは捨てます。
残しておくと、半透明の部分が濃く見えるようになるのと、調整作業に支障がでるからです。
あとはアルファ抜きをキレイにするために、背景色 をパーツの下にUVの範囲で塗ります。パーツで使用しているピクセルのカラーを範囲いっぱいまで拡げます。

アルファチャンネルはこうなってます。

塗りつぶす理由は図で説明したほうがわかりやすいと思います。


これがアルファブレンドというやつです。
RGBのカラー情報とアルファ情報が別々に切り離されて処理されるので、対策が必要なのです。
RGBとアルファを正しい知識できちんと整えてやるのは UIを開発するうえで大変重要です。
ですがPNGを使わない想定で説明しています。
ようやくゲージのテクチャアトラスは完成です。
続けて
技名も作っておきます。
技名は 2種類で足りるという想定でいくことにして、フチドリとカラー(ゲージ)を交互に並べて完成にします。詰めてみて微妙に入らなかったりすると、最適な解像度と分割数を再調整します。余白は有るほうがあとから融通が利いて安心です。
最初からタイトに詰めすぎないのがポイント。


同じサイズで統一すると、マテリアルのパラメータで切り替えるのが簡単になります。
同じUVサイズが並ぶときは、UVのアタリを1枚のレイヤーにまとめたりもします。

これで材料は準備できました。
汎用シェーダーを作る
エンジンにインポートしたテクスチャアトラスは、箱から出したばかりのプラモデルのような状態。必要なパーツだけを切り出すシェーダーを用意します。
これを汎用として、一つ作っておけば、あとはマテリアルインスタンスで対応できるようになるので、メモリの節約にもなります。
まずはパラメータ満載で作ります。

ScalarParameterノードの ScaleU と ScaleV は切り出したいサイズを指定するパラメータで、本当は繰り返す回数を意味する ”Tiling” というワードのほうが正しいと思いますが、ノードネットワーク的に掛け算するので、"Scale" にしました。

初期値は 1.0 にしておくことをお勧めします。

ゼロだとUV空間が消滅した状態です。マテリアルインスタンスで値を入れるので、とくに不都合はないのですが、そもそも 0 にするメリットは何もないです。
一方、Offset のほうは ゼロのままででOK。

テクスチャもパラメータ化できます。
TextureSampleParameter2D ノードです。


ヘッダに Param2D の文字があります。
マテリアルインスタンスから任意のテクスチャをセットすることができるようになります。
あとは保険で、アルファチャンネルに ScalarParameter ノードの Opacity を掛けています。

UMG にプロパティ Render Opacity があるので、なくても構わないのですが、マテリアルインスタンスを活かして、バリエーション違いを作ることがやりやすくなります。
カラーについても掛け算しておくのも有効です。

これでマテリアル ”汎用テクスチャ切り出しシェーダー” は完成です。
プロジェクトにこれがひとつあれば、テクスチャアトラスのほしいパーツをいくらでも切り出せます。
さっそくマテリアルインスタンスを作っていきます。

サイズ はレイヤーのサムネをクリックすると情報パネルに表示されるので、その W と H をテクスチャ解像度で割ると、それぞれ ScaleU と ScaleV になります。
切り出す位置は、テクスチャの左上からの距離を調べます。選択範囲をとると W と H が判るので、それをテクスチャ解像度で割ると、それぞれ OffsetU と OffsetV になります。

割り算するのは、エンジンの入力フォーム内でやるとラクです。
U は テクスチャ解像度の W と、 V は H とそれぞれ計算します。

この計算方法でパーツごとにパラメータをセットしたマテリアルインスタンスを用意していきます。
ゲージの枠はこんな感じです。

パーツのサイズが 384 x 56
テクスチャアトラスの解像度が 512 x 128
場所は左上なので、Offsetはどちらも 0 で初期値から変更なし
マテリアルインスタンスができたら、UMG のキャンバスに配置していきます。

Brush のプロパティ Image Size に表示したい大きさを入力します。
配置する親のパネルが Canvasの場合は、 Size to Content を有効にすると、Image Size を適応してくれます。
![]()
有効にすると Slot(Canvas Panel Slot) にあるプロパティ Size X と Size Y は無視されます。
Ovarlay パネルの場合は Size to Content が無いので、Brush の Image Size がそのまま利用されます。
デザイン通りに組みます。

次にゲージのシェーダーを用意します。

テクスチャでゲージを作る場合、先にも書きましたが、バイリニアフィルタ対策で、1px以上の空きをつくることが基本です。ゲージの増減は 0~1 なので、空きの分の誤差を吸収するのが Lerpノードとそのパラメータです。

これも初期値をきちんとセットしておけば、事故は防げます。
UVの計算をするパラメータを Scale と Offset をひとまとめにしました。
ScalarParameterノード ではなく VectorParameterノードを使っています。

(R, G, B, A) の 4つの Float を ( ScaleU, ScaleV, OffsetU, OffsetU ) の順でつないでいますが、順番を覚えていないと大変です。なので、チームやプロジェクトでルールを作って決めておくのをオススメします。
マテリアルインスタンスで値を入力するのですがラベルがRGBAなのです。

OffsetV だけとか パラメータを個別に扱うことが多そうなら、ScalarParameterノードで組みます。タイムラインやブループリントから扱うときにシンプルに操作できます。
一度パラメータをセットしたら動かさない場合や、同時に複数のパラメータを操作するときなどは、VectorParameterノードで組みます。
ゲージとしてのシェーダーは 21. とほぼ同じです。
ゲージ用の汎用マテリアルは完成です。
マテリアルインスタンスは、後から親マテリアルを変更することができます。

Parent のところを差し替えます。
パラメータの種類と名前が食い違う場合、残念ながら入力していた値は引き継がれず消えてしまいます。

親マテリアルを差し替える機会はそれほど多くはないと思いますが、パラメータの命名にルールを作っておくと少しは手数が減るかもしれません。
特に 透明度 は Opacity か Alpha かでブレやすいです。
差し替えるときのプチテクニックがあります。
変更後(先)のマテリアル側にいったんダミーで同じ名前のパラメータをつないでおきます。

この状態で差し替えると、過去に入力してあったパラメータが維持されるので、コピペできます。

パラメータの移行が終わったら、追加したノードは不要なので消します。また同じような移植の機会があるのであれば、いったん接続を切っておくといいです。

マテリアルインスタンスからはパラメータは消えます。
技名のゲージも同じように親マテリアルを切り替えます。
準備はできました。
動かしてみましょう。

ズレてますね。
シンクロさせる
このズレを マテリアルインスタンスのパラメータで補正します。
パーツの位置関係を調べるとこうなってました。

技名のゲージは短いので、ゲージの増減を合わせるには、差分を計算します。
表示サイズは256なので、
左側は 0 よりも左、 マイナスになります。 112 / 256 で 0.4375 RangeMin は -0.4375
右側は 1.0 以上になります。 16 / 256 で 0.0625 RangeMax は 1.0625


ここはテクスチャの解像度は関係なく、表示サイズと表示位置の関係で決まります。
ちょっとややこしいですね。

シンクロできました。
RangeMin、RangeMax はテクスチャ使用時のアルファに余白のあるゲージタイプのための調整用パラメータですが、こういった補正にも利用できます。
ゲージの増減はこれで完成です。
あとは、満タン時の演出を追加するだけです。
満タン時の演出をつくる
UMGのタイムラインでループアニメーションを作って、満タンの時に表示開始。

スキルを発動したら非表示にします。

RenderTransform のScale を変更するだけの単純な拡縮アニメーションですが、UVの範囲に収めた絵が、うまく中心に来てくれるとは限らないので、微妙にずれることはよくあります。
動かしながら、RenderTransform の Pivotを微調整します。

Pivot の数値は UVと同じように 表示パーツの左上が (0, 0) です。

今回は思ってた以上にボリュームが大きくなってしまいました。
ある程度実装経験のある方には、テクスチャ作成のあたりは退屈な内容になってしまったかもしれません。
シェーダーを扱うということは、効率よくマテリアルのアセットを管理できてこそなので、親マテリアル と マテリアルインスタンスの関係性について、より実用的な内容を目指して書いてみました。あとはテクスチャアトラスの重要性について書きたかったのもあってPhotoshopのオペレーションも含めて書きました。
これを機に実装できるマンが増えたら嬉しい。
実際にUIを作るのは、デザインするのとは別のフィードバックも得られます。
絵を作ってあとヨロ、はもったいないと思う。
長くなったので、ちょっと内容がおかしくなってる箇所があるかもしれません。
解りにくいとこや意味が不明なところなどあればご指摘いただければありがたいです。
いよいよ次がラストです。
ではでは
素敵なゲージライフを!
*1:
PNGだったら、背景色を置かなくてもいいのでは?という疑問が浮かぶと思います。
エンジンにインポートされたPNG画像はそのままのクオリティで画面に出ることはありません。
PNGは RGB だけを抽出すると半透明の部分にもカラーがしっかり残っているのがわかります。ところが 完全に透明の部分はカラー情報に意味がないため、(255, 255, 255)の白か、(0, 0, 0)の黒が入れられることがあります。
さらに、シェーダーを活用するようになるとRGBA 4つのチャンネルをフル活用したくなるのですが、その場合、PNGでは必ずアルファが 0 以上じゃないとカラー情報が維持できません。
また、BCx系に圧縮されるコンソール系やPCゲームでは、PNGを使うと予期しない場所に勝手に入れられた白いゴミドットがうっすら画面に現れることがあるので注意が必要になります。
PNGは便利ですが、時として描画の不具合の元になるので、全ての場面で安心して使えない。というのが僕の見解です。使っちゃダメという話ではなく、リスクを理解したうえで使用しましょうという話です。
ゲーム開発向けというと DDS が比較的万能感があるんですが、書き出しやプレビューの環境が簡単には揃わないのが残念です。