말감로그

[Unity] Valley - 아이템 팔기(+ 아이템 판넬 UI) 본문

TIL

[Unity] Valley - 아이템 팔기(+ 아이템 판넬 UI)

habbn 2024. 10. 29. 22:04
728x90


기존의 Player 스크립트에서 Player에 Raycast를 달아 ItemBox를 인식했던 것을 ItemBox 에서 OnTriggerEnter와 OnTriggerExit로 인식하는 것으로 변경하였다.

// ItemBox.cs

void OnTriggerEnter2D(Collider2D other)
{
    if (other.gameObject.name == "Player" && Input.GetMouseButtonDown(0) && !isBoxOpen)
    {
        Debug.Log("아이템 박스 오픈");
        isBoxOpen = true;
        anim.SetBool("isOpen", isBoxOpen);
        sellingPanel.SetActive(true);

        if (!GameManager.instance.uiManager.isInventoryOpen)
        {
            GameManager.instance.uiManager.ToggleInventoryUI();
        }
    }
}

void OnTriggerExit2D(Collider2D other)
{
    if (other.gameObject.name == "Player" && isBoxOpen)
    {
        Debug.Log("아이템 박스 클로즈");
        isBoxOpen = false;
        anim.SetBool("isOpen", isBoxOpen);
        sellingPanel.SetActive(false);

        if (GameManager.instance.uiManager.isInventoryOpen)
        {
            GameManager.instance.uiManager.ToggleInventoryUI();
        }
    }
}

 

그러나 이렇게 했을 때 마우스 이벤트를 인식하지 못하는 것을 발견했다. 그래서 gpt에게 물어보니

" OnTriggerEnter2D와 OnTriggerExit2D는 충돌이 발생하는 순간에만 실행되므로, 마우스 클릭 상태를 인식하는 데는 적합하지 않습니다. 이를 해결하려면 트리거 상태에서 Update 메서드를 사용하여 클릭 여부를 확인하는 방식으로 수정할 수 있습니다. " 라고 했다.

 

OnTriggerEnter2D와 OnTriggerExit2D는 트리거 콜라이더가 다른 콜라이더와 겹치는 순간과 겹침이 끝나는 순간에 한 번씩 호출된다. 이러한 트리거 메서드는 일정한 조건에 따라 계속 호출되는 것이 아니기 때문에 마우스 클릭과 같은 지속적인 입력 상태를 확인하는 데는 적합하지 않기 때문이다.

 

그래서 isPlayerInRange 불형 변수를 추가하여, 플레이어가 ItemBox의 트리거 범위에 있는지 확인한 후, Update 메서드에서 클릭을 감지하는 방식으로 구현해야 한다.

void Update()
{
    if (isPlayerInRange && Input.GetMouseButtonDown(0) && !isBoxOpen)
    {
        OpenItemBox();
    }
}

void OpenItemBox()
{
    Debug.Log("아이템 박스 오픈");
    isBoxOpen = true;
    anim.SetBool("isOpen", isBoxOpen);
    sellingPanel.SetActive(true);
    
    if (GameManager.instance.uiManager != null && !GameManager.instance.uiManager.isInventoryOpen)
    {
        GameManager.instance.uiManager.ToggleInventoryUI();
    }
}

void CloseItemBox()
{
    Debug.Log("아이템 박스 클로즈");
    isBoxOpen = false;
    anim.SetBool("isOpen", isBoxOpen);
    sellingPanel.SetActive(false);

    if (GameManager.instance.uiManager != null && GameManager.instance.uiManager.isInventoryOpen)
    {
        GameManager.instance.uiManager.ToggleInventoryUI();
    }
}

void OnTriggerEnter2D(Collider2D other)
{
    if (other.gameObject.name == "Player")
    {
        isPlayerInRange = true;
    }
}

void OnTriggerExit2D(Collider2D other)
{
    if (other.gameObject.name == "Player")
    {
        isPlayerInRange = false;
        if (isBoxOpen)
        {
            CloseItemBox();
        }
    }
}
}

 

 

인벤토리/툴바 위치 & Toggle 변경

InventoryPanel이 기존에는 E 키를 누르면 화면 가운데에 나타나고 사라지도록 설정했으나, 툴바 아래에 숨겨졌다가 E 키를 누르면 위로 나타나도록 변경했다.

코루틴을 활용해서 목표위치까지 Lerp를 통해 부드럽게 이동하도록 했다. 이 부분은 아직 매끄럽지 않아서 추후 수정해야한다.

Unity에서 UI는 스크린좌표를 기준을 배치되므로, RectTransform의 anchoredPosition을 변경하여 원하는 위치로 이동시킬 수 있다.

Transform의 position을 직접 사용하지 않는 이유는, RectTransform이 UI 전용 클래스이기 때문에 position보다는 anchoredPosition이 더 정확하고, UI 배치의 특성상 더욱 적합한 속성이기 때문이다.

//UIManager.cs

 void Update()
{
    if (Input.GetKeyDown(KeyCode.E) && !isInventoryMoving)
    {
        ToggleInventoryUI();
    }
}
public void ToggleInventoryUI()
{
    isInventoryOpen = !isInventoryOpen;
    Vector2 targetPosition = isInventoryOpen ? openPosition : closePosition;
    StartCoroutine(MovePanel(targetPosition));
}

IEnumerator MovePanel(Vector2 targetPosition)
{
    isInventoryMoving = true;
    while (Vector2.Distance(inventoryPanel.anchoredPosition, targetPosition) > 0.1f)
    {
        inventoryPanel.anchoredPosition = Vector2.Lerp(inventoryPanel.anchoredPosition, targetPosition, Time.deltaTime * moveSpeed);
        yield return null;
    }
    inventoryPanel.anchoredPosition = targetPosition;
    isInventoryMoving = false;
}

 

SellingPanel 제작

이제 선택한 슬롯의 아이템의 아이콘, 가격과 판매할 개수를 나타낼 SellingPanel을 만들었다.

 

아이템 가격 & 아이콘 나타내기, 팔기

우선 ItemData의 가격 변수를 추가해주고, Inventory 클래스에서 가격 또한 초기화해줘야한다. 

public class ItemData : ScriptableObject
{
    public string itemName = "Item Name";
    public Sprite icon;
    public int price; 
}
public class Inventory
{
    [System.Serializable]
    public class Slot
    {
        public string itemName;
        public Sprite icon;
        public int count;
        public int maxAllowed;
        public int price;
        public PlantData plantData;

        public Slot()
        {
            itemName = "";
            count = 0;
            maxAllowed = 99;
            price = 0;
            plantData = null;
        }

 

슬롯의 개수가 0이면 슬롯에서 사라져야되지만, 자꾸 사라지지 않는 문제가 있었다.

확인해보니 인벤토리를 갱신해주는 Refresh() 함수의 Slot_UI의 SetItem() 함수에서 slot의 개수가 0일 때의 조건문을 달아주지 않아 계속 유지되었던 것이다. 그래서 0일 시 슬롯에서 아이템을 제거하고 슬롯을 비워주도록 코드를 수정해주었다.

// Slot_UI.cs

public void SetItem(Inventory.Slot slot)
{
    if (slot != null)
    {
        itemIcon.sprite = slot.icon;
        itemIcon.color = new Color(1, 1, 1, 1);

        if (slot.count == 0)
        {
            slot.RemoveItem();
            EmptyItem();
        }
        else
            quantityText.text = slot.count == 1 ? "" : slot.count.ToString();
    }
}

 

아이템 박스 근처에서 우클릭 시 인벤토리와 SellingPanel이 잘 뜨고, 선택된 슬롯의 아이콘도 잘 뜨는 것을 확인할 수 있다. 마찬가지로 벗어나면 사라지는 것도 !

 

전체 코드

public class ItemBox : MonoBehaviour
{
    public Animator anim;
    public GameObject sellingPanel;
    public Image sellingIcon;
    public TextMeshProUGUI priceText;
    public TextMeshProUGUI countText;
    public Button minusBtn;
    public Button plusBtn;
    public Button checkBtn;
    public TextMeshProUGUI coinText;

    bool isBoxOpen = false;
    bool isPlayerInRange = false;
    int itemPrice = 0;
    int itemCount = 0;
    int sellingPrice = 0;

    Inventory.Slot selectedSlot;

    void Start()
    {
        anim = GetComponent<Animator>();
        if (sellingPanel != null)
        {
            sellingPanel.SetActive(false);
        }

        plusBtn.onClick.AddListener(OnPlusButtonClick);
        minusBtn.onClick.AddListener(OnMinusButtonClick);
        checkBtn.onClick.AddListener(OnCheckButtonClick);
        InitializePanel();
    }

    void Update()
    {
        if (isPlayerInRange && Input.GetMouseButtonDown(1) && !isBoxOpen)
        {
            OpenItemBox();
        }

        // 슬롯 선택 상태 확인
        var currentSlot = GameManager.instance.player.inventoryManager.toolbar.selectedSlot;
        if (currentSlot != selectedSlot)
        {
            selectedSlot = currentSlot;
            InitializePanel(); // 슬롯이 변경되면 초기화
        }
        UpdatePanel();
    }

    void OpenItemBox()
    {
        Debug.Log("아이템 박스 오픈");
        isBoxOpen = true;
        anim.SetBool("isOpen", isBoxOpen);
        sellingPanel.SetActive(true);

        InitializePanel();

        if (GameManager.instance.uiManager != null && !GameManager.instance.uiManager.isInventoryOpen)
        {
            GameManager.instance.uiManager.ToggleInventoryUI();
        }
    }

    void CloseItemBox()
    {
        Debug.Log("아이템 박스 클로즈");
        isBoxOpen = false;
        anim.SetBool("isOpen", isBoxOpen);
        sellingPanel.SetActive(false);

        if (GameManager.instance.uiManager != null && GameManager.instance.uiManager.isInventoryOpen)
        {
            GameManager.instance.uiManager.ToggleInventoryUI();
        }
    }

    void UpdatePanel()
    {
        if (selectedSlot != null)
        {
            sellingIcon.sprite = selectedSlot.count > 0 ? selectedSlot.icon : null;
            sellingIcon.color = selectedSlot.count > 0 ? new Color(1, 1, 1, 1) : new Color(1, 1, 1, 0);
            priceText.text = itemPrice.ToString();
            countText.text = itemCount.ToString();
        }
    }

    void InitializePanel()
    {
        sellingIcon.sprite = null;
        itemPrice = 0;
        itemCount = 0;
    }

    void OnPlusButtonClick()
    {
        if (selectedSlot != null && selectedSlot.count > 0 && itemCount < selectedSlot.count)
        {
            itemCount++;
            itemPrice = itemCount * selectedSlot.price;
            Debug.Log("selectedSlot" + selectedSlot.itemName + " : " + itemPrice);
            UpdatePanel();
        }
        else
        {
            Debug.Log("보유 아이템 없음");
        }
    }

    void OnMinusButtonClick()
    {
        if (selectedSlot != null && selectedSlot.count > 0 && itemCount > 0)
        {
            itemCount--;
            itemPrice = itemCount * selectedSlot.price;
            Debug.Log("selectedSlot" + selectedSlot.itemName + " : " + itemPrice);
            UpdatePanel();
        }
        else
        {
            Debug.Log("보유 아이템 없음");
        }
    }

    void OnCheckButtonClick()
    {
        if (selectedSlot != null && selectedSlot.count >= 0 && itemCount > 0)
        {
            sellingPrice += itemPrice;
            Debug.Log("sellingPrice" + selectedSlot.itemName + " : " + sellingPrice);
            selectedSlot.count -= itemCount;
            coinText.text = sellingPrice.ToString();
            GameManager.instance.uiManager.RefreshInventoryUI("Toolbar");
            InitializePanel();
        }
        else
        {
            Debug.Log("보유 아이템 없음");
        }
    }


    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.gameObject.name == "Player")
        {
            isPlayerInRange = true;
        }
    }

    void OnTriggerExit2D(Collider2D other)
    {
        if (other.gameObject.name == "Player")
        {
            isPlayerInRange = false;
            if (isBoxOpen)
            {
                CloseItemBox();
            }
        }
    }
}

 

여기까지하면 마이너스/플러스 버튼 클릭 시 개수 감소/증가 (최대 가지고있는 아이템 개수만큼) ,

체크 버튼 클릭 시 선택한 아이템 개수만큼 아이템 팔면 슬롯 갱신  -> 내 코인의 돈이 들어온 것을 확인할 수 있다.

또한 다른 슬롯 선택 시 초기화되고, 가지고 있는 아이템 전부를 팔아버리면 슬롯에서 삭제된다.

 

728x90