UnityでRPG風アイテムインベントリの作り方
みなさんこんにちは!ジェイです。今日は以下の様なRPGなどで使うアイテムや装備などのインベントリの作り方を説明します。
仕様としては、装備できるスロット3つアイテム欄からドラッグ&ドロップで操作して、回復アイテムはキャラクターにドラッグ&ドロップすると効果が出るというものです。
背景とアイテムを入れるSlotの作成
最初にアイテムの画像を用意します。画像をダウンロード
ダウンロードした画像をすべてインポートして、TexturTypeをDefaultからSpriteに変更し、Applyを押す

背景(SlotGrid)に均等にSlotを並べる
- UI→Image(アイテムのスロットのバックグラウンドを作る)
- CanvasのUIScaleModeをScale With Screen Sizeにする
- ImageをSlotGridと名付ける→ドラッグで大きさを適当に調整をして、SourceImageにBackGroundを指定する
- Slotを格子状に並べるために、SlotGridにGridLayoutGroupコンポーネントを追加
- SlotGridの子にUI→Imageを追加して、Slotと名前を付けて、SourceImageにSlotを指定する
- 追加したSlotGridのGridLayoutGropのCell SizeのXとYを60に設定しSpacingとPaddingをすべて10にして余白を作る(以下の画像を参照)

- コピペでslotの数を増やす(適当に10個くらい)
- SlotGridにcontent Size Filterを追加して、vertical Fitとhorizontal FitをMin Sizeに変更(高さを最小にする)(バックグラウンドにはスロットをぴったり包んでほしいため)
- GridLayoutGropのConstraintをFiexd Column Countを選択し、Constraint Countを4にして行と列のサイズを設定する(以下の画像を参照)

画像を設定しスロットの分だけ配置する
- バックグラウンドの設定は終わったので、Slotを1つだけ残してすべて削除する
- Slotの子オブジェクトにアイテムを表示させるためのImageを追加しItemImageと名前を付ける
- GridLayoutGroupのCellSizeをXY60にしたので、ItemImageのHeightとWitdthを50にする
- 試しにSouce Imageに画像を設定してみる

- SouceImageをnoneに戻す
- Slotをプレハブする
- HierarchyのSlotを消す
- SlotGridをPrefab化する
CItem.csスクリプトを追加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Items", menuName = "Items/Item")]
public class CItem : ScriptableObject
{
public string ItemName;
public Sprite ItemImage;
}
[CreateAssetMenu(fileName = “Items", menuName = “Items/Item")]の一行を追加することで、Assetで右クリック→Create→Items→items→Itemで作成することができるようになります。
Itemフォルダを作りCreate→Itemsでlife,key,shilde,Swordを作り名前とスプライトを設定
以下はF2キーで名前を付けることができます。


CSlot.csスクリプトを追加してプレハブ化しているSlotにアタッチする
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CSlot : MonoBehaviour
{
private CItem Item;
[SerializeField]
private Image ItemImage;
public void SetItem(CItem item)
{
Item = item;
if (item != null)
{
ItemImage.sprite = item.ItemImage;
}
}
}
CSlot(Script)のItemImageにSlotの子オブジェクトのItemImageを入れる

CSlotGrid.csスクリプトを作成しプレハブ化したSlotGridにアタッチする。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CSlotGrid : MonoBehaviour
{
[SerializeField]
private GameObject SlotPrefab;
private int SlotNumber = 20;
[SerializeField]
private CItem[] AllItem;
void Start()
{
for (int i = 0; i < SlotNumber; i++)
{
GameObject slot_obj = Instantiate(SlotPrefab, transform);
CSlot slot = slot_obj.GetComponent<CSlot>();
if (i < AllItem.Length)
{
// スロットにアイテムをセット
slot.SetItem(AllItem[i]);
}
else
{
slot.SetItem(null);
}
}
}
}
CSlotGrid(Script)のSlotPrefabにSlotを設定

AllItemsにItemをすべて選んで(life,key,shilde)ドラッグ&ドロップ(鍵のチェックを入れて固定する)
この作業が終わったらチェックした鍵をもう一度チェックして外しておきましょう。

以上でUIとアイテムを表示させるとこまでできました。
実行結果

ドラッグ&ドロップ
次にドラッグ&ドロップを実装します。
ドラッグしたアイテムをコピーする
CSlot.csを以下のように書き換えましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class CSlot : MonoBehaviour,IBeginDragHandler, IDragHandler
{
private CItem Item;
[SerializeField]
private Image ItemImage;
private GameObject DraggingObj;
[SerializeField]
private GameObject ItemImageObj;
private Transform CanvasTransform;
void Start()
{
CanvasTransform = GameObject.Find("Canvas").transform;
}
public void OnBeginDrag(PointerEventData eventData)
{
if (Item == null) return;
// ドラッグ開始後アイテムのイメージを複製
DraggingObj = Instantiate(ItemImageObj, CanvasTransform);
// 複製を隠れないように最前面に配置
DraggingObj.transform.SetAsLastSibling();
// 複製元の色を暗くする
ItemImage.color = Color.gray;
}
public void OnDrag(PointerEventData eventData)
{
if (Item == null) return;
// 複製がポインターを追従するようにする
DraggingObj.transform.position = Input.mousePosition;
}
public void SetItem(CItem item)
{
Item = item;
if (item != null)
{
ItemImage.color = new Color(1, 1, 1, 1);
ItemImage.sprite = item.ItemImage;
}
else
{
ItemImage.color = new Color(0, 0, 0, 0);
}
}
}
次に複製がレイキャストをブロックしないようにするために、ここで複製元にCanvasGroupを加えてBlocks Raycasteのチェックを外す。(ボタンを離したという情報は最前面にある複製が真っ先に受け止めてしまうので、ドラッグが終了した情報がスロットに届かなくなるためこの工程が必要です。)

ついでにSlotのCSlot.csのItemImageとItemObjに子オブジェクトのItemImageを登録しておきましょう。

ここまでで実行するとドラッグ&ドロップでアイテムが複製されるようになっています。
元の複製されたアイテムを消す
次はドラッグ&ドロップしたアイテムを新しい場所に移し、前のアイテムは消すようにします。そのためには、一旦アイテムを預ける仲介人の役割をするCHand.csを作成します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CHand : MonoBehaviour
{
private CItem GrabbingItem;
public bool IsHavingItem()
{
return GrabbingItem != null;
}
void Update()
{
transform.position = Input.mousePosition;
}
public CItem GetGrabbingItem()
{
CItem old_item = GrabbingItem;
GrabbingItem = null;
return old_item;
}
public void SetGrabbingItem(CItem item)
{
GrabbingItem = item;
}
}
空のオブジェクトを生成しHandと名前を付けてCHand.csをアタッチする。
ドロップしたアイテムを落とした先のスロットにコピーする
次にIDropHandler, IEndDragHandlerインターフェイスを実装し、CSlot.csを以下のように書き直す。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class CSlot : MonoBehaviour,IBeginDragHandler, IDragHandler, IDropHandler, IEndDragHandler
{
private CItem Item;
[SerializeField]
private Image ItemImage;
private GameObject DraggingObj;
[SerializeField]
private GameObject ItemImageObj;
private Transform CanvasTransform;
private CHand Hand;
void Start()
{
CanvasTransform = GameObject.Find("Canvas").transform;
Hand = FindObjectOfType<CHand>();
}
public void OnBeginDrag(PointerEventData eventData)
{
if (Item == null) return;
// ドラッグ開始後アイテムのイメージを複製
DraggingObj = Instantiate(ItemImageObj, CanvasTransform);
// 複製を隠れないように最前面に配置
DraggingObj.transform.SetAsLastSibling();
// 複製元の色を暗くする
ItemImage.color = Color.gray;
// 仲介人にアイテムを渡す
Hand.SetGrabbingItem(Item);
}
public void OnDrag(PointerEventData eventData)
{
if (Item == null) return;
// 複製がポインターを追従するようにする
DraggingObj.transform.position = Hand.transform.position;
}
public void OnDrop(PointerEventData eventData)
{
// 仲介人がアイテムを持ってなかったら
if (!Hand.IsHavingItem()) return;
// 仲介人からアイテムを受け取る
CItem get_item = Hand.GetGrabbingItem();
// もともと持っていたアイテムを仲介人に渡す
Hand.SetGrabbingItem(Item);
// アイテムを渡す
SetItem(get_item);
}
// OnDropが先に呼ばれる
public void OnEndDrag(PointerEventData eventData)
{
Destroy(DraggingObj);
// 仲介人からアイテムを受け取る
CItem get_item = Hand.GetGrabbingItem();
// アイテムを渡す
SetItem(get_item);
}
public void SetItem(CItem item)
{
Item = item;
if (item != null)
{
ItemImage.color = new Color(1, 1, 1, 1);
ItemImage.sprite = item.ItemImage;
}
else
{
ItemImage.color = new Color(0, 0, 0, 0);
}
}
}
以上でスロット内のアイテムをドラッグ&ドロップできるようになります。
実行結果

アイテムを使用できるようにする
キャラクターの当たり判定の追加
キャラクターの画像(UnityChan)を追加して、TextureTypeをSpriteに変更して、Herarchyに追加する。この時座標が移らない場所にあったら調整します。

プレイヤークラスの作成し、キャラクター画像のオブジェクトにアタッチして、Playerという名前に変更。さらにMainCameraにPhysic2DRaycastを追加して、PlayerにBoxCollider2Dを追加します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CPlayer : MonoBehaviour
{
private CItem Item;
public void SetItem(CItem item)
{
Item = item;
Debug.Log("装備アイテムは" + Item.ItemName+"です");
}
}
プレイヤー装備アイテム用スロットの作成
プレハブからSlotをCanvasの下に子オブジェクトとして追加して、PlayerSlotと名前をつけて、CSlotクラスを削除して以下のCPlayerSlotクラスをアタッチする
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayerSlot : CSlot
{
private CPlayer Player;
protected override void Start()
{
Player = FindObjectOfType<CPlayer>();
base.Start();
}
public override void OnDrop(PointerEventData eventData)
{
base.OnDrop(eventData);
Player.SetItem(Item);
}
}
このままではエラーが出るので、基底クラスのCSlot.csのOnDropにvirtualを以下のように追加します。
public virtual void OnDrop(PointerEventData eventData) それとprotected virtual void Start()も変更しておきましょう
まだエラーが出るはずなので、更にCSlot.csのprivate CItem Item;をprotected CItem Item;に変更しエラーをすべて消しましょう。
CPlayerSlot.csに必要なオブジェクトは以下を参照にアタッチしましょう。

ここまでで実行して、プレイヤーのスロットにドラッグアンドドロップすると、装備アイテム名が表示されます。(FindObjectOfTypeを使うようにしたので、CPlayerクラスのPlayerにアタッチしなくても良くなりました)
実行結果

アイテムに攻撃力を追加する
次は攻撃力を追加して、プレイヤーをクリックすると表示されるようにする
まずCItemクラスにAtkを追加して、Atkの項目に適当な数字を設定しましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Items", menuName = "Items/Item")]
public class CItem : ScriptableObject
{
public string ItemName;
public Sprite ItemImage;
public int Atk;
}
プレイヤー画像をクリックしたときに反応するようにCPlayer.csに以下を追加する。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayer : MonoBehaviour,IPointerClickHandler
{
private CItem Item;
[SerializeField]
private int Atk;
public int GetAtk()
{
int item_atk = 0;
if (Item != null) item_atk = Item.Atk;
return Atk + item_atk;
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("攻撃力は" + GetAtk() + "です");
}
public void SetItem(CItem item)
{
Item = item;
Debug.Log("装備アイテムは" + Item.ItemName+"です");
}
}
以上でプレイヤーをクリックすると攻撃力が表示されるようになります。
実行結果

アイテムの分類
前回までは、アイテムがすべて同じパラメーターを持っていましたが、今回からはアイテムの種類によって、違うパラメーターを設定できるようにします。
まずCItem.csを開きpublic int Atk;一行を消去します。するとCPlayerクラスのItem.Atkを参照してる場所にエラーが出るのでコメントアウトします。
武器アイテムの実装
そしてCItemクラスを継承したCArmorクラスを作成します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Items", menuName = "Items/Armor")]
public class CArmor : CItem
{
public int Atk;
}
次にメニューからItems→Item→Armorで新しい武器(Sword)を作成して、忘れず新しく作ったArmorをCSlotGridのAllItemに追加します。
先ほどエラーが出たCPlayerを以下の用に修正します。変更したのはGetAtk()のみです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayer : MonoBehaviour,IPointerClickHandler
{
private CItem Item;
[SerializeField]
private int Atk;
public int GetAtk()
{
int item_atk = 0;
CArmor armor = Item as CArmor;
if (armor != null) item_atk = armor.Atk;
return Atk + item_atk;
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("攻撃力は" + GetAtk() + "です");
}
public void SetItem(CItem item)
{
Item = item;
Debug.Log("装備アイテムは" + Item.ItemName+"です");
}
}
回復アイテムの実装
CArmorの時と同じようにCItemを継承してCRecoveryクラスを作成します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Items", menuName = "Items/Recovery")]
public class CRecovery : CItem
{
public int Hp;
public void ChangeHp(CPlayer player)
{
//player.ChargeHp(Hp);
}
}
コメントをしている1行はPlayerクラスにChargeHpを追加したらコメントアウトします。
これも忘れず新しく作ったRecoveryをCSlotGridのAllItemに追加します。(メニュからItems→Items→Recoveryで名前をlife Hpを10に設定)
次にプレイヤーを回復できるように以下のように書き直します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayer : MonoBehaviour,IPointerClickHandler,IDropHandler
{
private CItem Item;
[SerializeField]
private int Atk;
[SerializeField]
private int MaxHp;
private int Hp;
private CHand Hand;
private void Start()
{
Hp = MaxHp;
Hand = FindObjectOfType<CHand>();
}
public void ChargeHp(int delta)
{
int temp = Hp + delta;
if (temp <= 0) Hp = 0;
else if (temp > MaxHp) Hp = MaxHp;
else Hp = temp;
Debug.Log("現在のHpは"+Hp);
}
public int GetAtk()
{
int item_atk = 0;
CArmor armor = Item as CArmor;
if (armor != null) item_atk = armor.Atk;
return Atk + item_atk;
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("攻撃力は" + GetAtk() + "です");
}
public void SetItem(CItem item)
{
Item = item;
Debug.Log("装備アイテムは" + Item.ItemName+"です");
}
public void OnDrop(PointerEventData eventData)
{
CItem get_item = Hand.GetGrabbingItem();
CRecovery rec = get_item as CRecovery;
if(rec != null && Hp < MaxHp)
{
rec.ChangeHp(this);
}
else
{
Hand.SetGrabbingItem(get_item);
}
}
}
ここまでの方法で色々な値を持ったアイテムを作ることができるようになりました。
実行結果

装備スロットを複数に増やす
複数のアイテムに対応するには、まずCPlayer.csをItemをListでItemsとして初期化します。更に以下の用に書き換えます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayer : MonoBehaviour,IPointerClickHandler,IDropHandler
{
private List<CItem> Items = new List<CItem>();
[SerializeField]
private int Atk;
[SerializeField]
private int MaxHp;
private int Hp;
private CHand Hand;
private void Start()
{
Hp = 20;
Hand = FindObjectOfType<CHand>();
}
public void ChargeHp(int delta)
{
int temp = Hp + delta;
if (temp <= 0) Hp = 0;
else if (temp > MaxHp) Hp = MaxHp;
else Hp = temp;
Debug.Log("現在のHpは"+Hp);
}
public int GetAtk()
{
int item_atk = 0;
foreach (var item in Items)
{
CArmor armor = item as CArmor;
if (armor != null) item_atk += armor.Atk;
}
return Atk + item_atk;
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("攻撃力は" + GetAtk() + "です");
Debug.Log("装備アイテムは" + Items.Count + "です");
}
public void SetItem(CItem item)
{
if (item != null)
{
Items.Add(item);
}
}
public void RemoveItem(CItem item)
{
if (item != null)
{
Items.Remove(item);
}
}
public void OnDrop(PointerEventData eventData)
{
CItem get_item = Hand.GetGrabbingItem();
CRecovery rec = get_item as CRecovery;
if(rec != null && Hp < MaxHp)
{
rec.ChangeHp(this);
}
else
{
Hand.SetGrabbingItem(get_item);
}
}
}
overrideをOnDropからSetItemに変更する
次にCSlot.csのSetItemをCPlayer.csにコピーして基底クラスのSetItemにvirtualを付け派生クラスのSetItemにoverrideのキーワードを追加
出たエラーはprivateをprotectedに変更すれば消えます。以下がソースの全文です。OnDrop関数のvirtualがなくなっているのにも注意しましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class CSlot : MonoBehaviour,IBeginDragHandler, IDragHandler, IDropHandler, IEndDragHandler
{
protected CItem Item;
[SerializeField]
protected Image ItemImage;
private GameObject DraggingObj;
[SerializeField]
private GameObject ItemImageObj;
private Transform CanvasTransform;
private CHand Hand;
private void Start()
{
CanvasTransform = GameObject.Find("Canvas").transform;
Hand = FindObjectOfType<CHand>();
if (Item == null) ItemImage.color = new Color(0, 0, 0, 0);
}
public void OnBeginDrag(PointerEventData eventData)
{
if (Item == null) return;
// ドラッグ開始後アイテムのイメージを複製
DraggingObj = Instantiate(ItemImageObj, CanvasTransform);
// 複製を隠れないように最前面に配置
DraggingObj.transform.SetAsLastSibling();
// 複製元の色を暗くする
ItemImage.color = Color.gray;
// 仲介人にアイテムを渡す
Hand.SetGrabbingItem(Item);
}
public void OnDrag(PointerEventData eventData)
{
if (Item == null) return;
// 複製がポインターを追従するようにする
DraggingObj.transform.position = eventData.position;
}
public void OnDrop(PointerEventData eventData)
{
// 仲介人がアイテムを持ってなかったら
if (!Hand.IsHavingItem()) return;
// 仲介人からアイテムを受け取る
CItem get_item = Hand.GetGrabbingItem();
// もともと持っていたアイテムを仲介人に渡す
Hand.SetGrabbingItem(Item);
SetItem(get_item);
}
// OnDropが先に呼ばれる
public void OnEndDrag(PointerEventData eventData)
{
Destroy(DraggingObj);
// 仲介人からアイテムを受け取る(NULLを渡して前の場所のを消す)
CItem get_item = Hand.GetGrabbingItem();
SetItem(get_item);
}
public virtual void SetItem(CItem item)
{
Item = item;
if (item != null)
{
ItemImage.color = new Color(1, 1, 1, 1);
ItemImage.sprite = item.ItemImage;
}
else
{
ItemImage.color = new Color(0, 0, 0, 0);
}
}
}
そして、次に派生クラスのCPlayerSlot.csを書き換えます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayerSlot : CSlot
{
private CPlayer Player;
protected override void Start()
{
Player = FindObjectOfType<CPlayer>();
base.Start();
}
public override void SetItem(CItem item)
{
Player.RemoveItem(Item); // ここは基底クラスのItemなので注意
Player.SetItem(item); // ここは引数のitemなので注意
base.SetItem(item); // ここは引数のitemなので注意
}
}
OnDrop関数でなくSetItem関数をオーバーライドして使うよう変更しました。
これでPlayerSlotを複製しても対応できるようになりました。1番上のtweetと同じ状態です。
実行結果

番外編 1 アイテムを捨てる
アイテムを捨てるコツは、OnEndDragで呼ばれて、かつOnDropでは呼ばれてなければスロット外にドロップしたということはアイテムをスロット外に置いたということなので、捨てるという処理にします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CHand : MonoBehaviour
{
private CItem GrabbingItem;
public bool IsHavingItem()
{
return GrabbingItem != null;
}
// スロットの外に出して捨てる
private bool ItemFlag;
public void SetGrabbingItem(CItem item, bool item_flag)
{
GrabbingItem = item;
ItemFlag = item_flag;
}
public CItem GetGrabbingItem(bool is_on_end_drag)
{
CItem old_item = GrabbingItem;
GrabbingItem = null;
// OnEndDragで呼ばれて、かつOnDropでは呼ばれてなければスロット外にドロップしたということなのでnullを返す
if (is_on_end_drag && !ItemFlag) return null;
return old_item;
}
}
SetGrabbingItemの呼び出しをOnBeginDrag内ではfalseにして、OnDropではtrueにする。
GetGrabbingItemの呼び出しをOnDrop内ではfalseにして、OnEndDragではtrueにする。
以上でスロット外にアイテムをドロップすると捨てるようになります。
実行結果

番外編 2 装備スロットを限定する
あるタイプのアイテムだけ装備できるように1つずつ個別に装備のタイプを指定できるようにします。CItem.csにItemTypeを追加。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Items", menuName = "Items/Item")]
public class CItem : ScriptableObject
{
public string ItemName;
public string ItemType;
public Sprite ItemImage;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CPlayerSlot : CSlot
{
private CPlayer Player;
[SerializeField]
private string ItemType; // ここのタイプと同じアイテムだけ装備スロットに入れられる
protected override void Start()
{
Player = FindObjectOfType<CPlayer>();
base.Start();
}
public override void SetItem(CItem item)
{
Player.RemoveItem(Item); // ここは基底クラスのItemなので注意
Player.SetItem(item); // ここは引数のitemなので注意
base.SetItem(item); // ここは引数のitemなので注意
}
// 装備できるアイテムを限定するためにOnDropをオーバーライドする
public override void OnDrop(PointerEventData eventData)
{
// 仲介人がアイテムを持ってなかったら
if (!Hand.IsHavingItem()) return;
CItem get_item = Hand.GetGrabbingItem(false);
// 装備品以外は元の場所に戻す
if (get_item.ItemType != ItemType)
{
Hand.SetGrabbingItem(get_item, true);
}
else // 装備品はスロットに装備する
{
// もともと持っていたアイテムを仲介人に渡す
Hand.SetGrabbingItem(Item, true);
SetItem(get_item);
}
}
}
最後にCSlot.csのOnDrop関数をpublic virtual void OnDrop(PointerEventData eventData)と変えましょう。
これでItemTypeで指定した文字のアイテムだけ装備スロットにいれることができるようになります。