【Unreal Engine】BlueprintのArray・Set・Map入門:3つのコンテナの使い分け

作成: 2026-07-20

変数1個では足りなくなったら、コンテナの出番です。Array(順番がある)・Set(重複しない)・Map(キーで引く)の違いと主要ノードを図解し、アイテムIDと所持数をMapで数えるインベントリを作る実践つき。

インベントリを作ろうとして、Item1Item2Item3 という変数を並べる。敵を管理しようとして、Enemy1Enemy2 を作る。数が増えるたびに変数とノードが増えていき、10体目でつらくなります。

こういうときに使うのが コンテナ です。Blueprintには Array・Set・Map の3種類があり、どれも「同じ型の値をまとめて持つ」ための入れ物です。3つの違いは、 順番を持つか・重複を許すか・キーで引けるか の3点だけです。この記事では、3つの使い分けと主要なノード、そして初心者がほぼ必ず踏む「ループ中に配列をいじる」問題までを解説します。

Array・Set・Mapの3つの入れ物を並べたイメージ

この記事でわかること

  • Array=順番がある / Set=重複しない / Map=キーで引く
  • コンテナは変数の型の右にある 切り替えボタン で選ぶ
  • Arrayの主要ノードと、 Index out of bounds の直し方
  • ループ中に配列を書き換えてはいけない 理由
  • 実践: アイテムIDと所持数をMapで管理するインベントリ

Sponsored

3つのコンテナの違い

3つとも「複数の値を持つ」点は同じです。違うのは できる質問 です。

コンテナ持ち方得意な質問苦手なこと
Array(配列)順番に並べる。重複OK「3番目は何?」「全部で何個?」「これを持っているか」を大量の要素から探すのは遅い
Set(集合)重複なし。順番なし「これを持っているか?」「3番目」が存在しない
Map(辞書)キーと値のペア。順番なし「このキーの値は?」同じキーを2つ持てない
Arrayは番号付きの棚、Setは重複を弾く箱、Mapは名札付きの引き出し、として3つを対比した図

ゲームの例に置き換えると、選び方はかなりはっきりします。

  • Array: 拾った順のログ、パーティの並び、ウェイポイントの巡回順。 順番そのものが意味を持つ もの
  • Set: 一度取ったアイテムのID、解放済みのステージ、図鑑の登録済みモンスター。 同じものを二重に登録したくない もの
  • Map: アイテムID → 所持数、プレイヤー名 → スコア、キー名 → 設定値。 何かを手がかりに引きたい もの

迷ったら、まず 「番号で並べたいのか、名前で引きたいのか」 を自分に聞いてみてください。番号ならArray、名前ならMap、どちらでもなく「持っているかだけ知りたい」ならSetです。

コンテナは型の右で切り替える

Blueprintでは、コンテナは 専用の型ではなく、既存の型の「持ち方」 として指定します。

My Blueprintパネルで変数を作ると、詳細パネルに Variable Type の欄があります。型を選ぶドロップダウンの 右側に小さなボタン があり、ここで4つから選べます。

表示意味
Single Variable普通の変数(1個)
Array配列
Set集合
Map辞書。選ぶと 値側の型を選ぶ欄がもう1つ増える
変数の型ドロップダウンの右にあるコン��テナ切り替えボタンと、4つの選択肢を示した図

Mapを選んだときだけ、欄が2つになります。 左がキーの型、右が値の型 です。「アイテムID(Name)→ 所持数(Integer)」なら、左にName、右にIntegerを設定します。

キーにできない型があります。 SetとMapのキーは、内部でハッシュ値を計算して高速に検索するため、 ハッシュ化できる型しか使えません 。Integer・Float・Name・String・Enum・オブジェクト参照などは使えますが、Textや多くの構造体はキーにできず、コンパイル時にエラーになります。 アイテムIDにはTextではなくNameを使う のが定石です。

Sponsored

Arrayの主要ノード

3つの中でいちばん出番が多いのがArrayです。配列変数をグラフへドラッグして、ピンから線を引き出すと候補が出てきます。

Add・Insert・Remove・Get・Length・Containsなどの主要ノードを役割ごとに並べた早見図
ノードすること注意
Add末尾に追加する追加した位置のインデックスを返す
Insert指定インデックスに割り込ませる後ろの要素が1つずつ後ろへずれる
Remove Index番号を指定して消す後ろの要素が1つずつ前へ詰まる
Remove Item中身を指定して消す一致するものを全部 消す
Get (a copy)番号で取り出す範囲外を指定するとエラーになる
Set Array Elem番号を指定して上書きする範囲外はやはりエラー
Length要素数を返す空なら0
Last Index最後の番号(Length - 1)を返す空の配列では -1 になる
Contains Item含まれているかをboolで返す先頭から順に探すので、要素が多いと遅い
Find Item見つかった番号を返す見つからないと -1 を返す
Is Valid Indexその番号が使えるかをboolで返すGetの前の安全確認に使う

インデックスは 0から始まります 。要素が3個なら使える番号は0・1・2で、Lengthは3、Last Indexは2です。

ここでいちばん多いつまずきが、Output Logに出る次のエラーです。

Blueprint Runtime Error: "Attempted to access index 3 from array 'Inventory' of length 3!"

「3個入っている配列の、3番目にアクセスしようとした」という意味です。 入っている個数と、使える最大の番号は1つずれます 。Lengthをそのまま番号に使うと必ずこれになります。

防ぎ方は2つです。

  • Is Valid Index で確認してからGetする: 番号が外から来る場合(UIのクリック位置、セーブデータの値など)はこちら
  • Last Index を使う: 「最後の要素」が欲しいだけなら、Length - 1 を自分で計算せず Last Index を使う

Getしたものはコピーです。 配列の中身が構造体のとき、Get で取り出して書き換えても元の配列は変わりません。書き戻すには Set Array Elem を使います。これは詰まりやすいポイントなので、構造体の記事で仕組みと直し方を詳しく扱っています。

For Each Loopとループ中の書き換え

配列を1つずつ処理するには For Each Loop を使います。配列のピンから引き出すと候補に出ます。

ピン内容
Loop Body1要素ごとに実行される
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の敵を配列から取り除く」といった処理は、まさにこの形です。半分しか消えない、という症状が出ます。

対処は3つあります。

  • 消す対象を別の配列に集める: ループ中は RemoveTargets に追加するだけにして、Completed の後でまとめて Remove Item する。いちばん素直で読みやすい方法です
  • 逆順に回す: Last Index から0へ向かって Reverse for Loop で回します。後ろから消せば、前の要素の番号は動きません
  • 残すものだけで新しい配列を作る: 条件に合う要素だけを別の配列に Add して、最後に元の変数へ入れ替えます

追加についても同じです。ループ中に Add すると、Last Indexが伸び続けて ループが終わらなくなる ことがあります。

Sponsored

Set:重複しない入れ物

Setは 同じ値を2つ持てない 入れ物です。すでに入っている値を Add しても、要素数は増えません。エラーにもならず、静かに無視されます。

ノードすること
Add追加する。すでにあれば何も起きない
Remove中身を指定して消す
Contains含まれているかをboolで返す
Length要素数を返す
To Array配列に変換する(ループしたいときはこれ)

Setが向いているのは、 「持っているかどうか」だけを知りたい ケースです。

  • 一度取ったアイテムのID(同じ宝箱を二度開けても増えない)
  • 解放済みステージのID
  • 図鑑に登録済みのモンスターID
  • 会話済みのNPCのID

「二重登録を防ぐコードを書かなくていい」のがSetの価値です。Arrayで同じことをやろうとすると、Contains Item で調べてから Add する、という2手が毎回必要になります。

Setには 番号がありませんGet(0) のような取り出し方はできず、順番も保証されません。1件ずつ処理したいときは To Array で配列に変換してからループします。

Map:キーで引く入れ物

Mapは キーと値のペア を持つ入れ物です。C++の TMap と同じものです。

ノードすること注意
Addキーと値を登録する同じキーがあれば上書き される
Findキーから値を取り出す値と、見つかったかのboolを返す
Containsキーがあるかをboolで返す
Removeキーを指定して消す
Keysキーの一覧を 配列 で返すループしたいときはこれ
Values値の一覧を配列で返す
Lengthペアの数を返す
アイテムIDというキーから所持数という値を引くMapの構造と、Find・Add・Keysの役割を示した図

いちばん効くのが 「何かの数を数える」 場面です。Map<Name, Integer> を1つ持てば、「薬草を何個持っているか」に一発で答えられます。Arrayで同じことをやると、アイテム名の配列と個数の配列を並行管理することになり、ずれた瞬間に破綻します。

Mapで押さえておく点が2つあります。

1つ目は、Add が上書きだということ。 「すでにあったら何もしない」ではありません。数を増やしたいなら、 Find で今の値を取り、1を足して、Add で入れ直す という流れになります。この形は実践セクションで組みます。

2つ目は、順番が保証されないこと。 Keys で取り出した配列の並びは、追加した順とは限りません。要素を削除すると並びが変わることもあります。順番に意味があるなら、 表示順用のArrayを別に持つ か、Keys の結果を Sort してから使ってください。

Findで取り出した値もコピーです。 値の型が構造体のとき、Findで取ったものを書き換えても元のMapは変わりません。Add で同じキーに入れ直すのが確実です(→ 構造体の記事)。

Sponsored

実践:所持数を数えるインベントリを作る

RPGの所持品、ローグライクの床落ちアイテム、サバイバルゲームのクラフト素材。 「拾って数える」 はジャンルを問わず出てきます。ここでは同じ「拾う」を3つのコンテナで同時に記録して、 それぞれが答えられる質問が違う ことを目で確かめます。

動かすとこうなる

薬草を3回、ポーションを1回拾ったあと、3つのコンテナの中身がこうなります。

薬草3回とポーション1回を拾ったとき、Arrayは4件、Setは2件、Mapは薬草3ポーション1になることを並べて示した図
コンテナ中身答えられる質問
PickupLog(Array)薬草, 薬草, 薬草, ポーション(4件)「3番目に拾ったのは?」
FoundItems(Set)薬草, ポーション(2件)「ポーションを見たことがある?」
Inventory(Map)薬草→3, ポーション→1「薬草を何個持ってる?」

再現条件

Third Person テンプレート で新規プロジェクトを作り、BP_ThirdPersonCharacter を開いて次の変数を作ります。すべて 初期値は空 です。

変数名コンテナ初期値
PickupLogNameArray(空)
FoundItemsNameSet(空)
InventoryName → IntegerMap(空)

コンテナの切り替えは、詳細パネルの Variable Type の右のボタン です。Inventory はMapを選ぶと欄が2つになるので、 左をName、右をInteger にします。

PickupItem関数を作る

My Blueprintパネルの FunctionsPickupItem を新規作成し、入力ピンを1つ追加します。

入力ピン
ItemIDName

中身は、3つのコンテナへ順に記録していくだけです。

PickupItem関数のノードグラフ。Array Add、Set Add、Map Find→Add→Addの3段構成
関数: PickupItem(入力: ItemID / Name)

  → Add(Target: PickupLog, New Item: ItemID)        ← Arrayに拾った順で追加
  → Add(Target: FoundItems, New Item: ItemID)       ← Setに追加(重複は無視される)
  → Find(Target: Inventory, Key: ItemID)
      Value  → Add(A: Value, B: 1) の A へ
      Found? → 使わない(見つからないとき Value は 0 なので、そのまま +1 で 1 になる)
  → Add(Target: Inventory, Key: ItemID, Value: 上の加算結果)  ← 同じキーなので上書きされる

ポイントは最後の3行です。 Findで今の数を取り、1を足して、同じキーで入れ直す 。Mapの Add は上書きなので、これで「あれば増やす、なければ1で作る」が1本の線で書けます。

Find は見つからなかったとき、Integerの既定値である 0 を返します。そこに1を足せば1になるので、「初回かどうか」の分岐を書く必要がありません。

呼んで確認する

イベントグラフで4回呼び、結果を表示します。

Event BeginPlay
  → PickupItem(ItemID: Herb)
  → PickupItem(ItemID: Herb)
  → PickupItem(ItemID: Herb)
  → PickupItem(ItemID: Potion)

  → Length(Target: PickupLog)  → Print String(Append("Log: ", ToString))
  → Length(Target: FoundItems) → Print String(Append("Found: ", ToString))
  → Find(Target: Inventory, Key: Herb)
      Value → Print String(Append("Herb: ", ToString))

Playすると、画面左上にこう出ます。

Log: 4
Found: 2
Herb: 3

4回拾ったのでArrayは 4 、種類は2つなのでSetは 2 、薬草は3回拾ったのでMapの値は 3 です。 同じ4回の呼び出しから、3つの違う答えが出ている ことがこの実践の要点です。

ここで PickupItem(Herb) をもう1回足してみてください。 Logは5、Foundは2のまま、Herbは4 になります。Setだけが増えないのが、Setの性質そのものです。

うまくいかないときの切り分けです。

  • Herbが常に1 → Findの結果を足さずに、Add へ直接1を入れている
  • Herbが4回目でも1のまま → Mapの Add ではなく別のキーに入れている。キーがNameなので、Herbherb の打ち間違いも疑う
  • Foundが4になるFoundItems がSetではなくArrayになっている。詳細パネルのコンテナ設定を確認する
  • Logが0のままPickupLog への Add の Target が別の変数につながっている
  • Print Stringが何も出ないPickupItem の実行ピンが最後までつながっていない(→ Print Stringデバッグ

ポイントは2つです。

  • 「答えたい質問」からコンテナを選ぶ: 中身が同じでも、Arrayは順番、Setは有無、Mapは対応関係にしか素早く答えられません。作る前に「このデータに何を聞きたいか」を1文で書くと、選択は自動的に決まります
  • 数を数えるならMapの Find → +1 → Add: この3ノードの形は、所持数・撃破数・スコア集計など、あらゆる「数える」処理で同じまま使えます。ここだけ覚えて帰ってください

アイテムの中身が「名前と攻撃力とアイコン」のように増えてきたら、値を構造体にする段階です。構造体(Struct)の記事S_Item を作り、そのまま Data Table の行として使えます。

Sponsored

おまけ:先に知っておくと良いこと

  • Contains Item は要素数に比例して遅くなる: Arrayの Contains Item は先頭から順番に比べていきます。数十件なら気になりませんが、数千件を毎フレーム調べるなら SetかMapに持ち替える のが正解です。Set・Mapの Contains は件数が増えてもほぼ一定の速さで答えます
  • 配列は最初から確保しておける: 詳細パネルのDefault Valueで要素をあらかじめ入れておけます。「巡回ポイントを5つ」のように数が決まっているものは、実行時に Add するよりレベル上で設定するほうが確認しやすくなります
  • Mapはセーブと相性が良い: 「アイテムID → 所持数」はそのままセーブデータの形になります。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

コンテナで複数のデータを持てるようになったら、次は 状態 の整理です。boolを増やして破綻する前に、EnumとSwitchで状態を管理するへ進んでください。

いま作っているBlueprintに、末尾が数字だけ違う変数はありませんか?