XR플밍 - 12. UnityEngine3D Reactive 프로그래밍 - 기업협약 프로젝트 33일차 (10/1)

이형원·3일 전
0

XR플밍

목록 보기
212/215

1. 금일 한 업무 정리

  • 마법석 가챠 시스템 구현
    • 마법석 확률 SO 제작
    • 마법석 확률 시스템적 반영(다만 마법석 자체는 UI 요소만 구성하고 아직 실제 데이터 반영x)
    • 마법석 가챠 UI적 반영
    • 마법석 광고 가챠, 일일 무료 가챠 쿨타임 계산

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

2.1 마법석 가챠 확률과 SO 데이터 구성방법

마법석의 확률은 다음과 같다.

마법석을 뽑았을 경우 조각에 대한 가중치 확률도 들어간다.

이를 구성하기 위해서 다음과 같이 SO를 구성했다.

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

[CreateAssetMenu(fileName = "MagicStoneGachaSO", menuName ="Data/MagicStoneGachaSO")]
public class MagicStoneGachaSO : ScriptableObject
{
    public List<RewardEntry> rewards;
}

[Serializable]
public class RewardEntry
{
    public MagicStoneRewardType rewardType;
    public float RewardProbability;
    public List<PieceEntry> PieceEntries;
}

[Serializable]
public class PieceEntry
{
    public int PieceNum;
    public float PieceProbability;
}

public enum MagicStoneRewardType
{
    MagicStone,
    Gold500,
    Gold1000,
    Gold10000,
    Dia50,
    Dia100,
    Dia1000
}

2.2 가챠의 시스템적 효과 적용

  • RandomGachaSystem에는 이와 같이 반영하였다.
#region MagicStoneGachaButton

#region AdButton

/// <summary>
/// 광고 버튼 클릭 이벤트
/// </summary>
private async void StoneAdButtonClick()
{
    // 일일 광고 가챠가 가능할 경우 실행
    if (await DailyStoneAdGacha()) return;

    // 광고 가챠가 불가능할 경우 경고 팝업
    if (PopupManager.Instance != null)
    {
        PopupManager.instance.ShowPopup("일일 광고 가챠를 전부 사용하였습니다.");
    }
}

/// <summary>
/// 광고 가챠의 가능 여부를 판별하고 가능할 시 광고 시청 후 가챠를 진행
/// </summary>
/// <returns></returns>
private async Task<bool> DailyStoneAdGacha()
{
    // 광고 가챠가 가능할 때
    if (TimeManager.Instance.CanObtainAdGachaReward(GachaType.Stone))
    {
        // 광고 가챠 쿨타임 업데이트
        TimeManager.Instance.UpdateAdGachaResetTimeInfo(GachaType.Stone);
        // 1회 뽑기 진행
        await MagicStoneSelect(1);
        TimeManager.Instance.OnDailyGachaInfoChanged?.Invoke();
        return true;
    }

    return false;
}

#endregion

#region DailyButton

/// <summary>
/// 1회 뽑기 버튼 클릭 이벤트
/// </summary>
private async void StoneOneButtonClick()
{
    // 일일 무료 뽑기가 가능할 때 해당 뽑기 우선 진행
    if (await DailyStoneFreeGacha()) return;

    ConsumeGoodsButtonClick(GachaType.Stone, 1);
}

/// <summary>
/// 일일 뽑기의 가능 여부를 판별하고, 가능할 시 횟수를 소모하고 진행
/// </summary>
/// <returns></returns>
private async Task<bool> DailyStoneFreeGacha()
{
    // 일일 무료 뽑기가 가능할 때
    if (TimeManager.Instance.CanObtainedFreeGachaReward(GachaType.Stone))
    {
        // 1회 뽑기 진행
        await MagicStoneSelect(1);
        // 일일 무료 뽑기 쿨타임 업데이트
        TimeManager.Instance.UpdateDailyFreeGachaResetTimeInfo(GachaType.Stone);
        return true;
    }
    return false;
}

#endregion

...

private async Task MagicStoneSelect(int number)
{
    if (_magicStoneRewardRandom.GetList() == null) MagicStoneRandomInit(_stoneProb);

    for (int i = 0; i < number; i++)
    {
        MagicStoneRewardType type = _magicStoneRewardRandom.GetRandomItem();

        switch (type)
        {
            case MagicStoneRewardType.MagicStone:
                MagicStoneSelection(i);
                break;
            case MagicStoneRewardType.Gold500:
                await DBManager.Instance.AddGoldAsync(500);
                _resultUI.StoneGachaUpdate(type, i, 500.ToString());
                Debug.Log("골드 500");
                break;
            case MagicStoneRewardType.Gold1000:
                await DBManager.Instance.AddGoldAsync(1000);
                _resultUI.StoneGachaUpdate(type, i, 1000.ToString());
                Debug.Log("골드 1000");
                break;
            case MagicStoneRewardType.Gold10000:
                await DBManager.Instance.AddGoldAsync(10000);
                _resultUI.StoneGachaUpdate(type, i, 10000.ToString());
                Debug.Log("골드 10000");
                break;
            case MagicStoneRewardType.Dia50:
                await DBManager.Instance.AddDiamondAsync(50);
                _resultUI.StoneGachaUpdate(type, i, 50.ToString());
                Debug.Log("다이아 50");
                break;
            case MagicStoneRewardType.Dia100:
                await DBManager.Instance.AddDiamondAsync(100);
                _resultUI.StoneGachaUpdate(type, i, 100.ToString());
                Debug.Log("다이아 100");
                break;
            case MagicStoneRewardType.Dia1000:
                await DBManager.Instance.AddDiamondAsync(1000);
                _resultUI.StoneGachaUpdate(type, i, 1000.ToString());
                Debug.Log("다이아 1000");
                break;
        }
    }

    _resultUI.gameObject.SetActive(true);
}

private void MagicStoneSelection(int index)
{
    int pickedStone = UnityEngine.Random.Range(0, _stoneDatabase.MagicStoneDatas.Count);
    MagicStoneData data = _stoneDatabase.MagicStoneDatas[pickedStone];

    int pieces = _magicStonePieceRandom.GetRandomItem();

    _resultUI.StoneGachaUpdate(data, index, pieces.ToString());
}

가챠 종류에 따라 편하게 선택할 수 있도록 TimeManager를 다음과 같이 변경하였다.

using System;
using UnityEngine;

public enum GachaType
{
    Char,
    Stone,
}

[Serializable]
public class RewardInfo
{
    public long dateTicks; // DateTime을 Ticks로 저장
    public int state;      // 획득 여부 또는 스택 수

    /// <summary>
    /// 리워드 정보를 생성함.
    /// </summary>
    /// <param name="dateTicks">시간을 Long으로 변환</param>
    /// <param name="state">획득여부 혹은 Stack수</param>
    public RewardInfo(long dateTicks, int state)
    {
        this.dateTicks = dateTicks;
        this.state = state;
    }

    /// <summary>
    /// 시간 정보를 로드
    /// </summary>
    /// <returns></returns>
    public DateTime GetDateTime()
    {
        return new DateTime(dateTicks);
    }

    /// <summary>
    /// 시간 정보를 저장
    /// </summary>
    /// <param name="dateTime"></param>
    public void SetDateTime(DateTime dateTime)
    {
        dateTicks = dateTime.Ticks;
    }
}

public class TimeManager : MonoBehaviour
{
    #region Singleton
    public static TimeManager Instance { get; private set; }
    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
        Init();
    }
    #endregion

    [Header("Reference")]
    [SerializeField] private GoogleAdMob _adMob;

    // 캐릭터 가챠
    private RewardInfo _dailyCharFreeGachaRewardInfo;
    public RewardInfo DailyCharFreeGachaRewardInfo => _dailyCharFreeGachaRewardInfo;

    private RewardInfo _dailyCharAdGachaRewardInfo;
    public RewardInfo DailyCharAdGachaRewardInfo => _dailyCharAdGachaRewardInfo;

    // 마법석 가챠
    private RewardInfo _dailyStoneFreeGachaRewardInfo;
    public RewardInfo DailyStoneFreeGachaRewardInfo => _dailyStoneFreeGachaRewardInfo;

    private RewardInfo _dailyStoneAdGachaRewardInfo;
    public RewardInfo DailyStoneAdGachaRewardInfo => _dailyStoneAdGachaRewardInfo;

    // 상점 
    private DateTime _dailyShopResetTime;

    public Action OnDailyGachaInfoChanged;

    private void Init()
    {
        LoadDailyFreeGachaResetTimeInfo();
        LoadAdGachaResetTimeInfo();
    }

    #region Data Load & Save

    #region 일일 초기화(가챠 - 오전 6시 초기화)

    /// <summary>
    /// 일일 가챠 초기화 시간 및 횟수를 캐싱하여 저장하고,
    /// 업데이트가 필요할 시 업데이트를 바로 진행.
    /// </summary>
    private void LoadDailyFreeGachaResetTimeInfo()
    {
        // TODO : DB에 저장된 [일일 무료 가챠] 시간 및 횟수 데이터를 가져와서 캐싱

        // 테스트용: 오늘 아침 6시, 가챠횟수 1회
        DateTime todayReset = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 6, 0, 0);
        _dailyCharFreeGachaRewardInfo = new RewardInfo(todayReset.Ticks, 1);
        _dailyStoneFreeGachaRewardInfo = new RewardInfo(todayReset.Ticks, 1);

        if (_dailyCharFreeGachaRewardInfo.state == 0 && IsDailyResetTime(_dailyCharFreeGachaRewardInfo.GetDateTime()))
        {
            _dailyCharFreeGachaRewardInfo.state = 1;

            // TODO : DB에 [일일 무료 가챠] 시간과 상태를 저장 
        }
        if(_dailyStoneFreeGachaRewardInfo.state == 0 && IsDailyResetTime(_dailyStoneFreeGachaRewardInfo.GetDateTime()))
        {
            _dailyStoneFreeGachaRewardInfo.state = 1;

            // TODO : DB에 [일일 무료 가챠] 시간과 상태를 저장
        }

        OnDailyGachaInfoChanged?.Invoke();
    }

    /// <summary>
    /// 일일 무료 가챠 시간을 업데이트 - 횟수 사용 시
    /// </summary>
    public void UpdateDailyFreeGachaResetTimeInfo(GachaType type)
    {
        DateTime now = DateTime.Now;
        DateTime todayReset = new DateTime(now.Year, now.Month, now.Day, 6, 0, 0);
        DateTime nextResetDate = (now.Hour < 6) ? todayReset : todayReset.AddDays(1);

        switch(type)
        {
            case GachaType.Char:
                _dailyCharFreeGachaRewardInfo.SetDateTime(nextResetDate);
                _dailyCharFreeGachaRewardInfo.state = 0;

                // TODO : DB에 [일일 무료 가챠] 시간과 상태를 저장
                break;
            case GachaType.Stone:
                _dailyStoneFreeGachaRewardInfo.SetDateTime(nextResetDate);
                _dailyStoneFreeGachaRewardInfo.state = 0;

                // TODO : DB에 [일일 무료 가챠] 시간과 상태를 저장
                break;
        }

        OnDailyGachaInfoChanged?.Invoke();
    }

    #endregion

    #region 12시간 초기화(가챠)

    /// <summary>
    /// 광고 가챠 시간 및 횟수를 캐싱하여 저장하고,
    /// 업데이트가 필요할 시 바로 진행.
    /// </summary>
    private void LoadAdGachaResetTimeInfo()
    {
        // TODO : DB에 저장된 [광고 가챠] 시간 및 횟수 데이터를 가져와서 캐싱

        // 테스트용 초기값: 11시간 전, 가챠 횟수 1회
        _dailyCharAdGachaRewardInfo = new RewardInfo(DateTime.Now.AddHours(-11).AddMinutes(-59).Ticks, 1);
        _dailyStoneAdGachaRewardInfo = new RewardInfo(DateTime.Now.AddHours(-11).AddMinutes(-59).Ticks, 1);

        if (_dailyCharAdGachaRewardInfo.state < 2 && IsDailyCharAdGachaResetTime(out int stack))
        {
            _dailyCharAdGachaRewardInfo.state += stack;
            if (_dailyCharAdGachaRewardInfo.state > 2) _dailyCharAdGachaRewardInfo.state = 2;

            // TODO : DB에 [광고 가챠] 시간과 상태를 저장
        }
        if(_dailyStoneAdGachaRewardInfo.state < 2 && IsDailyCharAdGachaResetTime(out int stack2))
        {
            _dailyStoneAdGachaRewardInfo.state += stack2;
            if (_dailyStoneAdGachaRewardInfo.state > 2) _dailyStoneAdGachaRewardInfo.state = 2;

            // TODO : DB에 [광고 가챠] 시간과 상태를 저장
        }

        OnDailyGachaInfoChanged?.Invoke();
    }

    /// <summary>
    /// 광고 가챠 횟수를 업데이트 - 횟수 사용 시
    /// </summary>
    public void UpdateAdGachaResetTimeInfo(GachaType type)
    {
        switch(type)
        {
            case GachaType.Char:
                UpdateCharAdGachaResetTimeInfo();
                break;
            case GachaType.Stone:
                UpdateStoneAdGachaResetTimeInfo();
                break;
        }
        OnDailyGachaInfoChanged?.Invoke();
    }

    private void UpdateCharAdGachaResetTimeInfo()
    {
        if (_dailyCharAdGachaRewardInfo.state > 0)
        {
            // 해당 부분은 RandomGachaSystem으로 옮기는 게 적절해 보임
            // 추후에 옮길 예정
            if (_adMob.IsReady)
            {
                _adMob.LoadedAd.Show();
            }
            _dailyCharAdGachaRewardInfo.state -= 1;

            // TODO : DB에 [광고 가챠] 시간과 상태를 저장

            Debug.Log($"광고 가챠 스택 감소: {_dailyCharAdGachaRewardInfo.state}, 마지막 갱신: {_dailyCharAdGachaRewardInfo.GetDateTime()}");
        }
    }

    private void UpdateStoneAdGachaResetTimeInfo()
    {
        if (_dailyStoneAdGachaRewardInfo.state > 0)
        {
            // 해당 부분은 RandomGachaSystem으로 옮기는 게 적절해 보임
            // 추후에 옮길 예정
            if (_adMob.IsReady)
            {
                _adMob.LoadedAd.Show();
            }
            _dailyStoneAdGachaRewardInfo.state -= 1;

            // TODO : DB에 [광고 가챠] 시간과 상태를 저장

            Debug.Log($"광고 가챠 스택 감소: {_dailyStoneAdGachaRewardInfo.state}, 마지막 갱신: {_dailyCharAdGachaRewardInfo.GetDateTime()}");
        }
    }

    #endregion

    #region 일일 초기화(상점)

    // 상점의 경우 일일 초기화가 진행되는 시점인지 bool 여부만 판정하면 되므로
    // 가챠확률 계산과는 로직을 다르게 설계했습니다.
    // 만약 일일 퀘스트 등의 다른 컨텐츠도 초기화 시간이 같을 경우 이 함수로 전부 처리 가능합니다.

    /// <summary>
    /// 초기화가 되는 시점인지 확인하고 정보를 로드
    /// </summary>
    /// <param name="resetTime"></param>
    /// <returns></returns>
    public bool LoadDailyShopResetTime(out DateTime resetTime)
    {
        // TODO : DB에 저장된 [상점 초기화] 시간 데이터를 가져와서 캐싱
        // _dailyShopResetTime = {상점 초기화 시간}

        bool isResetTime = SaveDailyShopResetTime(_dailyShopResetTime);
        resetTime = _dailyShopResetTime;

        return isResetTime;
    }

    /// <summary>
    /// 초기화가 되는 시점일 때 업데이트를 진행함
    /// </summary>
    /// <param name="lastTime"></param>
    /// <returns></returns>
    private bool SaveDailyShopResetTime(DateTime lastTime)
    {
        if (IsDailyResetTime(lastTime))
        {
            DateTime now = DateTime.Now;
            DateTime todayReset = new DateTime(now.Year, now.Month, now.Day, 6, 0, 0);

            if (now.Hour < 6)
            {
                _dailyShopResetTime = todayReset;
            }
            else
            {
                _dailyShopResetTime = todayReset.AddDays(1);
            }

            // TODO : DB에 [상점 초기화] 시간을 저장
            return true;
        }
        return false;
    }

    #endregion

    #endregion

    #region Obtain 판정

    #region 일일 가챠 가능 여부 판정

    /// <summary>
    /// 일일 가챠가 가능한 상태인지 판별함.
    /// </summary>
    /// <returns></returns>
    public bool CanObtainedFreeGachaReward(GachaType type)
    {
        bool canObtain = false;

        switch (type)
        {
            case GachaType.Char:
                canObtain = CanObtainFreeCharGachaReward();
                break;
            case GachaType.Stone:
                canObtain = CanObtainFreeStoneGachaReward();
                break;                
        }

        return canObtain;
    }

    private bool CanObtainFreeCharGachaReward()
    {
        if (_dailyCharFreeGachaRewardInfo.state == 1) return true;
        if (IsDailyResetTime(_dailyCharFreeGachaRewardInfo.GetDateTime()))
        {
            _dailyCharFreeGachaRewardInfo.state = 1;

            // TODO : DB에 [일일 무료 가챠] 시간과 상태를 저장

            OnDailyGachaInfoChanged?.Invoke();
            return true;
        }
        return false;
    }

    private bool CanObtainFreeStoneGachaReward()
    {
        if (_dailyStoneFreeGachaRewardInfo.state == 1) return true;
        if (IsDailyResetTime(_dailyStoneFreeGachaRewardInfo.GetDateTime()))
        {
            _dailyStoneFreeGachaRewardInfo.state = 1;

            // TODO : DB에 [일일 무료 가챠] 시간과 상태를 저장

            OnDailyGachaInfoChanged?.Invoke();
            return true;
        }
        return false;
    }

    /// <summary>
    /// 마지막 저장 시간을 기점으로 초기화가 되는 시점인지 확인하는 함수.
    /// 현재 시간이 마지막 저장 시간보다 미래면 true, 아니면 false.
    /// </summary>
    /// <param name="date"></param>
    /// <returns></returns>
    private bool IsDailyResetTime(DateTime date)
    {
        DateTime now = DateTime.Now;

        if (now.Year > date.Year && now.Hour >= date.Hour) return true;
        if (now.Year == date.Year && now.Month > date.Month && now.Hour >= date.Hour) return true;
        if (now.Year == date.Year && now.Month == date.Month && now.Day > date.Day && now.Hour >= date.Hour) return true;

        return false;
    }

    #endregion

    #region 12시간 광고 가챠 가능 여부 판정

    /// <summary>
    /// 광고 가챠가 가능한 상태인지 판별함.
    /// </summary>
    /// <returns></returns>
    public bool CanObtainAdGachaReward(GachaType type)
    {
        bool canObtain = false;

        switch (type)
        {
            case GachaType.Char:
                canObtain = CanObtainCharAdGachaReward();
                break;
            case GachaType.Stone:
                canObtain = CanObtainStoneAdGachaReward();
                break;
        }

        return canObtain;
    }

    private bool CanObtainCharAdGachaReward()
    {
        if (IsDailyCharAdGachaResetTime(out int stack))
        {
            _dailyCharAdGachaRewardInfo.state += stack;
            if (_dailyCharAdGachaRewardInfo.state > 2) _dailyCharAdGachaRewardInfo.state = 2;

            // TODO : DB에 시간과 상태를 저장

            OnDailyGachaInfoChanged?.Invoke();
            return true;
        }

        if (_dailyCharAdGachaRewardInfo.state >= 1) return true;

        return false;
    }

    private bool CanObtainStoneAdGachaReward()
    {
        if (IsDailyStoneAdGachaResetTime(out int stack))
        {
            _dailyStoneAdGachaRewardInfo.state += stack;
            if (_dailyStoneAdGachaRewardInfo.state > 2) _dailyStoneAdGachaRewardInfo.state = 2;

            // TODO : DB에 시간과 상태를 저장

            OnDailyGachaInfoChanged?.Invoke();
            return true;
        }

        if (_dailyStoneAdGachaRewardInfo.state >= 1) return true;

        return false;
    }

    /// <summary>
    /// 12시간 단위 누적 스택 계산
    /// </summary>
    private bool IsDailyCharAdGachaResetTime(out int stack)
    {
        DateTime now = DateTime.Now;
        DateTime lastTime = _dailyCharAdGachaRewardInfo.GetDateTime();
        TimeSpan difference = now - lastTime;

        stack = 0;

        if (difference.TotalHours >= 12)
        {
            int stackCount = (int)(difference.TotalHours / 12);
            stack = Mathf.Min(stackCount, 2); // 최대 2 스택

            // 마지막 갱신 시간 이동
            _dailyCharAdGachaRewardInfo.SetDateTime(lastTime.AddHours(12 * stack));

            Debug.Log($"스택 증가: {stack}, 새로운 마지막 갱신 시간: {_dailyCharAdGachaRewardInfo.GetDateTime()}");
            return true;
        }

        return false;
    }

    private bool IsDailyStoneAdGachaResetTime(out int stack)
    {
        DateTime now = DateTime.Now;
        DateTime lastTime = _dailyStoneAdGachaRewardInfo.GetDateTime();
        TimeSpan difference = now - lastTime;

        stack = 0;

        if (difference.TotalHours >= 12)
        {
            int stackCount = (int)(difference.TotalHours / 12);
            stack = Mathf.Min(stackCount, 2); // 최대 2 스택

            // 마지막 갱신 시간 이동
            _dailyStoneAdGachaRewardInfo.SetDateTime(lastTime.AddHours(12 * stack));

            Debug.Log($"스택 증가: {stack}, 새로운 마지막 갱신 시간: {_dailyStoneAdGachaRewardInfo.GetDateTime()}");
            return true;
        }

        return false;
    }

    #endregion

    #endregion
}

2.3 UI로의 반영

UI적 반영을 위해 다음과 같이 구성하였다.

using System;
using System.Collections;
using System.Data;
using System.Net.Http.Headers;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class GachaUIController : MonoBehaviour
{
    [Header("CharAdButtonUI")]
    [SerializeField] private Image[] _charAdImages;
    [SerializeField] private GameObject _charAdCooltimeImage;
    [SerializeField] private TMP_Text _charAdCooltimeText;

    [Header("CharOneButtonUI")]
    [SerializeField] private TMP_Text _charOneText;
    [SerializeField] private GameObject _charFreeGacha;
    [SerializeField] private GameObject _charConsumeGacha;
    [SerializeField] private GameObject _charDailyCooltimeImage;
    [SerializeField] private TMP_Text _charDailyCooltimeText;

    [Header("StoneAdButtonUI")]
    [SerializeField] private Image[] _stoneAdImages;
    [SerializeField] private GameObject _stoneAdCooltimeImage;
    [SerializeField] private TMP_Text _stoneAdCooltimeText;

    [Header("StoneOneButtonUI")]
    [SerializeField] private TMP_Text _stoneOneText;
    [SerializeField] private GameObject _stoneFreeGacha;
    [SerializeField] private GameObject _stoneConsumeGacha;
    [SerializeField] private GameObject _stoneDailyCooltimeImage;
    [SerializeField] private TMP_Text _stoneDailyCooltimeText;

    private Coroutine _adCooltimeTimer;

    private Coroutine _dailyCooltimeTimer;

    private void Start()
    {
        UpdateUI();
    }

    #region Event

    private void OnEnable()
    {
        if (TimeManager.Instance != null)
            TimeManager.Instance.OnDailyGachaInfoChanged += UpdateUI;
        StartAdTimer();
        StartDailyTimer();
    }

    private void OnDisable()
    {
        TimeManager.Instance.OnDailyGachaInfoChanged -= UpdateUI;
        StopAdTimer();
        StopDailyTimer();
    }

    #endregion

    #region AdButtonTimer

    private void StartAdTimer()
    {
        if (_adCooltimeTimer != null)
        {
            StopCoroutine(_adCooltimeTimer);
        }
        _adCooltimeTimer = StartCoroutine(AdCooltimeCoroutine());
    }

    private void StopAdTimer()
    {
        if (_adCooltimeTimer != null)
        {
            StopCoroutine(_adCooltimeTimer);
            _adCooltimeTimer = null;
        }
    }

    #endregion

    #region DailyButtonTimer

    private void StartDailyTimer()
    {
        if (_dailyCooltimeTimer != null)
        {
            StopCoroutine(_dailyCooltimeTimer);
        }
        _dailyCooltimeTimer = StartCoroutine(DailyCooltimeCoroutine());
    }

    private void StopDailyTimer()
    {
        if (_dailyCooltimeTimer != null)
        {
            StopCoroutine(_dailyCooltimeTimer);
            _dailyCooltimeTimer = null;
        }
    }

    #endregion

    #region UIUpdate

    private void UpdateUI()
    {
        UpdateAdButton();
        UpdateOneButton();
    }

    private void UpdateAdButton()
    {
        if (TimeManager.Instance != null)
        {
            switch (TimeManager.Instance.DailyCharAdGachaRewardInfo.state)
            {
                case 2:
                    _charAdImages[0].color = Color.white;
                    _charAdImages[1].color = Color.white;
                    _charAdCooltimeImage.gameObject.SetActive(false);
                    break;

                case 1:
                    _charAdImages[0].color = Color.grey;
                    _charAdImages[1].color = Color.white;
                    _charAdCooltimeImage.gameObject.SetActive(true);
                    break;
                case 0:
                    _charAdImages[0].color = Color.grey;
                    _charAdImages[1].color = Color.grey;
                    _charAdCooltimeImage.gameObject.SetActive(true);
                    break;
                default:
                    break;
            }

            switch(TimeManager.Instance.DailyStoneAdGachaRewardInfo.state)
            {
                case 2:
                    _stoneAdImages[0].color = Color.white;
                    _stoneAdImages[1].color = Color.white;
                    _stoneAdCooltimeImage.gameObject.SetActive(false);
                    break;

                case 1:
                    _stoneAdImages[0].color = Color.grey;
                    _stoneAdImages[1].color = Color.white;
                    _stoneAdCooltimeImage.gameObject.SetActive(true);
                    break;
                case 0:
                    _stoneAdImages[0].color = Color.grey;
                    _stoneAdImages[1].color = Color.grey;
                    _stoneAdCooltimeImage.gameObject.SetActive(true);
                    break;
                default:
                    break;
            }
        }
    }

    private void UpdateOneButton()
    {
        if (TimeManager.Instance != null)
        {
            if (TimeManager.Instance.DailyCharFreeGachaRewardInfo.state == 1)
            {
                _charFreeGacha.SetActive(true);
                _charConsumeGacha.SetActive(false);
                _charDailyCooltimeImage.gameObject.SetActive(false);
                _charOneText.text = "일일 무료";
            }
            else
            {
                _charFreeGacha.SetActive(false);
                _charConsumeGacha.SetActive(true);
                _charDailyCooltimeImage.gameObject.SetActive(true);
                _charOneText.text = "1회 뽑기";
            }

            if (TimeManager.Instance.DailyStoneFreeGachaRewardInfo.state == 1)
            {
                _stoneFreeGacha.SetActive(true);
                _stoneConsumeGacha.SetActive(false);
                _stoneDailyCooltimeImage.gameObject.SetActive(false);
                _stoneOneText.text = "일일 무료";
            }
            else
            {
                _stoneFreeGacha.SetActive(false);
                _stoneConsumeGacha.SetActive(true);
                _stoneDailyCooltimeImage.gameObject.SetActive(true);
                _stoneOneText.text = "1회 뽑기";
            }
        }
    }

    #endregion

    private IEnumerator AdCooltimeCoroutine()
    {
        while (true)
        {
            if (TimeManager.Instance != null)
            {
                TimeManager.Instance.CanObtainAdGachaReward(GachaType.Char);
                TimeManager.Instance.CanObtainAdGachaReward(GachaType.Stone);
                
                DateTime now = DateTime.Now;

                DateTime charLastTime = TimeManager.Instance.DailyCharAdGachaRewardInfo.GetDateTime();
                TimeSpan charCooltime = charLastTime.AddHours(12) - now;
                _charAdCooltimeText.text = $"다음 초기화 : {charCooltime.Hours}시간 {charCooltime.Minutes}분";

                DateTime stoneLastTime = TimeManager.Instance.DailyStoneAdGachaRewardInfo.GetDateTime();
                TimeSpan stoneCooltime = stoneLastTime.AddHours(12) - now;
                _stoneAdCooltimeText.text = $"다음 초기화 : {stoneCooltime.Hours}시간 {stoneCooltime.Minutes}분";
            }

            UpdateAdButton();

            yield return new WaitForSeconds(1);
        }
    }

    private IEnumerator DailyCooltimeCoroutine()
    {
        while (true)
        {
            if (TimeManager.Instance != null)
            {
                TimeManager.Instance.CanObtainedFreeGachaReward(GachaType.Char);
                TimeManager.Instance.CanObtainedFreeGachaReward(GachaType.Stone);

                DateTime now = DateTime.Now;
                DateTime charNextdate = TimeManager.Instance.DailyCharFreeGachaRewardInfo.GetDateTime();
                TimeSpan charCooltime = charNextdate - now;
                _charDailyCooltimeText.text = $"다음 초기화 : {charCooltime.Hours}시간 {charCooltime.Minutes}분";

                DateTime stoneNextdate = TimeManager.Instance.DailyStoneFreeGachaRewardInfo.GetDateTime();
                TimeSpan stoneCooltime = stoneNextdate - now;
                _stoneDailyCooltimeText.text = $"다음 초기화 : {stoneCooltime.Hours}시간 {stoneCooltime.Minutes}분";
            }

            UpdateOneButton();

            yield return new WaitForSeconds(1);
        }
    }
}

가챠 결과에 대한 각 슬롯의 UI 표시는 다음과 같이 구성했다.

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class GachaResultUI : MonoBehaviour, IPointerClickHandler
{
    [Header("Prefab")]
    [SerializeField] private GameObject _gachaResultSlotUI;
    [Header("UI Reference")]
    [SerializeField] private Transform _content;
    [Header("Icon Reference")]
    [SerializeField] private Sprite _coinImage;
    [SerializeField] private Sprite _diaImage;
    [Header("Slots")]
    [SerializeField] private Image[] _gachaResultImages;

    private GameObject[] _slots = new GameObject[10];
    
    public void HeroGachaUpdate(UnitData data, int index, string amount)
    {
        if (_slots[index] == null)
        {
            _slots[index] = Instantiate(_gachaResultSlotUI, _content);
        }
        GachaResultUISlot slot = _slots[index].GetComponent<GachaResultUISlot>();
        slot.UpdateUI(data.Icon, amount);
    }

    public void StoneGachaUpdate(MagicStoneData data, int index, string amount)
    {
        if (_slots[index] == null)
        {
            _slots[index] = Instantiate(_gachaResultSlotUI, _content);
        }
        GachaResultUISlot slot = _slots[index].GetComponent<GachaResultUISlot>();
        slot.UpdateUI(data.Icon, amount);
    }

    public void StoneGachaUpdate(MagicStoneRewardType type, int index, string amount)
    {
        if (_slots[index] == null)
        {
            _slots[index] = Instantiate(_gachaResultSlotUI, _content);
        }
        GachaResultUISlot slot = _slots[index].GetComponent<GachaResultUISlot>();
        switch(type)
        {
            case MagicStoneRewardType.Gold500:
            case MagicStoneRewardType.Gold1000:
            case MagicStoneRewardType.Gold10000:
                slot.UpdateUI(_coinImage, amount);            
                break;
            case MagicStoneRewardType.Dia50:
            case MagicStoneRewardType.Dia100:
            case MagicStoneRewardType.Dia1000:
                slot.UpdateUI(_diaImage, amount);
                break;
        }        
    }

    // 비활성화와 동시에 슬롯을 한개만 남겨두고 전부 파괴(Grid UI를 위해서 임시 처리)
    // 오브젝트 풀 반영할 수 있을 것 같습니다 -> 추후 풀 반영
    private void OnDisable()
    {
        if (_slots.Length <= 1) return;
        else
        {
            for (int i = 1; i < _slots.Length; i++)
            {
                Destroy(_slots[i]);
            }
        }
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        gameObject.SetActive(false);
    }
}

3. 개선점 및 과제

3.1 마법석 강화 시스템 구현

3.2 가챠 연출 구현

3.3 UI 폴리싱

3.4 리팩토링

profile
게임 만들러 코딩 공부중

0개의 댓글