UE Blueprintで作る拡張性の高い汎用インベントリシステム
はじめに:Blueprintで汎用インベントリシステムを構築する意義
ゲーム開発において、インベントリシステムはプレイヤーの体験を豊かにする重要な要素です。アイテムの収集、管理、使用といった一連の流れは、RPG、サバイバル、アドベンチャーなど多くのジャンルで必要とされます。C++で複雑なシステムを構築することも可能ですが、Blueprintを活用することで、プロトタイピングから実装、イテレーションまでの開発サイクルを大幅に短縮できます。
本記事では、プログラミング経験をお持ちの読者の方々がBlueprintの強力な機能を最大限に活かし、拡張性と再利用性の高い汎用インベントリシステムを構築する手順をステップバイステップで解説します。構造体、データテーブル、Blueprintインターフェース、イベントディスパッチャといった要素を組み合わせることで、効率的かつ堅牢なシステムを実現します。
1. アイテムのデータ構造を定義する
まず、インベントリで管理するアイテムの基本的なデータ構造を定義します。C++のクラス定義や構造体定義に相当する概念として、Blueprintでは構造体(Structure)
とデータアセット(Data Asset)
を組み合わせるのが一般的です。今回は、アイテムの共通プロパティを構造体で定義し、具体的なアイテム情報をデータアセットとして管理する手法を採用します。
1.1. アイテム情報構造体の作成
インベントリ内の各アイテムが持つ共通の属性を定義する構造体を作成します。
- コンテンツブラウザで右クリックし、「Blueprint」-> 「構造体」を選択します。
- 名前を
F_InventoryItemInfo
とします。 F_InventoryItemInfo
を開き、以下の変数を作成します。ItemID
(Name型): 各アイテムを一意に識別するためのIDです。プログラミングにおけるenum
やstring
キーのような役割を果たします。DisplayName
(Text型): UI表示用のアイテム名です。Description
(Text型): アイテムの詳細説明です。Icon
(Texture2D Object Reference型): インベントリUIで表示されるアイコン画像です。bIsStackable
(Boolean型): アイテムがスタック可能かどうかを示します。MaxStackSize
(Integer型): 最大スタック数です。bIsStackable
がTrueの場合にのみ有効です。ItemType
(Enum型): アイテムの種類(例: 武器、防具、消耗品など)を分類します。事前にE_ItemType
という名前でEnumを作成しておくと良いでしょう。
この構造体は、インベントリ内のスロットが保持する「アイテムそのもの」ではなく、「アイテムの定義」を表します。
1.2. アイテム定義用データアセットの作成
次に、個々のアイテム(例: ポーション、剣、コイン)の具体的なプロパティを定義するために、データアセットを使用します。これにより、アイテムの追加や変更をBlueprintのコードに触れることなく行えるようになります。
- コンテンツブラウザで右クリックし、「Blueprintクラス」を選択します。
- 親クラスとして
DataAsset
を検索して選択し、「選択」をクリックします。 - 名前を
DA_InventoryItem
とします。 DA_InventoryItem
を開き、以下の変数を作成します。ItemInfo
(F_InventoryItemInfo型): 先ほど作成した構造体をプロパティとして持ちます。これにより、このデータアセットがF_InventoryItemInfo
で定義された全ての情報を持つことになります。
これで、コンテンツブラウザで DA_InventoryItem
を右クリックし、「Blueprintクラスの作成」を選択することで、具体的なアイテムアセット(例: DA_Potion
、DA_Sword
)を作成し、それぞれのItemInfo
変数を設定できるようになります。これは、プログラミングにおける設定ファイルやインスタンス作成に似ています。
2. インベントリ管理コンポーネントの作成
プレイヤーやコンテナがインベントリを持つための中心的なロジックを管理するBlueprintコンポーネントを作成します。これにより、インベントリの機能が特定のBlueprintに依存せず、再利用可能な形で提供されます。
2.1. インベントリコンポーネントのBlueprint作成
- コンテンツブラウザで右クリックし、「Blueprintクラス」を選択します。
- 親クラスとして
ActorComponent
を検索して選択し、「選択」をクリックします。 - 名前を
BPC_InventoryManager
とします。
2.2. アイテム格納ロジックの実装
BPC_InventoryManager
を開き、以下の変数と関数を作成します。
-
変数:
InventorySlots
(Array of F_InventoryItemInfo型): 現在インベントリに存在するアイテムとその数を管理する配列です。実際にはスタック数を管理する必要があるため、F_InventorySlot
のような別途構造体を作成し、F_InventoryItemInfo
とQuantity
(Integer型)を組み合わせるのがより実践的です。今回はF_InventoryItemInfo
と数量のペアを別途管理する形で進めます。OnInventoryUpdated
(Event Dispatcher型): インベントリの内容が変更された際に、UIなどに通知するためのイベントディスパッチャです。
-
関数:
-
AddItemToInventory
関数:- 入力:
ItemData
(DA_InventoryItem Object Reference型),Quantity
(Integer型) - 処理:
ItemData
からItemInfo
を取得します。InventorySlots
配列内で、同じItemID
を持つスタック可能なアイテムがあるか検索します。- スタック可能な場合: 既存のアイテムの数量を増やします。
MaxStackSize
を超えないように調整し、余剰分は新しいスロットに入れるか、追加できなかった場合は失敗を返します。 - スタック不可能、または既存スロットがない場合:
F_InventoryItemInfo
の新しいエントリをInventorySlots
配列に追加します。数量も考慮に入れます。 - アイテムが追加されたら、
OnInventoryUpdated
イベントディスパッチャを呼び出し、UIの更新を促します。
- 出力:
bSuccess
(Boolean型),RemainingQuantity
(Integer型)
- 入力:
-
RemoveItemFromInventory
関数:- 入力:
ItemData
(DA_InventoryItem Object Reference型),Quantity
(Integer型) - 処理:
InventorySlots
配列内で、ItemData
に対応するアイテムを検索します。- 指定された
Quantity
分だけアイテムを減らします。 - 数量が0になったアイテムは配列から削除します。
OnInventoryUpdated
イベントディスパッチャを呼び出します。
- 出力:
bSuccess
(Boolean型)
- 入力:
-
HasItemInInventory
関数:- 入力:
ItemData
(DA_InventoryItem Object Reference型),Quantity
(Integer型) - 処理: 指定されたアイテムが指定された数量だけインベントリにあるかを確認します。
- 出力:
bHasItem
(Boolean型)
- 入力:
-
これらの関数は、プログラミングにおけるデータ構造(配列やリスト)へのアクセス、条件分岐、ループ処理といった基本的な操作をBlueprintノード(ForEach Loop
、Branch
、Add
、Remove
など)で表現する良い例となります。
3. UI(UMG)との連携
インベントリシステムには、その内容をプレイヤーに視覚的に表示するためのUIが不可欠です。UMG (Unreal Motion Graphics) を用いて、インベントリUIを作成し、BPC_InventoryManager
と連携させます。
3.1. インベントリスロット用ウィジェットの作成
個々のアイテムスロットを表示するためのウィジェットを作成します。
- コンテンツブラウザで右クリックし、「ユーザーインターフェース」->「ウィジェットブループリント」を選択します。
- 親クラスとして「UserWidget」を選択し、名前を
WBP_InventorySlot
とします。 WBP_InventorySlot
を開き、以下の要素を追加します。Image
(アイコン表示用)TextBlock
(数量表示用)Button
(クリックイベント用)
- イベントディスパッチャへのバインド:
WBP_InventorySlot
には、表示するアイテムのF_InventoryItemInfo
を受け取る「SetItemInfo」のような関数と、ボタンが押されたときに親ウィジェットに通知するためのOnSlotClicked
といったイベントディスパッチャを作成します。
3.2. メインインベントリUIウィジェットの作成
複数のスロットを管理し、インベントリ全体を表示するウィジェットを作成します。
- 上記と同様に
WBP_InventoryMain
を作成します。 WBP_InventoryMain
を開き、WrapBox
やUniformGridPanel
などを用いて、複数のWBP_InventorySlot
を配置できるレイアウトを作成します。WBP_InventoryMain
のGraphタブで、BPC_InventoryManager
のOnInventoryUpdated
イベントディスパッチャにバインドします。Event BeginPlay
(または、BPC_InventoryManager
が有効になったタイミング) で、プレイヤーキャラクターのBPC_InventoryManager
コンポーネントへの参照を取得します。- その参照から「Assign
OnInventoryUpdated
」ノードを呼び出し、カスタムイベントをバインドします。 - このカスタムイベント内では、既存の
WBP_InventorySlot
を全てクリアし、BPC_InventoryManager
のInventorySlots
配列の内容に基づいて新しいWBP_InventorySlot
を作成・追加・更新します。
この仕組みは、Web開発におけるデータバインディングやObserverパターンに似ています。データが更新されたときに、それに応じたUIの更新が自動的に行われるようになります。
4. ワールドでのアイテム拾得と使用
プレイヤーがワールド上のアイテムを拾い、インベントリに追加するロジック、およびインベントリからアイテムを使用して効果を発動するロジックを実装します。
4.1. 拾えるアイテムのBlueprint作成
ワールドに配置される「拾えるアイテム」は、専用のBlueprintで表現します。
- コンテンツブラウザで右クリックし、「Blueprintクラス」->「Actor」を選択し、名前を
BP_PickupItem
とします。 BP_PickupItem
にStaticMeshComponent
(アイテムの外観) とSphereCollisionComponent
(プレイヤーとのインタラクション範囲) を追加します。DA_InventoryItem
型の変数ItemData
を追加し、インスタンス編集可能にします。これにより、ワールドに配置する際にどのアイテムかを指定できます。SphereCollisionComponent
のOnComponentBeginOverlap
イベントで、オーバーラップしたActorがプレイヤーの場合、プレイヤーのBPC_InventoryManager
のAddItemToInventory
関数を呼び出します。- アイテムが正常に追加されたら、
DestroyActor
ノードでワールドからアイテムを削除します。
4.2. アイテム使用インターフェースの作成
アイテム使用時に様々な効果(回復、武器装備など)をBlueprintに依存せずに実行できるように、Blueprintインターフェースを活用します。これはプログラミングにおける「インターフェース」の概念と非常に似ており、多態性を実現します。
- コンテンツブラウザで右クリックし、「Blueprint」->「Blueprintインターフェース」を選択し、名前を
BPI_UseItem
とします。 -
BPI_UseItem
を開き、UseItem
という関数を追加します。- 入力:
UserActor
(Actor Object Reference型),ItemInfo
(F_InventoryItemInfo型)
- 入力:
-
各アイテム(例: ポーション、剣)の具体的な使用効果を持つBlueprintクラス(例:
BP_PotionEffect
、BP_SwordEffect
)を作成し、これらのBlueprintがBPI_UseItem
インターフェースを実装するようにします。(クラス設定で「インターフェース」の項目に追加) BP_PotionEffect
のUseItem
イベントでは、UserActor
の体力回復ロジックなどを実装します。BP_SwordEffect
では、UserActor
の装備スロットに剣を設定するロジックなどを実装します。
4.3. インベントリUIからのアイテム使用
WBP_InventorySlot
のボタンクリックイベントで、以下の処理を行います。
- クリックされたスロットの
ItemInfo
を取得します。 ItemInfo
が持つItemType
に基づいて、適切なアイテムエフェクトのBlueprintクラスをスポーンするか、既に存在するエフェクトBlueprintへの参照を取得します。- そのエフェクトBlueprintが
BPI_UseItem
インターフェースを実装しているかを確認します(Does Implement Interface
ノード)。 - 実装している場合、
UseItem
メッセージを送信します。 - アイテムが使用されたら、
BPC_InventoryManager
のRemoveItemFromInventory
を呼び出し、インベントリからアイテムを減らします。
5. 効率的なBlueprint構築のヒント
- 関数化とマクロ化: 繰り返し使用するロジックは積極的に関数やマクロにまとめましょう。特に純粋な計算ロジックは純粋関数(Pure Function)として作成すると、ピン接続が簡潔になります。
- 構造体とデータテーブルの活用: アイテムデータのように、複数のBlueprintで共有されるデータは構造体で定義し、データテーブルやデータアセットとして管理することで、一元的な管理と簡単な調整が可能になります。これはプログラミングにおけるデータ駆動設計に相当します。
- Blueprintインターフェース: コンポーネント間の疎結合を実現するために、Blueprintインターフェースを積極的に活用しましょう。特定の型に依存せず、共通の機能を呼び出すことができます。
- イベントディスパッチャ: UI更新や状態変化の通知にはイベントディスパッチャが非常に有効です。ObserverパターンをBlueprintで実現する最も一般的な方法です。
まとめ:Blueprintで実現する堅牢なシステム
本記事では、Blueprintのみを使用して拡張性の高い汎用インベントリシステムを構築する基本的なアプローチを解説しました。アイテムデータの定義からインベントリコンポーネント、UI連携、そしてワールド上のインタラクションまで、主要な要素をカバーしています。
プログラミング経験をお持ちの方であれば、Blueprintの「ノードとピン」という表現形式に慣れることで、C++で記述する際に設計するクラス階層やデータ構造、イベントシステムといった概念をBlueprintで直感的に実装できることがご理解いただけたのではないでしょうか。
このシステムを基盤として、セーブ/ロード機能、アイテムのソート、ドラッグ&ドロップ、装備システムなど、さらに多くの機能を拡張していくことが可能です。Blueprintの持つ柔軟性と可視性を活かして、あなた自身のゲーム開発に役立ててください。