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

 

💬 목차

  • 총 세개의 목차로 구성되어 있습니다. 글 별로 아직 언급되지 않은 클래스의 호출이 있을 수 있습니다. 모든 글을 참조하면 충분히 이해할 수 있습니다.
  • 이번 글에서는 아이템 상점을 구현하여 직전 글에서 다뤘던 아이템 상점 슬롯을 활용하여 구현해보겠습니다.
  • 이번 글에서는 상점 데이터를 저장하고 불러오는 기능 함수 또한 함께 포함되어있지만, 세이브 및 로드 시스템은 추후에 다루겠습니다.

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

 

📖 구현 내용

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

 

✅ 구현

  • 이번 글의 목적은 아이템 상점을 구현하여 아이템 상점 슬롯을 활용하여 구현하는것을 다룹니다.

 

· ItemShop.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using GameSave;

public class ItemShop : MonoBehaviour
{
    [field: Header("아이템 상점의 고유번호")]
    [field: SerializeField] public int ShopId { private set; get; } = 0;

    [Header("상점에서 판매할 아이템")]
    [SerializeField] public ItemShopSlotInfo[] mSellItemInfos;

    [HideInInspector] public int ShopLevel = 0; // 상점의 아이템 판매 단계

    private void Awake()
    {
        // 인스턴스로 생성하여 삽입
        List<ItemShopSlotInfo> itemShopSlotInfos = new List<ItemShopSlotInfo>();
        foreach(ItemShopSlotInfo shotSlotInfo in mSellItemInfos)
            itemShopSlotInfos.Add(new ItemShopSlotInfo(shotSlotInfo));

        // 배열 재구성
        mSellItemInfos = itemShopSlotInfos.ToArray();
    }

    /// <summary>
    /// 게임이 로드되는경우 상점 정보를 불러옴
    /// </summary>
    /// <param name="shopInfo">불러올 상점 정보</param>
    public void LoadFromData(ShopInfo shopInfo)
    {
        for(int i = 0; i < mSellItemInfos.Length; ++i)
            mSellItemInfos[i].ItemAmount = shopInfo.sellItemInfos[i].itemAmount;

        ShopLevel = shopInfo.shopLevel;
    }

    /// <summary>
    /// 게임을 저장하는경우 상점의 정보를 내보냄
    /// </summary>
    /// <returns>저장하고자 하는 상점의 정보</returns>
    public ShopInfo GetShopGameData()
    {
        ShopInfo shopInfo = new ShopInfo();

        shopInfo.shopId = this.ShopId;
        shopInfo.shopLevel = this.ShopLevel;
        shopInfo.sellItemInfos =  mSellItemInfos
                                    .Select(sellItemInfo =>
                                    new SellItemInfo
                                    {
                                        itemAmount = sellItemInfo.ItemAmount,
                                        sellItemEntityCode = (int)sellItemInfo.SellItem.ID
                                    }).ToArray();

        return shopInfo;
    }
}

 

[field: Header("아이템 상점의 고유번호")]
[field: SerializeField] public int ShopId { private set; get; } = 0;
  • 상점의 고유 번호입니다.
  • 해당 상점의 번호를 이용하여 세이브 및 로드를 할 때 식별하여 정확한 상점에 로드 및 세이브를 할 수 있습니다.

 

[Header("상점에서 판매할 아이템")]
[SerializeField] public ItemShopSlotInfo[] mSellItemInfos;
  • 상점에서 판매할 아이템을 인스펙터에서 정의할 수 있습니다.
  • ItemShopSlotInfo는 판매할 아이템 하나의 정보를 정의하며 배열로 선언되어 여러개의 아이템을 한번에 판매할 수 있습니다.

 

[HideInInspector] public int ShopLevel = 0; // 상점의 아이템 판매 단계
  • 상점의 진척도 레벨을 정의합니다.
  • 이 값에 따라 플레이어가 상점에서 구매할 수 있는 아이템이 제한됩니다.
  • 외부에서 이 값을 변경하여 진척도를 바꿀 수 있습니다.

 

private void Awake()
{
    // 인스턴스로 생성하여 삽입
    List<ItemShopSlotInfo> itemShopSlotInfos = new List<ItemShopSlotInfo>();
    foreach(ItemShopSlotInfo shotSlotInfo in mSellItemInfos)
        itemShopSlotInfos.Add(new ItemShopSlotInfo(shotSlotInfo));

    // 배열 재구성
    mSellItemInfos = itemShopSlotInfos.ToArray();
}
  • 게임 세이브 및 로드에 상관 없이 씬이 불러와지면 인스펙터에서 정의한 아이템 판매 데이터들을 초기화하여 판매 준비를 합니다.

 

/// <summary>
/// 게임이 로드되는경우 상점 정보를 불러옴
/// </summary>
/// <param name="shopInfo">불러올 상점 정보</param>
public void LoadFromData(ShopInfo shopInfo)
{
    for(int i = 0; i < mSellItemInfos.Length; ++i)
        mSellItemInfos[i].ItemAmount = shopInfo.sellItemInfos[i].itemAmount;

    ShopLevel = shopInfo.shopLevel;
}
  • 씬이 이전에 저장되어 불러와진경우 해당 아이템 상점이 불러와진 데이터를 대상으로 기존의 데이터들을 재구성합니다.
  • 재고 및 아이템 상점의 진척도 등을 갱신합니다.

 

/// <summary>
/// 게임을 저장하는경우 상점의 정보를 내보냄
/// </summary>
/// <returns>저장하고자 하는 상점의 정보</returns>
public ShopInfo GetShopGameData()
{
    ShopInfo shopInfo = new ShopInfo();

    shopInfo.shopId = this.ShopId;
    shopInfo.shopLevel = this.ShopLevel;
    shopInfo.sellItemInfos =  mSellItemInfos
                                .Select(sellItemInfo =>
                                new SellItemInfo
                                {
                                    itemAmount = sellItemInfo.ItemAmount,
                                    sellItemEntityCode = (int)sellItemInfo.SellItem.ID
                                }).ToArray();

    return shopInfo;
}
  • 게임을 저장하기위해 현재 상점 데이터 정보를 필요한 데이터만 추출하여 세이브 및 로드 매니저에 리턴합니다.
  • 상점의 세이브 및 로드를 위한 ShopGameDataManager에서 사용하는 데이터 구조는 다음과 같습니다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Quest;

namespace GameSave
{
    ...
    
    #region ShopGameDataManager

    [Serializable]
    public struct SellItemInfo // 아이템 슬롯 한개의 판매 정보
    {
        public int sellItemEntityCode; // 판매 아이템의 아이템코드
        public int itemAmount; // 아이템의 남은 총 개수(재고량)
    }

    [Serializable]
    public struct ShopInfo
    {
        public int shopId; // 상점의 고유 ID
        public int shopLevel; // 현재 상점의 판매 단계
        public SellItemInfo[] sellItemInfos; // 상점에서 판매중인 아이템들의 정보
    }


    [Serializable]
    public struct ShopGameData
    {
        public ShopInfo[] shopInfos;
    }

    #endregion
}
  • 게임을 저장하고 로드하는 시스템은 추후에 다루겠습니다.

 

✅ 사용

· 판매할 아이템 정의

  • 아이템 상점을 사용할 대상에게 ItemShop 컴포넌트를 사용하도록 하고 판매할 아이템을 정의하도록 합니다.
  • 여러개의 아이템을 판매하도록 설정하며 판매할 아이템인 Sell Item은 인벤토리 시스템에서 사용하는 아이템들을 다룹니다.
  • Sell Item은 "[유니티] 인벤토리 시스템(2) - 아이템(ScriptableObject)"에서 다룹니다.

 

· 상점 열기

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

using NPC.State;

[RequireComponent(typeof(AnimatorBlender))]
public class NPCBlacksmith : NPCBase
{
   ...

    [field: Space(30)]
    [field: Header("[Blacksmith]")]
    [Header("아이템 상점 컴포넌트")]
    [SerializeField] private ItemShop mItemShop;

    ...

    public override void ExecQuoteSelectedBehavior(int selectedQuoteID)
    {
        switch (selectedQuoteID)
        {
            case 3:
                ItemShopManager.Instance.OpenItemShop(mItemShop.mSellItemInfos, mItemShop.ShopLevel);
                break;

            case 4:
                QuoteManager.Instance.DisplayQuoteOverlay(12, this);
                break;
        }
    }

    ...
    
}
  • "[유니티] 대화 시스템(1) - Overlay Canvas"에서 사용하는 기능을 이용하여 상점을 이용하고싶다고 한 경우 아이템 상점을 열 수 있습니다.
  • 대화 시스템은 열기위한 수단 중 하나일 뿐 아래의 함수가 상점을 여는 핵심 코드입니다.

[유니티]&nbsp;대화&nbsp;시스템(1)&nbsp;-&nbsp;Overlay&nbsp;Canvas

 

 

ItemShopManager.Instance.OpenItemShop(mItemShop.mSellItemInfos, mItemShop.ShopLevel);
  • 상점 매니저를 이용하여 아이템 상점을 열도록 합니다.
  • 전달하는 인자는 열고자하는 아이템 상점의 판매 아이템 정보들과 현재 상점의 진척도 레벨입니다.
  • 상점 매니저는 다음 글에서 다루겠습니다.

 

bonnate