게임에서 상점 시스템은 플레이어들이 게임 내 아이템이나 장비를 구매하고 업그레이드하는 방법을 제공하여 게임의 새로운 경험과 도전을 제공합니다. 상점 시스템을 구현하여 플레이어가 아이템을 구매할 수 있도록 하였습니다.

 

📺 미리보기



 

💬 목차

  • 총 세개의 목차로 구성되어 있습니다. 글 별로 아직 언급되지 않은 클래스의 호출이 있을 수 있습니다. 모든 글을 참조하면 충분히 이해할 수 있습니다.
  • 이번 글에서는 아이템 상점에서 사용될 "아이템 상점 슬롯"을 구현합니다.

[📌현재 글] 1. [유니티] 상점 시스템(1) - 상점 슬롯
2. [유니티] 상점 시스템(2) - 상점
3. [유니티] 상점 시스템(3) - 상점 매니저

 

📖 구현 내용

  • 플레이어는 화폐를 가지고 해당 아이템을 구매할 수 있습니다.
  • 에디터에서 상점에서 판매하는 아이템을 미리 등록합니다.
  • 각 상점별로 상점 진척도 레벨을 이용하여 구매할 수 있는 아이템을 제한할 수 있습니다.
  • 각 상점별로 판매하는 아이템은 재고가 있어 재고를 모두 소진할경우 해당 아이템을 더 이상 구매할 수 없습니다.
  • 상점의 재고 및 진척도 레벨은 저장되어 나중에 게임을 로드하면 현 상태를 불러옵니다.

 

✅ 구현

 

· ItemShopSlot.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class ItemShopSlot : MonoBehaviour
{
    [SerializeField] private InventorySlot mItemSlot;
    [SerializeField] private TextMeshProUGUI mNameLabel, mCostLabel;
    [SerializeField] private Button mBuyButton;

    private ItemShopSlotInfo mSellInfo; // 현재 판매중인 아이템의 정보
    private int mCalledShopLevel; // 현재 상점의 판매 단계

    public void RefreshSlot()
    {
        // 아이템의 요구 단계가 현재 상점의 판매 단계가 보다 높으면?
        if (mSellInfo.NeedShopLevel > mCalledShopLevel)
        {
            mCostLabel.text = "지금은 구매할 수 없습니다.";
            mBuyButton.interactable = false;

            return;
        }

        // 구매버튼 비활성화
        if (InventoryMain.Instance.CurrentCoin < mSellInfo.Cost
            || mSellInfo.ItemAmount <= 0)
            mBuyButton.interactable = false;
        else
            mBuyButton.interactable = true;

        // 텍스트 갱신
        mCostLabel.text = $"{mSellInfo.Cost} ({mSellInfo.ItemAmount}개 남음)";

    }

    public void InitSlot(ItemShopSlotInfo sellItem, int shopLevel)
    {
        // 정보 가져오기
        mCalledShopLevel = shopLevel;
        mSellInfo = sellItem;

        // 슬롯 초기화
        mItemSlot.ClearSlot();

        // 슬롯 설정
        InventoryMain.Instance.AcquireItem(mSellInfo.SellItem, mItemSlot, mSellInfo.GiveAmountPerTrade);
        mNameLabel.text = ItemDataManager.Instance.GetName(mSellInfo.SellItem.ID);
    }

    public void BTN_BuyItem()
    {
        // 가격 지불
        InventoryMain.Instance.CurrentCoin -= mSellInfo.Cost;

        // 인벤토리에 아이템 추가(구매)
        InventoryMain.Instance.AcquireItem(mSellInfo.SellItem, mSellInfo.GiveAmountPerTrade < mSellInfo.ItemAmount ? mSellInfo.GiveAmountPerTrade : mSellInfo.ItemAmount);

        // 개수 갱신
        mSellInfo.ItemAmount -= mSellInfo.GiveAmountPerTrade;
        mSellInfo.ItemAmount = Mathf.Clamp(mSellInfo.ItemAmount, 0, int.MaxValue);

        // 모든 슬롯을 갱신
        ItemShopManager.Instance.RefreshSlots();

        // 사운드 재생
        SoundManager.Instance.PlaySound2D("Item purchase " + SoundManager.Range(1, 3));
    }
}

 

[SerializeField] private InventorySlot mItemSlot;
  • 판매할 아이템을 보여주기 위한 인벤토리 슬롯입니다.

 

[SerializeField] private TextMeshProUGUI mNameLabel, mCostLabel;
  • 판매할 아이템의 이름과 가격을 크게 보여주기위한 라벨입니다.

 

[SerializeField] private Button mBuyButton;
  • 아이템을 구매하기위한 버튼입니다.
  • 만약 아이템을 구매할 수 없는경우, 해당 버튼을 비활성화합니다.

 

private ItemShopSlotInfo mSellInfo; // 현재 판매중인 아이템의 정보
  • 슬롯에서 현재 판매중인 아이템의 정보를 담습니다.

 

private int mCalledShopLevel; // 현재 상점의 판매 단계
  • 현재 상점의 진척도 레벨입니다.
  • 이 값이 판매 아이템 정보의 요구 레벨보다 크거나 같으면 아이템을 판매할 수 있게 합니다.

 

public void RefreshSlot()
{
    // 아이템의 요구 단계가 현재 상점의 판매 단계가 보다 높으면?
    if (mSellInfo.NeedShopLevel > mCalledShopLevel)
    {
        mCostLabel.text = "지금은 구매할 수 없습니다.";
        mBuyButton.interactable = false;

        return;
    }

    // 구매버튼 비활성화
    if (InventoryMain.Instance.CurrentCoin < mSellInfo.Cost
        || mSellInfo.ItemAmount <= 0)
        mBuyButton.interactable = false;
    else
        mBuyButton.interactable = true;

    // 텍스트 갱신
    mCostLabel.text = $"{mSellInfo.Cost} ({mSellInfo.ItemAmount}개 남음)";
}
  • 해당 슬롯을 갱신합니다.
  • 플레이어가 다른 아이템을 구매하여 소지금이 줄거나, 해당 아이템의 재고가 모두 떨어진경우 아이템 구매 버튼을 비활성화 할 수 있습니다.

 

public void InitSlot(ItemShopSlotInfo sellItem, int shopLevel)
{
    // 정보 가져오기
    mCalledShopLevel = shopLevel;
    mSellInfo = sellItem;

    // 슬롯 초기화
    mItemSlot.ClearSlot();

    // 슬롯 설정
    InventoryMain.Instance.AcquireItem(mSellInfo.SellItem, mItemSlot, mSellInfo.GiveAmountPerTrade);
    mNameLabel.text = ItemDataManager.Instance.GetName(mSellInfo.SellItem.ID);
}
  • 아이템 상점 다이얼로그 열릴 때 상점 매니저로부터 호출되며 슬롯에 판매할 아이템을 초기화해줍니다.

 

public void BTN_BuyItem()
{
    // 가격 지불
    InventoryMain.Instance.CurrentCoin -= mSellInfo.Cost;

    // 인벤토리에 아이템 추가(구매)
    InventoryMain.Instance.AcquireItem(mSellInfo.SellItem, mSellInfo.GiveAmountPerTrade < mSellInfo.ItemAmount ? mSellInfo.GiveAmountPerTrade : mSellInfo.ItemAmount);

    // 개수 갱신
    mSellInfo.ItemAmount -= mSellInfo.GiveAmountPerTrade;
    mSellInfo.ItemAmount = Mathf.Clamp(mSellInfo.ItemAmount, 0, int.MaxValue);

    // 모든 슬롯을 갱신
    ItemShopManager.Instance.RefreshSlots();

    // 사운드 재생
    SoundManager.Instance.PlaySound2D("Item purchase " + SoundManager.Range(1, 3));
}
  • 버튼을 이용하여 해당 아이템을 구매합니다.
  • 아이템을 지급하는 방식은 인벤토리 시스템을 이용합니다.

 

· ItemShopSlotInfo

  • 이 클래스는 판매할 아이템 정보를 정의하기위해 사용합니다. 이 글 이외에도 매니저 및 아이템 상점에서도 사용됩니다.
  • 클래스의 정의 및 각 변수의 사용 목적은 다음과 같습니다.
[System.Serializable]
public class ItemShopSlotInfo
{
    [Header("판매할 아이템")]
    [SerializeField] public Item SellItem; // 판매할 아이템

    [Header("거래 한번당 아이템의 비용")]
    [SerializeField] public int Cost; // 아이템의 비용

    [Header("아이템의 총 개수 (재고)")]
    [SerializeField] public int ItemAmount; // 아이템의 총 개수(재고량)

    [Header("거래 1회당 지급할 아이템 개수")]
    [SerializeField] public int GiveAmountPerTrade; // 거래 한번당 넘겨줄 아이템 개수

    [Header("아이템 판매 가능 단계 (상점의 진척도 단계)")]
    [SerializeField] public int NeedShopLevel; // 요구 단계

    public ItemShopSlotInfo(ItemShopSlotInfo origin)
    {
        this.SellItem = origin.SellItem;
        this.Cost = origin.Cost;
        this.ItemAmount = origin.ItemAmount;
        this.GiveAmountPerTrade = origin.GiveAmountPerTrade;
        this.NeedShopLevel = origin.NeedShopLevel;
    }
}

 

 

· UI

 

  • 판매할 아이템은 가장 왼쪽에 표시되며, 아이템의 이름 및 비용 및 구매 버튼이 함께 있습니다.

 

  • 프리팹 하나에 ItemShopSlot.cs을 사용하고 인스펙터에서 사용할 멤버변수들도 하위 구성 요소로 이루어져있습니다.

 

  • 프리팹의 UI 요소 구성입니다.
bonnate