XR플밍 - 11. 사전 합반 프로젝트 11일차(7/3)

이형원·2025년 7월 3일
0

XR플밍

목록 보기
123/215

0.들어가기에 앞서
작업물 합치기 단계를 계속 진행하고 있다. 슬슬 UI 연결과 게임매니저 데이터 연동 등의 작업을 하고 있다.

1. 금일 한 업무 정리, 구현 내용

  • UI의 전체적인 UI매니저 연동을 진행했고, 새로운 UI가 추가되었다. 수리 UI라는 것이 추가되었으며 이에 따른 새로운 레시피를 추가하여 연동하는 방식으로 운영할 생각이다.

수리 UI 버튼 누를 시 수리항목, 내용, 필요 에너지 출력하는 부분, 수리하는 부분까지는 구현완료

  • 게임매니저에 게임 데이터 영역에서 energy를 저장하기로 했다. 이에 따라 에너지의 증감에 대한 부분을 데이터로 저장하고, UI로 반영하는 작업을 완료했다.

  • 아이템 확률 계산에 대한 의사코드만 우선 작성

2. 문제의 발생과 해결 과정

오늘은 특별한 문제가 발생했다기 보다는 코드를 연결하는 과정에서 서로간 코드를 확인하고, 해석하는 과정이 있었다. 그 중에서 팀장님이 구현한 UI매니저에 대한 걸 다뤄보고자 한다.

2.1 IngameUIManager

내가 직접 작성한 코드는 아니다 보니, 전체적인 작동 방식의 흐름만 다뤄보려고 한다.

  1. UIBase라는 클래스를 만들고, UI가 나타나고 꺼지는 것만 Virtual로 구현한다.
  2. IngameUIManager로 관리하는 UI는 모두 UIBase를 달아주고, 그 목록을 enum으로 등록하고 배열에 해당 UI를 등록한다.
  3. 해당 UIBase를 담는 Stack을 생성하고, Push를 하거나 Pop을 하는 방식을 구현한 다음 인게임 내의 상호작용에서 UIBase enum을 키워드로 Push와 Pop을 하는 방식으로 유연한 UI 방식을 구현했다.

이와 같은 방식으로 겹겹이 뜨는 UI도 구현 가능하고, 단순한 한 겹의 UI도 구현 가능하다.

여기서 나의 경우에 크래프팅 UI에서 특정 버튼을 누르면 베이스캠프 수리라는 UI로 한 단계 더 들어가는 UI를 구현하는 경우, 아래와 같이 키워드를 설정하고 버튼의 OnClick 이벤트로 등록한다.

  • UI에 한 단계 더 들어가는 경우
public void GoToRepairMenu()
{
    GameManager.Instance.InGameUIManager.ShowUI(UIType.Repair);
}
  • 해당 UI에서 이전 UI로 돌아가는 경우
public void GoBackToCraftUI()
{
    GameManager.Instance.InGameUIManager.HideUI();
}

늘 UI매니저에 대해서 어떻게 생성해야 할지 고민이 많았는데, 이와 같은 방식으로 구현하는 것에 참신함을 느꼈다.

2.2 아이템 확률 계산에 대한 의사코드 작성

아이템 확률 계산 및 등장에 관해 어떻게 해야 할지 고민이 많았다. 하지만 의외로 번뜩이는 생각으로 현재 의사코드로 내용을 임시로 구현했다.

using System.Collections.Generic;
using UnityEngine;

enum BoxTier { Tier1, Tier2, Tier3 }

public class BoxItemRandomSystem : MonoBehaviour
{
    [SerializeField] BoxTier _tier;
    [SerializeField] BoxSystem _data;

    [Header("Item - Normal")]
    [SerializeField] ItemSO[] _itemA_Items1;

    [Header("Item - Rare")]
    [SerializeField] ItemSO[] _itemA_Items2;

    [Header("Item - Unique")]
    [SerializeField] ItemSO[] _itemA_Items3;

    [Header("Item - Legendary")]
    [SerializeField] ItemSO[] _itemA_Items4;

    [Header("Journal - List")]
    [SerializeField] CollectionSO[] _itemB_Journal;

    [Header("Food")]
    [SerializeField] ItemSO[] _itemC_Food;

    [Header("Collectible - List")]
    [SerializeField] CollectionSO[] _itemD_Collection;

    private WeightedRandom<ItemSO> _weightedRandomA = new WeightedRandom<ItemSO>();
    private WeightedRandom<CollectionSO> _weightedRandomD = new WeightedRandom<CollectionSO>();
    
    private Stack<CollectionSO> _journalStack = new Stack<CollectionSO>();

    private void Awake()
    {
        // Item A init
        switch (_tier)
        {
            case BoxTier.Tier1: ItemAInit(300, 145, 50, 10);
                break;
            case BoxTier.Tier2: ItemAInit(150, 200, 100, 100);
                break;
            case BoxTier.Tier3: ItemAInit(50, 125, 200, 250);
                break;
        }
        // Item B init
        ItemBInit();

        // Item C init
        ItemCInit();

        // Item D init
        ItemDInit();
    }

    private void OnEnable()
    {
        ItemAddToBox();
    }

    private void OnDisable()
    {
        _data.RemoveAllItem();
    }

    private void ItemAddToBox()
    {
        ItemASelect();
        ItemBSelect();
        ItemCSelect();
        ItemDSelect();
    }

    /// <summary>
    /// Select Item A.
    /// Item A is definitely collecitible, and the number of A item is 4.
    /// </summary>
    private void ItemASelect()
    {
        if (_weightedRandomA.GetList() == null) return;

        for (int i = 0; i < 4; i++)
        {
            _data.AddItemToBoxSlot(_weightedRandomA.GetRandomItem());
        }
    }

    /// <summary>
    /// Set Item A weightRandom. (Materials)
    /// </summary>
    /// <param name="normal"></param>
    /// <param name="rare"></param>
    /// <param name="unique"></param>
    /// <param name="legendary"></param>
    private void ItemAInit(int normal, int rare, int unique, int legendary)
    {
        for(int i = 0; i < _itemA_Items1.Length; i++)
        {
            _weightedRandomA.Add(_itemA_Items1[i], normal);
        }
        for(int i = 0; i < _itemA_Items2.Length; i++)
        {
            _weightedRandomA.Add(_itemA_Items2[i], rare);
        }
        for(int i = 0; i < _itemA_Items3.Length; i++)
        {
            _weightedRandomA.Add(_itemA_Items3[i], unique);
        }
        for (int i = 0; i < _itemA_Items4.Length; i++)
        {
            _weightedRandomA.Add(_itemA_Items4[i], legendary);
        }
    }

    private void ItemBSelect()
    {
        if(_journalStack.Count == 0) return;

        // if(GameManager.Instance.DayNightManager.CurrentDay == key)
        //{
            // float randomNum = Random.Range(0.0f, value);
            // if randonNum > value return;
            // 콜렉션을 추가하는 함수(_journalStack.Pop());
        //}
    }

    /// <summary>
    /// Set Item B weightRandom. (Diary)
    /// </summary>
    private void ItemBInit()
    {
        for(int i = 0; i < _itemB_Journal.Length; i++)
        {
            _journalStack.Push(_itemB_Journal[i]);
        }

        // 현재 일지 아이템이 없고 확률 테이블을 만들 방법에 대해 고민하고 있어
        // 의사 코드로 먼저 적습니다.

        // 일차 = key, 확률 = value로 된 dictionary를 생성하고, 데이터 테이블의 정보를 SO로 만들어놓는다.
        Dictionary<int, float> keyValuePairs = new Dictionary<int, float>();

        // 딕셔너리 정보를 전부 저장
    }

    private void ItemCSelect()
    {
        // if(GameManager.Instance.DayNightManager.CurrentDay == key)
        //{
        // float randomNum = Random.Range(0.0f, value);
        // if randonNum > value return;
        // _data.AddItemToBoxSlot(_itemC_Food);
        //}
    }

    /// <summary>
    /// Set Item C weightRandom. (Food)
    /// </summary>
    private void ItemCInit()
    {
        // 현재 일지 아이템이 없고 확률 테이블을 만들 방법에 대해 고민하고 있어
        // 의사 코드로 먼저 적습니다.

        // 일차 = key, 확률 = value로 된 dictionary를 생성하고, 데이터 테이블의 정보를 SO로 만들어놓는다.
        Dictionary<int, float> keyValuePairs = new Dictionary<int, float>();

        // 딕셔너리 정보를 전부 저장
    }

    /// <summary>
    /// Select Item D. (Collective)
    /// </summary>
    private void ItemDSelect()
    {
        if (_weightedRandomD.GetList() == null) return;

        float randomNum = Random.Range(0.0f, 1.0f);

        if (randomNum > 0.7f) return;

        // need to make collection item slot in box inventory.
        //_data.AddItemToBoxSlot(_weightedRandomD.GetRandomItem());
    }

    /// <summary>
    /// Set Item D weightRandom. (Collective)
    /// </summary>
    private void ItemDInit()
    {
        for(int i = 0; i < _itemD_Collection.Length; i++)
        {
            _weightedRandomD.Add(_itemD_Collection[i], 1);
        }
    }
}

3. 개선점 및 과제

3.1 수리아이템 수리완료 판정에 대한 부분

해당 부분에 대해 고민하는 방식은, GameManager의 GameData에 bool[] 값을 저장한 다음, 해당 조건에 따라 수리 버튼 활성화/비활성화 하는 방식을 생각해보고 있다.

3.2 아이템 확률 데이터 가져오기 - Dictionary로 가져오면?

아이템 확률이 일자별로 매일 확률이 달라지는 방식이며, 이를 상자 확률에 반영하기 위해서는 생각할 수 있는 방법이 해당 데이터를 Dictinary 형태로 생성하고 키(일자)를 바탕으로 값(확률)을 가져와서 확률 계산을 하는 방식을 생각해보고 있다.

3.3 아이템 확률 반영 - B, C 아이템 확률 반영

3.2의 방식을 바탕으로 B와 C의 아이템 확률 뽑기를 구현한다.

3.4 수집형 아이템 슬롯에 관하여 - 팀원이랑 같이 얘기

수집품이라는 아이템 항목을 따로 저장할 슬롯이 필요하므로, 수집품을 구현하고 있는 다른 팀원에게 슬롯을 구현한 내용이 있는지 확인하고 수집품을 담을 슬롯을 구현해 B와 D를 구현한다.

3.5 수집형 아이템 유형 요청 - 기획팀

수집품 아이템에는 일지와 수집품이 있는데, 둘 다 수집품으로 분류하고 유형을 나누는 편이 좋다고 생각한다. 이 부분에 대해 기획팀에 요청하려고 한다.

3.6 제작 중 이탈 방지

아이템 제작 중 이탈하면 아이템 제작이 불가능하거나, 제작중에 다른 아이템 버튼을 누르면 아이템이 결과물이 바뀌거나 제작 버튼 연발하면서 에너지를 낭비하는 방식에 대한 개선이 필요하다.

profile
게임 만들러 코딩 공부중

0개의 댓글