インベントリを作ろうとして、Item1・Item2・Item3 という変数を並べる。敵を管理しようとして、Enemy1・Enemy2 を作る。数が増えるたびに、変数と同じようなノードを追加することになります。
こういうときに使うのが コンテナ です。Blueprintには Array・Set・Map の3種類があり、どれも「同じ型の値をまとめて持つ」ための入れ物です。選ぶときは、順番を残したいか・種類だけを記録したいか・IDから個数を知りたいか を考えます。今回は同じ拾い物を3種類で記録し、それぞれ何が分かるかを比べます。
この記事でわかること
- Array=順番がある / Set=重複しない / Map=キーで引く
- コンテナは変数の型の右にある 切り替えボタン で選ぶ
- Arrayの主要ノードと、 Index out of bounds の直し方
- ループ中の追加・削除で、次の要素を飛ばす 理由
- 実践: アイテムIDと所持数をMapで管理するインベントリ
3つのコンテナの違い
3つとも複数のデータをまとめます。中に入る1件を 要素 と呼びます。違うのは、どのように探し、取り出すかです。
| コンテナ | 持ち方 | 得意な質問 | 苦手なこと |
|---|---|---|---|
| Array(配列) | 順番に並べる。重複OK | 「3番目は何?」「全部で何個?」 | 含まれる値を探すときは、順に比較する |
| Set(集合) | 重複なし。順番なし | 「これを持っているか?」 | 「3番目」が存在しない |
| Map(辞書) | キーと値のペア。順番なし | 「このキーの値は?」 | 同じキーを2つ持てない |
ゲームの例に置き換えると、選び方はかなりはっきりします。
- Array: 拾った順のログ、パーティの並び、ウェイポイントの巡回順。 順番そのものが意味を持つ もの
- Set: 一度取ったアイテムのID、解放済みのステージ、図鑑の登録済みモンスター。 同じものを二重に登録したくない もの
- Map: アイテムID → 所持数、プレイヤー名 → スコア、キー名 → 設定値。 何かを手がかりに引きたい もの
キー は、取り出したいデータを指定する手がかりです。Mapで「Herb → 3」と持つなら、Herbがキー、3が値です。配列の位置を示す番号とは役割が違います。

コンテナは型の右で切り替える
Blueprintでは、まず中身の型を選び、それを1個持つか、Array・Set・Mapにまとめるかを指定します。
My Blueprintパネルで変数を作ると、詳細パネルに Variable Type の欄があります。型を選ぶドロップダウンの 右側に小さなボタン があり、ここで4つから選べます。
| 表示 | 意味 |
|---|---|
| Single Variable | 普通の変数(1個) |
| Array | 配列 |
| Set | 集合 |
| Map | 辞書。選ぶと 値側の型を選ぶ欄がもう1つ増える |

Mapを選んだときだけ、欄が2つになります。 左がキーの型、右が値の型 です。「アイテムID(Name)→ 所持数(Integer)」なら、左にName、右にIntegerを設定します。
Name は、HerbやPotionのような識別名に使う型です。画面に出す「薬草」という文章と、処理で指定するIDを分けたいときに向いています。Setの要素やMapのキーに使えない型もあるため、今回のアイテムIDはNameで揃えます。
Arrayの主要ノード
3つの中でいちばん出番が多いのがArrayです。配列変数をグラフへドラッグして、ピンから線を引き出すと候補が出てきます。
| ノード | すること | 注意 |
|---|---|---|
| Add | 末尾に追加する | 追加した位置のインデックスを返す |
| Insert | 指定インデックスに割り込ませる | 後ろの要素が1つずつ後ろへずれる |
| Remove Index | 番号を指定して消す | 後ろの要素が1つずつ前へ詰まる |
| Remove Item | 中身を指定して消す | 一致するものを全部 消す |
| Get (a copy) | 番号で取り出す | 範囲外を指定するとエラーになる |
| Set Array Elem | 指定番号の値を書き換える | Size to Fitがオフなら既存の番号を使う。オンなら必要に応じて配列を広げる |
| Length | 要素数を返す | 空なら0 |
| Last Index | 最後の番号(Length - 1)を返す | 空の配列では -1 になる |
| Contains Item | 含まれているかをBooleanで返す | 見つかればtrue、なければfalse |
| Find Item | 見つかった番号を返す | 見つからないと -1 を返す |
| Is Valid Index | その番号が使えるかをboolで返す | Getの前の安全確認に使う |
インデックス は、Arrayの中の位置を指定する番号です。0から始まる ので、要素が3個なら番号は0・1・2。Lengthは個数の3、Last Indexは最後の番号の2です。Boolean(bool)は、条件が成り立つtrueと、成り立たないfalseを扱う型です。

範囲外の番号でGetすると、Output Logに次のようなメッセージが出ます。文言はノードや版によって異なりますが、indexとlengthを見ると原因を追えます。
Blueprint Runtime Error: "Attempted to access index 3 from array 'PickupLog' of length 3!"
「3個の配列に、番号3を指定した」という意味です。番号3は先頭から数えて4番目なので、存在しません。個数と最後の番号は1つ違います。
防ぎ方は2つです。確認したBooleanをBranchのConditionへ入れ、True側だけでGetを使います。Branchは、条件に応じてTrue/Falseへ実行を分けるノードです。
- Is Valid Index で確認してからGetする: 番号が外から来る場合(UIのクリック位置、セーブデータの値など)はこちら
- 最後の要素も、空かを確かめてから読む:
Last Indexは便利ですが、空配列では-1です。Length > 0のBranchでTrue側だけGetするか、Last IndexをIs Valid Indexで確認します
Get (a copy)は値をコピーして取り出します。 配列の中身が構造体のとき、取り出したコピーを書き換えるだけでは元の要素は変わりません。書き戻すには
Set Array Elemを使います。これは詰まりやすいポイントなので、構造体の記事で仕組みと直し方を詳しく扱っています。
For Each Loopとループ中の書き換え
配列を1つずつ処理するには For Each Loop を使います。配列のピンから引き出すと候補に出ます。
| ピン | 内容 |
|---|---|
| Loop Body | 1要素ごとに実行される |
| Array Element | そのときの中身 |
| Array Index | そのときの番号 |
| Completed | 全部終わったあとに1回だけ実行される |
途中で止めたいときは For Each Loop with Break を使います。Break ピンに実行を流すと、その時点でループが終わります。「最初に見つかった1件で処理を打ち切る」ときに便利です。
注意したいのは、For Each Loopの途中で、処理中の配列へ要素を追加・削除すること です。個数や番号が変わるため、想定した順で処理できなくなる場合があります。

理由は仕組みを見れば分かります。For Each Loopは 番号を0から1つずつ進めている だけです。A, B, C, D の4件があり、番号1の B を消したとします。すると C が番号1へ、D が番号2へ詰まります。ループは次に番号2を見るので、 番号1へ移動した C は一度も処理されません 。
「HPが0の敵を配列から取り除く」ときも、隣り合う対象を取りこぼす原因になります。
最初は、元の配列を読み終わるまで変えない方法が分かりやすいです。
- 消す対象を別の配列に集める: ループ中は
RemoveTargetsに追加するだけにして、Completedの後でまとめてRemove Itemする。いちばん素直で読みやすい方法です - 残すものだけで新しい配列を作る: 条件に合う要素だけを別の配列に
Addして、最後に元の変数へ入れ替えます
追加するときも、別の配列へ集めてから処理後に加えると、今回のループで扱う範囲がはっきりします。既存の要素の値を更新することと、個数や位置を変えることは区別してください。
Set:重複しない入れ物
Setは 同じ値を2つ持てない 入れ物です。すでに入っている値を Add しても、要素数は増えません。エラーにもならず、静かに無視されます。
| ノード | すること |
|---|---|
| Add | 追加する。すでにあれば何も起きない |
| Remove | 中身を指定して消す |
| Contains | 含まれているかをboolで返す |
| Length | 要素数を返す |
| To Array | 配列に変換する(ループしたいときはこれ) |
Setが向いているのは、 「持っているかどうか」だけを知りたい ケースです。
- 一度取ったアイテムのID(同じ宝箱を二度開けても増えない)
- 解放済みステージのID
- 図鑑に登録済みのモンスターID
- 会話済みのNPCのID

Setを選ぶと、「この一覧は同じIDを重ねて持たない」と構造で示せます。Arrayにも Add Unique という重複を追加しないノードがあります。順番も残したいならArray+Add Unique、有無を記録するのが主目的ならSet、という選び方ができます。
Setには 番号がありません 。Get(0) のような取り出し方はできず、順番も保証されません。1件ずつ処理したいときは To Array で配列に変換してからループします。
Map:キーで引く入れ物
Mapは キーと値のペア を持つ入れ物です。例えばHerbというIDと、所持数3を1組として持ちます。
| ノード | すること | 注意 |
|---|---|---|
| Add | キーと値を登録する | 同じキーがあれば上書き される |
| Find | キーから値を取り出す | 値と、見つかったかのboolを返す |
| Contains | キーがあるかをboolで返す | |
| Remove | キーを指定して消す | |
| Keys | キーの一覧を 配列 で返す | ループしたいときはこれ |
| Values | 値の一覧を配列で返す | |
| Length | ペアの数を返す |
今回は アイテムIDから所持数を知る ためにMapを使います。Map<Name, Integer> は「キーがName、値が整数のMap」という表記です。Herbを指定してFindすると、その所持数を取り出せます。
Mapで押さえておく点が2つあります。
1つ目は、Add が上書きだということ。 「すでにあったら何もしない」ではありません。数を増やしたいなら、 Find で今の値を取り、1を足して、Add で入れ直す という流れになります。この形は実践セクションで組みます。

2つ目は、順番が保証されないこと。 Keys で取り出した配列の並びは、追加した順とは限りません。要素を削除すると並びが変わることもあります。表示順を決めたいなら、表示順用のArrayを別に持ちます。 例えばArrayをHerb、Potionの順にし、そのIDでMapをFindすれば、登録順に頼らず並べられます。
Findで取り出した値もコピーです。 値の型が構造体のとき、Findで取ったものを書き換えても元のMapは変わりません。
Addで同じキーに入れ直すのが確実です(→ 構造体の記事)。
実践:所持数を数えるインベントリを作る
RPGの所持品、ローグライクの床落ちアイテム、サバイバルゲームのクラフト素材。 「拾って数える」 はジャンルを問わず出てきます。ここでは同じ「拾う」を3つのコンテナで同時に記録して、 それぞれが答えられる質問が違う ことを目で確かめます。
動かすとこうなる
薬草を3回、ポーションを1回拾ったあと、3つのコンテナの中身がこうなります。

| コンテナ | 中身 | 答えられる質問 |
|---|---|---|
PickupLog(Array) | 薬草, 薬草, 薬草, ポーション(4件) | 「3番目に拾ったのは?」 |
FoundItems(Set) | 薬草, ポーション(2件) | 「ポーションを見たことがある?」 |
Inventory(Map) | 薬草→3, ポーション→1 | 「薬草を何個持ってる?」 |
再現条件
Actorを親にしたBlueprint BP_ContainerTester を作り、レベルへ1個配置します。今回は取得処理をBeginPlayから呼ぶ練習なので、床のアイテムや操作入力は不要です。次の変数をVariablesに作り、Compile後の既定値はすべて 空 にします。
| 変数名 | 型 | コンテナ | 初期値 |
|---|---|---|---|
PickupLog | Name | Array | (空) |
FoundItems | Name | Set | (空) |
Inventory | Name → Integer | Map | (空) |
コンテナの切り替えは、詳細パネルの Variable Type の右のボタン です。Inventory はMapを選ぶと欄が2つになるので、 左をName、右をInteger にします。
PickupItem関数を作る
My Blueprintパネルの Functions で PickupItem を新規作成し、入力ピンを1つ追加します。
| 入力ピン | 型 |
|---|---|
ItemID | Name |
関数のPureはオフにします。履歴をArrayへ追加し、種類をSetへ追加してから、Mapの所持数を更新します。同じAddという名前でも種類が違うので、各コンテナ変数のGetを置き、そのピンからドラッグしてノードを作ってください。
まず、取得の履歴と見つけた種類を記録します。
- PickupItemの入口から、Array用のAddへ白い実行線をつなぐ。Target ArrayはPickupLog、New Itemは関数のItemID。
- 続けてSet用のAddへつなぐ。Target SetはFoundItems、New Itemは同じItemID。

同じIDを、今度はSetへ入れます。重複は自動で1件にまとまります。

図のGet ItemIDは、関数へ渡された入力値を読むノードです。関数の入口にあるItemIDピンから同じ値をつないでも構いません。新しいClass Variableは作りません。
続いて、所持数を更新します。PickupItemを開いた状態で、Local VariablesにIntegerの NewCount を作り、既定値を0にします。今回書き込む個数を、一度ここへ入れます。Local Variableは、その関数の中でだけ使う変数です(→ Blueprint変数入門)。
InventoryのGetから Find とMap用の Add を作り、次のようにつなぎます。
| 接続する先 | 渡す値 |
|---|---|
| FindのTarget Map | Inventory |
| FindのKey | ItemID |
Integerの + のA | FindのValue |
+ のB | 1 |
| MapのAddのTarget Map | Inventory |
| MapのAddのKey | ItemID |
| Set NewCountの値入力 | + の結果 |
| MapのAddのValue | Get NewCount |
Set用Addの白い実行出力をSet NewCountへ、その出力をMap用Addの実行入力へつなぎます。これで、新しい個数を計算してからMapへ書く順番になります。Findと整数の加算に は、白い実行線をつなぎません。 Set NewCountへ渡す値が必要になったときに計算されます。

数えた結果を、同じキーへ書き戻します。

ポイントは、Findで今の数を読み、1を足し、同じキーで入れ直す ことです。MapのAddは自動で個数を足すノードではありません。Valueへ毎回1を渡すだけだと、何回拾っても1へ上書きしてしまいます。
今回のMapは値がIntegerなので、Findで未登録のキーを探すとValueは0です。0+1で最初の1個になるため、この集計では見つかったかのBoolean出力を使わずに済みます。
一方、「未登録」と「登録されているが0個」を区別したい処理では、FindのBoolean出力もBranchで確認します。0という値だけでは、その違いは分かりません。
呼んで確認する
Event GraphのBeginPlayで4回呼びます。関数の作成後にCompileし、PickupItemの呼び出しノードを4個置いて、白い実行線を順につなぎます。ItemIDは最初の3個を Herb、最後を Potion にします。Herbは薬草、Potionはポーションを表すIDとして使います。
Event BeginPlay
→ PickupItem(ItemID: Herb)
→ PickupItem(ItemID: Herb)
→ PickupItem(ItemID: Herb)
→ PickupItem(ItemID: Potion)
→ Print String(Logの件数)
→ Print String(Foundの種類数)
→ Print String(Herbの所持数)
3つのPrint Stringへ渡す数字は、PickupLogのLength、FoundItemsのLength、InventoryをKey=HerbでFindしたValueです。LengthとFindには実行ピンがないので、数字の出力を表示用につなぎます。
数字をStringへ変換し、Append のBへ入れます。Aには順に Log: 、Found: 、Herb: を入れ、Appendの結果をPrint StringのIn Stringへ渡します。Integerの線をString入力へつなぐと、変換ノードが入ります。
Print StringのDurationを10秒にし、Compile・保存してPlayします。次の3つのメッセージが出れば成功です。画面での上下の順ではなく、ラベルと数字を見ます。
Log: 4
Found: 2
Herb: 3
4回拾ったのでArrayは 4 、種類は2つなのでSetは 2 、薬草は3回拾ったのでMapの値は 3 です。 同じ4回の呼び出しから、3つの違う答えが出ている ことがこの実践の要点です。
ここで、4回目のPickupItemと最初のPrint Stringの間へ、PickupItem(Herb) をもう1個足してみてください。 Logは5、Foundは2のまま、Herbは4 になります。Setだけが増えないのが、Setの性質そのものです。
うまくいかないときの切り分けです。
- Herbが常に1 → Findの結果を足さずに、
Addへ直接1を入れている - 同じアイテムが別々に数えられる →
HerbとHerbsなど、IDのつづりが違わないかを確認する。Nameは大文字・小文字を区別しないので、Herbとherbは同じIDとして扱われる - Foundが4になる →
FoundItemsがSetではなくArrayになっている。詳細パネルのコンテナ設定を確認する - Logが0のまま →
PickupLogへのAddの Target が別の変数につながっている - Print Stringが何も出ない →
PickupItemの実行ピンが最後までつながっていない(→ Print Stringデバッグ)
実際のゲームでは、触れたアイテムや報酬を受け取る処理からPickupItemを呼びます。今回は4回の呼び出しだけで、同じ出来事から「順番」「種類」「個数」を別々に取り出せることを確かめました。
アイテムの中身が「名前と攻撃力とアイコン」のように増えてきたら、値を構造体にする段階です。構造体(Struct)の記事で S_Item を作り、そのまま Data Table の行として使えます。
所持品を画面に並べて使うところへ進むなら、インベントリの実装が次の題材です。
おまけ:先に知っておくと良いこと
- 大量の検索では調べ方も見直す: ArrayのContains Itemは値を順に比べます。大量のデータを繰り返し探す処理が重い場合は、まず呼ぶ回数を確かめ、用途に合えばSetやMapで管理する方法を検討します。小さな一覧では、必要な順番やデータの意味を優先して選べます
- 配列は最初から確保しておける: 詳細パネルのDefault Valueで要素をあらかじめ入れておけます。「巡回ポイントを5つ」のように数が決まっているものは、実行時に
Addするよりレベル上で設定するほうが確認しやすくなります - 所持数を次回へ残すには保存する: 今回のActorのMapは、Playを止めると失われます。SaveGameに同じ型のMapを作って値を写し、保存処理を呼べば、次のプレイへ持ち越せます。変数を作るだけではファイルへ保存されません(→ セーブ/ロードの実装)
- 中身が固定のデータはDataTableへ: 「全アイテムの一覧」のように 設計時に決まっている データは、Blueprintの配列に手で入れるよりDataTableのほうが管理しやすくなります(→ Data Table入門、Data Assetでデータ駆動設計)
- 空のときの分岐を忘れない:
Lengthが0の配列に対してGet(0)を呼ぶとエラーになります。「最初の敵を狙う」のような処理は、 Length > 0 の確認をセット にしてください。この手のうっかりはBlueprintのよくあるミスにも まとめてあります
まとめ
| 質問 | 選ぶコンテナ |
|---|---|
| 順番が意味を持つ? | Array |
| 重複を許したくない・有無だけ知りたい? | Set |
| 何かを手がかりに引きたい? | Map |
- コンテナは 変数の型の右のボタン で切り替える。Mapだけ欄が2つになる
- Arrayの番号は 0始まり 。
Lengthと最大の番号は1ずれる - ループ中の追加・削除で、処理対象の番号を変えない 。最初は、別配列へ対象を集めてから処理する
- Mapの
Addは 上書き 。数を増やすなら Find → +1 → Add
複数の値をまとめる方法が分かったら、次は「待機中・移動中・攻撃中」のような状態の整理も試せます。EnumとSwitchで状態を管理するで扱います。
拾った順を残す、見つけた種類を記録する、所持数を数える。同じ拾い物でも、残したい情報に合わせて入れ物を選べます。