時は四年前。 2018年の2月に公開した内容で、間違いに気づかず、問題を避けるかたちで解決したことにしている部分があって、その雪辱戦といいましょうか、修正するにも当時のバージョンはすでになく、空きのないドライブと格闘してスクショ撮るのも大変そうなので、UE5版としてリニューアルすることにしました。配列変数の作り方を書いてるのはよかったのですが、既存のWidgetから昇格して作ったので、違うWidgetが追加できるわけもなく、当時の自分はこのミスに気づいてなくて、先日コメントでご指摘いただきました。今更ながら恥ずかしい限りです。
該当する一連の記事はこちらになります
スライダーUIを並べて操作してみる 《続き》 - みつまめ杏仁
スライダーUIを並べて操作してみる 《おまけ》 - みつまめ杏仁
結構な量書いてるな・・・。
4年越しでコピペするのは面白くないし、伝わりにくそうなとこを補間しながら、なるべくコンパクトを目指しつつ、UE5の差分とかで気になるものがあれば書いていこうと思います。
変数名やオブジェクト名、アセット名など一部名称を変更しているものがあります。
キーリピート機能をつくってみる
この機能を作ったところから始まったのでここから始めます。
まず Widgetブループリントを一つ作成。
コンテンツブラウザで右クリック > User Interface > Widget 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 らしく複数のスライダーを並べて操作できるようにしてみようと思います。
ではでは
ステキな キーリピート ライフを!