夜帰宅中、歩いていると聞きなれない音に気付いた、壊れた電子ブザーみたいな音で、長く一定のトーンで聞こえてくる音。まだ蛙の声も聞こえる時期なので秋の虫も少ないせいか際立って聞こえてくる。そういえばどうぶつの森で聞いたことがあるような・・・調べてみたらオケラだった。結構長くこの道歩いてるけど気にしたことなかったな。確か土のある所に暮らしているはずだけど、田んぼ付近はともかく、マンションと路地との隙間から聞こえてきたりする。最初は耳鳴りを疑ってしまった。
さてさて
別にカードゲームを作りたかったわけではなかったのだけど、先日雀魂(じゃんたま)を遊んでいて、牌の上をマウスカーソルが乗るとピコっと牌が少しだけ飛び出るフォーカス処理、あれを作ってみたくなって、UMGで試してました。
ただ横に並べるだけだとつまらないので、カードゲームの手札みたいに扇っぽくできないかな、ということでカードになりました。
試してみたこと2つ
- HorizontalBoxで扇状に並べることは可能か?
- HorizontalBox内のアイテムは優先順位は変えられるか?
過去に何度か触って答えはわかっているものの、バージョンが変わればひょっとしたら・・・みたいな期待感は常にあるので思いたったらとりあえず試す流れで。
ここからの内容は、見出しを付けていますが、つながりは弱いので気なる見出しだけ拾い読みしてもいいようにしています。
- やってみた結果
- スポットライトみたいな表現
- マウスカーソルの位置を反映
- スポット表現の移動はマテリアルパラメーターコレクションを使う
- カードのテクスチャの節約を考える
- Overlayってやつは
- MPCを使うパーツが増えたということは
- リトルノアクリア
やってみた結果
まずは重ねて角度をつけてみる。
ここからいい感じになるように計算しつつ並べてみたのがこれ。
HorizontalBoxでカードを手札っぽく表示してみようと思って実験
— みつまめ杏仁 (@MMAn_nin) 2022年7月2日
マウスカーソルの場所にスポットライトが当たるようにして注目してる感じを演出?#UE5 #UE5Study #ue5UMG pic.twitter.com/7WGMTRF9Ry
並べるカードはただ画像を並べるのではなく、別のWidgetでマウスイベントを仕込んであるのを並べています。
マウスカーソルが入るとぴょこっと上に動いて、カーソルが離れると元の位置に戻るアニメーションを用意しているので、並べるだけで作ってみたかったフォーカスの動きが完成。
ここまで作って終わるのがさみしくなって思いついたのが、スポットライトみたいな表現。
スポットライトみたいな表現
最近の個人的な流行りが、描画する画面内の位置をUV座標として扱う表現。
UIは、表示するパーツを画面内に配置するとき、遷移演出やインタラクティブに変化するものとしないものがあるので、用途ごとにデザインを部品として切り出して配置します。それぞれの部品は独立したものとして扱うことになるのがフツーで、連動してるように見せるのはダイナミックな演出が期待できる分、仕込むのにそこそこ手間がかかるのです。そこで活躍するのが、ScreenPositionを使った表現。
アニメでキャラクターが一瞬チラっと見える小さなナニカに気づいて注目する演出とか一人称視点になってスローモーションや静止画になったりモノローグで思考が語られたりするあれ。
そういえば最近『ラブレター』というカードゲームで遊んでるんですが、面白いですね。ヨミアイが熱いゲームは好きです。
Size: 256x256 px
このテクスチャをマテリアル内でMultiply(乗算)しています。
乗算を選択したのは、カードのカラーに対して輝度を下げることになり絵柄の情報を維持できるからです。加算した方が光を当てた表現に近くなりますが、当たっている場所のカラーが明るくなってディティールが失われやすい。カードのそもそもの輝度を下げて暗くしておいてから加算することで対策できますが、周囲を落とす方が中心もしっかり見せられるし、調整も簡単なので 乗算 というわけです。
テククスチャが256x256という小さな正方形なので、サイズ補正用の値をScreenPositionに掛けています。このせいで後から苦労するはめになります。
タイリングはしない設定(テクスチャのインポート設定で変更可能)なので、テクスチャの外側が無限に続く見え方になります。
ScreenPositionに対して、オフセット位置を足せば見た目に移動させることができます。マウスカーソルがカードに乗っている間だけカーソル位置に合わせればできあがり。
先に書いたようにスポットライトにしたかったわけではなかったのですが、円の周辺に補色を入れてみたらいい感じに色味が増えて光の表現ぽくなったので、面白いから採用することにしました。
マウスカーソルの位置を反映
これがなかなか難物でした。
マウスカーソルの位置をとってくるのは、onMouseMove関数を用意(MyBlueprintタブのFUNCTIONSからOverrideして作成)して、その中で下のようにつないでます。
この関数は名前を見る限りはマウスの動きを検出して実行されると思うですが、マウスに触れていなくても常時実行され続けるので変数に保存した値と差分が出たら実行するように分岐を入れています。
エディタ実行で確認すると若干誤差が出ますが、Standaloneで実行すると誤差は出ないのでひとまずこれで進めました。(2K解像度で 0.012くらいの補正を入れると目視でいい感じになる)
取得したマウスカーソルの位置を解像度で割ると、0~1.0の値になるので、Lerpノードを使って補正値を算出。その値をマテリアルパラメータコレクションに書き込みます。
テクスチャにスケールをかけない場合(Tiling=1.0)はカーソル位置とテクスチャのオフセット位置の関係は以下のようになります。
ここにスケール 3.2 : 2.0 をかけた場合こういった値になる。
+0.5した場所から、最大位置まではスケールと同じ数ということになる。
つまるところ、テクスチャの真ん中を画面の左上に持っていく補正(+0.5)が必要なだけで、あとは単純にタイリングのスケール分でよかったという事実。
UV空間ってスケールがかかるとイメージがしにくい(個人的な感想)ので上の画像のように理解して腑に落ちるまで、擦った揉んだしてしまいました。
スポット表現の移動はマテリアルパラメーターコレクションを使う
カードは一枚ごとにマテリアルを使ってテクスチャを描いています。
マウスイベントを受け取ったら複数のカードに、スポット表現の移動を反映しないといけないのは効率が悪い。そこでマテリアルパラメータコレクション(以降MPC)アセットを使うことにしました。
ブループリントからこのMPCに値を書き込むことで、カード個々のマテリアルはMPCの値を参照するようにしておけば勝手にそれぞれが反映するかたちになります。
MPCの値を受け取ってるマテリアルはMPCの影響を常に受けるのです。
ブループリントからMPCへ値を書き込むのが Set Vector Parameter Value ノード。
Set Scalar Parameter Valueも用意されています。
ブループリントって、たいてい操作対象に対してSetやGetをするので、ついついこのノードを使うときも先に変数を作っていました。
するとパラメータネームの部分がプルダウンリストになっているのですが、選択できないのです。
Collectionピンに変数を挿すとコレクションに設定したパラメータが選択できなくなる仕様。Name型の変数にパラーメータ名を入れてつなぐと解決はしますが・・・
何もつながずに、コレクションをセットすると、パラメータネームをプルダウンで選択できるようになります。
変数が要らなくなりました。
Tonkotsuさん(@tonkotsu3656)に教えていただきました。ありがとうございます。
カードのテクスチャの節約を考える
カードの絵柄を考えたとき、重要な情報はカードの大きさよりいくらか内側に小さくて、余白があるのがほとんどだと思う。そこで、カード一枚分を単体のテクスチャにして、絵柄は別のテクスチャにして詰める。
カードのテクスチャサイズは画面での表示サイズを考えて 192x288px に決定。
絵柄を切り抜くためのマスクも一緒に作成。
トランプの場合ジョーカーを除く52枚が必要。
カードの大きさ 192x288 をそのままテクスチャにすると、べき乗に美しく収まらないうえに、2048x2048px のサイズが必要になる。
余白が結構出るので、ここに裏(数字の書いてない方)のデザインバリエーションを仕込んだりジョーカー置いたりできます。
うまく収めるために、カードのサイズを見直してもいいのですが、UVの切り出しや圧縮を考えると奇数は出したくないし、こんなテクスチャ都合で、画面のデザインを変更するのは避けたいものです。そこで絵柄をミニマムに切り詰めて並べるとこうなりました。
2048x1024px です。半分に抑えられました。カードのベースとマスクを入れても2048x2048よりは全然ましです。
普段UIを実装する際はいつもこういったテクスチャのサイズと格闘しています。いかに効率よく最大数を得られるか、を検証し交渉材料にします。
例えばプランナーから「80種類もあったら大丈夫ですよ。足ります」と言われたら、僕は96枚くらい入るよう頑張ります。多すぎはよくないですが、ギリギリもモチベが下がるので、上にあげた例のように想定の範囲+αが出る程度にバッファを意識するとあの名セリフを吐くことができます。「こんなこともあろうかと」そして「そのかわりもうこれが限界」を合わせて言うと職人的信用度もあがるし貸しも作れます。
絵柄は一つにつき 144x256px
実はさっき書いたことと早速矛盾しますが、うまく収まるサイズを計算して調整しました。
マテリアル内で絵柄とベースを合成するのはこのようになりました。
途中ふと、トランプには重ねて持った時でもマークと数字が見えるようにデザインされているのを思い出した。せっかく余白を開けて絵柄を効率よくテクスチャアトラスにできても、これではトランプゲームは作れない。
そこで絵柄をカードのマテリアルで合成するのではなく、別々のパーツにして自由に配置できるようにしたほうがよくないか?という考えに至る。
Overlayってやつは
UMGはレイアウトする際に、ポジションを管理する方法が大きく分けて2種類あります。基準点となるアンカーの存在の有無です。
またUMGの表示優先は、CanvasPanel内のものはZOrderで制御できますが、アンカーを使わないOverlay系のものは、ZOrderというプロパティが存在せずHierarchyのみで制御されます。
HorizontaBox は Overley系に属するため、中の子WidgetにZOrderを設定していても関係なく、Add Child した順に描画されるようです。
ひとまずカードのベースと絵柄を別々のパーツにしてみる。
すると・・・
これは UMG ではなく OMG!
デバッグツール Widget Reflector で見てもよくわからず。というか ZOrder 的なプロパティが無いことに気づく。
いろいろ調べてようやくたどり着いたのがこのブログ。
K.Y.さんありがとうございます。
エンジンのプロジェクト設定を変えることで対策できるようですが、結局手札としてのふるまいを考えると、HorozontalBoxだと表現しきれないと判断してCanvasPanelで実装することにしました。
それはそれで、並べるのに苦労しました。
MPCを使うパーツが増えたということは
スポット表現が必要なパーツが増えたことで、MPCを乗算するマテリアルが複数必要になる。そこでマテリアルファンクションを作ってラクしようと思いついた。
一応スケールを調整できるようにしてあるけど、初期値として3.2と2.0は入れておく。
これでパーツごとにこのマテリアルファンクションをノードとして取り出してつなぐだけで、スポット表現が手に入る。
パーツごとのマテリアルにノードを置いていくだけ。
こんな感じにできました。
詰めてみると
パーツをわけたので、左きき対応してみた。
リトルノアクリア
面白かった。昭和のアーケードゲーム程度のボリュームで個人的にはちょうどいい感じ。お値段もお買い得な感じ。吉田明彦さんの絵が好きなのもある。
作業にならないようにランダム要素をベースにしつつも運に振り切ってる感じはしないし、結構細かい調整を感じる。操作感も悪くない。敵を浮かせる技を持ったアストラルが多いのでエーテルスラストという空中ダッシュを混ぜた空中コンボが気持ちいい。その辺を意識した滞空時の追撃エフェクトを持ったアクセサリーがあったりと、なかなかの手厚いフォローのアクションが楽しめる。出会ったアストラルや拾ったアクセサリーによってコンボスタイルが変化するのもなかなか飽きさせない要素だと思う。開始時のスタメンは毎回固定なのも、ゲームシステムに慣れるまでは心強い。途中でどんどん強いアストラルに出会うので入れ替えてみたくなります。
『ノーダメージで』『制限時間で』『既定のダメージ量で』『既定のHit数以上で』など部屋にミッションが設定されることがあって、達成時の追加報酬があったり、強敵の発生する部屋があるのだけれど、装置を起動しないとスポーンしないので、自信がないときはスルーできる。もちろんクリアすると大きな報酬が手に入るし、バーストゲージも溜まる。慣れてきたプレイヤー向けに難易度の幅が提供できているのは素晴らしいですね。
ステージ攻略中は遊びごたえが十分あるけど、拠点でできることは結構シンプルで、キャラ周りの世界観に浸るには物足りない印象はある。でもこれくらいが個人的にちょうどいい。最近のスマホゲームで拠点というかホーム画面で情報過多なゲームが多すぎる気がする。各種ボーナスを受け取るのはモチベにはつながっているとは思うけど、ホーム画面でいろいろあちこちタップしても、ゲーム体験としては「遊んでない」のと同じじゃないかと思う。時間を消費するなら少しでも楽しいゲームプレイ体験が残るようにしたいと考えているので リトルノアのゲームデザインは個人的に気に入ってます。
ではでは
今回はこの辺で
検証内容的にあまり参考にならない気もしますが
ステキなカードゲーム開発ライフを!