目的
UnrealEngine5を用いて、以下の条件を満たしたインベントリシステムを作成したかった。
- アイコンが並んでいる
- アイコンのクリック等の操作により、アイテムの説明が表示される
- アイコンのクリック等の操作により、アイテムに対する処理(使う・捨てる等)の選択肢が表示される
- 同じアイテムはスタックされ、いくつ所持しているかはアイコンの横等に数字で示される
要するにブレワイと似たシステムである。
上記画像はゼルダの伝説ブレスオブザワイルド 公式サイト より引用
動画
上記条件を満たすインベントリシステムの作成方法を紹介してくださっているYoutube投稿者がいた。
今回は、Beardgames氏による下動画のほとんどを用いた。
このインベントリシステムのすごいところは、レベルを移動してもインベントリ内容が保存される機能である。
これはなかなかない魅力。
Beardgames氏の動画
全編英語だけど自動翻訳字幕を使えば問題ない。超わかりやすい。
最初にダウンロードを要求されるが、ダウンロードしたものをそのまま使うだけで終わりではなく、必要なBPを自ら書き込む内容となっているので勉強になる。
動画はメイン2編+Final Tweaksの全3パート。最後の動画を見ないと一部うまく機能しない部分があるので必ず視聴する。
ただインベントリシステムが欲しいだけならこれで全てが賄える。
しかし、一部ちょっと使い勝手が悪かったり変更したい部分があったので、自己流で修正・追加した機能をここに備忘録として示したい。
従って本記事は、Beardgames氏の動画で紹介されたインベントリシステムをどのように編集したかの備忘録となっている。
編集・追加した機能
具体的に作成した機能は以下。
- アイテム取得時のキー入力編集
- アイテム情報を格納するSlotのウィジェット
- 使えない・捨てられないアイテムの実装
- インベントリウィジェット表示時のアニメーションを追加
- クラフトシステムの追加
1.キー入力について
アイテムを取得するキーと、別件で作成した会話システムに設定した話しかけるキーを同じにしたかったので、アイテム追加は会話システムの分岐に入れ込む方法を取った。
アイテム取得ノードにある、BPI(BluePrintInterface)関数の名前がBeardgames氏で指定されていたものと異なるが、これは会話システムで作成したBPIとの違いを分かりやすくするためである。実態は変わらない。
2.ウィジェット部分について
アイテムアイコンのマウスホバーにより説明が表示され、アイコンのクリックにより使う・捨てるボタンの表示が行われる。
このシステムそのものは変えなかったが、アイテム説明欄の位置・枠等を一部変更した。
もとのBeardgames氏の動画そのままのウィジェットだと、長い文章を入力すると読みにくくなってしまう。「説明文枠の追加」「説明文枠の位置変更」によりこれを解決しようと考えた。
また、Beardgames氏の方法だと、一度使う・捨てるボタンを表示させると、再度同じアイテムをクリックしないとボタンが消えないという仕様がある。
すなわち、一度アイテムAをクリック→隣にあるアイテムBを使いたくてクリック とすると、
アイテムA・Bそれぞれの使う・捨てるボタンが重なって表示されてしまう。
分かりにくいが、青いアイテムの「使う・捨てる」選択肢の上に赤いアイテムの選択肢が重なる。ちょっと気持ち悪い。
これを解決した。
3.使えない・捨てられないアイテムの実装
よく見る「大事なもの」枠である。
「捨てられないけど使える」「使えないけど捨てられる(?)」というアイテムも作成可能。
こんな感じ。
4.アニメーション追加
画質がとんでもないことになっているがこんな感じ。
5.クラフトシステムの追加
欲しかったので追加した。これはかなりボリューミーなので別記事に記録する。
以上5点の内、詳細が必要な2(ボタンの表示)、3(使えない、捨てられないアイテムの実装)、4(アニメーションの追加)について以下で解説する。
使う・捨てるボタン表示の制御
やりたいことは「今クリックされたスロット以外のスロットの使う・捨てるボタン表示をオフにする」機能である。
Beardgames氏のインベントリシステムを作成したならなんとなく分かると思うが、それぞれのアイテムスロットウィジェットはバラバラではない。
ひとつのスロットウィジェットを参照した変数の配列(Array)を、インベントリ本体のウィジェット側で作成することで複数のアイテムスロットを実現している。
(Slotの名前こそ違うが、中身はすべて同じ一つのウィジェット)
したがって、クリックされたスロットとそれ以外のスロットを分別する制御は、Inventory本体側で行わなければならない。
まずはカスタムイベントを作成する。カスタムイベントの名前は何でもいい。
このカスタムイベントにインプットを追加する。
詳細タブの一番下の「インプット」横+ボタンから追加できる。名前をSlotDataとし、型をInventorySlot(アイテムスロット用ウィジェットの名前)とする。
ブループリントノードは下の画像のように組む。Hide関数はBeardgames氏の動画で作成したものであり、使う・捨てるボタンを非表示にする処理につながっている。
これができたらコンパイルし、アイテムスロットウィジェット(ここでいうInventorySlot)のイベントグラフに移る。
Beardgames氏の動画で作成したShow/Hide Use/Drop Menu(アイテムボタンのOn Clickedイベント)イベントに追記する。
Add Child to Overlayノードの後に、使用しているプレイヤーキャラクターBPをCastする。ObjectにはGet Player Characterを接続する。
As(プレイヤーBP)ピンからリンクを伸ばして、プレイヤーBPに変数として作成してあるインベントリウィジェットの参照変数を接続する。(動画通りだとInventoryWGかな)
ここからリンクを伸ばして、先ほど作成したカスタムイベントを呼び出す。
SlotDataはSelf(Get Reference to Self)とする。
これでヨシ。
使えない・捨てられないアイテムの実装
今回は使えない・捨てられないアイテムを使おう(捨てよう)とした場合、一定時間「このアイテムはここでは使えない(捨てられない)」と一定時間だけ表示されるような処理を作成した。
先に、文字表示用の簡易なウィジェットを作成した。デザインはなんでもいいが、必ずパレットからTextウィジェットは階層に入れておく。
次にグラフタブへ移動し、Text型の変数を追加する。
名前は何でもいい。今回はAppearTextとした。
デザイナータブに戻り、Textウィジェットを選択状態とし、詳細タブ内のコンテンツ-Text横にある「バインド」ドロップダウンを選択する。
この中にある、先ほど作成したテキスト変数を選択する。
これで準備はOK。
アイテム情報をまとめた構造体(ItemInfoStructure)が作成してあることと思う。
ここに変数を追加した。
名前をそれぞれ「Can’tUse」「Can’tDrop」とし、変数型はBooleanとした。
続いてInventorySlotウィジェットBPを開き、UseItem,DropItem(それぞれ「使うボタン」「捨てるボタン」を押した場合の処理を示したイベント)を編集した。
現状だと、使うボタン/捨てるボタンを押した後は自動的にアイテムを使う/捨てる処理が行われるようになっているが、その処理の手前にブランチを置く。
上画像のように、ブランチのConditionピンにはItemInfoStructureを分解した中にあるCan’tUseピンを接続する。
Falseのピンには動画内で作成した使える処理をまるごと繋げる。
Trueのピンには「使えなかった」場合の処理を接続する。
Create Widgetノードを置き、Classに冒頭で作成した文字表示用ウィジェットを選択する。
ReturnValueピンからリンクを伸ばし、Set Appear Textを選ぶ。ここに好きな使えない通知文を入力する。
ReturnValueピンからもう一度リンクを伸ばして、Add to ViewPortを接続する。
あとはお好みの長さDelayを挟み、Remove from Parentノードでウィジェットを消す。
最後に「Hide」イベントを入れたのは、「使うボタン」をクリックした後にボタンウィジェットを消すため。
「捨てられない」イベントも同様。AppearTextを変えれば何にでも使えるので 冒頭で作成したウィジェットはぜひたくさん使いまわしてほしい。
アニメーションの追加
前提として、プレイヤーキャラクターのブループリント上のEventBeginPlayにおいて、Inventoryウィジェットを作成・変数に昇格・AddtoViewportしてある。
ごちゃごちゃして申し訳ないけどこんな感じ。
ただこのままだとInventoryが画面に常駐することになるので(そういうスタイルもあるけど)色々ウィジェットブループリント上で変更を行う。
InventoryのBPを開き、グラフタブを開く。
ここに変数を追加する。
名前をInventoryVisibility、変数型をESlate Visibilityとする。
続いて、デザイナータブへ移動し、Inventoryウィジェット全体をまとめているウィジェットを選択状態とする。
自分の場合は上から二番目のCanvasPanelがそれにあたる。
詳細タブのVisibilityを非表示とし、更にバインドタブからInventoryVisibilityを選択する。
もしかしたらこの設定(表示非表示)いらなかったかも。でもおまじないがてら念のため。
続いてアニメーションタブを開き、+ボタンからアニメーションを追加する。名前をInvAnimとした。
トラックの+ボタンを押し、CanvasPanel(Canvas Panel Slot)を選択する。
タイムラインの赤いカーソルが0.00になっていることを確認する。なっていなければ調整する。
続いてViewPort上のInventoryウィジェットを、画面外に移動させる。
どこでもいいが、上下から出てくるようなアニメーションとしたい場合は位置Yのみを、左右から出てくるようにしたい場合は位置Xのみを変更するとよい。
今回は画面左から出てきて欲しかったので、位置Xを動かした。
適当な位置に動かせたら、上画像のマル印で示したマーク(このプロパティに対してキーフレームを追加します)をクリック。
位置Yをいじった場合は位置Y横の同じマークを選択する。
次にタイムライン上の0.5へ赤いカーソルを合わせる。
最終的にInventoryがあってほしい位置まで移動させ、同様にキーフレーム追加アイコンをクリック。
下の方に再生ボタンがあるので試しに再生してみるといい。時間・位置等お好みで変えられる。
ここまでできたら保存し、プレイヤーキャラクターのブループリントに戻る。
先に変数を作成しておく。
名前をInventory Delay、MenuOpen、型はどちらもBooleanとする。
メニューキー入力時のイベントを作成する。
ここでは、EキーをIMC(Input Mapping Context)に設定しておいた。IMCの作り方はググるといい。
このノードのStartedピンからリンクを伸ばし、ブランチを繋げる。
ブランチの条件はInventoruDelayがTrueでないとき。ブランチの先にはFlipFlopを接続する。
まずFlipFlopのAの処理から。ここではインベントリ表示の処理を作る。
先ほど変数に昇格したInventoryウィジェット変数を呼び出し、再度AddtoViewportする(なぜかこれをしなきゃ動かなかった)。
InventoryWGをもう一つGetで呼び出し、ここからリンクをのばしてSet Inventory Visibleを選択。表示を選ぶ。
InmventoryWGから更にリンクを伸ばし、InvAnimをGetで呼び出す。ここからリンクを伸ばしてPlayAnimationを選択。
ターゲットにはInventoryWG、InAnimationにはInvAnimが接続されるようにする。
(リンクが先頭で2本出ているのは気にしなくてよい。)
ここからリンクを伸ばして、Set Inventory Delay、Set Menu Openを繋げる、両方チェックを入れる。
Set Input Mode Game And UIを入れる。In Wiget to FocusにはInventoryWGを、Player ControllerにはGet Player Controllerを繋げる。キャラクターを動かしたくなければDisableMovement等を入れればいいと思う。
最後にDelayを繋げ、Duration値は0.1とする。InventoryDelayをセットで呼び出し、チェックボックスを空にする。
これで表示時の処理は終わり。
続いて非表示時の処理。
InventoryDelayをセットで呼び出し、チェックを入れる。PlayAnimationを同様に繋げるが、PlayModeを逆再生とする。
次にInventoryWGをGetで呼び出し、そこからリンクを伸ばしてInventory Slot Array(インベントリスロットを配列化した変数名)をGetで取得。
ここから更にリンクを伸ばしてFor Each Loopを接続する。
Array Elementピンからリンクを伸ばし、Use Drop HUD WG(使う・捨てるボタンのウィジェット変数名)を呼び出す。
これをターゲットとしてRemove From Parentを接続する。
これは使う・捨てるボタンを表示させた状態でインベントリを閉じても破綻しないようにするための処理。何もしないと、次にインベントリを開いたとき、使う・捨てるボタンが残っていたり、変に重なってしまったりするかも。
あとはDelayを適度におき(なくてもいいかも。おまじない)、InventoryWGのInventoryVisibleを非表示・RemoveFromParentを置く。
MenuOpenのチェックボックスを空にする。SetInputModeGameOnlyを置いて、メニューを開いた処理のDelayノードに繋げればOK。
まとめ
以上。お疲れさまでした。