みつまめ杏仁

アンリアルエンジン(UE4)でGUIを作るためにゴニョゴニョしてます。UIデザイナーの皆様の助けになれば幸いです。

ボタン長押しゲージを作ってみた《ボタン編》

前回マテリアルを使ったゲージができたので、ボタンの表示とUI用のロジックを作っていきます。一例としてサンプル程度に見ていただけると助かります。

 

limesode.hatenablog.com

 

まずは簡単なボタンテクスチャのアトラスを用意します。

 

これをマテリアルで切り替えるようにします。

テクスチャを4分割しているので、UVに 0.5を掛けています。あとはUV座標をオフセットとして移動できるようにパラメータをつなぐだけのシンプルなもの。

UVオフセットの変更方法についてはいろいろパターンがあります。状況に応じて使い分けたいところ。

今回はブループリントから直接アクセスして操作するので、マテリアルインスタンスは使用しないつくりにしています。

 

新しくWidgetブループリントを作って、必要なパーツをキャンバスに配置します。

  • ボタングラフィック
  • ゲージ(前回の記事で作ったやつ)
  • ブルーム(強調用)

 

ここに長押しを示唆するパーツを追加

まだ一般的に長押しを促すシンボルなどは定着していないと思うので、自分なりに考えてみたものです。

 

この 4パーツを使って、4種類のアニメーションを作成します。

  • READY 準備
  • PROGRESS 進行中
  • ABORT 中断
  • ACCEPTED 受付完了

 

READY

長押しUIにとって無くてはならない演出。

ボタンを押下したタイミングで再生。尺はなるべく短くするのが大事。

今回は 0.17秒で作成しました。

ボタンを押した後で指を離すのがフツーなので、何かリアクション表現を見せて注目させるのが目的。ゲージが動き始めるまでの演出が長いと長押し操作に対してネガティブな印象を与えかねないので、ギリギリまで短くしたいところ。

 

 

PROGRESS

マテリアルのパラメータをUMGのトラックから制御できるのでそこで 1秒間のアニメーションとしてキーを打ちます。

ゲージの増加で打つキーの補間タイプは リニア にします。

このゲージが増えるだけのアニメーションを専用で用意するのは、再生速度を調整したいというのが理由です。

 

 

ABORT

指を離して中断した際のアニメーション

何事もなかったかのように元の状態に戻すだけの動きです。

 

 

ACCEPTED

最後まで押し続けたときに受理したことを表す表現です。

もう指を離していいよ、という意味合いになるので、それなりに感謝の意を込めるのがいいのではと思います。

次の状況への遷移があるので、あまり重い演出は避けたほうがいいと思います。

 

 

次にブループリントに進む前に、

今回使った Enum (イーナムとかイナムとか呼称されます)を2つほど紹介します。

あると大変便利なので、お勧めしたいなと。

コンテンツブラウザから右クリック Blueprints にあります。

まずはゲームパッドのボタン管理用

eHoldButton と命名

 

もう一つは、UIのステート(状況)の管理用

eUIState と命名

上にあげたのは一例です。UI設計に応じて追加したりしてメンテします。

 

 

ではボタンのブループリントについて

まずはキャンバスに置いた ボタン用 Imageパーツについて、Dynamic Material を準備

Event Pre Construct で作ることが多いです。

 

この Dynamic Material に対してパラメータを渡します。

その関数がこちら。

関数の入力にイーナムを使っています。

 

 

4番目はアルファ値(透明度)でマテリアルでは使いませんが、便宜的に 1.0 を入れています。

入れておかないと、ノードのプレビューが全部透明になってしまいます。

 

この関数は下のような形で使います。

外から直接呼ぶか、いったん変数に値を入れてから関数を実行するかはお好みで。

 

今回は、WidgetをViewportに描画する前に決まっている状態にしたかったので、変数をExpose on Spawn して運用しました。このケースだと、値を受け取るだけで実行はしないので、Event Construct でこの関数を呼び出しています。

イベント Event Construct は Viewport にAddされたタイミングで実行されます。

 

 

ここで今回ブループリント内で作った変数を公開

軽く用途を説明しておきます

  • MID_ButtonImage マテリアルアクセス用
  • CurrentAnim 最後に再生したアニメーションを保持
  • pregress_speed 長押し時間
  • faceButton ボタンの種類
  • UIState 処理状況管理

 

最後に再生したアニメーションを保持しておくと、別のイベントが割り込んできた際に状況確認がやりやすいので、個人的に気に入ってる方法です。

 

長押しボタンはキャンセルがいろんなタイミングで発生します。

また特定のアニメーション終了後に次のイベントに進むというのをやりたいので、Finishedイベントを多用しました。再生しきって終了なのか、アニメーションの停止なのか判別をつけるのが煩雑になったのもあって、ステート(状況)を管理する変数が、UIStateです。

 

というわけで、

メインのイベントを載せていきます。

 

 

ボタンを押したときのイベント

このイベントで READYアニメーションを再生します。連打されることもあるので、何かのアニメーションが動いていたら問答無用に止めます。何が動いていようが関係ないのが、WidgetAnimation型の変数で大変便利です。その代わり再生する直前で今から再生するアニメーションを変数に入れる必要があります。

 

 

長押し受付け開始

READYのアニメーションが無事に再生終わったということは、長押し継続という理屈なので、Animation Finished(****) イベントを利用して長押しゲージの進捗アニメーション PROGRESS を再生します。 ※ ****は対象のアニメーション名

この後の中断されたときのイベントで、UIStateの内容が変わります。中断されてこのイベントに来たら、READYからの流れじゃないということになります。

再生速度はここで反映するようにFloatの変数をつないでいます。

1.0で通常速度、 1以下でゆっくり、 1以上で速くなります。

 

 

ボタンから指を離した時のイベント

受け付けていない場合のみアニメーションを止めます。終了検知としてバインドしてるイベントが反応してしまうので、UIState を書き換えてから止めます。中断アニメーションを再生してまた次の開始を待ちます。

 

 

長押し完了イベント

これも長押しアニメーション PROGRESSが終了したら処理されるイベントになります。


イベント以上です

長押しボタンのWidgetはこれで一応動かせるようになります。

イベントディスパッチャー経由で、アニメーション終了を通知しています。

 

テスト

手っ取り早く試すためにレベルブループリントで直接動かしてみます。

 

呼び出すイベントは以下の2つ

 

30fpsでキャプチャしたGifなので、ちょっとぎこちないですが、今のところうまく動いてくれている様子。

Enumでステート管理すると、かなり状況把握がしやすくなると思います。

 

今回は以上になります

UI制作のヒントになれば幸いです

ではでは

素敵な長押しゲージライフを!

 

 

追記1

最初に実装し始めたころ、アニメーションの終了は SetTimerByEventを使って時間が来たら次のイベントに推移するようにしていました。

これだと割り込みが入ったときに、タイマーのリセットも必要で、対策していても、

何度かテストしていると、まれにタイマーリセットが間に合ってないのか、止めたはずのイベントが呼び出されてしまう事がありました。

仕方なくアニメーションの終了検知イベントが Widgetブループリントに用意されているのでそちらを利用することにしました。

上にも書いていますが、止まった理由がわからないまま終了検知イベントが動くので、対策として、ステートをチェックすることにして解決しました。

流れはシンプルなので、まさかこんなところで躓くとは想定してませんでしたが、結果的に思わぬ収穫になりました。やっぱり作ってみて気づくことは多いですね。

 

 

追記2

長押しは、気づかせるのが難しいのもあり、積極的な実装を見るようになったは、タッチデバイスの普及によるところが大きいのかなと思う。誤タッチ対策にもなるのでタッチデバイスとの親和性が高いのも手伝ってか、そこそこ見かけるようになった。気がする。

スマホのアプリを触っていると、キャラクターの多いアプリの場合、画面の情報量が多いので、遷移コストも相応に高そうなのも、無関係ではないと勝手に想像しています。

一方のコンソール系のゲームで長押しを導入するのは、あまり機会は少ないと思います。

よく見るのは、イベントシーンをスキップするとき。

せっかく時間とお金をかけて作ったイベントシーンをボタン連打でスキップされては堪らない、というのが制作側の本音としてあったりします。あとは世界観やストーリーを説明する大事な場面をスキップしてしまうと、ゲームの雰囲気に没入できないのではといった心配もあります。もちろんやりこみプレイで周回したときに飛ばしたい気持ちもわかるので、妥協点として初回はスキップできなくて一度見たらスキップボタンのナビが表示されてスキップできることが分かるようになるという仕様も見たことあるけど、見たか見ていないか、クリアしたかどうかみたいなチェックが面倒なので、わりとプログラマに敬遠される印象。その辺の事情もありやナシやで最近では、初回でも長押しでスキップできるようになるゲームが増えてきた印象。スキップする気はなくても、姿勢を正そうとしたり、コントローラを置こうとしたときにうっかりボタンに触れてしまい・・・というのも事故として報告は上がるので、なかなかうまくいかないですね。

 

最近はコンテンツも多くさっさとクリアして積みゲーを消化することが目的になっているプレイヤーもそれなりに見られますし、配信されやすくなったのも要因のひとつかなと思います。時代の流れというやつですかね。

ともかく イベントスキップについては、作る側と遊ぶ側とで議論の尽きない要素ではあると思います。

 

すこし話がそれましたが、

長押しの受付け表現としてはリングゲージとの相性が高いと思うので、このまま定着しそうですね。タップもしくはクリック位置から視線誘導もしなくていいし、そこを中心に時間経過を表現するので、起こっている事象と共にあとどれくらいそのままでいればいいのかがわかる。

長押しに気づけるかどうかという点では、

コンソール系はボタン操作のナビ表示があるので、まだ気づきやすいけど、スマホアプリは、どこを長押ししていいのか気づかせにくいのがUIデザイナーとして難しいな感じます。ハンバーグメニューみたいに、そのうちそれっぽいユニバーサル?なアイコンが発明されるのでは?と思ってます。

 

 

世間の長押しゲージについてはこちらの記事が参考になると思います

デザイン比較:長押しした時に表示されるゲージ - ゲームアプリのUIデザイン (hatenablog.com)

 

 

ボタン長押しゲージを作ってみた《ゲージ編》

まだ暑い日が続いているけれど、重そうに曲がった稲穂や果物売り場を見て秋の訪れを感じる今日この頃。円形のゲージを作ろうと思って、以前からちょくちょく遊んでたグラデーションマップ(Photoshopでいうところの)を試してみたら結構カンタンに作れたのでさっそく長押しボタンUIに適応してみました。

 

 

 

2部構成でまずはゲージ部分から書いてみたいと思います。

 

 

『グラデーションマップ』ってやつですが、考え方はシンプル。

まずグラデーションテクスチャを用意します。

0 ~ 1 のグレースケールさえあれば、UV座標に置き換えるだけで、グラデーションテクスチャから部分的にカラー情報を拾うことができます。

 

たとえば以下のようなテクスチャ

これが

こうなって

こうなります

マテリアルは以下

 

と原理をざっくり説明したところで、

円形のゲージを。

 

アンリアルエンジンのマテリアルにはとても便利なノードが用意されています。

VectorToRadialValueノードひとつで、3つの情報が取り出せます。

ノードで表示されているカラーは、値の並びをRGBAに置き換えてプレビューしているものになります。

3つのうち上は 赤、黄色、緑がグラデーションになっている状態から、赤(R) と 緑(G)が加算ブレンドされているようなので、Float2 で X と Y か U と V として扱うことができます。

3つのうち 真ん中と下のやつは、赤(R) しかないので、 Float1 として扱います。

ちなみに、一番上のピン Radial Coodinates を分解してみると

 

今回は 真ん中の Vector Converted to Angle と下の Linear Distance のピンを使います。

 

Float1ということはグレースケールとみなすことが可能で、さっそくグラデーションマップに利用してみます。

 

 

このようなグラデーションテクスチャをつなぐとリングになります。

あとはゲージとして機能させるために真ん中の Vector Converted to Angle を掛け合わせます。

ただ、初期値のままだと右向き、時計でいうと 3時方向から始まるゲージになるので、12時から始まるように、90度回します。

 

UMGで表示する際に 90度回せばいい話ではありますが、

せっかくなので マテリアル内で回す方法を書いておきます。

 

 

VectorToRadialValueノードは、Inputに何も挿さないといい感じですが、挿した途端オフセットの調整が必要になるので、UVから 0.5 引いて 2倍 します。

 

そして V方向だけにマイナスをかけてから、UとVを入れ替えると、時計回りに 90度回ります。結果的に 12時方向を開始位置にできます。

StepノードとScalarパラメータをつないで、ブループリントから増減できるようにしたら完成。

この例ではマテリアルで色を付けていますが、UMGで色を付ける場合は、マテリアルは白色にしておきます。

 

ゲージカラーにグラデーションをつけるのもカンタンです。

下のは Lerp(線形補間)ノードを使った 2色グラデ。

 

ここにもグラデーションマップを使うことで もっとカラフルにできます。

 

 

さらにグラデーションテクスチャのUVを操作すると、いろいろ楽しめます。

 

例えば、縦方向(V方向)に動かした場合。

右側の多重リングは、以下のテクスチャを使用しています

上下がシームレスにつながっています。

UVの移動は Time ノードを使っています。

Now Loading画面とか、カーソル位置を表すソナーっぽいやつとかに向いてる感じで、マテリアルだけで低コストなアニメーション表示が可能です。

 

リング状のテクスチャの表示サイズを変えると、線幅が変わってしまいますが、この方式だと、その悩みから解放されます。

 

以下のようなテクスチャだと

波紋のような表現も簡単です。

テクスチャのタイリング設定次第では少し改造が必要な場合があります。

上記のテクスチャは TilingMethod を Clamp 設定にしたので、Timeのところに Fracノードを追加しています。0~1を繰り返す場合は便利なノードです。

※Frac は 整数部分を捨てます。1.0 にはならないので、場面によっては向き不向きがあります

テクスチャ容量的にはちょっと もったいない作りですが、UMGのアニメーション制御と干渉しにくいのと、扱いやすいのがウリです。

緩急と太さはテクスチャをいじるだけでコントロールできます。

 

グラデーションテクスチャをエンジン内で作ってアセットにすると、テクスチャをインポートしなくてもいいので、調整の手間が省けます。

グラデーションの作り方次第では汎用的なつくりにもできます。

 

詳しい作り方は書きませんが、コンテンツブラウザで右クリックして、

Miscellaneous > Curve

リニアなグラデーションが作れます。

リニアなグラデは、モニタに表示する際にガンマ補正されるので上のように白に寄ってしまいますがそこは無視。

Curve Atlas にしてマテリアルに組み込ます。

いろいろ調整できるパラメータをつないでみた。

テクスチャの中央がグラデーションの一番値の大きいところなので、リングの大きさを変える場合は U値を増減します。あとはSmoothStepで線幅とボケ具合を調整。

実際の表示サイズにしてジャギーが出たらボケ味を足すといいです。

 

この一式があれば、いろんな場面でリングゲージが量産できそうです。

 

汎用性を検討するにあたって、チーム内のマテリアル編集の練度、ゴールライン、メモリ配分、などなど状況に合わせて柔軟に選択できればいいなと思います。

 

値の使い途を工夫するとまだまだいろんな表現ができそうな気がします。

 

というわけで《ゲージ編》はここまでにします。

次回引き続き《ボタン編》ということで、長押しボタンのイベント処理を紹介していきます。

 

ではでは

素敵なリングゲージライフを!

 

スライダーUIを並べて操作してみる UE5版 続き2

前回の続き

基本の土台としてはそれなりにカタチになったと思うので、スライダー以外のUIパーツを作ってみようと思います。

今回の仕様はキーボードのカーソルキーで操作する想定にしています。

 

 

スライダーがUIパーツとして登場する画面にありそうなやつを 3種 選んで作ってみようと思います。

 

Index

 

 

スピンボックス

あらかじめ決められたいくつかの選択肢が、順番に切り替えられるタイプ。コンシューマーゲームが登場したころからメジャーになった印象がありますが、今でもよく見かける定番の設定用UIです。コンパクトでレイアウトにやさしく作りやすい。選択肢がテキストラベルで示されるので分かりやすいのがメリットだけど、選択肢の全体が見えないので、全部を確認するために一通り見たくなるというのがデメリット。これについては、なるべくループさせないのが良さそう。数と内容にもよるけど、ループしたことがわからないことが多く行き過ぎても気づかないことが結構ある。さらに選択肢が増えることを想定するのであれば、知らせるのが難しいのでこのUIはお勧めできない。

今回作るものは、選択肢の数をコッソリ忍ばせています。

 

wb_Slider をDuplicate(複製)すると使いまわせるパーツもあるし、関数やイベントの改造程度で済むのと、BPインターフェイスの登録も済んでるのでラクチンです。というわけで複製前提で書いていきます。

 

wb_Spin命名

 

Designer

パーツの構成は以下

 

こっそりと HorizontalBoxを置いています。中身は空っぽ。高さは 1px。

Is Variableを有効にします。

 

ヒエラルキーは以下

アニメーションはスライダー同様に用意します。

ただし、HorozontalBoxの中身は面積が小さいのもありますが、ちょっと作りがややこしい(動的に生成)ので、ズルしてHorizontalBox のBehaviorにある Render Opacity(透明度) で明暗を変化させています。

 

タイムラインのトラックにあるパーツをキャンバスおよびヒエラルキーから削除すると赤字になります。

グラフに置いていた場合はコンパイル時にエラーが出るので気づけますが、演出物やデコレーションパーツだった場合はコンパイルエラーが出ません。新しく同じ名前で作り直して同じプロパティ(設定項目)があれば、しれっと普通のカラーに戻ります。赤字のまま放置することができますが、復活しないのが分かった時点で削除してしまいましょう。

 

Graph

初期化関数 InitValue はちょっと大きくなります。

選択肢を テキスト型の配列 で受け取るようにします。選択肢の数を調べて変数にお取り置きします。

上の続きはこちら

右下の SetHighlightColor という関数はこうなっています。

フォーカス/アンフォーカスのカラーをきっちりセットするならここで行うとよさそうです。

 

値を更新する関数は以下

選択肢の数と同じだけループを回して、フォーカスしている番号と比較してカラーをスイッチ。

 

左右キーを押して内部の数値を切り替えるところで、範囲チェックしているマクロはこんな形にしました。

 

使ってる変数は以下

 

改造と追加は以上です。

 

組み込みは後述しますが、一足お先に動作の様子を貼っておきます。

 

 

 

 

レベル選択

これもコンパクトで段階やグレードの設定にむいてるタイプ。スライダーと原理的には同じで、段階が荒くざっくりと設定するときに使われる。表現としてはゲージと同じようなつくりでデザインの幅が広いのがうれしい。

今回 10段階で作成しています。0を含めているので 状態としては 11レベルあります。

 

さっそく中身を見てみましょう

アセット名は  wb_Level命名

 

Designer

パーツの構成は以下

テクスチャを用意します。サイズは変則で 36 x 48px

表示したい全体の幅を10分割して、圧縮とアライメント対策として 4の倍数になるよう調整しています。

このテクスチャをマテリアルでタイリングします。

マテリアルで描画することによって、ゲージとしての表現がすごくカンタンになります。

マテリアルを使わずにタイリングできますが、ゲージとして扱うにはそこそこ手数が必要なので、今回マテリアルで省力化しました。

UMG の Image WidgetはBrushのところで画像と同じようにマテリアルをセットすることができます。

 

ヒエラルキーは以下

 

 

Graph

マテリアルの中に干渉するために、Dynamic Material Instanceを作っておきます。

Promote to Variable(変数に昇格)すると手間が省けます。

 

値を更新する関数で利用します。

 

初期化の関数はスライダーと同じなのでそのままで。

 

 

範囲チェックのマクロはループなしです。

 

今回使った変数は以下

 

改造と追加は以上です。

 

組み込みは後述しますが、一足お先に動作の様子を貼っておきます。



 

 

 

チェックボックス

スピンボックスで代用が可能なのと、今回他のタイプに合わせてコンパクトに作ったので、選択肢が少ないのが残念ではある。On/Off や True/False みたいな対義語ではなく、2つ並べないと比較できないような選択肢で効果的だと思う。

チェックボックスな見た目だけど、複数選択できないのでラジオボタンと同じふるまいをします。そういえば 近頃は「ラジオボタン」ってどれくらい通用するんだろうか。

基本の仕組みがあれば、いろいろアレンジできるよという例として見てもらえれば。

 

アセット名は wb_Checkbox命名

Designer

パーツの構成は以下

チェックボックスはテクスチャとマテリアルを使って見た目をスイッチします。

サイズは 128 x 64 px Grayscale

 

パーツの各サイズは

40 x 40 px

 

これをマテリアルで切り出します。

40 px 横に移動させると絵が変わります。2パターンしかないので、パラメータを 0 か 1に限定して扱うようにすると、 移動幅(この場合 40/128 = 0.3125)に掛け算することでスイッチとして機能するようになります。

 

 

ヒエラルキーは以下

 

Graph

マテリアルのパラメータをいじるために、Dynamic Material Instance を準備。

 

初期化の関数はこんな感じに

選択肢が 2択なので、Integer型だった変数 Value は Boolean型にタイプをチェンジして受け取り。

選択肢用のテキストラベルは数が固定なので、配列で受け取ってそのまま表示しに行きます。

 

値を更新する関数はこんな感じ。

マテリアルのパラメータをスイッチするだけの簡単なお仕事。

 

範囲チェックのマクロはループなしの場合

 

ループありの場合

ブールの値は、NOTノードを使うと否定されて反転することになるので、それを自身に代入することで トグルスイッチ として利用します。

 

今回使用した変数は以下

 

改造と追加は以上です

 

組み込みはこのすぐ後に続きますが、一足お先に動作の様子を貼っておきます。

 

 

 

組み込む

さてさていよいよ大詰め。先に作ったスライダー達と一緒に並べていきます。

前回作った wb_Settings を編集します。

 

Designer

はい、並べました。

ヒエラルキーはこうなりました

順番の入れ替えやら増えたり減ったりすることを思うと、このネーミングはよろしくないのですが、この後の配列に登録する場面では便利になるはず。

 

Graph

初期化のところで配列に追加します。

このグラフの後ろで、テキストラベル(表示名)と初期値をセットするのを追加します。テキストや数値、選択肢の量などは自由に変えてみてください。

 

以上で追加完了です。

カンタンですね。

あとはコンパイルして問題なく動作すれば完成。

 

動いてるのはツイッターに貼ります。

 

最後に

いかがでしたか?

設定画面としての完成は目指していないので、中途半端な終わり方かもしれないですね。ゲームのデータをどう反映し、またフィードバックするかはシステムを設計される方にお任せします。

ブループリントインターフェイスを使うと関数を共通化できて UIのパーツを派生させて制御するのが易しくなります。4年前の記事は一度できたものを修正するという構成だったので、今回は最初からBPインターフェイスを用意する流れに変えました。

UE5版でリライトのために読み直しましたが、昔の自分だけど知らない人の記事を読んでる感覚にもなって不思議な気分でした。

他の記事でもUE5版にして欲しいものがありましたら、コメント欄からどうぞ。

ぼちぼちの更新になりますが、できる限り対応していきたいと思います。

 

ではでは

今回はこの辺で

ステキなUIデザインライフを!

 

 

スライダーUIを並べて操作してみる UE5版 続き

前回の続き

 

スライダーUIを並べて操作してみる

キーリピートの処理が無事動かすことができました。

以前の記事だと、UIパーツとして整えていくところですが、順番を入れ替えてここでブループリントインターフェイスを用意するところから始めていきます。

 

 

Blueprint Interface

ブループリントインターフェイスは独立したアセットになります。

コンテンツブラウザで右クリック > Blueprints > Blueprint Interface を選択

プロジェクト内でユニークに扱われるので慎重に命名します。

とりあえず IF_SettingItem と名付けました。

編集ウィンドウを開くと、すでに 1つ作られていて、関数名を決めてほしそうに待機してます。気が早い。

関数の追加はFUNCTIONSの右端にある ⊕ボタンから。

BPインターフェイスでは関数の名前と入出力、その他設定だけの登録になります。

エディタのグラフに READ-ONLY とあるように関数の中身自体はここでは空っぽのままです。

今回は、イベントとして機能させればいいので、追加と命名だけでOK。

フォーカス状態が切りわったときの Focus Unfocus、左右キー入力時の Increment Decrement の 4つを用意します。

用意できたら保存して閉じます。

 

 

 

前回作ったWidgetブループリント wb_Slider を編集していきます。

 

複数のスライダーを並べた状態では、どのスライダーが操作対象か分かるようにしないといけません。

 

目指すイメージ

 

前回から追加したい部品は 2種類

  • 項目名を示す「ラベルテキスト」
  • キー入力の方向をなんとなく示す「<」と「>」の記号

 

そして必要になる演出(アニメーション)は 2種類

  • スライダーが操作対象であることを表す表現
  • キー入力を受け付けたことを表す表現

 

 

Designer

手間を省くために今回「<」と「>」は文字で表現することにしました。 演出用のパーツなので画像であっても問題なく進められます。

追加したのは 3つの TextBlock

ラベル用のTextBlockは、ブループリントから書き換えたいので、Is Variable を有効にします。

 

アニメーションを用意します。 まずは 4種類あればOK。

左右のキー入力で値の増減、上下のキー入力でフォーカスを切り替える想定。

  • FOCUS ・・・ フォーカスされたとき
  • UNFOCUS ・・・ フォーカスが外れた時
  • RIGHT_PRESSED ・・・ →右キーが押されたとき
  • LEFT_PRESSED ・・・ ←左キーが押されたとき

左右キーの入力については、数値とツマミが変動するので、必須というわけではないですが、操作に対してレスポンスがあるというのは、ユーザーとって手応えになるので、丁寧なつくりを目指すならあるとうれしいですね。こういうのをマイクロインタラクションとも言います。操作とそれに沿った動きを見せることで、記号と効果を強く紐づけることが期待できます。学習コストを下げるのを狙うのであれば、まさにこういったところにこだわるのは大事です。

 

アニメーションができたら次はブループリント

 

Graph

先に用意しておいた ブループリントインターフェイスを登録します。

ヘッダーメニューの Class Settings ボタンをクリック。

左下にあるDetailsタブから、Interfacesの項目を探して Addボタン(なぜかプルダウンな見た目)を押して、IF_SettingItem を探して選択すると登録完了。

登録されると、My Blueprintのタブに、INTERFACES が追加されます。

これをグラフに取り出すとイベントノードとして扱うことができます。

ドラッグ&ドロップはできない仕様です。

試しに、Incrementをグラフにドラッグ&ドロップしてみるとこんな警告がでます。

グラフへの取り出し方は 2つ。

一つはリストから右クリックして、Implement Eventする。

もう一つは、グラフ上で右クリックして見つける方法。

頭に"Event" が付けられるので、見つけにくいですが Add Event カテゴリに追加されています。

グラフに取り出すとこんな見た目のノードになります。

さっそく前回作ったカスタムイベントと入れ替えます。

 

ちなみに既に同じ名前の関数およびイベントが存在するとコンパイルエラーになります。

今回エラー回避のために被らないように命名してもよかったのですが、BPインターフェイスの方がプロジェクト内でグローバルな存在になるので、ローカルな存在のカスタムイベントに命名を譲るのはちょっと負けた気がしたので、わざと被らせました。キーリピートの仕組み作ってるときに対策できたんですが、わりと遭遇しやすいエラーなので、事例として紹介しておこうという魂胆です。

 

入れ替えついでに、左右キーを押して値が増減する時にリアクションするアニメーションをつなぎます。

4年前と違うのは、マクロの後ろに持ってきたところ。

今回はループするので、特に違いは出ないのですが、何らかの制限や条件があってアニメーションにも影響させるとなると、この順番のほうが都合がよさそうだからです。

 

たとえば値が端までいってもループしないとなると、マクロ内で更新しない方へ分岐できます。

値の増減で範囲を越えると値を更新せず抜けるようにOutputにピンを増やす。

 

そこから、これ以上は無理ですよのアニメーションを再生するようにつなぐと出来上がり。

実行される順番が分かりやすく、改造しやすいのがビジュアルスクリプティングならではだと思うのです。

 

 

続いて、スライダーがフォーカスされた時と解除された時のイベントを用意します。BPインターフェイスのイベントを利用します。

左右のキー入力時に、0.15秒のアニメーションをつけているので、アンフォーカスのイベントだけ、先にStopAnimationノードで止めています。今回作ったフォーカスとアンフォーカスのアニメーションが左右のキー入力アニメーションより短いので、残っていた場合に現れてしまうのを防ぐためです。

フォーカスする場合、別のスライダーWidgetにフォーカスすることになり、そこはすでにアンフォーカスの処理が済んでいる前提です。

 

これで、イベントの用意が全て整いました。

最後に初期値をもらう関数にノードを追加します。値と一緒にラベルテキストも受け取ります。

前回の記事で Event Construct に Update Value関数をつなぎましたが、↑ ここに移動させます。

Viewportに追加した際に、Event Construct が先に実行されてしまうので、UpdateValue関数の処理がすでに終わってから、この Init Value関数が呼び出されることになり、ツマミだけが反映されないことになります。なので値を受け取ったタイミングで確実に更新させられるようになります。

その代わりに、ここで アンフォーカスのイベントを呼び出すようにします。

My Blueprintタブの GRAPHSカテゴリにある EventGraphからドラッグ&ドロップします。

ノードの左にある Targetピンの色に注目。

 

これで画面表示開始時にアンフォーカスの状態で始まるようになります。

キャンバスに置いたパーツのデフォルト状態(エディタで何のアニメーションも適用していない状態)がアンフォーカスと同じになっていれば、このイベントを呼ぶ必要がなくなるのですが、アニメーションの調整作業していると、うっかりデフォルト状態に戻し忘れていることがあるので、保険的な意味合いです。

 

このスライダーWidgetはこれで完成です。

 

 

次に並べるためのレイアウト用Widgetを新しく作ります。

wb_Settings 命名

 

さっそくスライダーWidgetをキャンバスに並べます。

 

Designer

Paletteタブの USER CREATEDカテゴリから wb_Slider を探して配置します。

ひとまず縦方向に 3つ並べました。後からまとめて移動できて等間隔に並べるのが楽なVerticalBoxを利用しました。

キャンバスにドロップするとWidget名の後ろに自動的に番号が振られるので、上から順番に 1st , 2nd , 3rd にリネームしました。

 

レイアウトができたら、グラフを編集していきます。

 

Graph

まずは変数を 4つ追加

Integer型の変数 2種

focusIndex ・・・ 操作対象のスライダーを番号管理するためのもの

MaxItemCount ・・・ 操作対象を切り替えるとき、ループさせるのに使う

 

User Widget型の変数 2種

activeWidget ・・・ 操作対象のスライダーにアクセスするために使う

Items ・・・ レイアウトしたスライダーWidgetを格納するための配列

 

初期化の関数を用意します。

関数名は InitItems としました。

作っておいた User Widget型の配列 Items にキャンバスにレイアウトしたスライダーを上から順番に格納。配列の要素数を Lengthノードで調べて MaxItemCountに代入しています。そのあとで各スライダーの初期値とラベルテキストをセットしています。

これも 以前のと逆にしています。処理順はどちらでも特に有利不利はないと思います。ただ、こちらのほうが後々いろいろ追加があったときに編集しやすいと思ったのが理由。

User Widget型の変数は、Widgetブループリントを作るときに選ぶやつです。

基本的にこのクラスを親として子孫を作ることになるので、User Widget型の変数にはWidgetブループリントが格納できます。今まで拒否られたことがないのですが、フツーに作っている分には便利に利用できます。

 

つぎに、スライダーのフォーカスをセットする関数を用意します。

changeActiveItem と命名

 

用意した 2つの関数を Event Construct につないで、最初にフォーカスするスライダーをセットします。

Integer型の変数 focusIndex を初期化の関数に入れても問題ないです。今回入れなかったのは、状況によってはスタート時のフォーカス位置が可変することもあるから、というのが理由ですが、あまり強い理由ではないです。

 

操作対象のフォーカスを切り替えるためのカスタムイベントを2つ用意します。

これらは上下キーを押したときに呼び出されます。

切り替える前に、現在フォーカスしているのをアンフォーカスします。そのあとで focusIndex のカウントアップ・ダウンさせてループ処理を入れます。プラスの方向は%(剰余)が 0 → 1 → 2 → 0 → 1 → 2 → 0  ... と便利なんですが、マイナスの方向は 0 → -1 → -2 → 0 → -1 → -2 → 0 ... となってしまい、配列に使うとエラーが出ます。ブランチノードは使わず Selectノードを使ってループさせています。

左右入力の部分は、キーリピートのテストで作ったやつを、レベルブループリントから移植したいのですが変数は持ってこれなかったりコンパクトにしたりするので、慣れるためにも もう一度構成しなおします。

 

思い出の一枚

 

これをこうします。

左右キーの部分は、Inputノードから キーを押したときと離したときの 2系統を用意する必要があります、押したときはすでにある onKeyPressを利用し、離したときに相当するカスタムイベントは新しく追加しています。上図 onKeyRelease

 

増やした変数は 3つ。

 

キーリピートの間隔を初期化するところが必要

この部分は新しくカスタムイベントを用意して対応。

ついでに、 LeftとRight で分岐していた部分は あとの Boolean型の引数を受け取る形でまとめてしまいます。

onChangeValue と命名

これで準備完了。

外部から呼ばれるイベントは 4つ

  • onChangeValue ・・・ 左右キーを押したとき
  • onKeyRelesase ・・・ 左右キーから指を離したとき
  • Next ・・・ 下キーを押したとき
  • Previous ・・・ 上キーを押したとき

 

最後はレベルブループリントを整えます。

 

Level Blueprint

Viewportに書き出していたWidgetが変更になります。

スライダー単品をレイアウト用のWidgetに変えて、変数化していた wb_Slider も使えなくなるので作り変えます。

 

キー入力部分はこんなにシンプルに。

 

これで今回は完成です。

テストしてみましょう。

うまくいってるようです。

ということで今回はこの辺で。

次回は、スライダー以外のWidgetを作って追加してみようと思います。

ではでは

ステキな スライダーUIライフを!

 

 

補足

User Widget型の変数にWidgetを入れて使う場合、いろんなWidgetを格納できて便利なんですが、扱い方に ひと手間必要になります。

直接アクセスする場合はWidgetの持つ関数やイベントにアクセスする際、 call で検索するとリストアップされます。※Editor Language が Englishに設定されている場合

User Widget型の場合、User Widget型で間違いはないけど具体的なWidgetが特定できないのでリストアップされない。

そこで、特定するためにキャストノードをつなぎます。

今回はスライダーしか扱っていないですが、いくつかのWidgetミックスするためにUser Widget型を使う場合、このキャストノードを使った解決は、いちいち特定する手数が必要になります。そこで大変相性がいいのが、ブループリントインターフェイス。次回の記事でその辺の恩恵が実感できると思います。

 

 

 

 

 

スライダーUIを並べて操作してみる UE5版

時は四年前。 2018年の2月に公開した内容で、間違いに気づかず、問題を避けるかたちで解決したことにしている部分があって、その雪辱戦といいましょうか、修正するにも当時のバージョンはすでになく、空きのないドライブと格闘してスクショ撮るのも大変そうなので、UE5版としてリニューアルすることにしました。配列変数の作り方を書いてるのはよかったのですが、既存のWidgetから昇格して作ったので、違うWidgetが追加できるわけもなく、当時の自分はこのミスに気づいてなくて、先日コメントでご指摘いただきました。今更ながら恥ずかしい限りです。

 

該当する一連の記事はこちらになります

キーリピート機能をつくってみる - みつまめ杏仁

スライダーUIを並べて操作してみる - みつまめ杏仁

スライダーUIを並べて操作してみる 《続き》 - みつまめ杏仁

スライダーUIを並べて操作してみる 《おまけ》 - みつまめ杏仁

スピンボックスも作ってみた - みつまめ杏仁

 

結構な量書いてるな・・・。

4年越しでコピペするのは面白くないし、伝わりにくそうなとこを補間しながら、なるべくコンパクトを目指しつつ、UE5の差分とかで気になるものがあれば書いていこうと思います。

変数名やオブジェクト名、アセット名など一部名称を変更しているものがあります。

 

 

キーリピート機能をつくってみる

この機能を作ったところから始まったのでここから始めます。

まず Widgetブループリントを一つ作成。

 

コンテンツブラウザで右クリック > User InterfaceWidget Blueprint を選択

 

UE5 ではこんなポップアップが出るようになりました。

ここで同プロジェクト内で既に作った Widget ブループリントを親クラスとして選択できるようになっているので、開発の効率化を意識したフローになっているのはいいですね。

新しいものを作るので、Commonのところの User Widget ボタンを押すとアセットが現れます。

これに名前を付けて編集していきます。今回 wb_Slider と名付けて進めていきます。

 

Designer

エディタを開いた時、最初に置かれているのが Overlayだった場合

右クリックして、 Replace With... > Canvas Panel を選択すると入れ替えることができます。

Overlayで進めることもできるのですが、テキストを右詰めにしたかった(Overlayだとちょっと調整しにくい)のと、それぞれのパーツの位置関係を絶対値として扱いたかったのでCanvasPanelを選択しています。Overlayの下にCanvasPanel置けばええんやないの? という声が聞こえてきそうですが、階層構造が深くなると、その分レイアウトに必要な計算量が増えるます。できるだけ浅く作りたいものです。

 

この初期配置のWidgetは、

Edit > Project Settings > Editor > Widget Designer(team)  > Desinger > Default Root Widget

で変更できます。

 

キャンバスに Image を 2つと TextBlock を1つ配置します。

レイアウトはこのような配置。

右端がMAXの想定なので、ツマミ(Image_Knob)は左端に配置。

ブループリントから触るための Is Variable スイッチは、Image はデフォルトで有効になっていますが、TextBlock は無効なので有効にします。

 

パーツの配置が終わったのでグラフのほうを編集していきます。

 

Graph

変数を2つ用意します。

UE5になって変数の型が明示されるようになったのはいいですね。配信やスクショに配慮ということなのかな。

変数 MaxPos にツマミの移動量を入れておくために、Image_Base のサイズを調べます。

Event Pre Constructにつなぐと、コンパイルの時点で情報を取得することができるようです。

GetSize ノードの ReturnValue ピンは、濃い青色になっています。これは複数のピンが束ねられている状態(構造体)なので、バラす(Split)ことができます。

ピンの上で右クリックして Split Struct Pinを選択。

Breakノードを使わずにつなぐことができます。

 

デザイン的にスライダーの長さが確定するまでは、このパーツの長さを調べて代入するという接続が無難です。確定してもう変更しないとなれば、変数 MaxPos に初期値をセットしておけば、この調べるという処理は省略できます。

 

ちなみに Overlayで作る場合、サイズの取得方法が変わります。

Image Widget が Brush 情報として持っているのでそれを拾います。

CanvasPanelの場合、このBrush情報とは別に描画するサイズを指定できてしまいます。テクスチャ由来のサイズを基本とするならOverlay同様、Brushから取得するのが賢明です。

BrushでImageSizeを指定して、CanvasSlotでは Size To Contentを必ず有効にする。という方針を立てて開発するとよいかもしれません。

OverlayとCanvasPanelの混成する状況でもややこしくなくていいかも。

 

 

関数を2つ用意します。

ひとつはツマミを指定した位置に移動させ、数値を更新する処理。

便利な Lerpノードを使います。A と B につないだ 2つの値について、Alphaにつないだ割合(%)に応じてブレンドされた結果が取り出せます。活躍する場面が非常に多くて便利なので大変重宝します。

 

Integer型(整数)の割り算は少数以下を切り捨ててしまうので、先にFloat型(浮動小数)に変換します。

 

 

もう一つの関数は、外部から値をもらう関数です。

関数名に初期化をイメージする命名をすることで、値の使われ方を示す狙いがあります。このInteger型の Value という変数は、念のため Private に チェックをつけておくと、不用意に外からの書き換えを防げるので安全です。

 

つぎに、値を増減させるためのマクロを用意します。

受け取った値 +1-1 を 変数 Value に加算して、 0~100の範囲を越えないようにします。%ノードは 割った余り(剰余)を計算してくれるノード。直前に加算しているのでValueが 100 の時に +1されて101になり、それを 101で割った余りを求めると 0 になるので、100 の次は 0 になります。Valueが 0 のときに-1 を足すと 0以下になるので、Branchノードで判定してValueを 100 にします。

%ノードは値を循環させたい場合にとても重宝します。値がマイナスにならないのであれば判定部分は省略できます。

 

仕上げにイベントを用意します。

Event Construct に 値を反映する関数 UpdateValueをつないで、あとはカスタムイベントを2つ追加します。

名前を Increment と Decrement としました。それぞれにマクロをつないで、結果を UpdateValue関数につないで完成です。

 

キー入力を処理する外部のブループリントから、このカスタムイベントを呼び出します。

 

 

Level Blueprint

レベルブループリント編集を開始する方法がUE5のUI変更にともなって変わりましたね。コンテンツブラウザでレベルブループリントのアセットアイコンをダブルクリックしてもシーンが開くだけでブループリントの編集ができないのですよね。

このレベルを開いている状態でヘッダーメニューから Open Level Blueprintを選択。

 

 

Create Widget ノードからの Add to Viewport は Widgetを表示するための定番コンボ。

ここで wb_Slider を召喚します。後から利用するために、変数に昇格しておきます。

そこから関数を呼び出して、スライダーのツマミのスタート位置である初期値を渡します。

試しにコンパイルしてエラーが出ないのを確認して再生します。

関数に渡す Valueの値を変えてみてきちんと反映されていればOK。

 

キーリピート処理を作っていきます。

まず 新しく変数を2つ追加。

Float型とBoolean型。

3つめの青い Timer Handle型の変数はこの後のタイマーノードから作るとラクです。

 

カスタムイベントを1つ追加します。

そこへ Set Timer by Event ノードを取り出してつないでいきます。

このカスタムイベントと、Set Timer by Event ノードのコンビネーションは、もう黄金コンビ(古い?)ベストカップル(これも古い?)ゴハン何杯でもいけます。Event Tickを使わずに一定のタイミングでループ処理を作ることができます。

しかも任意のタイミングで、動かしたり止めたりができるので、無駄に動き続けるようなことはありません。

Set Timer by Event ノードは、指定した時間がきたら、左側の角丸のピンでつながった先のイベントを実行します。Delayノードと違うのは、あくまでもタイマーのスイッチを入れるノードなので、普通に後ろにつながったノードは実行されてゆきます。

今回のキーリピート処理では、初回だけ待機時間を長めにとって、2回目からは短くすることで、狙った動きにすることができます。

その 2回目以降の間隔を設定するために Do Once ノードをつなぎます。

リピートのために間隔を短くした値を Float型の変数に入れます。
とりあえず 0.025 という値を入れています。 単位は 秒です。

通常ゲーム画面は高速で絵を描きまくっているため動いて見えます。TVモニターの技術的な都合もあって、1秒間に60回の描画がベスト、とされている時代が長く続きました。フレームパーセカンド(=fps)を使って表記すると、 60fpsとなります。計算量が多く負荷の高いビジュアルを作ったり、データ量の多いアセットを扱うと、あっという間に60fpsを維持できなくなります。よくカクつくという表現で言われたりするあれです。

最近は単純にハードウェアのスペックが上がってて、伝送量が増えたりして伝送速度が上がったりHDMIの規格が増えたり、HMDの普及もあってより高速なフレームレートに対応できるようになってきました。とはいえ、ゲームでこの60fpsを維持するのはめちゃくちゃ難しいので60出てたら凄いってなります。

1秒間に60回ということは、画面を1回描ききるのに 1/60 で 約 0.016666...秒かかります。

なので、0.016秒より短い間隔にすると変更が目に見えないことがあるので、もろもろの処理負荷を減らす意味でも短くしすぎないようにするのは大事です。

リピート間隔を調整するための、指標みたいなものがあったほうが決めやすいかと思って長々と書いてみました。

 

あと少し

キー入力処理の部分。Input系 ノードを使います。

これは、キーボードやタッチパネル、マウスやゲームパッドなどの入力デバイスからイベントとして情報を受け取るためのノードです。

簡単にキーボード操作にするなら、Keyboard Eventsカテゴリの Left と Right ノードが便利。

 

カーソルキーの左右を押すたびにスライダーの値を更新したいので、下のようにつなぎます。

キーを押した瞬間だけこのInputイベントの Pressed が呼ばれ実行されます。

指を離すと Released が呼ばれ実行されます。

これで押した瞬間にタイマーをセットして指を離すとタイマーを解除(クリア)という流れになります。

指を離したときに、次回のために Do Onceノードをリセットする必要があります。

そこで Clear and Invalidate Timer by Handle ノードでタイマーをクリアした後に DoOnceノードにReset ピントつなぎます。

これで準備が整いました。

再生して確認してみましょう。

 

 

そういえば、最近はカーソルキーって言わないのかな?方向キー?矢印キー?

左右の矢印を変換するときに を入力して変換すると  が出てくるのを知ってからはよく使うようになりました。

以前は 「ひだり」と入力して「←」に変換してたんだけど、IMEが学習してしまい「ひだりうえ」を変換すると 「←うえ」みたいなことになってのが面倒で・・・

 

初回の少し長めの待機時間がなぜ必要か、というのが気になったら初回の待機時間を短くしてみることをおすすめします。

2か所の 0.75 のどちらか一方をリピートと同じ時間(今回の記事では 0.025)にしてみると効果が判りやすくなります。キーを押して離すまでに結構時間がかかっていることに気づくと思います。

 

次回より UI らしく複数のスライダーを並べて操作できるようにしてみようと思います。

 

ではでは

ステキな キーリピート ライフを!

 

雑記#20220718

夜帰宅中、歩いていると聞きなれない音に気付いた、壊れた電子ブザーみたいな音で、長く一定のトーンで聞こえてくる音。まだ蛙の声も聞こえる時期なので秋の虫も少ないせいか際立って聞こえてくる。そういえばどうぶつの森で聞いたことがあるような・・・調べてみたらオケラだった。結構長くこの道歩いてるけど気にしたことなかったな。確か土のある所に暮らしているはずだけど、田んぼ付近はともかく、マンションと路地との隙間から聞こえてきたりする。最初は耳鳴りを疑ってしまった。

さてさて

別にカードゲームを作りたかったわけではなかったのだけど、先日雀魂(じゃんたま)を遊んでいて、牌の上をマウスカーソルが乗るとピコっと牌が少しだけ飛び出るフォーカス処理、あれを作ってみたくなって、UMGで試してました。

ただ横に並べるだけだとつまらないので、カードゲームの手札みたいに扇っぽくできないかな、ということでカードになりました。

試してみたこと2つ

  • HorizontalBoxで扇状に並べることは可能か?
  • HorizontalBox内のアイテムは優先順位は変えられるか?

過去に何度か触って答えはわかっているものの、バージョンが変わればひょっとしたら・・・みたいな期待感は常にあるので思いたったらとりあえず試す流れで。

 

ここからの内容は、見出しを付けていますが、つながりは弱いので気なる見出しだけ拾い読みしてもいいようにしています。

 

 

 

やってみた結果

まずは重ねて角度をつけてみる。

 

ここからいい感じになるように計算しつつ並べてみたのがこれ。

 

並べるカードはただ画像を並べるのではなく、別の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 的なプロパティが無いことに気づく。

いろいろ調べてようやくたどり着いたのがこのブログ。

kumasan-debug.hatenablog.com

K.Y.さんありがとうございます。

 

エンジンのプロジェクト設定を変えることで対策できるようですが、結局手札としてのふるまいを考えると、HorozontalBoxだと表現しきれないと判断してCanvasPanelで実装することにしました。

それはそれで、並べるのに苦労しました。

 

 

MPCを使うパーツが増えたということは

スポット表現が必要なパーツが増えたことで、MPCを乗算するマテリアルが複数必要になる。そこでマテリアルファンクションを作ってラクしようと思いついた。

一応スケールを調整できるようにしてあるけど、初期値として3.2と2.0は入れておく。

これでパーツごとにこのマテリアルファンクションをノードとして取り出してつなぐだけで、スポット表現が手に入る。

パーツごとのマテリアルにノードを置いていくだけ。


こんな感じにできました。

詰めてみると

パーツをわけたので、左きき対応してみた。

 

 

 

リトルノアクリア

面白かった。昭和のアーケードゲーム程度のボリュームで個人的にはちょうどいい感じ。お値段もお買い得な感じ。吉田明彦さんの絵が好きなのもある。

littlenoah-s.com

作業にならないようにランダム要素をベースにしつつも運に振り切ってる感じはしないし、結構細かい調整を感じる。操作感も悪くない。敵を浮かせる技を持ったアストラルが多いのでエーテルスラストという空中ダッシュを混ぜた空中コンボが気持ちいい。その辺を意識した滞空時の追撃エフェクトを持ったアクセサリーがあったりと、なかなかの手厚いフォローのアクションが楽しめる。出会ったアストラルや拾ったアクセサリーによってコンボスタイルが変化するのもなかなか飽きさせない要素だと思う。開始時のスタメンは毎回固定なのも、ゲームシステムに慣れるまでは心強い。途中でどんどん強いアストラルに出会うので入れ替えてみたくなります。

『ノーダメージで』『制限時間で』『既定のダメージ量で』『既定のHit数以上で』など部屋にミッションが設定されることがあって、達成時の追加報酬があったり、強敵の発生する部屋があるのだけれど、装置を起動しないとスポーンしないので、自信がないときはスルーできる。もちろんクリアすると大きな報酬が手に入るし、バーストゲージも溜まる。慣れてきたプレイヤー向けに難易度の幅が提供できているのは素晴らしいですね。

ステージ攻略中は遊びごたえが十分あるけど、拠点でできることは結構シンプルで、キャラ周りの世界観に浸るには物足りない印象はある。でもこれくらいが個人的にちょうどいい。最近のスマホゲームで拠点というかホーム画面で情報過多なゲームが多すぎる気がする。各種ボーナスを受け取るのはモチベにはつながっているとは思うけど、ホーム画面でいろいろあちこちタップしても、ゲーム体験としては「遊んでない」のと同じじゃないかと思う。時間を消費するなら少しでも楽しいゲームプレイ体験が残るようにしたいと考えているので リトルノアのゲームデザインは個人的に気に入ってます。

 

 

ではでは

今回はこの辺で

検証内容的にあまり参考にならない気もしますが

ステキなカードゲーム開発ライフを!

 

雑記#20220710

ひさしぶりに書きます

ネタが途切れたのもあって、少ない脳ミソのリソースを会社員モードに全振りしていたらいつの間にか梅雨が明けて蝉が鳴く季節になるまで放置。さすがに寂れるだろうと思っていたんですが、しみじみと閲覧数を維持できているのは驚きと同時に感謝の念でいっぱいです。

UE5への移行で何か書けることあるかなと思ってアップデートしてみたものの、思いの外すんなり手癖を受け入れてくれる懐の深さ。いつも通りな感じで操作できてしまう自分がいるのでした。

差分情報などはすでにいくつかトピックが上がっていたりで、これといって新鮮なネタを思いつかない自分の役目は終わったな、などと書かない言い訳を拾い集めるのが心苦しくなってきたころに、ようやく方向性が見つかった気がしてきた今日この頃です。

 

過去記事をUE5でリフレッシュするのも考えてはいたけど、新しいことをしたい気持ちの方が強くて、UMGでもっといろいろ作ってみようと。その過程でネタが見つかるんじゃないかと。あとTwitterで晒して「それってどうやってるの?」みたいな反応があれば記事にしたらいいんじゃないかと。

まあそんな感じで、前置きが長くなりましたが、Twitterにあげたネタからいくつかピックしてツラツラ書いてみます。

 

 

スタイライズドなファイヤー

ここでいうスタイライズド(Stylized)という語は、記号化、様式化されたというニュアンスで使っています。ただリアルに物理現象を模すのとは対照的に、印象として記号感を残しつつもある特徴的なルールに従って表現されているので、アーティストの考えた世界観がぐいぐい出てくる。

たとえば、既存のアウトラインフォントを使うとき。フォントワークス社のラグランパンチやマティスEB、ロウディなんかを、ゲームで使うのはなかなか難しいのと状況が似ていると感じてます。

「難しい」とぼかして書きましたが、その特徴的な印象とプロダクトが1対1の関係に紐づいてしまうと、後から第3者が使うというのは、リスペクトかパクリかどちらにせよポジティブな印象に転化するのは簡単じゃないという意図です。

 

今回の炎は、過去に見たツイートで紹介されてたテクニックで、URLを遺失していたので記憶を辿りながら再現を試みてみました。確か英語で図解されていたと思います。原理的なものはそんなに外れてないはず。

 

フォントはボリュームのことを思うとアレンジするコストが膨大なので速攻で諦めるのですが、炎のマテリアルくらいだったら、がんばったらいろいろ工夫転用したり新しいヒントに出会えるかもしれません。ということで今回作ったやつを公開することにします。

 

ここからいくつか閲覧注意な画像が登場します。

 

 

肝になったテクスチャですが、シームレスで作る必要があって、Photoshopのパターンプレビュー機能(ver22.0以降)が大いに役立ちました。

適当な大きさのテクスチャに円形のシェイプを一つ。塗りを円形のグラデーションにします。

このシャイプレイヤーのブレンド効果を比較(暗)にします。

あとはこれを複製(Ctrl + J が便利)しつつ大きさを調整しながらひたすら埋め尽くします。適当にランダムに。

トライポフォビアの方ごめんなさい。ちょっとキモくなるけど、そこは我慢するか、グラデーションマップを一番上においてマイルドにするといいかも。

なぜかスマートオブジェクトにするとこの後の作業でブレンドがうまくいかなくなるので、ここはレイヤーグループにまとめます。

表示メニューからパターンプレビュー機能を有効にし

レイヤーグループを、コピーして8方向に移動。

移動量はテクスチャサイズと同量。

数値を気にしながらドラッグするのが面倒なときは、ざっくり動かした後でドキュメントを拡大してカーソルキーでドット単位でズレがなくなるように調整するといいです。

これでシームレステクスチャの完成です。

恐ろしいパターンプレビューを終了して、テクスチャとして書き出します。

これをマスクテクスチャとしてエンジンで利用します。

 

参考までにインポート設定。

用途がマスクなので、Grayscaleを選択。sRGBを無効にする場合、

マテリアルでは LinearGrayScaleとして扱うことになります。

 

炎は自然現象なので、基本繰り返すことはありません。でもテクスチャを使用すると同じパターンが繰り返し現れてしまいます。それをなんとかしてランダムに見えるように工夫しています。

 

あくまでも一例ですが、2種類の動きをレイヤーとして作っています。

まずはレイヤー1

パラメータノードを使っているのは、同じマテリアルを使用した場合に同じ動きをしてしまうので、意図的にばタイミングなどにバラつきを与えるためです。

Pannerノードでスクロールスピードを調整。

TimeからSine(サイン)ノードの流れは、左右に揺らすためのものです。

 

次にレイヤー2

構造の基本はレイヤー1と同じで、スピードの調整であえて差が出るようにしています。

2種類の最小公倍数を可能な限り大きくする感覚です。

 

2つのレイヤーを掛け算したものを確認してみます。

Named Rerouteノード便利ですね。多用すると追いかけるの大変になるけど、ノードに名前が付くので分かりやすい。状況確認しやすいのもGood。

こういったお試しの

 

これに調整を加えてから丸く切り取って、2諧調化して仕上げます。

Grayscaleはチャンネルを一つしか扱わないので、RGBAという4つにまとめられたFloatのうちの一番目、Rを使うので赤としてプレビューされます。

ですが、

最終的に流れてきたデータが、Rしかないときは、エンジンは気を利かせて、残りのGとBに同じ値を入れてグレーで描画してくれます。

MaterialのBlendModeを Additive にして完成です。

 

これをUMGに持っていって配置。

大きさを変えて2枚重ねにすることで、厚みと透明感を表現。

適当に着色すればいろいろ楽しめます。

 

Stepノード使うとトゥーンな見た目になるので、Stepノードを使わずに

SmoothStep とグラデーションテクスチャで、こんなやつも作れます。

知恵と勇気とこだわりがあれば、何とかなりそうな予感。

 

 

ビットでRGB

いわゆる光の3原色といえは、おなじみRGBの赤緑青です。

最近はHSVでカラーを調整するのがデフォという若者が増えて、嘆かわしいことですな。ふむ。

とかなんとか言いながら作ってみたのがこのパズルっぽいやつ。

狐の持つ炎の色が 赤、緑、青、のどれかに決まっていてランダムに配置。2匹が組み合わさることで、合成された色になるので、そこから組み合わせの内訳を推測して並べなおすという遊び。1匹だけサボってるやつがいるので、そこで確定させるとヒントになります。上の図だと、右端の2匹のうちどちらかがサボりで、どちらかが青。

 

この遊びを作るにあたって、ビットを使って制御したり判定することを思いついたのでいろいろ試した。

 

RGBのビット順については、勝手に下図のように決めました。

加法混色ということで、基本的に2進数で考えると扱いやすい。

というわけで整数にして扱うようにしていますが、その値にRGBの各要素が含まれるか調べる必要がある。その方法で使ったのがビットマスク。

 

任意の整数から、RGBが含まれているかチェックして、カラーに置き換える関数。

&というノードは ビットでの論理積で0と1の掛け算。

この”&”(AND)を使い、特定の狙ったビットだけを残して 0か1かをチェックすることで、そのビットがどういう状態なのかがわかるというもの。

 

こんなことせずとも、RGBそれぞれ個別に配列作って管理してもいいんですけどね。

ちょっとやりたかったというか、まぁ自己満足ですね。

 

 

 

灼熱のサドル地獄炙り

昼休みに出かける際 暑いのでさっさと行って帰ってくるのがベスト。で、自転車に乗るのですが、屋外に停めてあるので黒いサドルがとんでもなく熱くなっていて、立ちこぎするしかない状況になる。尻の肉が焦げてしまいそうなくらい熱くなっている。気がする。そこでふと頭に浮かんだのが、料理名のような灼熱のサドル尻肉の地獄炙り。

語感も悪くない気がする。尻肉がなんかイマイチなので、肉の部位名を調べたら「イチボ」というらしい。テールはしっぽの付け根だし、ランプはもう少し上の腰のあたり。尻側のもも肉ということでイチボ。焼肉食べにいきたくなってきた。

それにしても普段エアコンのある場所で生活しているので、年々暑さ寒さに対する耐性が下がっている気がする。

 

 

 

ではでは

今回はこの辺で

 

P.S. ウィンルキンソンのパイナップルフレーバー炭酸が好きすぎる