공부/유니티

개초보의 Unity 인벤토리 만들기 💖_ 00

림피 2022. 6. 14. 15:04

(20220611 15시 경 작성 시작)

 

교수님이 과제로 내주셨다.

내가 구현해야할 것은

1. 인벤토리(드래그 앤 드롭, 툴팁 포함)

2. 아이템 획득

3. 장비창

 

이상이다.

 

확실하게 저 기능을 모두 구현하는 강좌가 있었음 보고 그대로 했을텐데 ㅋ

항상 그렇듯 마음에 쏙 드는 것은 없기 때문에 또 여기저기서 보고 조각보 잇듯이 할 것이다...

 

단언해두지만 강좌글 아니다. 남의 유튜브 강좌 보고 따라하는 일지다.

그래도 스스로 공부 겸 혹시나 내 삽질이 누군가 도움이 될까봐 남긴다.

 

https://youtu.be/2WnAOV7nHW0

내가 선택한 강좌다. 비주얼 스튜디오 테마가 밝은색이라 눈이 아픈건 맘에 안 들지만

원하는 기능은 앵간해서 다 있다. 다만 장비창이 다른 강좌로 분리되어 있고

길게 에피소드로 이어지는 강좌라 나 같은 초짜는 다 찾아봐야 한다...

 

 

일단 C# 스크립트 파일을 두개 작성한다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Inventory
{
	private List<Item> itemList;
	public Inventory()
	{
		itemList = new List<Item>();

		Debug.Log("Inventory");
	}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Item
{
    public enum ItemType
	{
		Sword,
		HealthPotion,
		ManaPotion,
		Coin,
		Medkit,
	}
	public ItemType itemType;
	public int amount;
}

그리고 위와 같이 스크립트를 작성한다. 왜 monobehaviour를 없앴는진 모르겠다

아마 이 코드 상에선 순수한 C# 기능만을 써서 그런게 아닐까...

 

근데 갑자기 Player.cs를 열어서 저 한 줄을 쓰라고 한다 물론 난 player를 구현하지 않았다 ㅋㅋ

뭐 그냥 Awake에서 Inventory를 생성해주는 것 같은데

일단 그냥 넘어간다 하핫 어떻게든 되겠지

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Inventory
{
	private List<Item> itemList;
	public Inventory()
	{
		itemList = new List<Item>();

		AddItem(new Item { itemType = Item.ItemType.Sword, amount = 1 });
		Debug.Log(itemList.Count);
	}

	public void AddItem(Item item)
	{
		itemList.Add(item);
	}
}

그리고 다시 Inventory로 돌아와 상기한 것과 같이 작성한다.

itemList에 item을 추가하는 AddItem() 함수가 추가되었고

Debug.Log가 itemList의 수를 출력하는것으로 바뀌었으며

AddItem(new Item { itemType = Item.ItemType.Sword, amount = 1 });

이 줄은... 후후.. 다들 이 정도는 알거라 생각한다.

난 모르겠다.

 

저렇게 매개 변수가 들어갈 괄호 안에 중괄호가 들어가는 문법을 볼 때 마다 정신이 아찔해진다.

아마 새로운 Item을 만들고 그 Item의 값들을 설정해주는 듯 하다.

 

다음으로 넘어가자,

 

캔버스를 선택하라고 한다.

이전 강좌들에서 이어지는 것이기 때문에 난 당연히 저것들이 없다.

그냥 empty obejct 만들어서 이름 UI로 해주고 거기다 cavas 하나 넣어주자

아 지금보니 GameHandler라는 것도 있네 아마 GameManager 같은건가보다 저것도 emptyObejct로 만들어주자

 

Dekimasita-⭐

 

 

근데 또 만들라고 한다

Canvas 안에 UI_Inventory(empty Object)를 만들고 또 그 안에 background(Image)를 넣어준다.

 

그러더니 갑자기 유튜버가 자기 혼자서 뭔갈 뚝딱뚝딱 한다; background에 이미지를 넣고

에디터로 앵커나 크기등을 설정해준 것 같은데 이게 배속으로 빨리 넘어가서 볼 수 없다.

뭐지? 알아서 하란건가?

 

아쉽지만 알바를 하러갈 시간이라 나중에 이어서 쓰겠다.

 

20220612 13:04 이어서 작성

아침에 일어나서 전화영어하고 운동하고 샤워하고 학교 왔다. 더 일찍 왔어야 했는데 흡

앞으론 절대로 일찍 일어나서 전화영어 전에 운동하고 씻어야지.. 밥도 먹고..

 

 

아무튼 이어서 하겠다.

저 아저씨가 background에 이미지를 넣고 배경을 넣었기 때문에 나도 대충 크기를 키워서 인벤토리의

배경이 될만하게 만들었다.

 

 

그리고 위처럼 UI_Inventory 안에 empty object로 itemSlotContainer를 만들고

마찬가지로 empty로 그 안에 itemSlotTemplate를 만든 뒤 그 안에

UI - Image object로 Image, background, border 이렇게 세개의 오브젝트를 만든다.

이러면 background가 두갠데 오브젝트 이름이 중복되면 불편하지 않나 싶었지만 넘어갔다.

 

 

그러더니 또 배속 돌리고 자기 혼자서 무언갈 뚝딱뚝딱 한다;; 말이라도 해줘 자막이라도 넣거나

 

대충 눈치껏 보자면

지금 선택되어 있는 저 회색 오브젝트가 background고 청록색 테두리가 border, 빨간 물약이 image인가보다.

여기선 image를 직접 스프라이트에 넣어주는 모양인데 나중에 아이템 리스트에서 자동 적용되도록 만들고 싶다

 

 

눈치껏 만들었다. 어라 근데 image가 background 뒤에 가려서 안 보인다 ㅋ

강좌 보니까 중간에 hierarcky 창에서 오브젝트 순서를 바꾸던데 그래서 그런가 보다.

근데 슬프게도 유니티는 포토샵처럼 hierarcky 창에서 계층 순서를 바꾼다고 위로 올라와서 보이고 그러지 않는다

그래서 레이어 개념이 따로 존재하는데...

라곤 했는데 계층 순서 바꾸다 보니까 됐다.

유니티는 포토샵과 다르게 위로 갈수록 위에 위치하는 것이 아니라 hierarcky창에서 아래로 갈수록 위로 올라간다.

그래서 위와 같이 배치했다.

(좌)유튜버가 만든 것, (우)내가 만든 것

백그라운드에 사이즈를 좀 줄이고 위치시켜주었다.

그리곤 이걸 복제 시킬거라면서 itemSlotTemplate를 비활성화 시킨다.

저 체크박스를 해제하면 오브젝트 비활성화가 된다.

이제 UI_Inventory 라는 스크립트를 작성한다.

 

또 같은 이름으로 만들었던 빈 오브젝트에 넣어준다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Inventory : MonoBehaviour
{
	private Inventory inventory;

	public void SetInventory(Inventory inventory)
	{
		this.inventory = inventory;
	}
}

코드를 작성해준 뒤,

 

젠장... 또 Player.cs에 뭔갈 쓰라고 한다.

 

SerializeField로 UI_Inventory를 할당해주는 코드다.

 

...넘어가자.

 

이 코드도 써야 한다....

...

안되겠다. 간단하게 Player를 구현해보자. 인벤토리에 필요없는 기능을 다 뺄건데,

난 초짜라 괜히 이것저것 뺐다가 안 돌아가면 곤란하기 때문에 어떻게 될진 모르겠다

 

보이는가? 유튜브 재생바에 가렸지만 저 파란줄이 Player Object이다.

젤 밑에 위치하고 소속된 계층은 없어보인다.

 

강좌 속 Player의 Inspector이다. rigidbody2D 와 BoxCollider2D 컴포넌트, 또 스크립트들이 있는데

inventory 관련 코드는 Player에 썼으므로 Player.cs만 생성해서 Player 오브젝트에 넣어주자.

 

Player.cs 코드의 윗부분이다. using들이 적혀있는 줄에 인터페이스인가 처음보는거 두개가 있는데 넘어가자.

하나는 애니메이션 관련인듯 하고 하나는 유튜버 이름인걸 보아 커스텀한건가 보다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public static Player Instance { get; private set; }

	private const float SPEED = 50f;

	[SerializeField] private UI_Inventory uiInventory;

	private Inventory inventory;

	private void Awake()
	{
		Instance = this;

		inventory = new Inventory();
		uiInventory.SetInventory(inventory);

	}
}

난 일단 이것만 썼다. SPEED도 필요없긴 한데 그냥 썼다.

7번째 줄은 싱글톤인 것 같은데 아직도 낯선 개념이다 큰일이다 

 

그리고 Player 컴포넌트 UI inventory 부분에 UI_Inventory "오브젝트"를 끌어다 넣어주자.

스크립트 아니고 오브젝트다!!

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Inventory : MonoBehaviour
{
	private Inventory inventory;
	private Transform itemSlotContainer;
	private Transform itemSlotTemplate;

	private void Awake()
	{
		itemSlotContainer = transform.Find("itemSlotContainer");
		itemSlotTemplate = itemSlotContainer.Find("itemSlotTemplate");
	}

	public void SetInventory(Inventory inventory)
	{
		this.inventory = inventory;
		RefreshInventoryItems();
	}

	private void RefreshInventoryItems()
	{
		int x = 0;
		int y = 0;
		float itemSlotCellSize = 30f;

		foreach(Item item in inventory.GetItemList())
		{
			RectTransform itemSlotRectTransform =
				Instantiate(itemSlotTemplate, itemSlotContainer).
				GetComponent<RectTransform>();

			itemSlotRectTransform.gameObject.SetActive(true);

			itemSlotRectTransform.anchoredPosition = new Vector2(
				x * itemSlotCellSize,
				y * itemSlotCellSize);
			x++;
			if(x>4)
			{
				x = 0;
				y++;
			}
		}
	}
}

UI_Inventory 스크립트를 위와 같이 쓴다.

 

추가된 줄을 보자.

private Transform itemSlotContainer;
private Transform itemSlotTemplate;

Tramsform 형식으로 itemSlotContainer와 tiemSlotTemplate를 추가해줬다.

 

F12을 타고 거슬로 올라가 보면 Transform엔 위와 같이 위치, 회전 정보 등을 가질 수 있음을 알 수 있다.

 

private void Awake()
	{
		itemSlotContainer = transform.Find("itemSlotContainer");
		itemSlotTemplate = itemSlotContainer.Find("itemSlotTemplate");
	}

이건 아마.. 첫번째 줄은 "itemSlotContainer에 itemSlotContainer라는 오브젝트를 찾아 그 transform 값을 대입해준다.",

두번째는 "itemslotTemplate에 itemSlotTemplate라는 오브젝트를 찾아 itemSlotContainer를 넣어준다??"으에??

..넘어가자.

 

public List<Item> GetItemList()
	{
		return itemList;
	}

그리고 Inventory.cs의 마지막 줄에 해당 코드를 써준다.

 

 

private void RefreshInventoryItems()
	{
		int x = 0;
		int y = 0;
		float itemSlotCellSize = 30f;

		foreach(Item item in inventory.GetItemList())
		{
			RectTransform itemSlotRectTransform =
				Instantiate(itemSlotTemplate, itemSlotContainer).
				GetComponent<RectTransform>();

			itemSlotRectTransform.gameObject.SetActive(true);

			itemSlotRectTransform.anchoredPosition = new Vector2(
				x * itemSlotCellSize,
				y * itemSlotCellSize);
			x++;
			if(x>4)
			{
				x = 0;
				y++;
			}
		}
	}

RefreshInventoryItems() 함수를 살펴보자. 일단 이름을 보면 인벤토리 아이템들을 새로고침 해주는.. 함수라고 추측 가능하다. 아직 잘 안 와닿지만 계속 보자.

 

		float itemSlotCellSize = 30f;

itemSlotCellsize 라는 변수를 설정했다. 아이템 슬롯, 즉 칸들의 크기를 지칭하는 변수일 것이다.

 

		foreach(Item item in inventory.GetItemList())

foreach 문이다. in 다음에 써진 인덱스를 끝까지 순환한다. inventory.GetItemList()를 순환할 것이다.

 

RectTransform itemSlotRectTransform =
				Instantiate(itemSlotTemplate, itemSlotContainer).
				GetComponent<RectTransform>();

RectTransform 형식으로 itemslotRectTransform를 선언. 

 

RectTransform 형식 위에 마우스 포인터를 갖다대보면 툴팁이 뜬다.

rectangle(사각형)을 위한 position, size, anchor 그리고 pivot 정보라고 적혀있다.(맞나)

 

그래서 이 RectTransform 형식의 itemslotRectTransform이란 지역 변수에

Instantiate()란 함수를 써준다. Instantiate는 오브젝트 복제 함수다.

첫번째 인자는 복사해줄 원본 개체이고 두번째 인자는 복사본의 생성 위치다.(위치야 위치)

 

https://uhhyunjoo.tistory.com/10

 

[Unity] Object.Instantiate 정리

간단하게 설명하기! 기존의 object를 복제하여 새로운 object를 생성하는 함수이다. 복제할 때 parameter들을 이용하여 parent object, position, rotation 등의 값들을 지정할 수 있다. Object.Instantiate publi..

uhhyunjoo.tistory.com

자세한건 이 쪽을 참고하자.

 

아, 다시 알바를 가야한다.

 

20220613 15시 경 이어서 작성

 

자 그럼 우린 Instantiate(itemSlotTemplate, itemSlotContainer)가 itemSlotTemplate를 itemSlotContainer에

복사해준다는 의미라는 것을 알 수 있다.

뒤에 .GetComponent<RectTransform>();가 붙는데 RectTransform 컴포넌트 정보를 가져온단 뜻일거다.

(여기서 의문점. 꼭 저렇게 가져와야 할 정보형식을 다 적어줘야하나? 그냥 온전히 복사할 수는 없나?

나중에 알아봐야겠다.)

 

itemSlotRectTransform.gameObject.SetActive(true); 이 부분은 간단하다.

itemSlotRectTransform 게임 오브젝트를 활성화하는 것이다.

 

itemSlotRectTransform.anchoredPosition = new Vector2(
				x * itemSlotCellSize,
				y * itemSlotCellSize);

이건 포지션(anchoredPosition은 상위개체 앵커로부터의 거리)을 설정해주는 듯 하다. 처음엔 x,y 둘 다 0으로 초기화 돼서 0이겠지만

x++;
			if(x>4)
			{
				x = 0;
				y++;
			}

x++ 해준 다음 x가 4보다 크다면 x를 0으로 맞추고 y++ 해준다.

긍까 가로줄에서 인벤토리 칸이 4개가 넘으면 다음줄로 넘어가는 것이다.

 

그리고 우린 이걸 foreach문으로 돌렸으니 아이템 리스트가 다 찍힐 때 까지 이걸 반복하는거다.

 

또 아이템 리스트에 아이템을 추가해준다'ㅅ'

 

자 이제 유니티 에디터로 돌아가서 플레이 해주면~❤

 

처참한 현실을 마주하게 된다. 아무래도 상위 개체의 앵커가 중앙으로 된 듯 하다

또 저렇게 겹치는 이유는... 음..어. 내가 아이템 슬롯 크기를 저 유튜버 아저씨 보다 크게 했기 때문일 것이다.

itemSlotCellSize 변수를 키워주면 고쳐질 것.

 

쨘! 80으로 고쳤더니 저렇게 잘 배열되었다. 허나 여전히 위치가 문제인데 암만 상위 객체들 앵커 위치를 바꿔보아도

여전히 저 모양이다. 사실 확실히 어디 객체 기준으로 정해지는지도 모르겠다. 상위객체가 많아서...

 

그렇게 삽질을 하던 중...

상위 객체가 아니라 원본 객체인 itemSlotTemplate의 앵커 위치를 바꾸자 복제 슬롯들의 위치도 바뀌었다.

기억하자. 상위객체가 아니라 원본객체이다.

 

그런데 의문.

실행 전 에디터 화면에서 itemSlotTemplate가 선택된 모습이다. 분명 인벤토리 좌측상단에 잘 위치해 있는데...

 

앵커를 우측하단에 뒀을 때다. 암만 봐도 위의 에디터에 보이는 위치로 기준점이 된 것 같진 않다.ㅋㅋ

 

친구에게 도움을 요청했다.

친구 : 졸려서 집중이 안 돼

나 : 그럼 내거나 도와줘

 

친구 : 이거의 상위 계층 앵커를 좌측상단으로 바꾸고.. 보면 앵커가 좌측상단에 있을 때 포지션이 (0, 0)이 돼야 하는데 아니잖아 그러니까 shift를 눌러서...

 

(itemSlotContainer 앵커 이미지를 눌러서 앵커 선택 화면을 열고 shitf를 누른 다음 좌측 상단의 것을 선택 한다.)

 

나 : 오오오.....고마워

친구 : 졸려

 

이렇게 드디어 아이템(복수)을 출력할 수 있게 되었다.

 

또 새로운 스크립트를 생성한다.

유튜버 아저씨가 싱글톤을 만들거라고 한다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemAssets : MonoBehaviour
{
    public static ItemAssets Instance { get; private set; }

	private void Awake()
	{
		Instance = this;
	}

	public Sprite swordSprite;
	public Sprite healthPotionSprite;
	public Sprite manaPotionSprite;
	public Sprite coinSprite;
	public Sprite medkitSprite;
}

본 것 중 가장 심플한 싱글톤이다. 얌전히 따라쳐주자.

 

	public ItemType itemType;
	public int amount;

	public Sprite  GetSprite()
	{
		switch(itemType)
		{
			default:
			case ItemType.Sword:		return ItemAssets.Instance.swordSprite;
			case ItemType.HealthPotion:	return ItemAssets.Instance.healthPotionSprite;
			case ItemType.ManaPotion:	return ItemAssets.Instance.manaPotionSprite;
			case ItemType.Coin:			return ItemAssets.Instance.coinSprite;
			case ItemType.Medkit:		return ItemAssets.Instance.medkitSprite;

		}
	}
}

또 Item.cs에 해당 코드를 추가한다.

아이템 스프라이트를 가져오는 코드인데 이렇게 귀찮게 해야하는건가 싶지만 넘어가자.

 

그 다음 UI_inventory.cs 윗줄에 using UnityEngine.UI;를 추가하고

 

해당 위치에 Image 관련 코드 두 줄을 심는다. image 변수에다 itemSlotRectTransform에서 image라는 이름인 오브젝트의

이미지 정보를 넣어준다. itemSlotRectTransform에 현재 복사된 아이템 슬롯의 정보가 있을테니 그 슬롯 안의

아이템 이미지 정보를 얻어오는 것이다.

image.sprite = item.GetSprite(); 는 Item.cs에서 switch문으로 아이템 타입 마다 에셋, 즉 스프라이트 정보를 얻어오는

GetSprite()라는 함수를 구현했으므로 image.sprite에 해당 스프라이트를 넣어주는 것이다. 

C#이 아직 어색해서(안 어색한건 있냐?) image 변수에 itemSlortRectTransform.어쩌구를 대입한다고 해서

다음 줄에 image.sprite에다 스프라이트 넣어준다고 그게 적용되는게 신기하다

 

그리고 유튜브 아저씨가 다른 스프라이트를 더 추가할거라고 한다.

 

20220614 13시 33분 이어서 작성

 

ItemAssets 이란 이름의 빈 오브젝트를 만들어서 아까 쓴 ItemAssets.cs를 넣고

inspector창에서 저 None (Sprite)라고 써진 곳에 에셋 창에서 스프라이트를 끌어다 넣자.

 

잘 출력된다. Inventory에서 AddItem()을 세번 밖에 해주지 않았기 때문에 저거 세개만 뜬다.

 

public Inventory()
	{
		itemList = new List<Item>();

		AddItem(new Item { itemType = Item.ItemType.Sword, amount = 1 });
		AddItem(new Item { itemType = Item.ItemType.HealthPotion, amount = 1 });
		AddItem(new Item { itemType = Item.ItemType.ManaPotion, amount = 1 });
		Debug.Log(itemList.Count);
	}

전에 썼던 해당 코드.

 

다음은 캐릭터가 움직이는 필드에 아이템을 출력하는 것을 해볼 것이다.

글이 길어져서 다음으로 넘어간다.