4 분 소요

Intro

이번포스팅에서는 플레이어 아이템 슬롯에 아이템을 등록하고, 사용할 수 있는 기능을 구현해보았습니다.


아이템 슬롯 GUI

아이템 슬롯의 GUI는 아래와같이 만들었습니다.

image

image

  • Canvas
    • Player Item Gruop UI(아이템 슬롯 그룹)
      • Item Slots(개별 아이템 슬롯들)
    • Profile UI(캐릭터 정보창 UI)
    • Inventory UI(인벤토리 UI)

기존 구조 개선

먼저, 아이템슬롯은 인벤토리와 연계되어 구현되므로, 관련된 부분들의 구조 개선이 필요했습니다.

인벤토리의 마우스 이벤트는 현재 인벤토리 클래스가 담당하고있습니다.

아이템 슬롯은 인벤토리의 아이템을 드래그앤드롭으로 아이템을 등록할 수 있어야하고,

아이템사용은

  1. 인벤토리에서 직접 아이템을 사용
  2. 아이템 슬롯에 등록된 아이템을 키입력을 통해 사용

두가지 경우가 있을뿐더러, 두 경우 모두 아이템사용시 인벤토리 및 아이템슬롯의 아이템 정보(갯수 등)가 갱신되어야합니다.

따라서 인벤토리 클래스의 마우스 이벤트 부분을 수정하고, 새로 추가될 아이템슬롯과 인벤토리를 연계하여야합니다.


InventoryUI 클래스

InventoryUI 클래스에는 마우스 이벤트가 구현되어있습니다.

인벤토리내의 아이템을 드래그하여 드롭했을때 발생하는 이벤트 EndDrag() 함수에 아이템슬롯에 아이템을 등록하는 로직을 추가하였습니다.

그리고 현재 마우스위치의 UI의 컴포넌트 정보를 가져오는 함수 RaycastAndgetFirstComponent() 를 제거하고,

부모 오브젝트 Canvas 에서 처리하도록 하였습니다.

    [SerializeField] private UIRaycaster rc;                // 레이캐스터

    // 플레이어 아이템창에 아이템 등록
    private void TryRegisterItem(int index, PlayerItemSlotUI end)
    {
        inventory.AddItemAtPlayerItemSlot(index, end);
    }

    // 마우스 드래그 종료 처리(아이템 교환, 이동, 버리기 등)
    private void EndDrag()
    {
        PlayerItemSlotUI playerSlot = rc.RaycastAndgetFirstComponent<PlayerItemSlotUI>();

        // 플레이어 아이템창에 아이템 등록
        if (playerSlot != null)
        {
            TryRegisterItem(beginDragSlot.Index, playerSlot);
        }

        // ...
    }


  • 또한 RaycastAndgetFirstComponent() 함수가 사용되는 부분은 모두 수정하였습니다.

UIRaycaster 클래스

원래 InventoryUI 클래스에 있던 함수 RaycastAndgetFirstComponent()를 따로 빼서 클래스로 만들었습니다.

public class UIRaycaster : MonoBehaviour
{
    private GraphicRaycaster gr;
    private PointerEventData ped;
    private List<RaycastResult> rrList;

    private void Awake()
    {
        gr = GetComponent<GraphicRaycaster>();
        ped = new PointerEventData(EventSystem.current);
        rrList = new List<RaycastResult>();
    }

    private void Update()
    {
        ped.position = Input.mousePosition;
    }

    // T 컴포넌트를 가지고있는 첫번째 오브젝트의 T 반환
    public T RaycastAndgetFirstComponent<T>() where T : Component
    {
        // 리스트 초기화
        rrList.Clear();

        // 현재 마우스 위치에서 감지된 UI요소 저장
        gr.Raycast(ped, rrList);

        // 없으면 null
        if (rrList.Count == 0)
            return null;

        for(int i = 0; i< rrList.Count; i++)
        {
            T component = rrList[i].gameObject.GetComponent<T>();
            if(component != null)
            {
                return component;
            }
        }

        return null;
    }
}


  • 기존의 RaycastAndgetFirstComponent() 함수는 레이캐스트한 첫번째 결과(rrList[0])만 반환하는 문제점이 있었습니다.
    • 예를들어, 4개의 UI가 겹쳐져있을 경우 맨 위의 UI만 반환
  • 따라서 모든 레이캐스트 결과를 순회하며, 필요한 정보를 찾았을경우에 리턴하도록 했습니다.

PlayerItemGroupUI 클래스

PlayerItemGroupUI 클래스는 아이템슬롯들의 정보를 관리하고, Inventory 클래스와의 통신을 담당합니다.

public class PlayerItemGroupUI : MonoBehaviour
{
    [SerializeField] private List<PlayerItemSlotUI> slots = new();           // 슬롯 데이터
    [SerializeField] private Inventory inventory;

    private void Update()
    {
        GetInput();
    }

    private void GetInput()
    {
        if(Input.GetKeyDown(KeyCode.Alpha1))
        {
            slots[0].UseItem(inventory);
        }
        if(Input.GetKeyDown(KeyCode.Alpha2))
        {
            slots[1].UseItem(inventory);
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            slots[2].UseItem(inventory);
        }
        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            slots[3].UseItem(inventory);
        }
    }

    // 모든 슬롯 갱신
    public void UpdateSlots()
    {
        for(int i = 0;i<slots.Count;i++)
        {
            slots[i].UpdateSlot();
        }
    }

    // 삭제할 아이템을 참조하는 슬롯 제거
    public void RemoveItem(CountableItem ci)
    {
        for(int i = 0;i<slots.Count;i++)
        {
            if(slots[i].HasItem(ci))
            {
                slots[i].RemoveItem();
            }
        }
    }

    // 슬롯에 아이템 등록
    public void SetItemIconAndAmountText(int index, CountableItem ci)
    {
        slots[index].SetItem(ci);
    }

}


  • 사용자의 키 입력을 받습니다.
    • 키보드 숫자키 1, 2, 3, 4
  • 인벤토리와의 연계에 사용될 함수
    • SetItemIconAndAmountText() : 인벤토리에서 아이템 등록시
    • RemoveItem() : 아이템을 모두 사용하거나, 인벤토리에서 아이템을 버렸을 경우
    • UpdateSlots() : 아이템을 사용했을 때 아이템정보를 갱신

PlayerItemSlot 클래스

PlayerItemSlot 클래스는 각 아이템 슬롯의 아이템정보를 가지고있으며, 아이템 등록, 사용, 제거 등을 담당합니다.


/*
                     PlayerItemSlotUI

                - 플레이어 아이템 슬롯 관리      
                - 슬롯에 아이템을 가져다놓으면 아이템을 등록
                    - 인벤토리에서 아이템을 사용하거나 제거하면 반영
 */

public class PlayerItemSlotUI : MonoBehaviour
{
    [SerializeField] private Image icon;
    [SerializeField] private Text amount;
    public int index;

    private CountableItem slotItem;             // 이 슬롯의 아이템


    private void ShowAmount() => amount.gameObject.SetActive(true);
    private void HideAmount() => amount.gameObject.SetActive(false);

    // 슬롯 업데이트
    public void UpdateSlot()
    {
        SetItem(slotItem);
    }

    // 슬롯에 아이템 등록(아이콘이미지, 수량텍스트)
    public void SetItem(CountableItem item)
    {
        if (item == null)
        {
            return;
        }

        slotItem = item;

        ResourceManager.Instance.LoadIcon(item.Data.ItemIcon, sprite =>
        {
            if (sprite != null)
            {
                icon.sprite = sprite;
                icon.color = new Color(1f, 1f, 1f, 1f);

                if(slotItem.Amount > 1)
                {
                    ShowAmount();
                }
                else
                {
                    HideAmount();
                }
                
                amount.text = item.Amount.ToString();
            }
            else
            {
                Debug.Log($"Failed to load icon for item : {item.Data.ItemIcon}");
            }
        });
    }

    // 슬롯의 아이템 제거
    public void RemoveItem()
    {
        icon.sprite = null;
        icon.color = new Color(1f, 1f, 1f, 0f);
        slotItem = null;
        HideAmount();
    }

    // 해당 아이템이 등록되어있는지 여부
    public bool HasItem(CountableItem ci)
    {
        return slotItem == ci;
    }

    // 슬롯의 아이템 사용
    public void UseItem(Inventory iv)
    {
        iv.Use(slotItem);
    }
}


Inventory 클래스

Inventory 클래스의 아이템 사용 및 갱신 부분을 수정하였습니다.

    // 해당 인덱스 슬롯 갱신
    private void UpdateSlot(int index)
    {
        // 유효한 슬롯만
        if (!IsValidIndex(index)) return;

        // 해당 아이템
        Item item = items[index];

        // 슬롯에 아이템 존재
        if(item != null)
        {
            // 1. 수량이 있는 아이템인 경우
            if(item is CountableItem ci)
            {
                inventoryUI.SetItemIconAndAmountText(index, item.Data.ItemIcon, ci.Amount);
                playerItemGruopUI.UpdateSlots();
            }
            // 2. 장비 아이템
            else if(item is EquipmentItem ei)
            {
                inventoryUI.SetItemIconAndAmountText(index, item.Data.ItemIcon);
            }
            // 3. 그 외
            else
            {
                // 아이콘 표시
                inventoryUI.SetItemIconAndAmountText(index, item.Data.ItemIcon);
            }
        }
        // 슬롯에 아이템이 없을 때
        else
        {
            Remove(index);
            playerItemGruopUI.UpdateSlots();
        }

        // 인벤토리 데이터 저장
        SaveInventoryData();
    }

    // 아이템 슬롯의 아이템 사용
    public void Use(CountableItem ci)
    {
        for(int i = 0;i<=items.Length;i++)
        {
            if (items[i] == ci)
            {
                Use(i);
                return;
            }
        }
    }


  • UpdateSlot() 함수에서 아이템 슬롯의 아이템이 사용되었을 경우의 처리를 추가하였습니다.
    • 아이템이 모두 사용되었을 경우의 처리는 Use() 함수에서 처리하고있으므로, 제거하였습니다.
  • Use() : 아이템 슬롯의 아이템 사용을 처리하기위해, 기존의 Use() 함수를 오버로딩하여 처리하였습니다.

댓글남기기