【Unreal Engine】インベントリ入門:拾う・使う・満杯になるカバンを作る

作成: 2026-07-20最終更新: 2026-09-07

UE5のBlueprintで、薬草を拾って使えるインベントリを作ります。Data Assetの定義とMapの所持数を分け、種類ごとの上限・カバン全体の容量・0個になったときの削除・変更時だけ更新するUIまで、数値で確かめながら組み立てます。

薬草を拾うと9個まで増え、10個目は持てない。カバン全体が満杯になっても、薬草を使えば、そのぶん別のアイテムを入れられる。今回は、そんな持ち物の仕組みを作ります。

インベントリ は、「何を、いくつ持っているか」を管理する仕組みです。アイテムの説明、個数の増減、画面表示を一度に作ろうとすると、どこを直せばよいか見えにくくなります。まずは、薬草の性質と、自分が持つ薬草の数を分けるところから始めましょう。

アイテムの定義と、カバンが持つ種類ごとの所持数を分ける

この記事でわかること

  • アイテムの定義と、プレイヤーごとの所持数を分ける
  • 種類ごとの上限と、カバン全体の容量を守って増減する
  • 薬草を1個使い、CharacterのHPを回復する
  • 所持数が変わったときだけ、簡単な持ち物表示を更新する

Blueprintの変数・関数・Branchを使える人向けの実践です。Data AssetMapActor Componentを組み合わせます。Third Personテンプレートの1人プレイで、キー入力を「拾う」操作の代わりに使います。

Sponsored

薬草の性質と、持っている数を分ける

薬草を3個持つ人も、9個持つ人も、1個使ったときの回復量は同じです。この「薬草とは何か」という共通の情報を 定義 と呼び、Data Assetへ置きます。

一方、「自分はいま3個持っている」は、そのプレイヤーだけの 所持状態 です。こちらはカバン側で管理します。

分けるもの薬草の例置き場所
定義表示名は薬草、回復量20、上限9個DA_Herb
所持状態DA_Herbを3個持っているInventory ComponentのItems
薬草の定義1枚を共有し、各プレイヤーが自分の所持数を持つ

分けておくと、回復量を20から25へ調整するときは、定義を1か所直せます。所持品ごとに回復量のコピーを持たせていると、そちらも更新する必要があります。今回の所持データには、どの定義を使うかという参照と、個数 を入れます。

今回は、1種類につき1つの個数を持つ

Map は、「調べるためのキー」と「対応する値」を組にして持つ入れ物です。今回は、アイテム定義をキー、所持数を値にします。DA_Herbを渡せば、薬草の個数を取り出せます。

Mapで種類ごとの合計を持つ方法と、配列で個別のスロットを持つ方法
欲しいカバン扱いやすい形
薬草9個、鍵1個のように、種類ごとの合計だけ分かればよい今回のMap
薬草を「9個の枠」と「1個の枠」に分けたいスロットの配列
同じ剣でも耐久値72と13を区別したい個体の情報を持つ構造体の配列など

スタック は、同じ種類をまとめて持つことです。今回はMapなので、薬草は合計9個で上限とし、10個目を別枠へ回しません。また、容量15は「15種類」「15枠」ではなく、全種類を合わせて15個 です。このルールを先に決めてから作ります。

準備:4種類の定義とカバンを作る

1. アイテム定義を作る

Third PersonのBlueprintプロジェクトを使います。Variantを選べる版ではNoneを選び、標準のBP_ThirdPersonCharacterで進めます。

コンテンツブラウザで右クリックし、「Blueprint Class → All Classes」からPrimary Data Assetを親にして、BPDA_ItemDefinition を作ります。次の変数を追加し、すべてInstance Editableを有効、Privateを無効にして、コンパイル・保存します。

変数名初期値
DisplayNameText空欄
MaxStackInteger1
HealAmountFloat0.0

続いて「Miscellaneous → Data Asset」でBPDA_ItemDefinitionを選び、4枚作ります。Blueprintのクラスを複製する操作ではなく、値を入れるデータを作る操作です。

アセット名DisplayNameMaxStackHealAmount
DA_Herb薬草920.0
DA_Potionポーション550.0
DA_Key10.0
DA_Bomb爆弾30.0
同じBPDA_ItemDefinitionから4種類のデータを作る

今回は名前と個数を表示するので、アイコン画像の準備は不要です。データの型と実データの違いは、Data Assetの記事でも説明しています。

2. 所持数を管理するComponentを作る

Actor Componentを親に BP_InventoryComponent を作ります。ComponentはActorへ付け足せる機能の部品です。カバン自体の位置は必要ないので、Scene ComponentではなくActor Componentを使います。

用意するもの名前設定
変数ItemsキーがBPDA_ItemDefinitionのObject Reference、値がIntegerのMap。初期値は空。Privateを有効
変数MaxCapacityInteger、初期値15。Instance Editableを有効
Event DispatcherOnInventoryChanged入力なし

Itemsの型をBPDA_ItemDefinitionの Object Reference にし、コンテナをMapへ切り替え、右側の値の型をIntegerにします。Class Referenceを選ばないようにしてください。

定義をキー、個数を値にしたItemsのMapと、Componentの変更窓口

Event Dispatcher は、登録した相手へ「変わりました」と知らせる仕組みです。「My Blueprint」のEvent DispatchersからOnInventoryChangedを追加します。通知を受け取る表示側は後で作ります。

Itemsは外から直接書き換えず、これから作るAddItemとRemoveItemを通して変更します。上限の確認や通知を、この2つの入口へまとめるためです。

現在の個数を調べる

先に、Componentの中へ3つの関数を作ります。後の追加処理とUIが、この関数を使って現在値を調べます。

GetCount:1種類の個数

関数 GetCount に、入力Item(BPDA_ItemDefinitionのObject Reference)、出力Count(Integer)を追加し、Pureを有効にします。

ItemsのGetからMap用の Find を作り、Keyへ入力のItem、ValueからReturn NodeのCountへつなぎます。FindのBooleanのReturn Valueは今回は使いません。指定したキーがなければ、IntegerのValueは既定値の0になります。

ItemsとItemをFindへ渡し、ValueをGetCountのCountへ返す

GetCountは値を調べるだけの Pure(純粋)関数 なので、呼び出す側に白い実行ピンはありません。Findも同じです。

GetKindCount:持っている種類数

関数 GetKindCount は入力なし、出力Kinds(Integer)で、Pureを有効にします。ItemsのGetからMap用の Length を作り、その出力をReturn NodeのKindsへつなぎます。

これは個数の合計ではありません。薬草9個、ポーション5個、鍵1個なら、Mapの項目は3つなので、種類数は3です。

GetTotalCount:カバン全体の個数

関数 GetTotalCount は入力なし、出力Total(Integer)で、Pureは無効にします。関数内だけで使う ローカル変数 として、Sum(Integer)を作ります。

白い線を、関数入口→Set Sum=0→Map用のValues→For Each Loopへつなぎます。ValuesのTarget MapはItems、配列出力ValuesはFor Each LoopのArrayへ渡します。

Sumを0にしてからValuesで個数の一覧を取り、ループで足す

Loop BodyからSet Sumへつなぎ、「SumのGet+Array Element」を代入します。CompletedからReturn Nodeへつなぎ、TotalへSumを渡します。

ValuesはMapの値を配列へ取り出すノードで、白い実行ピンがあります。9、5、1を順番に足し、ループが終わってから15を返します。毎回最初にSumを0へ戻すこと、ReturnをLoop Bodyへつながないことを確認してください。

コンパイルして保存します。これで、所持数を外へ見せる入口ができました。

Sponsored

追加する:入る分を確かめてから書き換える

関数 AddItem を作ります。入力はItem(BPDA_ItemDefinitionのObject Reference)とAmount(Integer)、出力はSuccess(Boolean)です。Pureは無効にし、ローカル変数CurrentCount(Integer)を追加します。

Amountは「今回入れたい個数」です。今回は、全部入るなら追加し、入らないなら1個も追加しないルールにします。

1. アイテムと個数を確かめる

関数入口から、実行ピンのある Is Valid へつなぎ、Input ObjectへItemを渡します。Is Not Validは「アイテムが未指定です」とPrint Textで表示し、Return Nodeへ進めてSuccess=falseにします。

Is Valid側からBranchへ進み、Conditionを Amount <= 0 にします。Trueは「個数は1以上にしてください」と表示してSuccess=falseで返します。FalseからSet CurrentCountへ進み、GetCount(Item)のCountを代入します。

Is ValidとAmountの確認を通った場合だけ、現在数の取得へ進む

Is Valid は、指定した相手を使えるか調べる入口です。ここを通ってから定義のMaxStackを読みます。0個や負数は追加・削除として受け付けず、所持数を変えません。

以後の失敗経路も、Print Text→Return Node(Success=false)で終えます。Return Nodeは関数の結果を返して終了するノードです。失敗箇所ごとに置いて構いません。

この記事で使うPrint Textは、Print to ScreenとPrint to Logを有効、Duration=5、Key=Noneにそろえます。失敗した理由を画面とログの両方で確認するためです。

2. 種類ごとの上限と、全体の容量を調べる

Set CurrentCountの後にBranchを置き、Conditionを Amount > ItemのMaxStack - CurrentCount にします。Trueは「これ以上重ねられません」と表示してfalseで返します。

Itemの青いピンからドラッグしてGet Max Stackを作ると、その定義の上限を読めます。そこからCurrentCountを引き、Amountとの比較結果をBranchへ渡します。

FalseからGetTotalCount(Target=Self)を呼び、その白い出力から次のBranchへ進みます。今度のConditionは Amount > MaxCapacity - Total です。Trueは「カバンがいっぱいです」と表示してfalseで返します。

種類ごとの残りと、カバン全体の残りを順番に調べる

「上限から現在数を引く」と、あと何個入るかが分かります。薬草8個なら残りは 9 - 8 = 1。1個の追加は通りますが、2個の追加は断ります。カバン全体も、15個入っていれば残り0です。

比較は > です。残り1個に1個入れる操作まで断る >= にしないようにします。

3. 現在数に足して、Mapへ入れ直す

2つ目のBranchのFalseから、Map用のAdd→Call OnInventoryChanged→Return Nodeの順につなぎ、最後のSuccessをtrueにします。

Addの入力はTarget Map=Items、Key=Item、Value=CurrentCount + Amount です。

上限確認後にMapのAddへ新しい個数を渡し、変更通知を出してtrueを返す

Mapの Addは、同じキーがあれば値を上書きする操作 です。1を渡せば常に1個になってしまいます。現在数を調べ、足した結果を書き戻すことで、1→2→3と増やせます。

減らす:0個になった種類は消す

関数 RemoveItem も、入力Item・Amount、出力Successで作り、Pureは無効にします。ローカル変数CurrentCountとNewCountをIntegerで作ります。

最初のIs ValidとAmount<=0の確認はAddItemと同じです。失敗時のメッセージとfalseのReturnも同じにします。通ったら、GetCount(Item)の結果をCurrentCountへ代入します。

次のBranchで CurrentCount < Amount を調べます。Trueは「持っていません」と表示してfalseで返します。Falseから NewCount = CurrentCount - Amount をSetし、もう一つのBranchで NewCount == 0 を調べます。

減らした結果が0ならRemove、残るならAddで新しい数を書き戻す
分岐Mapの操作その後
True:0個になったRemove。Target Map=Items、Key=ItemCall OnInventoryChanged→Return(Success=true)
False:まだ残っているAdd。Target Map=Items、Key=Item、Value=NewCountCall OnInventoryChanged→Return(Success=true)

通知とReturnは、それぞれの枝に置いて構いません。RemoveのBooleanの戻り値は使いません。

0個のキーを消すと、GetKindCountが「実際に持っている種類数」を返せます。薬草を使い切れば、薬草・ポーション・鍵の3種類から、ポーション・鍵の2種類になります。

実践:拾って、薬草を使う

1. Characterへカバンを付ける

BP_ThirdPersonCharacterを開き、ComponentsのAddからBP_InventoryComponentを追加し、名前を Inventory にします。選択したComponentのMaxCapacityが15になっていることを確認します。

CharacterにはCurrentHealth(Float、初期値50.0)とMaxHealth(Float、100.0)を追加します。さらにHerbDefinition(BPDA_ItemDefinitionのObject Reference)を作り、コンパイル後、既定値へDA_Herbを指定します。

2. キー入力で拾う

イベントグラフへキーボードの 1 を追加します。Components欄のInventoryをグラフへドラッグして参照を出し、そこからAddItemを作ります。Pressedを白い実行入力へつなぎ、Item=DA_Herb、Amount=1にします。

1キーから、Characterに付けたInventoryのAddItemを呼ぶ

同じ形で、2・3・4キーも作ります。Itemの欄では、文字を打つ代わりに対象アセットを選びます。Successはこの段階では接続不要です。失敗時はComponent側が理由を表示します。

キーItemAmount
1DA_Herb1
2DA_Potion1
3DA_Key1
4DA_Bomb1

キー入力は拾う処理を確かめるための仮操作です。床のアイテムへ触れた場合も、同じAddItemを呼び、Success=trueのときだけ床のActorを消す形へつなげられます。

3. 1個減らせたときだけ、HPを回復する

Characterへキーボードの Q を追加し、PressedからInventoryのRemoveItemを呼びます。ItemへHerbDefinitionのGet、Amountへ1を渡します。

白い出力からBranchへ進み、ConditionへSuccessをつなぎます。Falseは終了。TrueだけSet CurrentHealthへ進めます。

RemoveItemのSuccessがtrueの場合だけ、Characterの回復処理へ進む

Setする値は、CurrentHealth + HerbDefinitionのHealAmountClamp (Float) へ通した結果です。Min=0、MaxへMaxHealthを渡します。Clampは、計算結果を指定範囲に収める処理です。90に20を足しても、上限100で止まります。

現在HPへ回復量を足し、Clampで最大HP以内に収める

Setの後にFormat Textで HP: {HP} を作り、HPへCurrentHealthを入れ、Print Textへ渡します。

今回は HPが満タンでも薬草を消費する ルールで、0個になるまでの動きを確認します。満タン時に使わせたくなったら、RemoveItemの前にCurrentHealth<MaxHealthを調べる分岐を追加します。

回復をCharacterへ置いたことで、Inventory Componentは特定のCharacterへCastする必要がありません。宝箱へ付けた場合も、同じ追加・削除の処理を使えます。

Sponsored

変更があったときだけ表示を更新する

最後に、名前と個数だけの小さなUIを付けます。4種類を固定順で表示し、持っていない種類は0と表示します。アイコンを並べる前に、所持数と表示が一致すること を確かめるための画面です。

今回は表示名をUIへ直接書くので、定義のDisplayNameを変えても、この画面の名前は変わりません。

1. 表示用Widgetを用意する

User Widgetを親に WBP_InventorySummary を作ります。DesignerにCanvas Panelを置き、その子へTextを追加し、名前を SummaryText、Is Variableを有効にします。左上基準でPosition=(40,40)、Size=(600,220)、Font Size=22を目安にします。TextのBindは設定しません。

WidgetへInventoryRef(BP_InventoryComponentのObject Reference)を作り、Instance EditableとExpose on Spawnを有効にします。Expose on Spawn は、Widgetを作るノードに、この値を渡す入力欄を出す設定です。

Characterが持つInventoryをWidgetへ渡し、同じカバンの数を表示する

2. RefreshInventoryで現在値を表示する

Widgetに関数 RefreshInventory を作ります。入力・出力なし、Pureは無効です。入口から実行ピン付きIs ValidでInventoryRefを確認し、Is Valid側だけGetTotalCountへ進めます。TargetはInventoryRefです。

Format Textへ次の文字を入力します。改行は入力欄で Shift + Enter を使えます。

薬草: {Herb} / ポーション: {Potion}
鍵: {Key} / 爆弾: {Bomb}
合計: {Total} / 種類: {Kinds}

Herb・Potion・Key・Bombには、InventoryRefをTargetにしたGetCountをそれぞれつなぎ、Itemへ対応するDA_Herbなどを指定します。TotalはGetTotalCountの出力、KindsはGetKindCount(Target=InventoryRef)の出力です。

GetTotalCountの白い出力からSetText (Text)へつなぎます。TargetへSummaryTextのGet、In TextへFormat TextのResultを渡します。

4種類の個数と合計・種類数を文字にし、SummaryTextへ設定する

3. 変更通知を受け取り、初回も表示する

コンパイルしたら、イベントグラフのConstructからIs Valid(InventoryRef)へ進み、有効側からBind Event to OnInventoryChangedを呼びます。TargetはInventoryRefです。

赤いEvent入力から Create Event を作り、Object=Self、関数の選択欄をRefreshInventoryにします。これで、通知が届いたときに呼ぶ処理を指定できます。Bindの白い出力から、RefreshInventory(Target=Self)を1回呼びます。

通知先の登録、初回の表示、変更後の更新を分けた流れ

Bind は、通知先を登録する操作です。登録しただけでは過去の変更は届かないので、初回の表示は自分で呼びます。その後はAddItem・RemoveItemが通知するたびに更新されます。

DestructからもIs Valid(InventoryRef)を通し、有効ならUnbind Event from OnInventoryChangedを呼びます。同じInventoryRefと、同じSelf/RefreshInventoryを指定したCreate Eventを渡してください。表示を外した後の不要な購読を終えるためで、ほかの購読まで外すUnbind Allは使いません。

4. Characterから画面へ追加する

BP_ThirdPersonCharacterのBeginPlayへCreate Widgetをつなぎ、Class=WBP_InventorySummary、Owning Player=Get Player Controller(Player Index=0)、Inventory RefへComponents欄から出したInventoryの参照を渡します。既存のBeginPlay処理がある場合は残し、その後へつなぎます。

Create Widgetの白い出力からAdd to Viewportへつなぎ、TargetへReturn Valueを渡します。これで、キャラクターが使っているカバンと、画面が読むカバンが同じになります。

UIのTickで作り直す処理はありません。表示を詳しく作るときも、データはComponentが持ち、UIは現在値を読む関係を保てます。Event Dispatcherの記事UMGの記事も参考になります。

確認:満杯のカバンに、また入る

すべてコンパイル・保存し、Playします。画面をクリックしてキー入力を受け取れる状態にし、次の順で試してください。

操作確認する結果
開始直後4種類とも0、合計0、種類0
1を9回薬草9、合計9、種類1
1をもう1回「これ以上重ねられません」。数は変わらない
2を5回、3を1回薬草9・ポーション5・鍵1、合計15、種類3
4「カバンがいっぱいです」。爆弾0のまま
Q薬草8、合計14、種類3。HPは50→70
Qをさらに2回薬草6、合計12。HPは90→100
Qをさらに6回薬草0、合計6、種類2。HPは100のまま
Qをもう1回「持っていません」。数もHPも変わらない
4今度は爆弾1を持てる。合計7、種類3
合計15で爆弾を断り、薬草を使い切ると合計6になり、爆弾を入れられる

満杯だったカバンが、使ったぶんだけまた入るようになる。 ここまで通れば、上限の判定・削除・表示更新が、同じ現在値を使ってつながっています。

境界も少し試してみる

Playを止め、1キーのAmountを2へ変えて再開します。4回押すと薬草8個になり、5回目は断られて8個のままです。一部だけ入れるルールではないので、9個にはなりません。確認後はAmount=1へ戻します。

Amount=0や-1でも数が変わらず、個数の確認メッセージが出ることを試せます。最後に1へ戻してください。

うまく動かないとき

症状確認するところ
薬草が常に1個AddへCurrentCount+Amountを渡したか
9個目が入らない残り数との比較が > になっているか
合計数がおかしいValuesの各値を足したか。Sumを毎回0へ戻したか。Completedから返しているか
薬草を使い切っても種類30の枝でMapのRemoveを呼んだか
回復しないHerbDefinitionがDA_Herbか。RemoveItemのSuccessで分岐しているか
UIが最初から空欄Create WidgetのInventory Ref、Construct後の初回Refresh、SummaryTextのIs Variable
UIだけ数が古い変更後のCall OnInventoryChanged、BindのTargetとEventを確認
キーが反応しない標準Characterが操作対象か、ゲーム画面に入力フォーカスがあるか

おまけ:自分のゲームへ広げる

同じ名前でも、別のアセットは別種類

Mapのキーは表示名ではなく、DA_Herbというアセットへの参照です。複製したDA_Herb2は、画面に同じ「薬草」と表示されても別のキーになります。試すなら、カバンが空の状態で両方を1個ずつ追加すると、種類数は2になります。

Map自体の並び順も表示順として当てにしません。今回のUIは薬草・ポーション・鍵・爆弾の順を明示しています。種類が増えたら、表示順を決める定義の配列を別に持つ方法があります。

個体差やスロットが必要になったら

剣1本ずつの耐久値や、アイテムを置いた枠の位置も残すなら、スロットを表す構造体の配列へ広げます。コピーとして取り出した構造体を変更する場合は、元の配列への書き戻しも必要です。構造体の記事で確認できます。

重量制にする場合も、追加前に空きを調べる考え方は同じです。ただし、定義へWeightを足し、合計を「個数×重さ」で求め、上限や比較も重さの単位へそろえる必要があります。

レベル移動と、次回の起動へ残す

今回のInventoryはCharacterに付いているので、Characterが作り直されれば中身も初期状態になります。レベル移動で持ち越すには、GameInstanceへ必要な所持データを預けます。

次回の起動にも残すなら、アイテムIDと個数をSave Gameへ書き出し、ロード時にIDから定義を引き直す形にできます。Data Assetへの参照を記録することと、アセットの内容そのものをセーブへ複製することは別です。IDを使う場合も、IDと定義の対応を用意する処理が必要になります。

今回できたのは、出し入れのルールをComponentへまとめたカバンです。拾うきっかけやUIを変えるときも、AddItemとRemoveItemを通して所持数を変えることで、上限確認と変更通知を共通に使えます。

まとめ

  • アイテムの「定義」と、プレイヤーの「所持数」は別に持つ
  • 増やす前に、種類ごとの上限とカバンの容量を確かめる
  • 0個になった種類は、Mapから消して残さない
  • 表示の更新は、数が変わったときだけ呼ぶ

作るときの問いかけは、「これは全員で同じ値か、その人だけの値か」 です。同じならData Asset、その人だけならComponentへ持たせます。

データの持ち方は Array・Map・Set、定義側は Data Asset で扱っています。

参考:MapのFindMapのAddMapのValuesEventのBindとUnbind

Unreal Engine このセクションのノート98