RPGの持ち物、サバイバルゲームの所持品、クラフトゲームの素材欄——インベントリは、多くのゲームに欠かせないシステムです。ところがいざ作ろうとすると、アイテムの定義・所持数の管理・スタック・UIの更新が絡み合って、どこから手を付けるか迷いがちです。ここでも鍵は データとUIの分離 。「何をいくつ持っているか(データ)」と「それをどう並べて見せるか(UI)」を分けると、驚くほどすっきり作れます。
この記事では、拡張しやすいインベントリの土台を組み立てます。アイテムを ScriptableObject で定義し、スロットのデータ構造、追加・削除・スタック 処理、グリッドUI への反映、そして セーブ まで。RPGの持ち物欄、サバイバルのバッグ、ローグライクの所持品——どれもこの設計から作れます。
この記事でわかること
- アイテムを ScriptableObject で定義する(アイテムマスタ)
- インベントリの データ構造(スロットのリスト)
- 追加・削除・スタック の処理
- グリッドUI への反映(Layout Group)
- 実践:拾う・使う・並べるインベントリ
動作確認環境: Unity 6 (6000.x)
データとUIを分離する
インベントリ作りで最初に決めるべきは、 「持ち物のデータ」と「表示するUI」をはっきり分ける ことです。ここを混ぜると、アイテムを1個拾うたびにUIコードを直す羽目になります。

構成は3つの層に分かれます。
| 層 | 役割 |
|---|---|
| アイテムマスタ(ScriptableObject) | アイテムの「設計図」。名前・アイコン・最大スタック数など、種類ごとに1つ |
| インベントリデータ(スロットのリスト) | プレイヤーが「何をいくつ持っているか」。アイテムへの参照+個数の集まり |
| グリッドUI(Canvas) | データを見て、アイコンと個数をマス目に並べて表示する |
大事なのは、 データが変わったらUIを再描画する という一方向の流れです。「拾う」「使う」といった操作はデータだけを変え、UIはそれを見て描き直すだけ。この分離が、あとでスタックや並べ替えを足すときに効いてきます。
アイテムをScriptableObjectで定義する
まずアイテムの「設計図」を ScriptableObject で定義します。「ポーション」「鉄の剣」といった アイテムの種類ごとに1つのアセット を作ります。

using UnityEngine;
[CreateAssetMenu(fileName = "NewItem", menuName = "Inventory/Item Data")]
public class ItemData : ScriptableObject
{
public string itemName; // アイテム名
public Sprite icon; // アイコン画像
[TextArea] public string description;
public int maxStack = 99; // 1スロットに重ねられる最大数
}
[CreateAssetMenu] で Create > Inventory > Item Data からアイテムを作れます。ポーションなら maxStack = 99、剣なら maxStack = 1(重ねられない)というように、種類ごとの性質をアセットに持たせます。この ItemData は アイテムの種類そのもの で、全プレイヤー・全ドロップで共有される「マスタデータ」です。プレイヤーが持つのは、この参照+個数だけになります。
スロットのリストとスタック処理
プレイヤーの持ち物は、 スロット(マス)のリスト で表します。各スロットは「どのアイテムを、いくつ持っているか」を持ちます。

using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class InventorySlot
{
public ItemData item; // どのアイテムか(種類への参照)
public int count; // いくつ持っているか
}
public class Inventory : MonoBehaviour
{
public List<InventorySlot> slots = new();
public int maxSlots = 20; // 持ち物欄のマス数上限
// アイテムを追加する(スタック対応)
// 戻り値:入りきらなかった個数(0なら全部入った)
public int AddItem(ItemData item, int amount = 1)
{
// おかしな呼び出し(0個以下・最大数が0)は何もしない。放っておくと個数が減ったり、空のマスを作り続けたりする
if (item == null || amount <= 0 || item.maxStack < 1) return amount;
// 1. 同じアイテムで空きのあるスロットへ、「入るぶんだけ」詰める
foreach (var slot in slots)
{
if (slot.item == item && slot.count < item.maxStack)
{
int space = item.maxStack - slot.count; // このスロットの空き
int moved = Mathf.Min(space, amount); // 空きと追加数の小さい方だけ入れる
slot.count += moved;
amount -= moved;
if (amount == 0) break;
}
}
// 2. まだ余っていれば、新しいスロットへ(マスが空いている限り)
while (amount > 0 && slots.Count < maxSlots)
{
int moved = Mathf.Min(item.maxStack, amount);
slots.Add(new InventorySlot { item = item, count = moved });
amount -= moved;
}
RefreshUI(); // データが変わったのでUI更新
return amount; // 0でなければ「持ちきれなかった」
}
// UIの描き直し(中身は次の節の手順で書く)
void RefreshUI() { }
}
ポイントは スタック(重ね持ち)の判定 です。アイテムを追加するとき、 同じ種類のスロットが既にあれば「空きのぶんだけ」個数を増やし、余りは次のスロットへ 回します。ここで「空きのぶんだけ」を省いてslot.count += amountと書いてしまうと、たとえば残り9個しか入らないスロット(90/99)に20個追加したとき 110個になってmaxStackを突き破る バグになります。Mathf.Min(空き, 追加数)のひと手間が、この定番バグを防ぎます。
もう1つの工夫が 「入りきらなかった個数を返す」 ことです。戻り値が0でなければ持ち物が満杯だったということなので、呼び出し側は「持ち物がいっぱいだ!」の表示や、拾えなかったアイテムをフィールドに残す処理につなげられます。削除は逆に、個数を減らし、0になったらスロットごと取り除きます。配列とList の操作に慣れておくと、この辺りがスムーズです。
グリッドUIへ反映する
データができたら、それを マス目状のUI に並べます。ここで uGUIのLayout Group が活躍します。

Grid Layout Group コンポーネントを付けた親オブジェクトの下に、スロットUI(アイコン画像+個数テキスト)を並べると、 自動で格子状に整列 します。マスの大きさや間隔もコンポーネントで設定でき、自分で座標計算する必要はありません。
UIの更新は、先ほどの RefreshUI() で行います。流れはシンプルです。
- インベントリの
slotsを1つずつ見る - スロットUIに、
item.icon(アイコン)とcount(個数)をセットする - 空きスロットは、アイコンを消して空欄にする(アイコンの
Imageのenabledをfalseにする。spriteを外すだけだと白い四角が出ます)
「データを見て、UIを描き直す」だけ。データが増えても減っても、この RefreshUI() を呼べば表示が同期します。 UIはデータの鏡 、という関係を保つのがコツです。
実践:拾う・使う・並べるインベントリ
ここまでを組み合わせて、 アイテムを拾うと増え、使うと減る インベントリを完成させましょう。RPGの持ち物、サバイバルのバックパック、ローグライクの所持品——どれも同じ土台です。

全体の流れはこうです。
- フィールドのアイテム(
ItemDataを持つ)に触れる →inventory.AddItem(itemData)を呼ぶ。戻り値が0でなければ満杯なので「持ちきれない!」を表示 AddItemがスタックを判定してデータを更新し、RefreshUI()でグリッドを描き直す- スロットUIをクリック →
inventory.UseItem(slot)で効果を発動し、個数を1減らす - 個数が0になったらスロットを削除し、再描画
// アイテムを使う(個数を減らし、0で削除)
public void UseItem(InventorySlot slot)
{
// 空のスロットや、この持ち物欄にないスロットでは何もしない(効果だけ出てしまうのを防ぐ)
if (slot == null || slot.count <= 0 || !slots.Contains(slot)) return;
// ここで効果を発動(回復・装備など)
ApplyEffect(slot.item);
slot.count--;
if (slot.count <= 0) slots.Remove(slot); // 使い切ったらスロット削除
RefreshUI();
}
ApplyEffect は、回復や装備などアイテムごとの効果を書く、自分で用意するメソッドです。
ポイントは2つです。 「操作はデータだけを変える」(拾う・使う・捨てるは slots を書き換えるだけ。UIは触らない)と、 「変更のたびにRefreshUIを呼ぶ」(データを変えたら必ずUIを描き直す。この習慣で表示ズレが起きなくなる)。装備品・所持金・クラフト素材なども、ItemData に種別(enum)や追加パラメータを足していけば、同じ仕組みで拡張できます。持ち物の中身は セーブ&ロード で保存すれば、次回起動時も引き継げます。
おまけ:先に知っておくと良いこと
- セーブは「参照」でなく「ID」で:
ItemData(ScriptableObject)そのものはJSONで保存できません。セーブ時は各アイテムに一意のID(文字列)を振り、「IDと個数」を保存します。ロード時にIDからItemDataを引き直す仕組みにしましょう。詳しくは セーブ&ロード を参照。 - ドラッグ&ドロップの並べ替え: スロットの入れ替えは、
slotsの要素を入れ替えてRefreshUI()するだけです。UIのドラッグはIBeginDragHandlerなどのインターフェースで実装します。 - アイテム種別で色分け:
ItemDataに種別(武器・消耗品・素材)を持たせ、スロットUIの枠色を変えると、一気にゲームUIらしくなります。 - 重量・所持数制限: スロット数の上限や、アイテムの重量合計で制限をかけると、サバイバルらしい駆け引きが生まれます。いずれもデータ側のチェックで実装できます。
- 集めたアイテムを依頼に使う: 「薬草を3つ集めて」のような依頼の進み具合を持たせるなら クエスト・目標管理
まとめ
- インベントリは アイテムマスタ(ScriptableObject)・データ(スロットのリスト)・UI(グリッド) の3層に分ける
- アイテムは ScriptableObject で種類ごとに定義。最大スタック数などの性質をアセットに持たせる
- 所持品は スロットのリスト(アイテム参照+個数)。追加は「既存の空きに入るぶんだけ→余りは新規スロットへ」。入りきらない個数は戻り値で返す
- UIは Grid Layout Group で自動整列。データを見て
RefreshUI()で描き直す - 操作は データだけを変え、変更のたびにUIを再描画。UIはデータの鏡
まずは「ポーションを3個持つ」だけの小さなインベントリを、スロットのリストとグリッドUIで表示してみてください。拾って増え、使って減るところまで作れば、もう立派な持ち物システムです。あなたのゲームのプレイヤーは、どんな宝物をカバンに詰め込みますか?
さらに学ぶために
- ScriptableObjectでデータ駆動設計 ——アイテムマスタの土台
- C#の配列とList ——スロットのリスト操作
- uGUIのLayout Groupで自動整列 ——グリッドUIの構築
- セーブ&ロード(JSON) ——持ち物の保存(IDベース)
- Unity公式ドキュメント:ScriptableObject ——データアセットの一次情報