유니티 RPG - 21. 인벤토리-8(갯수가 있는 아이템 처리)
Intro
저번 포스팅에서 수량이 있는 아이템을 만들어보았습니다.
이번 포스팅에서는 인벤토리에서 수량이 있는 아이템들의 처리를 구현하고,
소모성 아이템의 사용까지 구현해보았습니다.
Inventory 클래스
인벤토리에 아이템이 추가될 때, Swap될 때 수량이 있는 아이템일 경우를 추가했습니다.
// 인벤토리 앞쪽부터 갯수 여유가 있는 Countable Item 슬롯 인덱스 탐색
private int FindCountableItemSlotIndex(CountableItemData target, int startIndex = 0)
{
for(int i = startIndex; i<Capacity;i++)
{
var current = items[i]; // 탐색중인 아이템 기억
// 빈 슬롯일 때
if (current == null)
continue;
// 아이템이 target과 일치
if(current.Data == target && current is CountableItem ci)
{
// 갯수 여유가 있는지 여부
if (!ci.IsMax)
return i; // 여유가 있으면 인덱스 반환
}
}
// 없으면 -1 반환
return -1;
}
// 해당 인덱스 슬롯 갱신
private void UpdateSlot(int index)
{
// 유효한 슬롯만
if (!IsValidIndex(index)) return;
// 해당 아이템
Item item = items[index];
// 슬롯에 아이템 존재
if(item != null)
{
// 1. 수량이 있는 아이템인 경우
if(item is CountableItem ci)
{
// 수량이 없을 때 아이템 제거
if(ci.IsEmpty)
{
items[index] = null;
RemoveIcon();
return;
}
// 아이콘 및 수량 표시
else
{
inventoryUI.SetItemIconAndAmountText(index, item.Data.ItemIcon, ci.Amount);
}
}
// 2. 수량이 없는 아이템인 경우
else
{
// 아이콘 표시
inventoryUI.SetItemIconAndAmountText(index, item.Data.ItemIcon);
}
}
// 슬롯에 아이템이 없을 때
else
{
RemoveIcon();
}
// 아이콘 제거 함수
void RemoveIcon()
{
inventoryUI.RemoveItem(index);
inventoryUI.HideItemAmountText(index);
}
}
// 인벤토리에 아이템 추가(잉여 아이템 갯수 리턴, 리턴이 0이면 모두 성공)
public int AddItem(ItemData itemData, int amount = 1)
{
int index;
// 1. ItemData가 CountableItem일 경우 => 갯수 1개~99개까지 가능
if(itemData is CountableItemData ciData)
{
bool findNextCi = true;
index = -1;
// 남은 아이템 수량이 없을때까지 반복
while(amount > 0)
{
// 추가할 아이템이 인벤토리에 존재
if(findNextCi)
{
// 개수 여유가 있는 슬롯 탐색
index = FindCountableItemSlotIndex(ciData, index + 1);
// 없다면
if(index == -1)
{
findNextCi = false;
}
// 있다면 합치기
else
{
CountableItem ci = items[index] as CountableItem;
// 기존에 있던 아이템 갯수에 추가 및 초과량 반환
amount = ci.AddAmountAndGetExcess(amount);
UpdateSlot(index);
}
}
// 추가할 아이템이 인벤토리에 존재하지 않을 때
else
{
index = FindEmptySlotIndex(index + 1); // 빈슬롯 찾기
// 빈슬롯이 없을 때
if(index == -1)
{
break;
}
else
{
// 새로운 아이템 생성
CountableItem ci = ciData.CreateItem() as CountableItem;
ci.SetAmount(amount);
// 슬롯에 아이템 추가
items[index] = ci;
// 남은 갯수 계산
amount = (amount > ciData.MaxAmount) ? (amount - ciData.MaxAmount) : 0;
UpdateSlot(index);
}
}
}
}
// 2. 나머지(수량 없는 아이템)
else
{
if (amount == 1)
{
// 빈 슬롯을 찾아서 아이템 생성 후 슬롯에 추가
index = FindEmptySlotIndex();
// 빈 슬롯이 있다면
if (index != -1)
{
items[index] = itemData.CreateItem();
amount = 0;
// 슬롯 갱신
UpdateSlot(index);
}
}
}
return amount;
}
// 두 슬롯 아이템 스왑
public void Swap(int beginIndex, int endIndex)
{
// 접근불가 슬롯 처리
if (!IsValidIndex(beginIndex) || !IsValidIndex(endIndex)) return;
Item itemA = items[beginIndex];
Item itemB = items[endIndex];
// (A -> B 슬롯 스왑)
// 1. Countable Item 이면서 동일한 아이템일 때
if(itemA != null && itemB != null && itemA.Data == itemB.Data &&
itemA is CountableItem ciA && itemB is CountableItem ciB)
{
int maxAmount = ciB.MaxAmount; // B 슬롯의 최대량
int sum = ciA.Amount + ciB.Amount; // 두 아이템 합한 갯수
// 두 아이템을 합쳐도 최대치보다 크지않을 때
if(sum <= maxAmount)
{
ciA.SetAmount(0);
ciB.SetAmount(sum);
}
else
{
ciA.SetAmount(sum - maxAmount);
ciB.SetAmount(maxAmount);
}
}
// 2. 일반적인 경우
else
{
// 아이템 위치 변경
items[beginIndex] = itemB;
items[endIndex] = itemA;
}
// 슬롯 갱신
UpdateSlot(beginIndex, endIndex);
}
// 해당 슬롯 인덱스의 아이템 사용
public void Use(int index)
{
if (!IsValidIndex(index)) return;
if (items[index] == null) return;
// 사용 가능한 아이템일 때
if(items[index] is IUsableItem usable)
{
// 사용
bool success = usable.Use();
// 성공
if(success)
{
UpdateSlot(index);
}
}
}
각 함수에 추가된 내용 및 새로 추가된 함수 정리입니다.
- AddItem : 아이템을 인벤토리에 추가
- 기존 코드는 수량이 없는 아이템일 경우로 뺐습니다.
- 수량이 있는 아이템의 경우, FindCountableItemSlotIndex() 함수를 통해
-
- 만약 추가될 아이템과 같은 아이템이 인벤토리에 존재할 경우
- 갯수가 합쳐질 여유가 있다면 합친 후 남은 수량 반환
- 남은 수량이 없거나, 합쳐질 곳이 없을때까지 반복합니다.
- 만약 추가될 아이템과 같은 아이템이 인벤토리에 존재할 경우
-
- 새롭게 추가되어야 한다면 FindEmptySlotIndex() 함수를 통해 빈 슬롯을 찾아 아이템을 추가합니다.
-
- UpdateSlot : 슬롯 정보를 갱신
- 수량이 있는 아이템의 경우를 추가했습니다.
- 아이템 아이콘 등록후 갯수 텍스트 설정하는 부분의 작업이 비동기적으로 이루어지는 문제가 발생하여 둘의 작업이 동기적으로 실행될 수 있도록 함수를 묶었습니다.
- SetItemIconAndAmountText() : 아이템 아이콘과 수량 텍스트를 설정합니다.
- Swap : 두 아이템의 위치를 Swap
- 수량이 있는 아이템의 경우를 추가했습니다
-
- 스왑하는 두 아이템이 같은 경우 합쳐질 수 있습니다.
- 예를들어 A아이템이 30개, B아이템이 40개인 경우 A->B 스왑이 이루어질 때 B아이템 70개가 됩니다.
- A 슬롯에 있던 아이템은 Amount가 0이되므로 없어지게됩니다.
- 스왑하는 두 아이템이 같은 경우 합쳐질 수 있습니다.
-
- 스왑하는 두 아이템이 다를 경우는 위치만 변경됩니다.
-
- 수량이 있는 아이템의 경우를 추가했습니다
- FindCountableItemSlotIndex : 인벤토리 앞쪽부터 target(Countable)과 같은 아이템이 있는 슬롯 인덱스를 찾습니다.
- 동일 아이템이 있고, 갯수에 여유가 있다면 해당 인덱스를 반환합니다.
- 없다면 -1를 반환합니다.
- Use : 아이템을 사용
- 소모성 아이템인 경우 마우스 우클릭시 갯수가 -1 됩니다.
InventoryUI 클래스
마우스 우클릭 이벤트 및 아이템 아이콘, 수량 텍스트를 설정하는 함수를 추가했습니다.
// 마우스 눌렀을 때 처리
private void OnPointerDown()
{
// 마우스 우클릭(아이템 사용)
else if (Input.GetMouseButtonDown(rightClick))
{
ItemSlotUI slotUI = RaycastAndgetFirstComponent<ItemSlotUI>();
{
TryUseItem(slotUI.Index);
}
}
}
// 해당 인덱스 슬롯의 아이템 아이콘 등록 및 수량 표시
public void SetItemIconAndAmountText(int index, string icon, int amount = 1)
{
slotUIList[index].SetItemIconAndAmount(icon, amount);
}
// 아이템 사용
private void TryUseItem(int index)
{
inventory.Use(index);
}
- 마우스 우클릭시 Inventory 의 Use 함수를 호출하여 아이템 갯수를 차감합니다.
- SetItemIconAndAmountText() : 아이콘 등록과 텍스트 설정이 같이 이루어집니다.
ItemSlotUI 클래스
// 아이템 아이콘 등록 및 수량 표시
public void SetItemIconAndAmount(string itemSprite, int amount)
{
if (itemSprite != null)
{
// 아이콘 데이터 가져오기
ResourceManager.Instance.LoadIcon(itemSprite, sprite =>
{
// 성공
if (sprite != null)
{
// 아이콘 이름 저장
iconName = itemSprite;
// 아이콘 설정
iconImage.sprite = sprite;
if(amount > 1)
{
ShowText();
}
else
{
HideText();
}
ShowIcon();
amountText.text = amount.ToString();
}
else
{
Debug.Log($"Failed to load icon for item : {itemSprite}");
}
});
}
else
{
RemoveItemIcon();
}
}
- 기존의 SetItemIcon 함수와 SetItemAmount 함수를 합쳤습니다.
- 기존의 함수는 다른곳에서 사용되므로 없애지 않고 그대로 두어야합니다.
이렇게 바꾼 이유는 Inventory 클래스의 UpdateSlot 함수에서 슬롯을 갱신할 때
먼저, 아이콘 등록 후 CountableItem일 경우 수량 텍스트 표시를 처리했으나
아이콘을 등록하는 함수가 비동기적으로 실행되어
아이콘등록함수 시작 -> 수량 텍스트 표시함수 시작 -> 수량 텍스트 표시함수 끝 -> 아이콘 등록완료 순서로 처리가되었습니다.
문제는 수량텍스트를 표시함수가 HasItem 이라는 변수로 해당 슬롯에 아이템이 존재하는지 확인하는 부분인데,
HasItem은 슬롯의 아이템 아이콘 여부로 처리되기때문에 계속 실패하게됩니다.
따라서 아이콘 등록이 완전히 끝난 뒤, 수량 텍스트를 표시하도록 처리했습니다.
댓글남기기