Print Friendly and PDF

유니티/게임 제작

[디펜스 게임] 우편 UI 및 기능

나는야 개발자 2025. 7. 16. 23:47
반응형

이전 시간에 뒤끝 서버를 이용해 로그인, 데이터 저장, 로그를 추가 하였으니 이번 시간에 우편 기능을 추가해보려 합니다.

 

우편 기능의 경우 전체적으로 아이템을 전달하거나 아니면 특정 상황에 대한 보상이나 특정 유저에 대한 보상을 지급할때 매우 유용한 기능중 하나라 생각합니다.

 

우편 UI 래퍼런스

 

 

플로우 차트

 

우편 추가

- 차트를 이용해서 아이템 정보를 추가한 후 우편 발송

 

 

우편 UI 제작

 

뒤끝 우편 코드 작성

using System;
using System.Collections.Generic;
using UnityEngine;
using LitJson;
using Cysharp.Threading.Tasks;
using BackEnd;

public class BackEndPOST
{
    /// <summary>
    /// 우편 아이템 정보를 담는 구조체
    /// </summary>
    [System.Serializable]
    public struct UPostChartItem
    {
        public string chartFileName;
        public int itemID;
        public string itemName;
        public string hpPower;
        public int itemCount;
        public string chartName;
    }

    /// <summary>
    /// 우편 정보를 담는 구조체
    /// </summary>
    [System.Serializable]
    public struct UPostItem
    {
        public PostType postType;
        public string title;
        public string content;
        public DateTime expirationDate;
        public DateTime reservationDate;
        public DateTime sentDate;
        public string nickname;
        public string inDate;
        public string author; // 관리자 우편만 존재
        public string rankType; // 랭킹 우편만 존재
        public List<UPostChartItem> items;

        /// <summary>
        /// 만료일까지 남은 시간 (TimeSpan)
        /// </summary>
        public TimeSpan TimeRemaining => expirationDate - DateTime.UtcNow;

        /// <summary>
        /// 우편이 만료되었는지 확인
        /// </summary>
        public bool IsExpired => DateTime.UtcNow >= expirationDate;

        /// <summary>
        /// 만료일까지 남은 시간을 문자열로 반환
        /// </summary>
        public string TimeRemainingString
        {
            get
            {
                if (IsExpired)
                {
                    return "Expired";
                }

                var timeSpan = TimeRemaining;

                if (timeSpan.TotalDays >= 1)
                {
                    return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours}h {timeSpan.Minutes}m";
                }
                else if (timeSpan.TotalHours >= 1)
                {
                    return $"{timeSpan.Hours}h {timeSpan.Minutes}m";
                }
                else if (timeSpan.TotalMinutes >= 1)
                {
                    return $"{timeSpan.Minutes}m {timeSpan.Seconds}s";
                }
                else
                {
                    return $"{timeSpan.Seconds}s";
                }
            }
        }

                /// <summary>
        /// 만료일까지 남은 시간을 간단한 형식으로 반환
        /// </summary>
        public string TimeRemainingSimple
        {
            get
            {
                if (IsExpired)
                {
                    return "Expired";
                }

                var timeSpan = TimeRemaining;
                
                if (timeSpan.TotalDays >= 1)
                {
                    return $"{(int)timeSpan.TotalDays}d";
                }
                else if (timeSpan.TotalHours >= 1)
                {
                    return $"{timeSpan.Hours}h";
                }
                else if (timeSpan.TotalMinutes >= 1)
                {
                    return $"{timeSpan.Minutes}m";
                }
                else
                {
                    return $"{timeSpan.Seconds}s";
                }
            }
        }

        /// <summary>
        /// 우편 아이템들을 RewardManager에서 처리할 수 있는 형태로 변환하여 반환
        /// </summary>
        /// <returns>보상 아이템 리스트</returns>
        public List<St_RewardItemList> GetRewardList()
        {
            var rewardList = new List<St_RewardItemList>();
            
            if (items == null || items.Count == 0)
            {
                return rewardList;
            }
            
            foreach (var item in items)
            {
                var rewardItem = new St_RewardItemList
                {
                    _itemid = item.itemID,
                    _itemvalue = item.itemCount
                };
                rewardList.Add(rewardItem);
            }
            
            return rewardList;
        }

        
    }

    /// <summary>
    /// 관리자 우편 리스트를 가져오는 메서드 (비동기)
    /// </summary>
    /// <param name="limit">불러올 우편 개수</param>
    /// <returns>우편 리스트</returns>
    public static async UniTask<List<UPostItem>> GetPOSTList(int limit)
    {
        // limit이 10 미만일 경우 10으로 고정
        if (limit < 10) limit = 10;
        if (limit > 100) limit = 100;

        var completionSource = new UniTaskCompletionSource<List<UPostItem>>();

        Backend.UPost.GetPostList(PostType.Admin, limit, bro =>
        {
            if (!bro.IsSuccess())
            {
                Debug.LogError($"관리자 우편 불러오기 실패: {bro.ToString()}");
                completionSource.TrySetResult(new List<UPostItem>());
                return;
            }

            List<UPostItem> postList = ParsePostList(bro, PostType.Admin);
            completionSource.TrySetResult(postList);
        });

        return await completionSource.Task;
    }

    /// <summary>
    /// 우편 하나를 수령하는 메서드 (비동기)
    /// </summary>
    /// <param name="postInDate">수령할 우편의 inDate</param>
    /// <returns>수령 성공 여부</returns>
    public static async UniTask<bool> RemovePost(string postInDate)
    {
        var completionSource = new UniTaskCompletionSource<bool>();

        Backend.UPost.ReceivePostItem(PostType.Admin, postInDate, bro =>
        {
            if (!bro.IsSuccess())
            {
                Debug.LogError($"우편 수령 실패: {bro.ToString()}");
                BackEndLog.WriteLog(LogType.POST, $"우편ID:{postInDate} 수령 실패");
                completionSource.TrySetResult(false);
                return;
            }

            Debug.Log($"우편ID:{postInDate} 수령 성공");
            BackEndLog.WriteLog(LogType.POST, $"우편ID:{postInDate} 수령 성공");
            completionSource.TrySetResult(true);
        });

        return await completionSource.Task;
    }

    /// <summary>
    /// 모든 우편을 수령하는 메서드 (비동기)
    /// </summary>
    /// <returns>수령 성공 여부</returns>
    public static async UniTask<bool> RemoveAllPost()
    {
        var completionSource = new UniTaskCompletionSource<bool>();

        Backend.UPost.ReceivePostItemAll(PostType.Admin, bro =>
        {
            if (!bro.IsSuccess())
            {
                Debug.LogError($"모든 우편 수령 실패: {bro.ToString()}");
                completionSource.TrySetResult(false);
                return;
            }

            Debug.Log("모든 우편 수령 성공");
            completionSource.TrySetResult(true);
        });

        var success = await completionSource.Task;
        if (success)
        {
            BackEndLog.WriteLog(LogType.POST, $"모든 우편 수령 성공");
        }
        else
        {
            BackEndLog.WriteLog(LogType.POST, $"모든 우편 수령 실패");
        }

        return success;
    }

    /// <summary>
    /// BackendReturnObject에서 우편 리스트를 파싱하는 메서드
    /// </summary>
    /// <param name="bro">뒤끝 응답 객체</param>
    /// <param name="postType">우편 타입</param>
    /// <returns>파싱된 우편 리스트</returns>
    private static List<UPostItem> ParsePostList(BackendReturnObject bro, PostType postType)
    {
        List<UPostItem> postItemList = new List<UPostItem>();

        JsonData postListJson = bro.GetReturnValuetoJSON()["postList"];

        for (int i = 0; i < postListJson.Count; i++)
        {
            UPostItem postItem = new UPostItem();

            postItem.inDate = postListJson[i]["inDate"].ToString();
            postItem.title = postListJson[i]["title"].ToString();
            postItem.postType = postType;

            if (postType == PostType.Admin || postType == PostType.Rank)
            {
                postItem.content = postListJson[i]["content"].ToString();
                postItem.expirationDate = DateTime.Parse(postListJson[i]["expirationDate"].ToString());
                postItem.reservationDate = DateTime.Parse(postListJson[i]["reservationDate"].ToString());
                postItem.nickname = postListJson[i]["nickname"]?.ToString();
                postItem.sentDate = DateTime.Parse(postListJson[i]["sentDate"].ToString());

                if (postListJson[i].ContainsKey("author"))
                {
                    postItem.author = postListJson[i]["author"].ToString();
                }

                if (postListJson[i].ContainsKey("rankType"))
                {
                    postItem.rankType = postListJson[i]["rankType"].ToString();
                }
            }

            // 아이템 파싱
            postItem.items = new List<UPostChartItem>();
            if (postListJson[i]["items"].Count > 0)
            {
                for (int itemNum = 0; itemNum < postListJson[i]["items"].Count; itemNum++)
                {
                    UPostChartItem item = new UPostChartItem();
                    item.itemCount = int.Parse(postListJson[i]["items"][itemNum]["itemCount"].ToString());
                    item.chartFileName = postListJson[i]["items"][itemNum]["item"]["chartFileName"].ToString();
                    item.itemID = int.Parse(postListJson[i]["items"][itemNum]["item"]["itemID"].ToString());
                    item.itemName = postListJson[i]["items"][itemNum]["item"]["itemName"].ToString();
                    item.hpPower = postListJson[i]["items"][itemNum]["item"]["hpPower"].ToString();

                    // chartName 필드가 있는 경우 추가
                    if (postListJson[i]["items"][itemNum]["item"].ContainsKey("chartName"))
                    {
                        item.chartName = postListJson[i]["items"][itemNum]["item"]["chartName"].ToString();
                    }

                    postItem.items.Add(item);
                }
            }

            postItemList.Add(postItem);
        }

        return postItemList;
    }
}

- 우편 가이드 참고

 

 

우편 UI 코드 작성

using System.Collections.Generic;
using System.Linq;
using BackEnd;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class UI_POST : MonoBehaviour
{
    [SerializeField] GameObject _none_post;
    [SerializeField] GameObject _slot;
    [SerializeField] Transform _parent;


    List<UI_POST_Slot> _postslotlist = new List<UI_POST_Slot>();

    void OnEnable()
    {
        SettingSlot().Forget();
    }

    async UniTaskVoid SettingSlot()
    {
        var postlist = await BackEndPOST.GetPOSTList(10);
        var maxcount = postlist.Count;
        foreach (var item in _postslotlist)
        {
            item.gameObject.SetActive(false);
        }

        if (maxcount <= 0)
        {
            _none_post.SetActive(true);
            return;
        }
        _none_post.SetActive(false);

        for (int i = 0; i < maxcount; i++)
        {
            UI_POST_Slot slot = null;
            if (i < _postslotlist.Count)
            {
                slot = Instantiate(_slot, _parent).GetComponent<UI_POST_Slot>();
                _postslotlist.Add(slot);
            }
            else
            {
                slot = _postslotlist[i];
                slot.gameObject.SetActive(true);
            }

            slot.Setting(postlist[i]);
        }
    }


    public async void Btn_AllReward()
    {
        var result = await BackEndPOST.RemoveAllPost();
        if (result == false)
        {
            return;
        }

        // 모든 활성화된 우편 슬롯에서 보상 리스트를 가져와서 하나로 합치기
        var allRewardList = new List<St_RewardItemList>();
        var activeSlots = _postslotlist.Where(x => x.gameObject.activeSelf);

        foreach (var slot in activeSlots)
        {
            var postRewards = slot.GetPostInfo().GetRewardList();
            allRewardList.AddRange(postRewards);
        }

        // 모든 보상을 한번에 처리
        if (allRewardList.Count > 0)
        {
            RewardManager.instance.CraeteReward(allRewardList);
        }

        // 모든 슬롯 비활성화
        foreach (var slot in activeSlots)
        {
            slot.gameObject.SetActive(false);
        }
    }
}
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class UI_POST_Slot : MonoBehaviour
{
    [SerializeField] TextMeshProUGUI _title;
    [SerializeField] TextMeshProUGUI _explain;
    [SerializeField] TextMeshProUGUI _time;
    [SerializeField] Transform _parent;
    [SerializeField] GameObject _slot;
    BackEndPOST.UPostItem _postinfo;

    public BackEndPOST.UPostItem GetPostInfo() => _postinfo;

    public void Setting(BackEndPOST.UPostItem postinfo)
    {
        _postinfo = postinfo;
        _title.text = postinfo.title;
        _explain.text = postinfo.content;
        _time.text = postinfo.TimeRemainingString;
        SettingSlot();
    }

    void SettingSlot()
    {
        foreach (var item in _postinfo.items)
        {
            var slot = Instantiate(_slot, _parent).GetComponent<UI_ItemSlot>();
            slot.Setting(item.itemID, item.itemCount);
        }
    }

    public async void Btn_Reward()
    {
        var result = await BackEndPOST.RemovePost(_postinfo.inDate);
        if (result == false)
        {
            return;
        }
        // UPostItem에서 보상 리스트를 가져와서 한번에 처리
        var rewardList = _postinfo.GetRewardList();

        if (rewardList.Count > 0)
        {
            RewardManager.instance.CraeteReward(rewardList);
        }

        this.gameObject.SetActive(false);
    }
}

 

 

결과

- 정상적으로 아이템이 받아지는 것과 슬롯이 사라지는거 확인 완료

 


20250716 21:00~22:00 뒤끝 우편 플로우차트/UI/코드 추가

20250716 23:10~23:50 뒤끝 우편 추가/뒤끝 차트 추가/UI 수정/코드 수정

반응형