게임에서 상자 시스템은 보상을 통해 유저들에게 재미와 긴장감을 제공하며, 게임 내 아이템을 무작위로 획득하는 기회를 제공합니다. 또한 유저는 상자에 아이템을 보관하는 기능을 이용할 수 있습니다. 상자 시스템을 구현하고 이를 정리하였습니다.

 

📺 미리보기

 

💬 목차

  • 총 두 개의 목차로 구성되어 있습니다.
  • 글 별로 아직 언급되지 않은 클래스의 호출이 있을 수 있습니다. 이 사항은 모든 글을 참조하면 충분히 이해할 수 있습니다.

1. [유니티] 상자 시스템(1) - 상자 데이터

[📌현재 글] 2. [유니티] 상자 시스템(2) - 상자 다이얼로그

 

📖 구현 내용

  • 월드에 배치되어있는 상자를 바라보고 키를 누르면 상자가 열립니다.
  • 플레이어에게 아이템을 지급하기위한 목적으로 상자를 처음 열을경우 설정에 따라 아이템이 무작위로 생성됩니다.
  • 아이템이 생성될 때 아이템이 생성되는 위치 또한 무작위로 설정됩니다.
  • 처음으로 상자를 여는것이 아닌경우 저장된 아이템이 유지되며 해당 아이템을 꺼낼 수 있습니다.
  • 플레이어의 아이템을 상자에 집어넣어 보관할 수 있습니다.
  • 상자의 데이터가 저장되어 게임을 세이브 및 로드를 할 때 해당 사항이 저장됩니다.
  • 상자를 열 때 UI의 영역의 크기를 다르게 설정할 수 있습니다. 예) 2x3 크기, 5x5크기 등...

 

 

✅ 구현

  • 이번 글에서는 월드에 배치할 상자를 이용할 수 있도록 다이얼로그 창을 제공하는 기능을 다룹니다.

 

· ChestDialogManager

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

namespace ChestSystem
{
    public class ChestDialogManager : Singleton<ChestDialogManager>
    {
        private static bool mIsDialogActive = false;
        public static bool IsDialogActive
        {
            get
            {
                return mIsDialogActive;
            }
        }

        [Header("다이얼로그 최상위 게임오브젝트")]
        [SerializeField] private GameObject mDialogGo;

        [Header("다이얼로그의 그리드 레이아웃 컴포넌트")]
        [SerializeField] private GridLayoutGroup mGridLayout;

        [Header("인벤토리 슬롯 오브젝트 프리팹")]
        [SerializeField] private GameObject mSlotPrefab;

        private RectTransform mDialogRectTransform;
        private ChestController mCurrentChest; // 현재 다이얼로그박스에 표시된 상자의 정보

        private void Awake()
        {
            // 초기화시 전역 활성화상태 해제
            ChestDialogManager.mIsDialogActive = false;

            mDialogRectTransform = mDialogGo.GetComponent<RectTransform>();
        }

        private void Update()
        {
            // 다이얼로그가 활성 상태에서 ESC를 누르면 ?
            if (mIsDialogActive && Input.GetKeyDown(KeyCode.Escape))
                CloseDialog();
        }

        public void TryOpenDialog(ChestController chestController)
        {
            // 이미 다이얼로그가 활성화 되어있다면?
            if(mIsDialogActive)
                return;

            // 현재 정보를 등록
            mCurrentChest = chestController;

            // 다이얼로그 크기 및 슬롯 초기화
            InitDialog(mCurrentChest.ChestInfo.row, mCurrentChest.ChestInfo.col);

            foreach (ChestSlotItem slotItem in mCurrentChest.ChestInfo.chestSlotItems)
            {
                InventorySlot slot = mGridLayout.transform.GetChild(slotItem.itemPositionIndex).GetComponent<InventorySlot>();
                InventoryMain.Instance.AcquireItem(slotItem.itemCode, slot, slotItem.itemCount);
            }

            mDialogGo.SetActive(true);
            mIsDialogActive = true;

            UtilityManager.UnlockCursor();

            mCurrentChest.PlayChestAnim(true);
        }

        public void CloseDialog()
        {
            // 현재 상자 인벤토리의 모든 슬롯을 가져옴
            InventorySlot[] inventorySlotItems = mGridLayout.transform.GetComponentsInChildren<InventorySlot>(false);

            // 아이템 슬롯 데이터 리스트를 를 생성후 개수가 0개 이상의 아이템들만 갱신
            List<ChestSlotItem> slotItems = new List<ChestSlotItem>();
            for (int i = 0; i < inventorySlotItems.Length; ++i)
                if (inventorySlotItems[i].ItemCount > 0)
                    slotItems.Add(new ChestSlotItem()
                    {
                        itemCode = inventorySlotItems[i].Item.ID,
                        itemCount = inventorySlotItems[i].ItemCount,
                        itemPositionIndex = i,
                    });

            // 슬롯 아이템의 정보를 대치
            mCurrentChest.ChestInfo.chestSlotItems.Clear();
            mCurrentChest.ChestInfo.chestSlotItems = slotItems;

            // 오브젝트 비활성화
            mDialogGo.SetActive(false);
            mIsDialogActive = false;

            // 마우스 잠금
            UtilityManager.TryLockCursor();

            // 아이템 설명 비활성화
            ItemDescription.Instance.DisableToolTip();

            mCurrentChest.PlayChestAnim(false);
        }

        /// <summary>
        /// 다이얼로그를 초기화하고 인벤토리 슬롯과 크기를 자동으로 조정
        /// </summary>
        private void InitDialog(int row, int col)
        {
            // 현재 자식(인벤토리 슬롯) 개수 구하기
            int currentSlotCount = mGridLayout.transform.childCount;

            // 부족한 인벤토리 슬롯 인스턴스
            for (int i = currentSlotCount; i < row * col; ++i)
                Instantiate(mSlotPrefab, Vector3.zero, Quaternion.identity, mGridLayout.transform);

            // 첫번째 인덱스부터 총 개수까지 슬롯 활성화 및 슬롯 초기화
            for (int i = 0; i < row * col; ++i)
            {
                InventorySlot slot = mGridLayout.transform.GetChild(i).GetComponent<InventorySlot>();
                slot.ClearSlot();
                slot.gameObject.SetActive(true);
            }

            // 초과하는 인벤토리 슬롯 비활성화
            for (int i = row * col; i < currentSlotCount; ++i)
                mGridLayout.transform.GetChild(i).gameObject.SetActive(false);

            // 셀 한개의 사이즈 구하기
            float cellWidth = mGridLayout.cellSize.x + mGridLayout.spacing.x;
            float cellHeight = mGridLayout.cellSize.y + mGridLayout.spacing.y;

            // 트랜스폼의 가로, 세로 길이 계산 및 적용
            float width = row * cellWidth - mGridLayout.spacing.x;
            float height = col * cellHeight - mGridLayout.spacing.y;
            mDialogRectTransform.sizeDelta = new Vector2(width + mGridLayout.padding.left + mGridLayout.padding.right, height + mGridLayout.padding.top + mGridLayout.padding.bottom);
        }
    }
}

 

private static bool mIsDialogActive = false;
public static bool IsDialogActive
{
    get
    {
        return mIsDialogActive;
    }
}
  • 현재 상자 다이얼로그 창이 열려있는지 확인하기위한 변수입니다.
  • 상자 다이얼로그 창이 열려있을 때 또 다른 창 열기를 시도할 때 중복으로 열리는 상황을 방지하고자 사용할 수 있습니다.

 

[Header("다이얼로그 최상위 게임오브젝트")]
[SerializeField] private GameObject mDialogGo;
  • 다이얼로그 창을 토글하기위한 오브젝트입니다.

 

[Header("다이얼로그의 그리드 레이아웃 컴포넌트")]
[SerializeField] private GridLayoutGroup mGridLayout;
  • 상자의 가로 세로 크기를 기반으로 크기를 재조정하기위한 그리드 레이아웃 컴포넌트입니다.

 

[Header("인벤토리 슬롯 오브젝트 프리팹")]
[SerializeField] private GameObject mSlotPrefab;

 

private RectTransform mDialogRectTransform;
  • 다이얼로그 창의 크기를 조절하기위한 RectTransform입니다.

 

public void TryOpenDialog(ChestController chestController)
{
    // 이미 다이얼로그가 활성화 되어있다면?
    if(mIsDialogActive)
        return;

    // 현재 정보를 등록
    mCurrentChest = chestController;

    // 다이얼로그 크기 및 슬롯 초기화
    InitDialog(mCurrentChest.ChestInfo.row, mCurrentChest.ChestInfo.col);

    foreach (ChestSlotItem slotItem in mCurrentChest.ChestInfo.chestSlotItems)
    {
        InventorySlot slot = mGridLayout.transform.GetChild(slotItem.itemPositionIndex).GetComponent<InventorySlot>();
        InventoryMain.Instance.AcquireItem(slotItem.itemCode, slot, slotItem.itemCount);
    }

    mDialogGo.SetActive(true);
    mIsDialogActive = true;

    UtilityManager.UnlockCursor();

    mCurrentChest.PlayChestAnim(true);
}
  • 다이얼로그 창 열기를 시도합니다.
  • 만약 이미 창이 열려있다면 리턴하여 창을 열지 않도록 합니다.
  • 현재 설정된 인벤토리 슬롯들에게 아이템을 설정하여 상자의 아이템들을 보여줍니다.
  • InitDialog를 이용하여 다이얼로그 창 구성을 초기화합니다.

 

/// <summary>
/// 다이얼로그를 초기화하고 인벤토리 슬롯과 크기를 자동으로 조정
/// </summary>
private void InitDialog(int row, int col)
{
    // 현재 자식(인벤토리 슬롯) 개수 구하기
    int currentSlotCount = mGridLayout.transform.childCount;

    // 부족한 인벤토리 슬롯 인스턴스
    for (int i = currentSlotCount; i < row * col; ++i)
        Instantiate(mSlotPrefab, Vector3.zero, Quaternion.identity, mGridLayout.transform);

    // 첫번째 인덱스부터 총 개수까지 슬롯 활성화 및 슬롯 초기화
    for (int i = 0; i < row * col; ++i)
    {
        InventorySlot slot = mGridLayout.transform.GetChild(i).GetComponent<InventorySlot>();
        slot.ClearSlot();
        slot.gameObject.SetActive(true);
    }

    // 초과하는 인벤토리 슬롯 비활성화
    for (int i = row * col; i < currentSlotCount; ++i)
        mGridLayout.transform.GetChild(i).gameObject.SetActive(false);

    // 셀 한개의 사이즈 구하기
    float cellWidth = mGridLayout.cellSize.x + mGridLayout.spacing.x;
    float cellHeight = mGridLayout.cellSize.y + mGridLayout.spacing.y;

    // 트랜스폼의 가로, 세로 길이 계산 및 적용
    float width = row * cellWidth - mGridLayout.spacing.x;
    float height = col * cellHeight - mGridLayout.spacing.y;
    mDialogRectTransform.sizeDelta = new Vector2(width + mGridLayout.padding.left + mGridLayout.padding.right, height + mGridLayout.padding.top + mGridLayout.padding.bottom);
}
  • 상자 정보에서 row, col을 기반으로 상자 다이얼로그 창의 크기와 그리드 레이아웃의 행과 열의 수를 재구성합니다.
  • 인벤토리 슬롯이 부족한경우 인스턴스를 하여 슬롯을 초기화해줍니다.
  • 만약 이미 슬롯들이 충분히 있다면 해당 슬롯을 재사용합니다. 슬롯이 초과되었다면 사용할 슬롯을 제외하고 나머지 슬롯들을 비활성화하여 그리드 레이아웃에서 보이지 않도록 설정합니다.

 

public void CloseDialog()
{
    // 현재 상자 인벤토리의 모든 슬롯을 가져옴
    InventorySlot[] inventorySlotItems = mGridLayout.transform.GetComponentsInChildren<InventorySlot>(false);

    // 아이템 슬롯 데이터 리스트를 를 생성후 개수가 0개 이상의 아이템들만 갱신
    List<ChestSlotItem> slotItems = new List<ChestSlotItem>();
    for (int i = 0; i < inventorySlotItems.Length; ++i)
        if (inventorySlotItems[i].ItemCount > 0)
            slotItems.Add(new ChestSlotItem()
            {
                itemCode = inventorySlotItems[i].Item.ID,
                itemCount = inventorySlotItems[i].ItemCount,
                itemPositionIndex = i,
            });

    // 슬롯 아이템의 정보를 대치
    mCurrentChest.ChestInfo.chestSlotItems.Clear();
    mCurrentChest.ChestInfo.chestSlotItems = slotItems;

    // 오브젝트 비활성화
    mDialogGo.SetActive(false);
    mIsDialogActive = false;

    // 마우스 잠금
    UtilityManager.TryLockCursor();

    // 아이템 설명 비활성화
    ItemDescription.Instance.DisableToolTip();

    mCurrentChest.PlayChestAnim(false);
}
  • 상자 다이얼로그 창을 닫을경우 상자 슬롯의 아이템들을 확인하여 현재 아이템들의 상태를 저장합니다.

 

· UI

  • 인벤토리 슬롯과 비슷한 디자인으로 구성하였습니다.
  • 하지만 이 UI의 크기는 상자의 설정에 따라 유동적으로 변합니다.

 

  • 그리드 레이아웃의 하나의 인벤토리 슬롯을 넣어두고 이를 복사하여 사용할 수 있도록 합니다.
bonnate