본문 바로가기

Unity

유니티 (Unity) - 실전 인벤토리 제작 (5부)

반응형

 

 

5부의 내용은 아이템 클릭 시 해당 아이템의 메뉴창을 만들어 보겠습니다.

우선 앞에 만들었던 아이템 두 개의 이름을 변경하도록 하겠습니다.

 

 

 

아이템 이름을 한글에서 영문으로 변경하겠습니다. 

한글 폰트설치 때문에 변경하니 한글폰트가 설치되어 있다면 변경 없이 그대로 사용하시면 됩니다.

 

 

Canvas > ItemMenu 가 오도록 위치시켜 주세요 그런 후 Inventory 밑에 위치시켜 주시면 됩니다.

Plus Button, Minus Button, Sell Button, Close Button 들은 이름 그대로 Button 컴포넌트를 추가해 주시면 됩니다.

ItemMneu 스크립트 먼저 보시겠습니다. 위 그림의 최상단의 ItemMenu 오브젝트에 넣어 주세요

 

using System.Collections;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class ItemMenu : MonoBehaviour
{
    [Header("아이템 메뉴 설정")]
    [SerializeField] private Image itemImage;
    [SerializeField] private TextMeshProUGUI itemNameText;
    [SerializeField] private TextMeshProUGUI goldText;
    [SerializeField] private TextMeshProUGUI countText;
    [SerializeField] private TextMeshProUGUI calculatorCountText;
    [SerializeField] private RectTransform menuRect;

    private InventoryItem inventoryItem;
    private int countCalculator;
    private int itemCount;
    private string itemName;
    private long gold;
    private long addGold;
    private float orgPosX = 1500;
    private bool isPointerCheck = false;
    private float timeCheck;

    public void ItemMneuOpen(InventoryItem item, Vector3 pos)
    {
        inventoryItem = item;
        itemName = inventoryItem.item.itemName;
        transform.position = pos;
        itemCount = inventoryItem.count;
        countCalculator = 1;
        gold = inventoryItem.item.gold;
        ItemDataView();
        if (gameObject.activeSelf == false)
        {
            gameObject.SetActive(true);
            transform.DOScaleY(1, 0.4f).SetEase(Ease.OutBounce);
        }
    }

    public void ItemMenuClose()
    {
        transform.DOScaleY(0, 0.2f).OnComplete(() => {
            gameObject.SetActive(false);
            inventoryItem = null;
            menuRect.anchoredPosition = new Vector3(orgPosX, 0, 0);
        });
    }

    private void ItemDataView()
    {
        itemImage.sprite = inventoryItem.item.itemImage;
        itemNameText.text = itemName;
        countText.text = $"{itemCount}";
        goldText.text = $"{gold}";
        calculatorCountText.text = $"{countCalculator}";
    }

    public void OnPointerDown(bool isAdd)
    {
        isPointerCheck = true;
        StartCoroutine(ItemAddEvent(isAdd));
    }

    public void OnPointerUp()
    {
        isPointerCheck = false;
    }

    IEnumerator ItemAddEvent(bool isAdd)
    {
        timeCheck = 0;
        Calculator(isAdd);

        while (true)
        {
            if (!isPointerCheck) break;

            if (timeCheck >= 40)
            {
                yield return new WaitForSeconds(0.1f);
                Calculator(isAdd);
            }
            else
            {
                timeCheck++;
            }
            yield return null;
        }
    }

    private void Calculator(bool isAdd)
    {
        switch (isAdd)
        {
            case true:
                if (countCalculator < itemCount)
                {
                    countCalculator++;
                    addGold = gold * countCalculator;
                    CalculatorText();
                }
                break;
            case false:
                if (countCalculator > 1)
                {
                    countCalculator--;
                    addGold = gold * countCalculator;
                    CalculatorText();
                }
                break;
        }
    }

    private void CalculatorText()
    {
        calculatorCountText.text = $"{countCalculator}";
        goldText.text = GoldTextConvert(addGold);
    }

    public void SellButton()
    {
        itemCount -= countCalculator;
        countText.text = $"{itemCount}";
        countCalculator = 1;
        addGold = inventoryItem.item.gold;
        CalculatorText();

        if (itemCount <= 0)
        {
            Destroy(inventoryItem.gameObject);
            ItemMenuClose();
        }
    }

    private string GoldTextConvert(long gold)
    {
        return gold > 0 ? gold.ToString("#,##0") : "0";
    }
}

 

코드 설명은 조금 있다 하겠습니다.

 

 

ItemMenu의 Plus Button에 Event Trigger를 추가해 줍니다.

Button은 그냥 눌려지는 효과만 표시하는 기능을 하게 되며 실제 클릭효과는 Event Trigger에서 담당하게 됩니다.

그런 후 Add New Event Type 버튼을 클릭하여

Pointer Down과 Pointer Up을 추가해 주세요.

 

 

Minus Button도 위와 같이 추가해 주시고 이미지처럼 위에서 만든 ItemMenu 스크립트를 연결하여

Plus Button에는 OnPointerDown에 체크를 Minus Button에는 OnPointerDown에 체크를 하지 않습니다.

그리고 두 곳 다 OnPointerUp 역시 이벤트에 추가해 주세요.

 

일반 Button을 사용하지 않고 Event Trigger를 사용하는 이유는

+ / - 버튼을 꼭 누르는 효과를 보기 위해서입니다.

누르는 효과에 따라 수량이 자동 증가 및 감소를 나타내게 될 것입니다.

Button 기능이 있는 나머지 두 개도 ItemMenu 스크리트의 메서드와 연결해 주겠습니다.

 

Sell Button -> ItemMenu의 SellButton 메서드 연결

 

Close Button -> ItemMenu의 ItemMenuClose 메서드 연결

 

    [SerializeField] private Image itemImage;
    [SerializeField] private TextMeshProUGUI itemNameText;
    [SerializeField] private TextMeshProUGUI goldText;
    [SerializeField] private TextMeshProUGUI countText;
    [SerializeField] private TextMeshProUGUI calculatorCountText;

이미지처럼 각 이름에 맞게 Image와 Text를 연결해 주세요

    [SerializeField] private RectTransform menuRect;

이건 아래 이미지의 ItemMenu를 연결해 주시면 됩니다.

 

ItemMenu의 Rect Transform 설정입니다

Pos X를 1500에 Scale Y를 0으로 그리고 오브젝트를 비활성화시켜 주시면 됩니다.

 

    private InventoryItem inventoryItem;
    private int countCalculator;
    private int itemCount;
    private string itemName;
    private long gold;
    private long addGold;
    private float orgPosX = 1500;
    private bool isPointerCheck = false;
    private float timeCheck;

 

inventoryItem 앞에서 만든 Item프리펩에 들어가 있는 스크립트입니다. 

다시 말해 현재 인벤토리에 생성된 하나의 아이템이라고 보시면 됩니다.

나머지 설정 값들은 밑에 메서드 설명에서 다시 나오기 때문에 생략하겠습니다.

    public void ItemMneuOpen(InventoryItem item, Vector3 pos)
    {
        inventoryItem = item;
        itemName = inventoryItem.item.itemName;
        transform.position = pos;
        itemCount = inventoryItem.count;
        countCalculator = 1;
        gold = inventoryItem.item.gold;
        ItemDataView();
        if (gameObject.activeSelf == false)
        {
            gameObject.SetActive(true);
            transform.DOScaleY(1, 0.4f).SetEase(Ease.OutBounce);
        }
    }

 

ItemMenuOpen 메서드는 말 그대로 아이템을 클릭 시 해당 아이템의 메뉴를 열게 해 줍니다.

inventoryItem = item;

클릭된 아이템을 받아 옵니다.

transform.position = pos;

메뉴의 위치를 클릭된 아이템 위로 옮겨 줍니다.

        itemName = inventoryItem.item.itemName;
        itemCount = inventoryItem.count;
        gold = inventoryItem.item.gold;

아이템의 이름과 수량 개당 가격을 각각 넣어줍니다.

countCalculator = 1;

메뉴를 열 때 증감 숫자는 항상 1로 표시됩니다.

ItemDataView();

위에서 받은 데이터를 이용해서 메뉴의 각 Text에 정보를 표시합니다.

        if (gameObject.activeSelf == false)
        {
            gameObject.SetActive(true);
            transform.DOScaleY(1, 0.4f).SetEase(Ease.OutBounce);
        }

마지막으로 메뉴를 활성화시켜 화면에 표시해 줍니다.

 

여기서 설정을 하나 더 하고 가겠습니다.

아이템 프리펩을 열어 주세요.

아이템 프리펩에 아래 이미지처럼 Button을 생성 후 Item에 들어 있는 InventoryItem 스크립트 중

SlotButton 메서드를 연결해 줍니다.

 

InventoryItem에 SlotButton 메서드만 있고 안에 내용이 없었는데

    public void SlotButton()
    {
        DataManager.Instance.inventory.itemMenu.ItemMneuOpen(this, transform.position);
    }

위 코드와 같이 내용을 추가해 주세요.

 

 

다시 ItemMenu로 넘어와서

    public void ItemMenuClose()
    {
        transform.DOScaleY(0, 0.2f).OnComplete(() => {
            gameObject.SetActive(false);
            inventoryItem = null;
            menuRect.anchoredPosition = new Vector3(orgPosX, 0, 0);
        });
    }

메뉴를 닫고 위치를 orgPosX 값으로 이동합니다.

 

    private void ItemDataView()
    {
        itemImage.sprite = inventoryItem.item.itemImage;
        itemNameText.text = itemName;
        countText.text = $"{itemCount}";
        goldText.text = $"{gold}";
        calculatorCountText.text = $"{countCalculator}";
    }

메뉴가 열릴 때 각종 정보를 표시해 줍니다.

 

    public void OnPointerDown(bool isAdd)
    {
        isPointerCheck = true;
        StartCoroutine(ItemAddEvent(isAdd));
    }

Plus Button과 Minus Button의 Event Trigger의 Pointer Down에 연결된 메서드입니다.

isPointerCheck = ture 이벤트 시작을 알려 줍니다.

코루틴 ItemAddEvent를 실행하는데 isAdd가 true면 Plus / flase 면 Minus가 됩니다.

 

    public void OnPointerUp()
    {
        isPointerCheck = false;
    }

역시 Plus Button과 Minus Button의 Event Trigger의 Pointer Up에 연결된 메서드입니다.

Button의 누르고 있는 효과가 끝이 났다면 마우스 클릭이나 터치가 끝났다면 발생하는 이벤트입니다.

isPointerCheck = flase를 주어 코루틴에 이벤트가 끝났음을 알려 줍니다.

 

    IEnumerator ItemAddEvent(bool isAdd)
    {
        timeCheck = 0;
        Calculator(isAdd);

        while (true)
        {
            if (!isPointerCheck) break;

            if (timeCheck >= 40)
            {
                yield return new WaitForSeconds(0.1f);
                Calculator(isAdd);
            }
            else
            {
                timeCheck++;
            }
            yield return null;
        }
    }

위 OnPointerDown() 메서드에서 이벤트가 발생하면 여기로 들어오게 됩니다.

timeCheck = 0 Down 이벤트의 발생 시간을 체크합니다.

Calculator 실제 계산을 하는 곳입니다 한번 클릭된 상태에서 들어왔기에 isAdd에 따라

값을 변경해 줍니다.

 

 if (!isPointerCheck) break;

while문이 돌면서 Down 이벤트가 끝났다면 while문을 빠져나갑니다.

            if (timeCheck >= 40)
            {
                yield return new WaitForSeconds(0.1f);
                Calculator(isAdd);
            }
            else
            {
                timeCheck++;
            }

timeCheck 가 40에 도달했다면 본격적으로 증감 시작 합니다.

 

   private void Calculator(bool isAdd)
    {
        switch (isAdd)
        {
            case true:
                if (countCalculator < itemCount)
                {
                    countCalculator++;
                    addGold = gold * countCalculator;
                    CalculatorText();
                }
                break;
            case false:
                if (countCalculator > 1)
                {
                    countCalculator--;
                    addGold = gold * countCalculator;
                    CalculatorText();
                }
                break;
        }
    }

 

isAdd값에 맞게 Plus 또는 Minus 시켜 줍니다.

계산 부분은 더하고 빼는 것뿐이라 설명할 게 없네요.

 

    private void CalculatorText()
    {
        calculatorCountText.text = $"{countCalculator}";
        goldText.text = GoldTextConvert(addGold);
    }

증감되는 수만큼 ItemMenu에 변경된 값을 표시해 줍니다.

 

    private string GoldTextConvert(long gold)
    {
        return gold > 0 ? gold.ToString("#,##0") : "0";
    }

이건 long 또는 int type의 값을 받아 그 값이 0 보다 작다면 그냥 0을 리턴하고 아니면 천 단위 콤마를 표시해 주는

문자로 변환해서 리턴해 줍니다.

 

    public void SellButton()
    {
        itemCount -= countCalculator;
        countText.text = $"{itemCount}";
        countCalculator = 1;
        addGold = inventoryItem.item.gold;
        CalculatorText();
        // 판매한 수량만큼 골드를 추가

        if (itemCount <= 0)
        {
            Destroy(inventoryItem.gameObject);
            ItemMenuClose();
        }
    }

마지막으로 판매 버튼입니다.

판매 후 변경된 수량을 메뉴에 표시해 주며 또는 itemCount가 0이라면 해당 아이템을 인벤토리에서 삭제 후

메뉴를 닫아 줍니다.

앞에서도 말씀드렸지만 이건 쉽게 설명을 위한 내용이라 Destroy로 아이템을 삭제했지만

실제 적용하신다면 Object Pool을 이용해서 사용과 반납을 적용하시기 바랍니다.

인벤토리의 아이템이 슈팅게임의 총알처럼 몇백 발씩 순간 생성과 삭제를 하는 게 아니기에 Destroy를

사용해도 무방한 수준이긴 하지만 그래도 오브젝트 풀로 적용하시길 권장합니다.

 

 

이제 게임을 플레이해서 테스트해 보겠습니다.

위 이미지처럼 Item Count 7에서 5개를 판매 후 2개가 남은 것과 최종 2개까지 판매하여 인벤토리에서

아이템이 삭제된 것까지 그리고 5개 추가하면서 증가된 금액 등등 잘 작동하네요.

 

인벤토리 자체를 닫고 열기는 오늘 보여드린 아이템 메뉴의 열기 및 닫기를 참고하시면 되겠습니다.

이것으로 인벤토리 제작을 마무리하겠습니다.

반응형