横方向のアイテム選択UI
今回はコメントを頂いたのでその返答として書きます。コメントいただけると嬉しいですね。続けてきた甲斐があるってものです。
で、作ってみたのはこれ。
いくつかのアイテムを左右にスクロールさせて選択するメニューUIです。
今回アイテムの絵素材として FLAT ICON DESIGNさんとこのを使わせていただきました。
3つのWidgetアセットで構成します。
まず、選択用に並べるパーツとしての Widgetとフォーカス状態を表すWidget、
そして、これらをまとめるWidgetです。(※名前は適当です)
ではさっそく、並べる方から用意していきます。
WD_Item
UMGのキャンバスはシンプルです。
並べた時にくっつきすぎないようにCanvasPanel (Sizeは120x120)を置いて、中にImage を子供にして配置します。
つぎにGraphを編集します。
テクスチャを受け取る関数を用意します。
次にカスタムイベントを用意します。
これはフォーカスされているときと、されていない時で表示/非表示を切り替えるためのイベントです。カスタムイベント側の緑のピンは、右のSet Visibilityノードにある 緑のピンからドラッグ&ドロップすると、簡単に追加できて便利です。
以上で並べるためのパーツは完成。
次は、フォーカスされたアイテム用のWidgetを作ります。
WD_ItemFocus
これもキャンバスはシンプルな構成で十分です。
ただ大きいだけだと面白くないのでアニメーションを仕込んでみます。
キャンバスはこの辺で、次にGraphを編集します。
アイテムを切り替える際、見えない状態から始めないとタネがバレるので、カラーを透明にしておきます。
この Event Pre Construct につなぐと、エディット中から適用されるので、Add to Viewport したときのチラ見えを安全に防ぐことができます。
次はフォーカスされて表示を更新するカスタムイベントです。
フォーカスされるアイテムはプレイヤーの動作に応じて動的に変化するので、このイベントで、テクスチャを受け取ってアニメーションを再生しています。
以上でパーツのWidgetは用意できました。
次に、全体をまとめて管理するWidgetです。
WD_ItemList
まずUMGのキャンバスは以下の構成になります。
最初からあるCanvasPanelとは別に、新しく置いた CanvasPanel は、HorizontalBox をクリッピングする(CanvasPanel の外側は描画しない)ために用意しました。後述。
ヒエラルキーはこんな状態
HorizontalBox の大きさは、Size To Content にチェックをつけて有効にしておきます。
この時点で中身が空なので、左上に縮こまってます。
CanvasPanelの大きさは、実際に表示する大きさなので、アイテムWidgetのサイズと表示個数によって調整します。今回アイテムWidget1個のサイズが120x120で、7個が見えている状態にします。さらに両端に半分が見えるようにしたいので、合計8個分として 120 × 8 = 960 の幅で作成します。両端のチラ見せは、並んだアイテムの続きがあることを印象付けるためです。
今回アイテム120x120が 8個 並ぶ想定でサイズを決めました。
また、少し前のVerから、キャンバスの外側にも子供を表示する仕様がデフォルトになったので、キャンバスパネルの設定を変更する必要があります。
CanvasPanelを選んだ状態でのDetailsタブにClippingの項目があるので、これを探して変更しておきます。 Inherit → Clip to Bounds
最後に、フォーカス表示用のWidgetを、User Created の中から探してキャンバスに追加します。ZOrderの値を大きくして、手前に重なるようにします。
これは入れ子にはしません。
つぎに Graphの編集に移ります。
アイテムを半分チラ見せしたいがために、ちょっとだけ表示位置の調整が必要になります。
このために、Float型の変数を用意します。
CanvasPanel(CanvasPanelListと命名)のサイズを取得して計算し初期位置を求めます。
今回のスタイルのUIは、スクロール方向がキー操作と逆にしているので、アイテムWidgetの表示サイズをマイナスの値で扱うことにします。最初からマイナスにしておくことで計算が一つ減ります。
この辺の事前準備は、必要なパーツも最初からキャンバスに揃っているので、Event Pre Construct で行っています。
続いて Integer型の変数を2つ用意します。フォーカス中のIndex番号を保持するためのものと、アイテムを必要個数分生成して追加しつつスクロールの端をチェックするための2つです。
まん中の関数は、アイテムの中身をセッティングする関数です。内容は以下。
ここはプロジェクトのデータ管理方法によっては違うつくりになると思いますが、この関数で表示するためのアイテムを準備します。
後から追加しやすいことを考えて、テクスチャアトラスは作らず、バラバラで個数分用意しました。
200x200
それをTexture2D型の配列変数にあらかじめ登録しておきます。
大量のテクスチャアセットをインポートする場合に便利なのがこれ↓
コンテンツブラウザで、対象の画像アセットを複数選択しておいて右クリックすると選択できます。扱い方はこちらのブログに詳しく紹介されています。
【UE4】Sound Cueのパラメータを一覧して一括変更する方法 - SAT04 CREATIVE SPACE
サウンドアセット用の解説なのでパラメータは違いますが、基本的な操作は同じです。
次に、アイテムを並べていきます。 Forloop ノードを使って必要な個数分の処理を回します。
Create Widget したあとすぐに WD_Item型の配列変数に追加(Addノード)しています。これは、並べたアイテムを後からIndex番号で扱いたいからです。
HorizontalBoxに子供として追加すると自動的に水平方向(基本的に左から右)に並ぶことになります。
ひととおり並べ終わったら、フォーカス表示用のWdgetにテクスチャを渡すのと、アイテム名の表示を行う関数を用意して、Forloopノード Completeピンにつなぎます。
次はスクロールの動きを作る関数です。
慣れないとややこしそうに見えますが、このブログでもよく登場するやつです。
( 目的地 - 現在地 )× 減速率 を 現在地に足す ことで減速しながら目的地に向かいます。
減速率の 0.25 という値をいろいろいじって加減してみてください。
この関数を、Event Tick につなぎます。
次に、アイテムのフォーカス位置(FocusIndex)を増やしたり減らしたりするイベントを用意します。
左端のイベントノードは、一つはカスタムイベントですが、もう一つは、ボタンから生成したイベントノードです。作り方は簡単。編集モードをDesigner に切り替えます。
キャンバスのButtonパーツを選択した状態でDetailsタブの一番下にEvents という項目があります。この緑色の + ボタンをクリックすると、Graph に出現します。
このイベントで、FocusIndexの値が更新されたので、見た目に反映する必要があります。最後に関数をつないで完成です。
と言いたいところですが、あとひとつ関数を追加します。
これはフォーカスを切り替えた際に、非表示にしていたアイテムを戻す関数です。
このイベントの処理を簡単にまとめると、
という流れになります。
さっそくテストしてみましょう。
レベルブループリントから、ビューポートに追加します。
これで再生すると動きますが、キーボード操作用のイベントも追加しておきます。
再生してみます。
GIFなのでブラウザによっては速度がでないかもしれないですが、結構いい感じになったと思うのですがいかがでしょうか?左右の端に行くとループしているので反対側に一気に飛びます。
左右にある灰色の四角はUMGに用意されているもので、グラフィックを設定していないので、見た目はショボいですがちゃんと反応します。
今回決定処理はないですが、ひとまずそれっぽい挙動は作れたかと思います。
説明が分かりにくいところとかあればツッコミコメントをお願いします。
ではでは
ステキなアイテム選択UIライフを!
スピンボックスも作ってみた
前の記事でインターフェイスを作りましたが、せっかくなのでスピンボックスも作ってみることにしました。
スライダー同様、ゲームの設定画面とかでよく見かけます。スピンボックスはGUIの1スタイルでMicrosoft Developer Networkのドキュメントに簡単な説明があります → スピンボックス
▲と▼をクリックするたびに値が1段階変化します。数値を増減させるときに便利なのですが仕組みとしてテキストなどの文字列を入れて使われることもあります。数値のみの場合は明確に区別するために、 Numeric Stepper と呼ぶこともあるようです。
テキストの場合は、序列や段階を意図した言葉選びが重要です。したがって、このUIのメリットは 選択中の状態が分かりやすい ことと、その前後の内容が予測しやすい ことにあります。なので、増減と関連性のない、前後の脈絡のないテキストを選択肢にすると、複数ある選択肢の全体が把握しづらいため一通り送って確認しないと選べないので時間がかかります。
ゲームクリア後や実績解除などで、後から選択肢を追加しても画面のレイアウトに影響がないので、開発者にとっては都合のいいUI だったりします。でも途中で内容が増えても気づかないので、Newマークを付けたり、お知らせ表示を入れたりと、開発側としては結局手間がかかることに・・・。用法容量を守り正しく活用したいものです。
このへんがデメリットとなって使いづらさにつながるので、段階や序列のないテキストを選択肢にする場合はコンボボックスやリストボックスがオススメです。もちろん表示に余裕があれば全部並べて選択させるのがベストです。ゲームのUIってほとんどが選択肢と言っても過言じゃありません。なんらかの選択をユーザーにさせてユーザーの意思を尊重しているかのようにふるまいつつ、都合の悪い選択肢なんて最初から無かったことに・・・うわやめろなにを・・・ゴフゥ・・・中断
げふげふ(さてさて)、
今回はスライダーなどと操作方法を統一するために ↑ ↓ キーで項目を選んで、←→キーで内容の切り替えを行うようにします。レイアウトはスライダーと同じように並べるので↓のようになります。
新しくWidgetを用意してもいいですが、スライダーWidgetと共通の部分が多いので複製します。
UMG
不要なパーツを消してテキストブロックに置き換えます。
テキストブロックは is Variable にチェックを付けるのを忘れないように。
Widgetブループリント
必要な変数を用意します。複数の選択肢を扱うので、その個数を保持する integer型の変数と、受け取った選択肢用の Text型の配列変数です。
値を受け取る初期設定用の関数 initValue は ↓ のように変更します。
値を見た目に反映する関数 updateValue の中身は受け取った配列の中身を取り出すだけなので、 ↓ のようになります。
配列のエラーチェックをする場合は IS VALID INDEX ノードが便利です。
値をアイテムの数だけでループするようにするので、rangeCheck マクロを編集します。
これでコンパイルしてエラーが出なければ完成。
制御用Widget
キャンバスにできたスピンボックスのWidgetを追加します。
初期化の関数に追加します。
選択肢用のアイテムはここで、Make Arrayノードで渡します。
アクティブなWidgetを切り替える関数にも追加します。Switch on Int ノードをAdd pinして割り込ませます。
以上です。メンバーが一つ追加になったのにこの簡単さ。これもインターフェイスのおかげです。
さっそく再生してみましょう。
これでどんなセッティング画面が来ても対応できそうな気がしてきました。
あと必要そうな処理としては、制御用Widgetに値を返すようにしたり、変更内容をリアルタイムに反映したいときのために イベントディスパッチャーを用意したりすれば完璧な予感。
なんかUIって、表のグラフィックデザインにスポットが当たりがちですが、実際はこういった地味な内部処理が頑張ってたりするんですよね~ とか呟いてみたり。
コンポーネントとして用意されているので、わざわざイチから起こさなくても・・・
という声が聞こえてきそうですが、やっぱり仕組みを知ることで、次の新しいUIが生まれてくる気がするんですよ。
今回インターフェイスは使い回したので説明はしてませんが、汎用的なやつが一つあれば結構いろんなUIパーツに適用できるのでおすすめです。
というわけで今回はこの辺で。ツッコミとかリクエストとかあればコメントください。
ではでは
ステキなBPインターフェイスライフを!
スライダーUIを並べて操作してみる 《おまけ》
キーリピート処理の説明用にスライダーUIを作ってみたら、なんとなくいい感じになってきたので調子に乗って”初期設定に戻す”機能を追加してブループリント インターフェイスまで扱うとこまできたのですが、もうひとつ「ミュート」の切り替えができるようにして完了にしようと思います。
前回の記事はこちら
スライダーUIを並べて操作してみる 《続き》 - みつまめ杏仁
アイコン
まずはアイコンの仕込みから
テクスチャは簡単な2パターンを用意します。2つの画像に分けてもよかったりしますが、今回はマテリアルで切り替えます。
Size: 96x96
画像をテクスチャとしてインポートしたら、そこから Create Material します。
UVスクロールさせるだけのシンプルなマテリアルです。
Interface
インターフェイスにあとひとつ関数名を追加します。
これは A とか 〇 とかの「決定」ボタンを押したときに呼び出すイベント用です。
コンパイルして閉じます。
ここでちょっと注意が必要なのが、インターフェイスを編集して保存すると、そのインターフェイスをセットしているブループリント全てが、再コンパイル対象になってしまうということです。
なので、UE4を閉じるときに、普段触っていなかったブループリントが「save する?」
って訊いてきて慌てることがあります。インターフェイスを触るときは、心の準備をしてから触るようにしましょう。エラーが出てるとマズいので、アスタリスクのついたアセットは、面倒ですが一度開いてコンパイルしてから閉じるようにします。
今回のスライダーのWidgetはそのまま編集をします。
UMG
まずはスライダーWidgetのキャンバスにアイコンを配置します。
Imageパーツを表示したいサイズに調整して、作っておいたマテリアルをセットしてやります。
Widgetブループリント
つぎにGraphの編集です。
まず、 ブーリアン型の変数 "isMute" を一つ追加します。
この変数を使って見た目を切り替える関数 "switchMuteDisp" を用意します。
今回 ミュートのON / OFF で変更するのは3パーツ。
扱う値が違うので結果的に3種類のSelectノードを使うことになりました。
Selectノードは ブーリアン型の値、 True か False を判定して、あらかじめ決めておいた対応する値を取り出してくれる大変便利なノードです。Brunch ノードで組むこともできますが、白いラインが分岐することを考えると、Selectノードを使う方がスッキリして見えます。
つないでみて気づくのですが、テキストのカラーについては SlateColor と呼ばれる特殊なカラー値なので、LinearColor が使えません。さらに面倒なことにSelectノードの上下にならんだピンの扱いがなぜか逆という・・・
さてさて 見た目をチェンジする関数ができたところで、初期設定用の関数に追加した変数を加えます。
新しく引数(Inputs)ピンも追加。最後に、できたての見た目チェンジ関数をつなぎます。
あとはボタンを押したときに呼ばれるイベント。
ブーリアン型の変数 isMute の内容をスイッチングします。スイッチングしたあとは見た目に反映するためにつくった関数をつないでいます。
これでスライダー用のWidgetは完成です。
制御用Widget
スライダーWidgetの 初期設定用の関数に引数を追加したので、ノードが変化しているのが確認できます。
前回の記事でリセットするイベントを作りましたが、そこにノードを追加します。
単純にIndexFocus の値が3 以外なら、onDecide のイベントを呼ぶようにします。
これで、できあがりです。
再生して確認してみましょう。
うまくいきました。
ただこの操作はユーザーが気づきにくいので、画面の隅にでも操作ガイド的なナビ表示があった方いいでしょうね。マウスクリックを受け付けるなら、おそらく直接スピーカーのアイコンを触るので心配なさそうですが、そうなるとアイコンのデザインも、もう少しボタン感のある見た目にしないといけないですね。
ひとまずスライダーUIに関してはここまでにします。
ではでは
ステキなスライダーUIライフを!
スライダーUIを並べて操作してみる 《続き》
前回それなりにサウンド設定ができそうな雰囲気を醸し出すことができました。
そこにリセット用の項目を追加してみます。
右端にもオマケでアイコンを追加しています。
前回の記事はこちら。limesode.hatenablog.com
↑これをベースにいじっていきます。
Blueprint Interface
今回の肝は、タイプの異なるWidgetを並べて操作できるようにする部分になります。そのための準備として、インターフェイスを用意します。
とりあえず ”IF_Settings” と名付けました。
ダブルクリックして、エディタ右上にある追加ボタンで関数名を4つ追加します。
コンパイルして保存したら閉じます。
Interfaceを登録
前回作ったスライダーパーツのWidgetを編集します。
エディタをGraphに切り替えて、Class Settings をクリック。
Detailsタブに Interface と書かれた項目があるので、そこの Add ボタンを押して、
さきほど用意した IF_Settings をセットします。
ここでコンパイルするとエラーがでます。
同じ名前のカスタムイベントがいるためです。
そこで急いでイベントを入れ替えていきます。
いつものグラフで右クリックしてノードが検索できるやつで探すと出てくるので、つないであったカスタムイベントと入れ替えます。ちょっと見つけにくいけど、Add Event の中にいます。
Add Event カテゴリにいるのは、Interfaceで関数に戻り値(Return Value)を設定しなかったのが理由ですが、UE4は戻り値がない関数はイベントとして扱われることがあります。頭に"Event" と付けられていますが、本当の名前には付いていないので名前が被ることになりました。
↑この4つのカスタムイベントをいったん削除して付け替えます。 ↓
これでコンパイルしてもエラーは出なくなります。
ちなみに、Interfaceで定義した関数はグラフに置けるのは1つだけなので、2つ目を置こうとすると、すでに置いてある場所にフォーカスが移ります。こういった事故を防ぐ仕様がステキですUE4。
スライダーの改造をひと段落つけて、新しいWidgetを用意します。
テキストだけのWidget
キャンバスには、スライダーと同じ大きさとカラーのテキストブロックを一つレイアウトします。
is Variable にチェックを付けておきます。
フォーカス切り替えのアニメーションを付けておきます。
グラフでスライダー同様にイベントノードにつなぐのですが、先に Interfaceを登録します。
コンパイルしてエラーが出ないのを確認したら、Animationの再生ノードをつなぎます。
このテキストだけのWidgetも汎用性を高くしたいので、初期設定用の関数を用意しておきます。
テキストを受け取って差し替えるだけのシンプルな関数です。
これで完成です。
つぎは前回作った制御用のWidgetを編集します。
制御用Widget
作ったテキストだけのWidgetをキャンバスに並べます。
初期化している関数 を編集します。
まず配列変数周りをなくして、
テキストだけのWidgetに、ラベルのテキストを渡します。
この関数の外にスライダーWidgetの数を保持する変数が待機しているので、そこに今回の項目数を戻り値として渡します。
配列変数で処理するのをやめたのは、選択項目に別のWidgetが追加になって配列変数が使えなくなったためです。配列変数の型を wd_slider型 から UserWidget型 に変更しても、その配列変数には 1種類の同型のUserWidgetしか格納できませんでした。
2022/7/16 追記>>
あらんさん にご指摘(ブログ下部コメント参照)いただいて調べてみました。
記事を読み返してみて、前回の操作ですでに並べたWd_Slider型のオブジェクトから Promote to variable(変数に昇格) してる箇所があります。それを配列にしてるのでUserWidgetというカテゴリではありますが、VariableTypeが Wd_Slider型の配列になっているので、当然別のWd_TextOnly型が挿さらないわけです。当時の自分にツッコミに行きたい。
というわけで、Promote to variable せずに 新しくVariableTypeが UserWidget型の配列変数をつくるとエラーが出ずに、スルっと追加できます。
← ver4.27のキャプチャ
あらんさん ありがとうございます!
記事を書き直そうかと思ったのですが、差し替える必要のある画像が結構あって、しかも手元にあるUE4系は 4.27しかない状況。4.27でもプロジェクトファイルを開くことができました(この記事執筆時は4.18使用)が、アニメーションの挙動がおかしいのと、微妙にエディタのUIが変更になっているので、改めて UE5 で書き直そうと決意しました。便利なイベントも増えてますし。少し先になりますがなるべく近いうちにUE5版を公開します。
この記事を見つけて試していただいているのに失敗につき合っていただくのは心苦しいのですが、修正範囲が大きいのものあり、動かないわけではないのでこの記事はこのままにします。
<<
そこで、前回は wd_slider型のWidgetしかなかったので、配列変数に格納できてそこから数を調べていましたが、今回は直接数を数えて返しています。
ちょっと改造の手間を省こうとしてマジックナンバーで対応していますが、
プログラマに渋い顔をされてしまう場合は、親になっているキャンバスやその他パネルで余計なパーツが入らないようにしておけば、下のようなやり方もオススメです。
ゲームに限らないと思いますが、UIを作るうえでのあるあるのひとつに、あとから項目の数が変更になるのが挙げられるとおもいます。すべてのノードをいつまでも把握しておくのは難しいので、あらかじめヒューマンエラーを起こさないように自動化しておくのはできるだけやっておいた方がいいです。
つぎに新しく、UserWidget型の変数をひとつ追加します。
前回作った関数 "changeActiveWidget" を大改造します。
↑これを ↓このようにします。
ActiveWidgetという器(UserWidget型の変数)に、適宜該当するWidgetを入れていきます。
右端のフォーカスするイベントの呼び出しは、Interfaceで定義した関数にしないといけないので、検索して 作ったInterfaceカテゴリ探します。
これが Interface の持つステキな仕様です。
なかなかピンとこないかもしれないですが、ActiveWidget に何か適当なWidgetが入っている状態だと仮定して、呼び出すための関数ノードをつなぐことができるのです。
普段、関数を呼び出す際には、呼びたい関数を持っていないとつなぐことができませんが、Interface 経由だと、関数を持っていなくても問題にならないのです。
あとは、wd_Slider型の ActiveSlider変数でつないでいるところを、入れ替えていきます。
後はここ↓
入れ替えてコンパイルに問題なければ完成です。
お役御免になった変数を削除します。
確認してみましょう。
フォーカスがいい感じに切り替われば成功です。
仕上げに、リセットのイベントを追加します。
フォーカス用Index番号が 3 の時だけ受け付けるようにします。ちょっと雑ですが初期設定の関数をそのままつないで初期化として使ってます。
完全に初期化するか、変更前に戻すかは、決めておく必要がありますが、丁寧に作るならリセット用の関数を用意しておくと後から融通を効かせられます。スライダーWidgetの方にも指定した値に変更する関数があった方が便利です。
Index番号で判定する方法については、あとから項目の数が変わったり順番が入れ替わると、きちんと値を変えてやる必要があります。対策の方法はいくつかあると思いますが、今回はこれで。
レベルブループリント
リセットのイベントを呼ぶために、Inputノードを追加します。
動かしてみます。
いい感じになってきました。
いろんな種類のWidgetパーツを、Interface と UserWidget型の変数 を使って、汎用的に処理する方法を紹介してみました。
長くなったので今回はこの辺にします。
ではでは ステキな設定初期化ライフを!
スライダーUIを並べて操作してみる
前回 キーリピート処理を試すために作ったUIパーツのスライダーを並べてそれっぽく操作できるようにしてみようというのが今回の記事です。
まずは スライダーのWIdgetにパーツとアニメーションを追加します。追加するパーツは3つ。
項目名を示す「ラベル」と、「左右のキーを入力してね」と「いまこの項目を触ってるよ」という意味を持たせる一組の < > になります。
ラベル用のテキストブロックは、ブループリントから内容を書き換えるので、
Is Variableにチェックを付けます。あとの< > はブループリントからは直接触らないので画像等にしてもOK。
アニメーションは全部で4種類。
- FOCUS ・・・ フォーカスされたとき
- UNFOCUS ・・・ フォーカスが外れたとき
- RIGHT ・・・ →右キー を入力したとき
- LEFT ・・・ ←左キー を入力したとき
左右のキーを入力したときのアニメーションは、ツマミが動くので、必ず必要ということはないですが、キー入力に対するリアクションは、分かりやすいほどユーザーフレンドリーなUIとなります。また、ユーザーは操作しながら無意識的にこのリアクションを記憶して学習していくので、「< > を見かけたら 左右入力できそう」というUI操作に対する期待感を育てることができます。これもひとつのUXです。デザインの段階で記号化のルール作りはとても重要になります。ここ試験に出ますよ(何のだ?)
Widgetブループリント
初期状態の値をもらう関数にラベルをセットする処理を追加します。
Text型の引数(Inputsピン)を追加します。最後にスライダーの値更新用の関数をつないでいます。これは前回作ったやつで、処理の順番でおこる不具合対策でここに移動してきました。
というわけで、EventGraphが変わります。
前回↓のようになってたのを・・・
こうします。
ついでに左右キーを押したときに呼ばれるイベントなので、アニメーションの再生もここで行います。
続いてフォーカスとアンフォーカスのアニメーションを再生するイベントを新たに用意します。ただアニメーションをイベントとして再生するだけなのでカスタムイベントにつなぎます。
この2つの違いは、アニメーションの作り方によるものです。
UMGのアニメーションは、基本的にタイムラインの最後まできちんと再生しようとします。しかも同じ要素でのアニメーションがバッティングするとあとから再生したもので上書きされます。最終的に尺の長いアニメーションが勝ちます。
今回の4つのアニメーションでは、左右キー入力したときの LEFT と RIGHTのみ 0.2秒の尺で作りました。フォーカス切り替えでは、0.0 にキーを打っただけです。4つともカラーアニメーションです。
なので、左右キーを押して ”LEFT” か ”RIGHT” のアニメーションしている途中(0.2秒までの間)でフォーカスを切り替えると、一瞬だけ ”UNFOCUS” が再生されて残りの ”LEFT” か ”RIGHT” のアニメーションが再生されます。結果、”UNFOCUS” のアニメーションは敗北することになります。今のところ解決策は2つが考えられます。
- 動いているであろうアニメーションを止める
- アニメーションの要素がバッティングしないようにする
この挙動はバグにも思えますが、利用できる場面があるので、修正されないことを願っています。しくみが分かればきちんと対策できるので。(修正されるといろいろ面倒な処理が必要になる・・・)
このあたりの仕様を踏まえて、必要に応じてStopAnimationノードをつなぎます。
これでスライダーは完成です。次に並べて制御するためのWidgetアセットを新しく用意します。
制御用Widget
スライダーが一番マッチするサウンド設定の想定です。
キャンバスにバージョンアップしたスライダーWidgetを並べます。
ブループリント
まずは準備する関数 "initSlders" を用意します。
この関数を、EventPreConstructionにつないでコンパイルしてみると、
キャンバスの内容が書き換わるのが確認できます。
引き続き関数を編集します。変数を2つ用意したいのでスライダーのWidgetノードから、Promote to Variable を2回します。
作ったらすぐに消します。Variables のリストには残るので、リネームして一つは配列化します。
この配列の方を関数に Set で戻します。そこに Make Array ノードをつないで、キャンバスに並べたWidgetを登録します。
仕上げに int型の戻り値(Outputsピン)に配列の登録した数をつないでこの関数は完成。
次に、int型の変数を2つ用意します。
配列に登録した3つのスライダーWidgetは、0~2の番号を使って扱うためと、スライダーの個数を保持しておくための変数です。
先の関数からの戻り値を受け取るような形でつなぎます。
この続きには操作するスライダーWidgetを切り替える関数 "changeActiveWidget" を作って、
くっつけます。
あとは、前回のキーリピート処理を、レベルブループリントから持ってきます。
変数は移植できないので、ちょっと面倒ですが再び同じようにfloat型、TimerHandle型、Boolean型の変数をそれぞれ1つずつ用意しつつ、
カスタムイベント "onKeyPress" を置いてつないでいきます。
TimerHandle型は、Set Timer by Event のReturn Valueピンから Promote to Variable してもOK。
DoOnceノードのResetピンにつないでいた部分は、新たにカスタムイベントをつなぎます。
前回のキー入力部分は、
コンパクトにします。
そして最後に、上下に並んだスライダーのフォーカスを順次切り替えるイベントを用意します。
上の2つのイベントは、←→左右キー用と、↑↓上下キー用になります。
それぞれの処理は上下と、左右で内容がほぼ同じなので、trueかfalseか、プラスかマイナスか、で分岐するようにして使い回せる形にしました。
このWidgetは一応完成です。
レベルブループリント
前回のレベルを改造するのであれば、Add to ViewportしていたWidgetが変わるので、前回 Promote to Variable していた変数が使い物にならなくなります。
Input ノードで、↑ ↓ ← → のキー入力を検出して、それぞれのイベントを呼び出します。
これで完成です。
再生して確認してみます。
うまくいきました。
今回はこの辺で。それなりの操作ができるとこまでは来たかなと思います。
次回もうちょっとだけ手を入れます。
ではでは ステキなスライダーUIライフを!
キーリピート機能をつくってみる
パソコンでキータイプしていると気づくこともあると思いますが、カーソルキーとか同じキーを押しっぱなしにしたときに、1文字目と2文字目の間にちょっとだけ間があります。タン、タタタタタタ・・・・・・ 初めてパソコンを触ったとき、当時子供だった私はモヤモヤした経験を覚えています。UIを意識するようになってようやく腑に落ちたものです。
ゲームでも、音量調節のスライダーなんかにごく当たりまえに実装されているので、身近なやつを見れば・・・って、最近はタッチデバイスなのであまり身近じゃないですね。でもまだPS4とかゲームパッドで遊ぶUIはあるので、今回ブループリントで作ってみました。
UMG
キーリピートの挙動を見るために、シンプルなボリュームスライダーを用意します。
キャンバスに3つのパーツを配置します。
右端がMAX想定なので、ツマミは左端に置きます。
ブループリントから触るので3つとも Is Variable にチェックを付けます。
触るといっても、Image_Baseだけはサイズを取得するだけです。
Widgetブループリント
変数を2つ用意します。
まずは、スライダーの移動範囲を調べて変数に入れます。
すでにキャンバスに置いているパーツの長さを調べるので、Event Pre Constructで問題ないです。Get Size で調べたImage_Base(ラインみたいなやつ)の長さを変数 MaxPos に Set します。これは、後からデザインやレイアウトが変更になっても、キャンバスを調整するだけ(Blueprintは無傷)で済むのでオススメです。ひと手間かかってますが、こういったつくりは後で地味に効いてきます。
次に関数を2つ用意します。
まず一つ目は指定した値の場所にツマミを移動して、値を表示する関数になります。
便利な Lerpノードを使います。AとB2つの値を線形補間したうえで、Alphaの値に応じた途中の値を返してくれるノードで、MaxPos を Bのピンにつなぐことで、どんな長さのスライダーでも対応できるようになります。MaxPosはパーツの長さを調べてるのでデザインが変更されても、勝手にこのBの値が変わっていることになります。
もう一つは初期状態の値をもらう関数です。
ゲームでは設定した値をセーブしているのが普通なので、まずは初期値を受け取れるように関数で用意します。この int型の Value という変数は、念のため Private にチェックをつけておくと安全です。
関数が用意できたら、今度はマクロを用意します。
受け取った値を Valueに加算して、0~100の範囲を越えないようにします。
越えたらループするようにしています。
減っていくとき、3 → 2 → 1 → 0 → 100・・・
増えていくとき、98 → 99 → 100 → 0・・・
このWidget以外から呼ばれる処理ではないので関数ではなくマクロにしました。
仕上げにイベントを用意していきます。
カスタムイベントを2つ置いてマクロと関数をつなぎます。
引き算のノードを使わなくても、マイナスの値は、足し算することで引き算と同じ結果になります。汎用性の高いマクロや関数を作るときに応用できます。
Widgetは完成です。
次は動かすためにレベルブループリントを編集します。
レベル
おなじみのやりかたでWidgetをViewportに追加すると、表示された瞬間EventConstructが動くので、先に初期値を渡しておきます。
試しに再生してみると、
ちゃんと反映されていればOK。
では、キーリピートの処理を作ります。
必要な変数を用意します。
まず、float型の変数は、キーリピートの間隔を保持します。スライダーが増えるか減るかをフラグで管理するためにBoolean型の変数を。一番上の RepeatTimerHNDLという名前の変数はTimerHandle型の変数で、Set TImer Event ノードの戻り値 Return Value ピンからPromote to Variable すると簡単に作れます。
カスタムイベントを用意して、下のようにつなぎます。
右端のノードは、Widgetに作っておいた関数を呼び出しています。増えても減っても基本的なキーリピート処理は変わらないので、極力使い回せるものは使い回すようにしています。
続きはこのようになっています。
DoOnce ノードでキー入力初っ端だけ通すようにして、2回目以降のリピート間隔を float型の変数にセットしています。
まだ続きます。DoOnceはリセットすることができるので、キー入力をやめたらリセットするようにつなぎます。下図はカーソルキーの左右を押した場合です。
これで完成です。
さっそく 動かしてみましょう。
キー入力を開始して、ひと呼吸置く感じで動きます。
基本的な仕組みはイベントの自己呼び出し。
あとは、DoOnceノードで、RepeatInterval 変数の値を 0.75 から 0.025 に変えます。
これでキー入力後の《 間 》が作れたことになります。この値は 1.0 が1秒です 。フレームレートによりますが 60fpsだと、0.0167 くらいで 1フレーム分です。これを変えてみるといい感じに調整できますよ。
この《 間 》が無いとどうなるか、0.75 をセットしてるとこを 0.025 にしてみると答えが分かります。
次回は、せっかく作ったスライダーをいくつか並べて操作できるようにしてみようと思います。
ではでは
ステキな キーリピート ライフを!
UIデザインというお仕事
気がつけば2018年の12分の1が終わってちょっと焦り気味な今日この頃。残り少ない平成時代を精一杯堪能したいと思うんだけど、何をすればいいか思いつきません。あ、平成30年発行の硬貨を記念に取っておくとか?
それはさておき
2018年最初の記事は雑記で始めてみたいと思います。画像もなんもなく文字だらけなうえにテクニックとか有益な情報はないので悪しからず・・・
とりあえず、UIデザインというお仕事について、私なりに感じている事を書いていきます。
自分のこと
私がゲームのUIを作る仕事に携わってかれこれ四半世紀。UIという言葉がまだなかったころから活動しております。最初は専門職という認識は誰にもどこにもなく、新人や比較的手の空いたメンバーが担当するのがUIだった時代です。
最近では専門職としてブイブイ言わしてもらえ・・・てはいないですが、一応専門職ということになっております。今までにいろんなUIを作ってきました。「作った」と言っても一人で作れるわけはなく、私はデザイナーなので実際はプログラマとの共同作業です。
アーケードゲームの開発から始まって家庭用のコンソール機用ゲームと多くのタイトル開発に携わっていろんなことを学びました。 今やアーケードゲームはほとんどが音ゲーや特殊な大型筐体ばかりになってしまいましたが、このアーケードゲームにおいてのUI開発はとても多くの示唆を与えてくれました。その知見が今でも十分に役に立っています。このへんのノウハウはうまくまとめられる自信がないので、今後それとなくこのブログに散りばめていこうと思っています。
ゲームのUI
先に書いた通り私はゲームのUIを専門にしています。ゲームのUIというのはゲームという面白い遊びに、ユーザーを引き込んでコミュニケーションするためのもので、ユーザーとゲームシステムがコミュニケーションするためのインターフェイスのことを指します。「インターフェイス」という語についてはニュアンスをうまく伝えるのが難しい語なのですが、平易な書き方で「やり取りのための仕組み」くらいがゲームUIとしては一番近いニュアンスかなと思っています。そのUIを考えるうえで外せないのが「時間」の扱いです。
プレイ時間とUI
ゲームにはユーザーがプレイし始めてから終了するまで、という「時間」があります。この「時間」はとても重要で大変貴重なものです。もちろんUIにとっても。
昔のアーケードゲームでは単位時間あたりのインカム数(コイン投入数)がビジネスとして重視されました。プレイ開始から、いかに短時間でプレイヤーを満足させるか、またゲームオーバーになっても、コンティニューしたくなるようにするのが課題でした。そのためにはキャラクター選択や必殺技のデモ演出というようなゲームプレイ(体験)とはあまり関係ない画面はインカム数にダメージを与えるとして、徹底的に切り詰められました。とにかくパッと見て必要な情報をシンプルに分かりやすく配置し、短時間に何をどう操作して決定させるかが、UIに課せられた任務でした。100円で満足するには、UIではなくキャラを操作したいですよね。
残念ながら、どれだけ素晴らしいデザインのキャラセレ画面やアイテム管理画面なんかを操作しても、それを思い出として語るプレイヤーはなかなかいませんし、ゲームをプレイしたという思い出にUIは含まれないものと了解するしかありません。空気のような存在感のUIでユーザーがゲームプレイに満足できれば、そのゲームのUIは素晴らしいUIである。と本気で思っています。
ちなみに「GAME OVER」という表示は、アーケードゲームならではの演出です。その理由のひとつとして、ゲームセンターという公共の場所にカギがあります。乱暴な書き方をすると、ゲームオーバーの文字が表示されたら、さっさと席を譲れ。ということです。あなたの100円でのプレイ可能時間はここで終わったんだよ、と。
アーケードゲームに限らず、このプレイ時間(アプリの場合は操作時間)というものは、プレイする側にとっても、プレイさせる側にとっても大変重要な要素であることは疑いようもなくて、ここの意識が弱いと、必要な操作なのに分かりにくくてモタついたり、目的の状態まで進めていくのに演出の尺が長いといった、テンポの悪いUIを許してしまう恐れがでてきます。
そういえば、UIを操作している時間が貴重であることに気づいた偉い人が、何年か前にUX(ユーザー体験)という言葉をハイライトして、ユーザーがよりシンプルでスマートに目的を達成するためのUI設計を考えましょう。と提案したことで「UI/UX」というパワーワードが俄かに脚光を浴びることになったのだと思っているのですが、ゲームセンターでの教訓を学術論文にでもして発表できていれば、ゲームの世界からUI/UXが語られていたかもしれません。
さてさて、この「時間」というものを意識してUIを考えると、ただグラフィックがそこにあるだけではUIは成り立たないことに気づきます。グラフィックの存在と合わせてそのグラフィックの行方をどうするか、がUIを作るうえで重要になってきます。
振る舞いと意味
ゲームシステムからUIを通じて選べと言われてユーザーは選んで決定し、ゲームシステムはその情報を元に次のフローに進みます。このゲームシステムとユーザーとの対話がスムーズに進むようにするのがUIの役割です。ユーザーに不安を抱かせないように気つけながらいろんな手段でユーザーに選択を促しその結果をフィードバックします。この時の対話で何らかの動き=「振る舞い」を使うとやり取りの効率がぐっと上がります。
分かりやすいのは決定した項目をハイライトしたり、カーソルを点滅させたりとかです。UIとして画面に表示されるグラフィックはすべて、必ず何らかの振る舞いを持ちます。「意味を持つ」と言い変えてもいいでしょう。パッと出て、パッと消えるのも振る舞いです。ユーザーの目に触れてから消えるまでの存在すべてが振る舞いであり、ユーザーへ伝わる「意味」となるのです。
動きには当然時間が伴います。時間といっても非常に短い時間ですが、この小さな動き=振る舞い(新しい言葉でマイクロインタラクションとでも言えばいいのでしょうか)がつながってゲーム全体のテンポに影響を与えます。
UIデザインというお仕事
だいぶ回りくどい感じになってしまいましたが、ようやく表題に近づいてきました。
UIはメタで抽象的な世界から答えを見つけてきて表現することが多いので、「正解」というものはいくらでもあります。その中から「Bestな正解」を、ワインのソムリエのごとく見つけてきて開発チームに提案します。それが受け入れられれば幸せですが、受け入れられなければまた数多の正解の中から次の正解を探す旅に出ることになります。ステージに草を生やしたい。と言われれば草に見えるものを置けばいいのですが、UIにはゲームシステムからの情報伝達という大事な役割があるので、そういった具象的なものをそのまま置くだけでは意味が伝わりにくいです。ユーザーにUIとしての存在意義を疑われたらおしまいです。齟齬やミスリードの無いように慎重に設計する必要があります。どういったタイミングで、どういった情報をユーザーに伝えて判断を促すのがいいのか、どういった振る舞いをすればゲームシステムとの対話になるのか、どこまでが装飾であるのか、こういった部分をひたすら考えます。
振る舞いも含めてデザインするためには、何らかの動き(アニメーションなど)を検証するところから始めて、Bestな正解を探します。光り方や点滅速度、移動の方向や速度など、ある程度はAfterEffectやDCCツール、Flashなどタイムラインの仕組みを持つツールがあれば検証できますが、それはあくまでもシミュレーションにすぎません。やはり対話には「コール&レスポンス」の心地よさが大事です。そこを実際に動かすことができるのは、ソフトウェアとしてのプログラムということになります。UIパーツの振る舞いを考え、それをプログラマに伝えて組み込んでもらいます。
動いたら触ってみて感触を確かめます。手触りの悪いところは調整しつつ、仕様要件を満たしているか、また将来追加される仕様に対応できるかなども確認します。こうして限りなく製品に近いモックアップをプログラマと一緒に作って、レビューしてもらうのが理想形です。
プログラマに面倒な動きをお願いしたり、レビューの意見に振り回されたりしつつも二人三脚で作っていくのがUIデザインというお仕事の基本になります。このあたりの具体的なテクニックやノウハウなども、機会があれば書いてみようと思います。
アンリアルエンジン推し
UIをデザインしてモックアップを作る環境が理想なのですが、そこにはプログラマが必要です。絵にかいた餅を食べられるようにしてくれるのがプログラムなのです。デザインして食べてみておいしいかどうか。これを繰り返してUIデザイナーという職能(=スキル)を高めることができると思います。一人でUI開発するのは難しいです。振る舞いをどうにかしてくれるプログラマの存在が欠かせないからです。
ただ画面のイメージ画を作って見せても、いまいち具体的な操作感や振る舞いをイメージしてもらうのは、頭の中でシミュレーションできる人じゃないと難しいです。
なんとかして動くものを見てもらいたい、そのための研究としてOff-JTしたいなーと思っていたところにアンリアルエンジンと出会って、すっかり魅了されてしまいました。シンプルで柔軟なアセット管理ができるのも分かりやすいし、作ってみたいUIをすぐに試せる気軽なUMGとWidgetブループリントはもう手離せません。プログラマにお願いする前にプロトタイプを作って検証できるのがとにかく素晴らしいのです。アイディアを形にしやすいのがアンリアルエンジンのビジュアルスクリプトだと思っています。ブループリントの魅力は触ってみて実際に作ってみないと実感しにくいので、言葉で説明するのはやめておきます。UI的にどんなことができるかは、当ブログの記事で確認してもらえればと思います。まだまだこれからも頑張って紹介していくので、興味が沸いてきたならぜひ触ってみてください。
結構な文字量になってきたのでこの辺で筆を置こうと思います。(←古風な言い回しですがこの表現好きです)
UIデザインって実はとても大変なんです。グラフィックデザインを学んだからといって一朝一夕でできるようなお仕事では決してないことを言いたいがために、時間と振る舞いに重点を置いてみたのですが、いかがだったでしょうか。長くこの仕事をしていると、一家言どころか、百家言くらい言いたいことがあったりするのですが、こちらもまた別の機会にします。感想やらツッコミなどありましたらコメントとかでよろしくお願いします。
ではでは みなさん
ステキなUIデザイナーライフを!
今年も当ブログをよろしくお願いいたします