유니티 인벤토리 배열 - yuniti inbentoli baeyeol

이번 포스팅은 #15-1 유니티 인벤토리 만들기(캔버스, UI, 텍스트) 포스팅에 이어 진행하도록 하겠습니다.

저번 포스팅에서는 인벤토리(UI)를 만들었습니다. 이번포스팅은 동전아이템을 먹으면 동전아이템이 인벤토리에 저장되는 기능을 만들겠습니다.

동전아이템에 관한 포스팅 링크입니다.

#13-1 유니티 아이템 줍기(써클 콜라이더)

이번 포스팅은 아이템 획득에 관하여 포스팅을 진행해 보겠습니다. 포스팅은 유니티 2D 게임 개발(게임 개발 프로그래밍)에 나온 예제로 진행합니다. 유니티 2D 게임 개발 COUPANG www.coupang.com 아이

my-develop-note.tistory.com

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

#15-2 유니티 인벤토리 만들기(캔버스, UI, 텍스트) 포스팅은 하단에 첨부하겠습니다.

15-1 유니티 인벤토리 만들기(캔버스, UI, 텍스트)

이번 포스팅은 #14-1 유니티 체력바 만들기(캔버스, UI, 텍스트) 포스팅을 활용한 인벤토리 만들기를 진행하겠습니다. #14-1 유니티 체력바 만들기(캔버스, UI, 텍스트) 포스팅은 하단에 배치하였습

my-develop-note.tistory.com

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

포스팅은 유니티 2D 게임 개발(게임 개발 프로그래밍)에 나온 예제로 진행합니다.

#1 유니티2D 개발 시작하기

안녕하세요 첫 포스팅입니다. 유니티2D 관련 포스팅을 진행하겠습니다. 게임개발에 필요한 지식을 전달하고 함께 학습하겠습니다. 먼저 유니티2D 개발 블로그 포스팅에 앞서 참고한 책을 소개합

my-develop-note.tistory.com

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

인벤토리 스크립트(C#)

먼저 슬롯 안에서 사용할 텍스트 오브젝트의 참조를 저장하는 간단한 스크립트를 만들겠습니다.

Slot 스크립트를 저장할 폴더를 생성하겠습니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

Assets > Scripts > MonoBehaviour 폴더에 Inventory폴더를 생성하였습니다.

Inventory폴더 안에 Slot 스크립트(C#)을 생성하였습니다.

Slot 스크립트(C#)의 전체코드입니다.

using UnityEngine;
using UnityEngine.UI;

public class Slot : MonoBehaviour
{
    public Text qtyText;
}

MonoBehaviour클래스를 상속받는 Slot 클래스는 Text 오브젝트를 참조하고있습니다. 유니티에서 설정하겠습니다.

스크립트를 저장을 합니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

Slot 프리팹에 방금만든 Slot 스크립트(C#)을 컴포넌트로 추가합니다.

Slot 컴포넌트의 Qty Text 속성을 추가하려고 합니다. 먼저 Qty Text 속성 오른쪽에 작은 동그라미를 클릭하여도 아무것도 나오지 않습니다.

Qty Text 속성을 설정하려면 아래의 방법을 이용합니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

1. Assets > Prefabs 폴더에서 Slot 프리팹을 더블클릭합니다.

2. Hierarchy창에서 Slot 오브젝트를 선택하고 Slot 오브젝트의 Inspector창을 띄웁니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

Slot 오브젝트의 Slot 스크립트(C#)컴포넌트의 Qty Text 속성에 작은 동그라미를 선택한 후에 QtyText를 적용합니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

다시 Slot 프리팹으로 돌아와 확인하면 Qty Text 속성에 QtyText가 적용이 되어있는 것을 볼 수 있습니다.

이 방법은 오브젝트의 순서가 바뀌거나 새로운 컴포넌트를 추가하면 인덱스가 바뀌어서 스크립트가 제대로 동작하지 않을 수 있습니다.

인벤토리를 관리하고 인벤토리(UI)를 표현을 담당하는 스크립트를 만들겠습니다.

Assets > Scripts > MonoBehaviour > Inventory폴더 안에 Inventory 스크립트(C#)을 생성하겠습니다.

Inventory 스크립트(C#)의 전체 코드 입니다. 

using UnityEngine;
using UnityEngine.UI;

public class Inventory : MonoBehaviour
{
    public GameObject slotPrefab;
    public const int numSlots = 5;
    Image[] itemImages = new Image[numSlots];
    Item[] items = new Item[numSlots];
    GameObject[] slots = new GameObject[numSlots];

    public void Start()
    {
        CreateSlots();
    }

    public void CreateSlots()
    {
        if (slotPrefab != null)
        {
            for (int i = 0; i < numSlots; i++)
            {
                GameObject newSlot = Instantiate(slotPrefab);
                newSlot.name = "ItemSlot_" + i;

                newSlot.transform.SetParent(gameObject.transform.GetChild(0).transform);

                slots[i] = newSlot;
                itemImages[i] = newSlot.transform.GetChild(1).GetComponent<Image>();
            }
        }
    }

    public bool AddItem(Item itemToAdd)
    {
        for(int i = 0; i < items.Length; i++)
        {
            if(items[i] != null && items[i].itemType == itemToAdd.itemType && itemToAdd.stackable == true)
            {

                items[i].quantity = items[i].quantity + 1;
                Slot slotScript = slots[i].gameObject.GetComponent<Slot>();
                Text quantityText = slotScript.qtyText;
                quantityText.enabled = true;
                quantityText.text = items[i].quantity.ToString();
                return true;

            }

            if(items[i] == null)
            {
                items[i] = Instantiate(itemToAdd);
                items[i].quantity = 1;
                itemImages[i].sprite = itemToAdd.sprite;
                itemImages[i].enabled = true;
                return true;
            }
        }
        return false;
    }
}

코드를 작성후에 저장합니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

Assets > Prefabs 폴더에 InventoryObject를 열고 Inspector창에서 Inventory 스크립트(C#)을 추가합니다.

Inventory 스크립트(C#) 컴포넌트의 Slot Prefab 속성을 같은 폴더에 있는 Slot 프리팹을 드래그 앤 드롭하여 추가합니다.

코드를 확인해보도록 하겠습니다.

public GameObject slotPrefab;
public const int numSlots = 5;
Image[] itemImages = new Image[numSlots];
Item[] items = new Item[numSlots];
GameObject[] slots = new GameObject[numSlots];

Slot 프리팹의 참조를 저장하고, public으로 되어있기에 유니티에서 추가하겠습니다.

const 키워드를 사용하여 실행 중에 절대 변경할 수 없게 만들었습니다. 인벤토리 슬롯의 개수를 5개로 정하였습니다.

크기가 5인 Image 형식의 배열을 만들었습니다.

크기가 5인 Item 형식의 배열을 만들었습니다.

크기가 5인 GameObejct 형식의 배열을 만들었습니다. 이 배열의 각 인덱스는 Slot 프리팹을 가르킵니다.

Start() 메서드를 확인하기 전에 CreateSlots() 메서드를 확인하겠습니다.

 public void CreateSlots()
    {
        if (slotPrefab != null)
        {
            for (int i = 0; i < numSlots; i++)
            {
                GameObject newSlot = Instantiate(slotPrefab);
                newSlot.name = "ItemSlot_" + i;
                newSlot.transform.SetParent(gameObject.transform.GetChild(0).transform);
                slots[i] = newSlot;
                
                itemImages[i] = newSlot.transform.GetChild(1).GetComponent<Image>();
            }
        }
    }

유니티에서 추가한 slotPrefab이 null이 아닌지 확인합니다.

null이 아니면 numSlots = 5의 수만큼 for문을 실행합니다.

Slot 프리팹의 복사본을 인스턴스화 하여 newSlot에 대입합니다. name속성에 인덱스 번호를 붙여 대입합니다.

이 스크립트는 InventoryObject에 추가된 스크립트입니다. 

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

먼저 InventroyObject의 자식 오브젝트 중에 인덱스가 0인 오브젝트는 InventoryBackground입니다.

인스턴스화한 Slot의 부모 오브젝트를 InventoryBackground로 설정합니다.

newSlot 오브젝트를 slots 배열의 현재 인덱스에 대입합니다.

아래의 사진 처럼 Slot의 자식 오브젝트 중 인덱스 1에 해당하는 오브젝트는 ItemImage입니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

ItemImage에서 이미지 컴포넌트를 얻어서 itemImage배열에 대입합니다.

Start() 메서드입니다.

 public void Start()
    {
        CreateSlots();
    }

위에서 설명한 CreateSlots() 메서드를 실행합니다.

다음은 AddItem() 메서드입니다.

    public bool AddItem(Item itemToAdd)
    {
        for(int i = 0; i < items.Length; i++)
        {
            if(items[i] != null && items[i].itemType == itemToAdd.itemType && itemToAdd.stackable == true)
            {

                items[i].quantity = items[i].quantity + 1;
                Slot slotScript = slots[i].gameObject.GetComponent<Slot>();
                Text quantityText = slotScript.qtyText;
                quantityText.enabled = true;
                quantityText.text = items[i].quantity.ToString();
                return true;

            }

            if(items[i] == null)
            {
                items[i] = Instantiate(itemToAdd);
                items[i].quantity = 1;
                itemImages[i].sprite = itemToAdd.sprite;
                itemImages[i].enabled = true;
                return true;
            }
        }
        return false;
    }

items 배열의 크기만큼 루프를 수행합니다.

items 배열이 null이 아닌지, items 배열의 itemsType이 Item 형식의 매개변수와 itemType이 같은지 확인하고,  Item 형식의 매개변수의 stackable이 true인지 세가지 조건을 확인하고 모두 맞다면 if문 안의 내용을 실행합니다.

아래의 포스팅에 나와있는 Item 스크립트(C#)를 확인하면 enum형식의 ItemType, bool 형식의 stackable을 확인할 수 있습니다.

#13-2 유니티 아이템 Scriptable Object(스크립터블 오브젝트)

이번 포스팅은 #13-1 유니티 아이템 줍기(써클 콜라이더)에 이어 진행하도록 하겠습니다. 포스팅은 유니티 2D 게임 개발(게임 개발 프로그래밍)에 나온 예제로 진행합니다. 유니티 2D 게임 개발 COUP

my-develop-note.tistory.com

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

if문 안의 내용입니다.

아이템 배열의 현재 인덱스에 있는 아이템의 수량을 늘립니다.

Slot 프리팹을 인스턴스화하면 Slot 스크립트가 들어있는 게임오브젝트가 만들어지고 Slot 스크립트(C#)컴포넌트의 참조를 얻습니다.

Slot 스크립트를 보면 Text 형식의 자식 오브젝트인 QtyText가 있습니다.  Text 오브젝트의 참조를 저장합니다.

수량을 표시할 Text 오브젝트를 활성화시키고, Text의 내용을 items배열의 수량을 나타내는 quantitiy 속성을 String 형식으로 변환합니다.

true 값으로 반환합니다.

다음 if문 입니다.

items배열이 null인지 확인합니다. 이 조건의 의미는 지금 추가하려는 아이템은 처음 추가하는 아이템이거나 스태커블 아이템이 아니라는 의미입니다.

items배열에 itmeToAdd의 복사본을 인스턴스화해서 대입합니다.

items배열에 Item오브젝트의 수량을 1로 설정합니다.

itemImages배열의 스프라이트속성을 ItemToAdd의 스프라이트로 대입합니다.

itemImages배열의 이미지를 활성화 합니다.

true값으로 반환합니다.

마지막으로 두개의 if문을 통해 인벤토리에 추가하지 못했다면 인벤토리가 가득찼다는 의미입니다.

false를 반환합니다.

플레이어 오브젝트에 인벤토리의 존재를 알리기 위하여 Player 스크립트를 수정하겠습니다.

Player 스크립트(C#)의 전체 코드입니다.

using UnityEngine;

public class Player : Character
{
    //수정한 부분 <------
    public Inventory inventoryPrefab;
    Inventory inventory;
    //------->

    public HealthBar healthBarPrefab;
    HealthBar healthBar;

    float speed = 10;


    public void Start()
    {
        //수정한 부분 <------
        inventory = Instantiate(inventoryPrefab);
        //------->
        HP.value = StartingHP;
        healthBar = Instantiate(healthBarPrefab);
        healthBar.Character = this;
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.CompareTag("CanBePickedUp"))
        {
            Item hitObject = collision.gameObject.GetComponent<Consumable>().item;
            
            if(hitObject != null)
            {

                bool shouldDisappear = false;

                print("Hit: " + hitObject.objectName);

                switch (hitObject.itemType)
                {
                    case Item.ItemType.COIN:

                        //수정한 부분 <-----
                        shouldDisappear = inventory.AddItem(hitObject);
                        // ----->
                        break;
                    case Item.ItemType.HEALTH:
                         shouldDisappear = AdjustHP(hitObject.quantity);
                        break;
                    default:
                        break;

                }
                if (shouldDisappear)
                {
                    collision.gameObject.SetActive(false);
                }
                
            }
        }
    }
    public bool AdjustHP(int amount)
    {
        if(HP.value < maxHP)
        {
            HP.value += amount;
            print("Adjusted HP by: " + amount + ". New Value: " + HP.value);

            return true;
        }
        return false;
    }


}

코드를 수정하고 저장합니다.

수정한 부분만 확인하겠습니다.

public Inventory inventoryPrefab;
Inventory inventory;

public으로 유니티에서 Inventory 프리팸의 참조를 저장합니다.

inventory 변수를 만들었습니다.

public void Start()
    {
        //수정한 부분 <------
        inventory = Instantiate(inventoryPrefab);
        //------->
        HP.value = StartingHP;
        healthBar = Instantiate(healthBarPrefab);
        healthBar.Character = this;
    }

inventory 변수에 인스턴스화한 inventoryPrefab의 참조를 저장합니다.

private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.CompareTag("CanBePickedUp"))
        {
            Item hitObject = collision.gameObject.GetComponent<Consumable>().item;
            
            if(hitObject != null)
            {

                bool shouldDisappear = false;

                print("Hit: " + hitObject.objectName);

                switch (hitObject.itemType)
                {
                    case Item.ItemType.COIN:

                        //수정한 부분 <-----
                        shouldDisappear = inventory.AddItem(hitObject);
                        // ----->
                        break;
                    case Item.ItemType.HEALTH:
                         shouldDisappear = AdjustHP(hitObject.quantity);
                        break;
                    default:
                        break;

                }
                if (shouldDisappear)
                {
                    collision.gameObject.SetActive(false);
                }
                
            }
        }
    }

hitObject를 인수로 전달하여 AddItem() 메서드를 호출하고 호출결과를 shouldDisappear에 대입합니다.

유니티 인벤토리 배열 - yuniti inbentoli baeyeol

PlayerObject 프리팹에 Player 스크립트(C#) 컴포넌트의 Inventory prefab 속성을 InventoryObject 프리팹으로 채워줍니다.

Sence뷰에 동전아이템을 여러개 추가하고 게임을 진행하고 인벤토리를 확인합니다.

마지막으로 Ctrl + S를 눌러 Scene을 저장합니다!


감사합니다! :)