
UE5のインベントリとUI作成の難しさ – ゲームパッド・マウス対応の実装ガイド
Unreal Engine 5でゲーム開発を始めた多くの開発者が、最初に直面する大きな壁がある。それがインベントリシステムとUIの実装だ。見た目はシンプルに見えるインベントリ画面も、その裏側には複雑なロジックとデータ管理が潜んでいる。さらに、ゲームパッドとマウスの両方に対応させようとすると、難易度は一気に跳ね上がる。
「アイテムを拾って、インベントリに表示して、使えるようにする」この一見単純な機能を実装するだけで、何日も悩むことになる。なぜこれほどまでに難しいのか、そしてどうすれば効率的に実装できるのか。本記事では、実際のブループリント実装方法を含めて、徹底的に解説していく。
インベントリシステムが難しい理由
データ構造の複雑性
インベントリシステムの根幹は、データ管理にある。各アイテムは単なる画像ではなく、多様な情報を持つデータの塊だ。アイテムの名前、説明文、アイコン画像、スタック可能数、重量、レアリティ、使用効果、装備可能な部位など、管理すべき情報は膨大だ。
さらに厄介なのは、これらのデータを効率的に保存し、検索し、更新しなければならない点だ。プレイヤーがアイテムを拾うたび、使うたび、捨てるたび、データの整合性を保ちながら処理する必要がある。配列のインデックス管理を間違えれば、アイテムが消失したり、複製されたりするバグが発生する。
UI表示とロジックの分離
多くの初心者が陥る罠が、UIとロジックを混同してしまうことだ。「インベントリを開いたときにアイテムを表示する」という処理を、Widget Blueprintの中に直接書いてしまう。これは小規模なプロトタイプでは機能するが、規模が大きくなると破綻する。
理想的な設計は、データ管理を行うインベントリコンポーネント(ActorComponent)と、表示を担当するUI(Widget)を完全に分離することだ。しかし、この分離をBlueprint上で実現するには、イベントディスパッチャー、インターフェース、カスタムイベントなどの概念を理解し、適切に使い分ける必要がある。
リアルタイム更新の同期
インベントリは静的なものではない。戦闘中にアイテムを使う、NPCと取引する、アイテムが時間経過で劣化する、など様々なタイミングでデータが変化する。この変化を即座にUIに反映させる仕組みが必要だ。
しかも、複数のUIが同じデータを参照している場合もある。メインのインベントリ画面、クイックスロット、アイテム数を示すHUD、これらすべてが同期して更新されなければならない。一箇所でも更新が漏れれば、プレイヤーは混乱する。
ゲームパッドとマウスの両対応がさらに難易度を上げる
入力モードの切り替え
UE5のWidgetは基本的にマウス操作を前提としている。ボタンをクリックする、スロットをドラッグする、これらは直感的だ。しかし、ゲームパッドでの操作となると話は別だ。
ゲームパッドでは「フォーカス」の概念が重要になる。どのボタンが現在選択されているか、十字キーで次にどのボタンに移動するか、これらを明示的に管理しなければならない。さらに厄介なのは、プレイヤーが途中で入力デバイスを切り替えた場合だ。ゲームパッドで操作していたのに、突然マウスを動かした瞬間、UIはその変化を検知して適切に反応しなければならない。
ナビゲーションシステムの構築
UE5のWidgetには「Navigation」という機能があり、これを使えば方向キーでのUIナビゲーションを実装できる。しかし、この設定は想像以上に複雑だ。各ウィジェットに対して、上下左右の移動先を個別に指定する必要がある。スロットが動的に増減するインベントリでは、この設定を動的に更新しなければならない。
さらに、ループ構造をどうするか、端に到達したときの挙動をどうするか、グリッド状のレイアウトでの斜め移動をどう処理するか、など細かな仕様決定が無数にある。これらすべてに対して、プレイヤーが違和感を感じないような自然な挙動を実装する必要がある。
ドラッグ&ドロップの代替実装
マウスではアイテムをドラッグ&ドロップで並び替えたり、装備したりできる。これは直感的で分かりやすい。しかし、ゲームパッドにはドラッグという概念がない。代わりに、「選択→決定→移動先選択→決定」という複数ステップの操作を実装する必要がある。
この二つの操作方法を同じインベントリシステムで共存させることが、最大の難関だ。マウス用のドラッグ処理とゲームパッド用の選択処理を、互いに干渉せずに実装しなければならない。状態管理が複雑になり、バグの温床となる。
ブループリントによる実装方法:ステップバイステップガイド
ここからは、実際にUE5のブループリントを使って、ゲームパッド・マウス両対応のインベントリシステムを構築する方法を解説する。
ステップ1:アイテムデータ構造の設計
まず、アイテムの情報を格納するStructure(構造体)を作成する。
実装手順:
- コンテンツブラウザで右クリック→Blueprints→Structure
- 名前を「S_ItemData」とする
- 以下の変数を追加:
- ItemID(Name型):アイテムの一意識別子
- ItemName(Text型):表示名
- ItemDescription(Text型):説明文
- ItemIcon(Texture2D型):アイコン画像
- ItemType(Enum型:消耗品、装備、素材など)
- MaxStackSize(Integer型):最大スタック数
- ItemWeight(Float型):重量
- bIsUsable(Boolean型):使用可能か
- bIsEquippable(Boolean型):装備可能か
- ItemRarity(Enum型:Common、Rare、Epicなど)
次に、実際のインベントリスロットを表す構造体を作成する。
実装手順:
- 新しいStructureを作成し、「S_InventorySlot」と命名
- 以下の変数を追加:
- ItemData(S_ItemData型):アイテムの情報
- Quantity(Integer型):所持数
- SlotIndex(Integer型):スロット番号
この二段階の構造により、同じアイテムの情報を複数のスロットで共有でき、メモリ効率が良い。
ステップ2:インベントリコンポーネントの作成
データ管理を担当するActorComponentを作成する。
実装手順:
- コンテンツブラウザで右クリック→Blueprint Class→Actor Component
- 名前を「BP_InventoryComponent」とする
- 以下の変数を追加:
- InventorySlots(Array of S_InventorySlot):全スロットの配列
- MaxSlots(Integer、Default: 20):最大スロット数
- CurrentWeight(Float):現在の総重量
- MaxWeight(Float、Default: 100.0):最大重量
- イベントディスパッチャーを追加(UI更新通知用):
- OnInventoryUpdated(パラメータなし)
- OnItemAdded(パラメータ:S_ItemData, Integer)
- OnItemRemoved(パラメータ:S_ItemData, Integer)
関数「AddItem」の実装:
この関数はアイテムをインベントリに追加する核となる処理だ。
- Function「AddItem」を作成
- 入力:ItemToAdd(S_ItemData型)、AmountToAdd(Integer型、Default: 1)
- 出力:Success(Boolean型)
ブループリント内の処理フロー:
- まず、追加しようとしているアイテムがスタック可能か確認
- スタック可能な場合、既存のスロットに同じアイテムがあるか配列をループで検索
- 見つかった場合、そのスロットのQuantityを増やす(ただしMaxStackSizeを超えない)
- 余った数量がある場合、または新規アイテムの場合、空きスロットを探す
- 空きスロットが見つかったら新しいスロットを作成
- スロットの総数がMaxSlotsを超える場合は失敗を返す
- 成功した場合、CurrentWeightを更新
- OnInventoryUpdatedイベントディスパッチャーを呼び出してUI更新を通知
関数「RemoveItem」の実装:
- Function「RemoveItem」を作成
- 入力:ItemID(Name型)、AmountToRemove(Integer型)
- 出力:Success(Boolean型)
処理フロー:
- ItemIDで該当するスロットを検索
- 見つかったスロットのQuantityから数量を減らす
- Quantityが0になったらそのスロットを配列から削除
- CurrentWeightを更新
- OnInventoryUpdatedを呼び出し
関数「UseItem」の実装:
- Function「UseItem」を作成
- 入力:SlotIndex(Integer型)
- 出力なし
処理フロー:
- SlotIndexの有効性を確認
- 該当スロットのItemDataを取得
- ItemDataのbIsUsableを確認
- Trueの場合、アイテムタイプに応じた効果を発動(別の関数に分岐)
- 消耗品の場合はQuantityを1減らす
- OnItemRemovedを呼び出し
ステップ3:インベントリUI(Widget)の作成
次に、実際に画面に表示されるUIを作成する。
実装手順:
- コンテンツブラウザで右クリック→User Interface→Widget Blueprint
- 名前を「WBP_InventoryMenu」とする
UIレイアウト構成:
- Canvas Panel(ルート)
- Overlay(背景用)
- Image(半透明の黒背景)
- Vertical Box(メインコンテナ)
- Text Block(タイトル:”Inventory”)
- Uniform Grid Panel(アイテムスロットを配置)
- 名前を「GridPanel_Slots」とする
- Slot Padding: 5.0
- Horizontal Box(下部情報表示)
- Text Block(重量表示:”Weight: 45.5 / 100.0″)
- Text Block(スロット使用数:”Slots: 12 / 20″)
- Overlay(背景用)
変数の追加:
- InventoryComponent(BP_InventoryComponent型、Instance Editable):参照用
- SlotWidgetClass(Widget Classへの参照型):スロットのテンプレート
- CurrentSelectedSlot(Widget型):ゲームパッドで選択中のスロット
- IsUsingGamepad(Boolean型):現在の入力モード
Event Construct(初期化処理):
- InventoryComponentのOnInventoryUpdatedにバインド→RefreshInventoryUI関数を呼ぶ
- 初回のRefreshInventoryUIを実行
関数「RefreshInventoryUI」の実装:
この関数がUI更新の中核となる。
処理フロー:
- GridPanel_SlotsのすべてのChildrenをClearする
- InventoryComponentのInventorySlotsをループ処理
- 各スロットに対してSlotWidgetClassからWidgetを作成
- 作成したWidgetにアイテムデータを渡す(後述のWBP_InventorySlot参照)
- GridPanelに追加(行と列を計算して配置)
- 重量とスロット数のテキストを更新
ステップ4:個別スロットWidget の作成
各アイテムスロットを表すWidgetを作成する。
実装手順:
- 新しいWidget Blueprintを作成、名前を「WBP_InventorySlot」
- Buttonをルートに設定(クリック検知用)
- Button内にOverlayを配置
- Overlay内に以下を配置:
- Image(アイテムアイコン):名前を「Image_Icon」
- Border(選択時のハイライト):名前を「Border_Selected」
- Text Block(数量表示):名前を「Text_Quantity」
- Image(レアリティ表示用の枠)
変数の追加:
- SlotData(S_InventorySlot型):このスロットのデータ
- SlotIndex(Integer型):配列内のインデックス
- ParentInventory(WBP_InventoryMenu型):親UIへの参照
関数「SetSlotData」の実装:
外部からデータを設定する関数。
入力:NewSlotData(S_InventorySlot型)、Index(Integer型)
処理フロー:
- SlotDataとSlotIndexを更新
- Image_IconにSlotData.ItemData.ItemIconを設定
- Text_QuantityにSlotData.Quantityを表示(1の場合は非表示)
- レアリティに応じてBorderの色を変更
イベント「OnButtonClicked」の実装(マウス用):
Buttonのイベントにバインド。
処理フロー:
- 親のWBP_InventoryMenuのOnSlotClicked関数を呼ぶ
- SlotIndexを渡す
イベント「OnButtonHovered」の実装:
マウスホバー時の処理。
処理フロー:
- Border_Selectedの可視性をVisibleに
- 親UIのShowItemTooltip関数を呼び、アイテム詳細を表示
イベント「OnButtonUnhovered」の実装:
処理フロー:
- ゲームパッドモードで選択されていない場合のみBorder_Selectedを非表示
- Tooltipを非表示
ステップ5:ゲームパッド対応の実装
ここが最難関。ゲームパッドでのナビゲーションを実装する。
WBP_InventoryMenuに戻り、以下を追加:
関数「SetGamepadNavigation」の実装:
この関数はスロットが配置された後に呼ばれ、ナビゲーションを設定する。
処理フロー:
- GridPanel_SlotsのすべてのChildrenを取得
- 各Childに対してループ処理
- グリッドの行列計算(例:5列の場合、Index 0は(0,0)、Index 5は(1,0))
- 各方向(Up、Down、Left、Right)の移動先Widgetを計算
- 上:同じ列の前の行のWidget(行-1)
- 下:同じ列の次の行のWidget(行+1)
- 左:同じ行の前の列のWidget(列-1)
- 右:同じ行の次の列のWidget(列+1)
- 端の場合はループするか、nullにするか選択
- Set Navigation Rule For Each Directionを使用して設定
- 最初のスロットに初期フォーカスを設定
Event Construct内に追加:
- SetGamepadNavigation関数を呼ぶタイマーを0.1秒後に設定(UIの構築完了を待つ)
入力検知の実装:
WBP_InventoryMenu内に以下のInputアクションを追加:
- Override→On Preview Key Downイベント
- キー判定:
- Gamepad Face Button Bottomが押されたら→現在選択中のスロットを使用
- Gamepad Face Button Rightが押されたら→メニューを閉じる
- マウスが動いたら→IsUsingGamepadをFalseに
- ゲームパッドのスティックやボタンが押されたら→IsUsingGamepadをTrueに
関数「OnSlotFocused」の実装:
ゲームパッドでスロットがフォーカスされたときの処理。
WBP_InventorySlotに追加:
- Override→On Focusedイベント
- 処理フロー:
- Border_Selectedを表示
- ParentInventoryのCurrentSelectedSlotを自分に設定
- アイテム詳細を表示
ステップ6:ドラッグ&ドロップの実装(マウス用)
マウスでのアイテム移動を実装する。
WBP_InventorySlotに以下を追加:
Override→On Mouse Button Downイベント:
処理フロー:
- マウスボタンが左クリックか確認
- Detect Drag If Pressedノードを使用
- Drag Key: Left Mouse Button
- これによりドラッグ検知が開始される
Override→On Drag Detectedイベント:
ドラッグが検知されたときの処理。
処理フロー:
- Drag and Drop Operationを作成
- Operation Class: 新しく作成する「BP_ItemDragDropOperation」
- Drag Visual: 自分のWidgetのクローンまたは簡易版
- Payload: SlotDataを含むカスタムデータ
- Drag開始時に元のスロットを半透明にする
BP_ItemDragDropOperationの作成:
- Blueprint Class→Drag Drop Operationを継承
- 変数追加:
- DraggedSlotData(S_InventorySlot型)
- SourceSlotIndex(Integer型)
Override→On Dropイベント(WBP_InventorySlot内):
ドロップされたときの処理。
処理フロー:
- Drag Drop Operationから元のスロット情報を取得
- 親のInventoryComponentのSwapSlots関数を呼ぶ
- 引数:SourceIndex、TargetIndex(自分のSlotIndex)
- SwapSlots関数内で配列の要素を入れ替える
- OnInventoryUpdatedを呼んでUI更新
ステップ7:ゲームパッドでのアイテム移動実装
ゲームパッドでは「選択→移動先選択」の二段階操作を実装する。
WBP_InventoryMenuに変数追加:
- ItemBeingMoved(S_InventorySlot型、nullable):移動中のアイテム
- SourceSlotIndex(Integer型):移動元のインデックス
- IsInMoveMode(Boolean型):移動モード中か
関数「OnGamepadSelectPressed」の実装:
ゲームパッドの決定ボタンが押されたときの処理。
処理フロー:
- IsInMoveModeをチェック
- Falseの場合(通常モード):
- CurrentSelectedSlotのデータを取得
- ItemBeingMovedに保存
- SourceSlotIndexを保存
- IsInMoveModeをTrueに
- UIに「移動先を選択してください」と表示
- 選択中のスロットに「移動中」マーカーを表示
- Trueの場合(移動先選択モード):
- CurrentSelectedSlotのインデックスを取得
- InventoryComponent→SwapSlotsを呼ぶ
- IsInMoveModeをFalseに戻す
- UIメッセージをクリア
- 移動完了のフィードバック(サウンドやエフェクト)
関数「CancelMoveMode」の実装:
キャンセルボタンが押されたときの処理。
処理フロー:
- IsInMoveModeをFalseに
- ItemBeingMovedをクリア
- UIメッセージをクリア
ステップ8:入力モード自動切り替えの実装
プレイヤーが途中で入力デバイスを変更しても対応できるようにする。
WBP_InventoryMenuのEvent Tick内:
(パフォーマンスのため、0.1秒ごとのタイマーを使う方が良い)
処理フロー:
- Get Player Controllerから最後に使用された入力デバイスを確認
- ゲームパッド入力があった場合:
- IsUsingGamepadをTrue
- すべてのスロットのホバーハイライトを無効化
- 現在選択中のスロットにフォーカスハイライトを表示
- カーソルを非表示(Set Show Mouse Cursor: False)
- マウス/キーボード入力があった場合:
- IsUsingGamepadをFalse
- フォーカスハイライトを無効化(選択解除)
- ホバーハイライトを有効化
- カーソルを表示
より高度な実装:Enhanced Input Systemの活用
UE5.1以降では、Enhanced Input Systemを使うことで、より柔軟な入力管理が可能になる。
- Input Action Asset「IA_InventoryConfirm」を作成
- Gamepad Face Button BottomとLeft Mouse Buttonの両方をマッピング
- Input Mapping Contextで優先度を設定
- Blueprint内でBind Input Actionを使用
- これにより、入力デバイスに関係なく同じアクションで処理できる
ステップ9:アクセシビリティとUX改善
基本機能が実装できたら、ユーザー体験を向上させる。
視覚フィードバックの強化:
- アイテム追加時のアニメーション:
- Widgetアニメーションで、新しく追加されたスロットがフェードイン
- サイズを少し大きくしてから元に戻す「バウンス」効果
- 選択フィードバック:
- ゲームパッドで選択されているスロットを明確にハイライト
- マウスホバーとは異なる色や形状を使用
- 移動モード中の視覚変化:
- 移動中のアイテムスロットを半透明に
- 移動可能なスロットを明るく、不可能なスロットを暗く表示
オーディオフィードバック:
- サウンドキューの作成:
- アイテム追加音
- アイテム使用音
- スロット移動音
- エラー音(満杯時など)
- 各アクションでPlay Sound 2Dを呼び出す
パフォーマンス最適化:
- Refresh UIを毎フレーム呼ばない:
- イベントディスパッチャー経由でのみ更新
- スロットWidgetのプーリング:
- 使わないWidgetを破棄せず再利用
- Object Poolパターンの実装
- 大量のアイテム表示時の対策:
- Scroll Boxを使用
- 可視範囲外のWidgetは描画しない(Virtualization)
ステップ10:デバッグとテスト
実装後は徹底的なテストが必要だ。
デバッグ用の機能追加:
- 開発者コマンドの実装:
- チートコンソールで「AddItem [ItemID] [Amount]」を実装
- 「ClearInventory」コマンド
- 「FillInventory」コマンド(テスト用に満杯にする)
- ログ出力の追加:
- Print Stringでアイテム追加/削除時にログ
- Output Logで配列の状態を確認
テストケース:
- 基本機能テスト:
- アイテムの追加、削除、使用が正常に動作するか
- スタック処理が正しく機能するか
- 重量制限とスロット制限が機能するか
- UI操作テスト:
- マウスでのドラッグ&ドロップが正常か
- ゲームパッドでの移動が正常か
- 入力デバイス切り替えがスムーズか
- エッジケーステスト:
- インベントリが満杯の状態でアイテム追加
- 存在しないアイテムを削除しようとする
- スタック上限を超えてアイテム追加
- 移動中にインベントリを閉じる
- パフォーマンステスト:
- 100個以上のアイテムを表示
- 高速でアイテムを追加/削除
- メモリリークがないか確認
よくある問題と解決策
問題1:アイテムが複製される
原因: 配列操作時のインデックス管理ミス、またはスタック処理の不備
解決策:
- AddItem関数内でデバッグログを追加し、処理の流れを追跡
- スタック追加時に既存の数量と追加後の数量を明示的にログ出力
- アイテム追加後に配列全体をダンプして確認
問題2:ゲームパッドでフォーカスが消える
原因: UI更新時にフォーカスがリセットされる
解決策:
- RefreshUI前に現在のフォーカスWidgetを保存
- UI再構築後、保存したWidgetまたは同じインデックスのWidgetにSet Focusを実行
- または、部分更新を実装してスロット全体を再生成しないようにする
問題3:マウスとゲームパッドの入力が競合
原因: 両方の入力が同時に有効になっている
解決策:
- IsUsingGamepadフラグを厳密に管理
- マウス入力イベント内で「IsUsingGamepad = False」を必ず実行
- ゲームパッド入力イベント内で「IsUsingGamepad = True」を必ず実行
- フラグに応じて、不要な入力処理を早期リターンで無視
問題4:UIが重くて動作がカクつく
原因: 毎フレームの不要な処理、または非効率的なWidget構成
解決策:
- Event Tickを使用せず、イベント駆動の更新のみにする
- Widgetの階層を深くしすぎない(3-4層まで)
- 大量のスロットがある場合はScroll Boxを使用
- Invalidation Boxを活用して再描画を最小限に
問題5:セーブ/ロード時にデータが失われる
原因: 構造体や配列のシリアライズが正しく設定されていない
解決策:
- インベントリコンポーネントを「Replicated」に設定(マルチプレイヤーの場合)
- Save Game Objectに明示的にInventorySlotsを保存
- ロード時に配列全体を復元し、UI更新を実行
応用編:さらに高度な機能の実装
基本的なインベントリシステムができたら、以下の機能を追加してより洗練されたシステムにできる。
クイックスロットシステム
よく使うアイテムを素早くアクセスできる機能。
実装方法:
- WBP_QuickSlotMenuを作成(Horizontal Boxに4-8個のスロット)
- InventoryComponentに「QuickSlotArray」を追加(固定サイズ)
- メインインベントリからQuickSlotへのアサイン機能(右クリックメニューなど)
- ゲームパッドのD-Padやキーボード数字キーで直接使用
- クイックスロットとメインインベントリのデータ同期
アイテムソート機能
アイテムを自動で整理する機能。
実装方法:
- ソート基準のEnum作成(名前順、タイプ順、レアリティ順、重量順)
- 関数「SortInventory」を作成
- 配列の「Sort」ノードを使用
- カスタムコンパレータ関数で比較ロジックを実装
- ソート後にOnInventoryUpdatedを呼び出し
アイテムフィルター機能
特定タイプのアイテムのみ表示する機能。
実装方法:
- WBP_InventoryMenuにフィルターボタンを追加(All、Weapons、Consumables等)
- 変数「CurrentFilter」を追加(Enum型)
- RefreshInventoryUI内で、CurrentFilterに基づいて表示するスロットをフィルタリング
- フィルター変更時にUI再描画
スタックスプリット機能
スタックされたアイテムを分割する機能。
実装方法:
- WBP_SplitDialogを作成(スライダーとボタン)
- InventorySlotの右クリックメニューに「Split」オプション追加
- ダイアログ表示、スライダーで分割数を選択
- 確定時に元のスロットの数量を減らし、新しいスロットを作成
- 空きスロットがない場合はエラー表示
アイテム比較機能
装備アイテムを現在の装備と比較する機能。
実装方法:
- EquipmentComponentを作成(装備中のアイテム管理)
- WBP_ItemComparisonTooltipを作成(左右に並べて表示)
- 装備アイテムにホバー時、現在の装備と比較表示
- ステータス差分を色分け(上昇=緑、下降=赤)
コンテキストメニュー(右クリックメニュー)
アイテムに対する各種アクションを選択できる機能。
実装方法:
- WBP_ContextMenuを作成(Vertical Boxにボタンリスト)
- アイテムタイプに応じて表示するオプションを動的に変更
- 消耗品:Use、Drop、Destroy
- 装備:Equip、Drop、Destroy
- 素材:Drop、Destroy
- ゲームパッドの場合は専用ボタン(例:Yボタン)で表示
- マウスの場合は右クリックで表示
- 選択されたアクションに応じた処理を実行
アイテム検索機能
大量のアイテムから特定のものを探す機能。
実装方法:
- WBP_InventoryMenuに検索バー(Editable Text Box)を追加
- テキスト変更イベントにバインド
- 入力文字列でアイテム名をフィルタリング
- マッチするアイテムのみ表示
- ゲームパッド用に仮想キーボードを実装(オプション)
重量オーバー時の警告と制限
実装方法:
- AddItem関数内で、追加後の総重量を事前計算
- MaxWeightを超える場合は追加を拒否
- UIに警告表示(赤いテキストや警告アイコン)
- サウンドとエフェクトでフィードバック
- 重量が限界に近い場合は黄色で警告(例:90%以上)
アイテムの自動整理(オートソート)
実装方法:
- 設定で「オートソート」のオン/オフを追加
- オンの場合、アイテム追加時に自動的にソート
- ソート基準も設定可能に(プレイヤーの好みで選択)
- パフォーマンスのため、ソートは追加完了後に一度だけ実行
マルチプレイヤー対応の考慮事項
オンラインゲームでインベントリを実装する場合、追加の考慮が必要だ。
レプリケーションの設定
実装方法:
- InventoryComponentの「Replicates」をTrueに
- InventorySlotsを「Replicated」変数に設定
- OnRep関数を作成してクライアント側でUI更新
- サーバーでのみアイテム操作を許可(不正防止)
- クライアントからの操作はRPCでサーバーに送信
サーバー検証
実装方法:
- すべてのアイテム操作関数を「Server」RPCに
- サーバー側で妥当性チェック:
- プレイヤーが実際にそのアイテムを持っているか
- 重量制限を超えていないか
- チート検知(不正な数量など)
- 検証後に実際の処理を実行
- 成功/失敗をクライアントに通知
同期の最適化
実装方法:
- すべてのスロット変更で同期しない
- バッチ更新:複数の変更をまとめて送信
- 差分のみ送信:変更されたスロットのみ同期
- 更新頻度の制限:秒間10回まで等
パフォーマンス最適化の詳細テクニック
大規模なゲームでは、パフォーマンスが重要になる。
オブジェクトプーリング
実装方法:
- SlotWidgetPoolを作成(Array of WBP_InventorySlot)
- 初期化時に必要数のWidgetを事前生成
- RefreshUI時、配列から取得して再利用
- 使用済みWidgetは非表示にしてプールに戻す
- これによりCreate Widgetのコストを削減
遅延ロード(Lazy Loading)
実装方法:
- アイテムアイコンを即座にロードしない
- Async Load Asset関数を使用
- ロード完了後にImageに設定
- ロード中はプレースホルダー画像を表示
- メモリ使用量の削減
UIの部分更新
実装方法:
- 全体をRefreshせず、変更されたスロットのみ更新
- 関数「UpdateSingleSlot」を作成
- スロットIndexを引数に、該当Widgetのみ更新
- 大幅なパフォーマンス改善
Widgetの可視性制御
実装方法:
- インベントリが閉じているときはUI全体を「Collapsed」に
- 開いているときのみ「Visible」に
- Event Tickが不要なWidgetは完全に停止
- CPU負荷の削減
トラブルシューティングガイド
開発中によく遭遇する問題と解決法のまとめ。
UI更新が反映されない
チェックポイント:
- OnInventoryUpdatedが正しくバインドされているか確認
- イベントディスパッチャーが実際に呼ばれているか(Print Stringで確認)
- RefreshInventoryUI関数が実行されているか
- Widgetへの参照が有効か(IsValidでチェック)
ゲームパッドでフォーカスが効かない
チェックポイント:
- Widgetの「Is Focusable」がTrueになっているか
- Navigation設定が正しいか
- Set User Focusが呼ばれているか
- Input ModeがUI Onlyまたはgame and UIになっているか
ドラッグ&ドロップが機能しない
チェックポイント:
- Buttonの「Is Variable」がTrueか
- On Drag Detectedがオーバーライドされているか
- Drag Drop Operationが正しく作成されているか
- On Dropイベントで「Return true」しているか
メモリリークが発生する
チェックポイント:
- Create Widgetしたものをすべて適切に破棄しているか
- イベントディスパッチャーのバインドを解除しているか
- タイマーを停止しているか
- 循環参照が発生していないか
段階的な実装が成功の鍵
UE5でのインベントリとUI実装は確かに難しい。特にゲームパッドとマウスの両対応は、考慮すべき事項が膨大だ。しかし、正しいアプローチで段階的に実装すれば、必ず完成させることができる。
重要なのは、以下のポイントだ:
- データとUIの分離 – ロジックとビューを明確に分ける
- 小さく始めて拡張 – 最初は最小限の機能から実装
- 徹底的なテスト – 各段階で動作確認を怠らない
- ログとデバッグ – 問題発生時にすぐ原因を特定できるように
- リファクタリング – 動くようになったら、コードを整理する
最初から完璧を目指さず、動くプロトタイプを作り、そこから改善していく。この反復プロセスが、複雑なシステムを完成させる唯一の道だ。
インベントリシステムの実装は、Blueprint開発の集大成とも言える。配列操作、構造体、イベント、UI、入力処理、すべてのスキルが試される。だからこそ、これを完成させたとき、開発者として大きく成長している自分に気づくはずだ。
ゲームパッドとマウスの両対応は確かに手間がかかる。しかし、それによってプレイヤーに提供できる体験の幅は大きく広がる。コンソールでもPCでも快適にプレイできるゲーム。その実現に向けて、一歩ずつ進んでいこう。
エラーに直面したとき、思い通りに動かないとき、それは成長のチャンスだ。ログを確認し、デバッグし、試行錯誤する。その過程で得られる知識と経験こそが、本当の財産となる。UE5のインベントリ実装は難しい。だが、不可能ではない。このガイドを参考に、あなた自身の最高のインベントリシステムを作り上げてほしい。






