[유니티/개발일지] 픽셀 리마인드(PixelRemind) - 도감 시스템 및 이어하기 구현하기

2023. 7. 31. 22:40·유니티/작업

| 작업 목표 📋

1. 픽셀 아트를 플레이 하면서 복원을 모두 완료 시켰을 때, 도감리스트에 추가하는 도감 시스템 구현할 예정입니다.

2. 그동안 미뤄뒀던 이어하기 기능을 구현 할 예정 입니다.


| TO DO LIST 💡

  • 도감 UI 구성
    • 수집된 토픽 슬롯 UI
    • 수집된 픽셀 아트 슬롯 UI
    • 수집된 픽셀 아트 데이터 정보 팝업 UI
  • 도감 추가 기능 구현
    • CollectedTopicData, CollectedPixelArtData 추가
    • 복원이 완료 된 PixelArtData 도감 반영
    • 도감 씬 내 수집 토픽, 픽셀 아트 슬롯 생성
    • 슬롯 내 데이터. 정보 팝업 UI 업데이트 구현
  • 이어하기 기능 구현


| 작업 과정 🖥️

=> 도감 UI 구성

우선 추가한 도감 씬 전체 UI 입니다.

아직 UI 디자인을 적용 시키지 않고 구조만 잡은 모습 입니다.

도감씬의-구성된-전체적인-UI-모습
도감씬의 구성된 전체적인 UI 모습

| 수집된 토픽 슬롯 UI

수집된 토픽 데이터(CollectedTopicData)를 포함하며 아래와 같은 요소들을 담고 있습니다.

  • 타이틀
  • 복원 완료된 픽셀 아트의 수
  • 해당 토픽의 추가되어 있는 전체 픽셀 아트의 수
  • 복원 완료된 픽셀 아트 슬롯들

수집-토픽-슬롯-UI
수집 토픽 슬롯 UI

| 수집된 픽셀 아트 슬롯 UI

수집된 토픽 데이터(CollectedPixelArtData)를 포함하며 아래와 같은 요소들을 담고 있습니다.

  • 타이틀
  • 썸네일 이미지

수집-픽셀-아트-슬롯-UI
수집 픽셀 아트 슬롯 UI

| 수집된 픽셀 아트 데이터 정보 팝업 UI

클릭한 수집 픽셀 아트 슬롯을 누르면 해당 픽셀 아트의 대한 정보를 알려주는 팝업 UI입니다.

  • 타이틀
  • 썸네일
  • 설명

수집-픽셀-아트-데이터-정보-팝업-UI
수집 픽셀 아트 데이터 정보 팝업 UI

=> 기능 구현 / CollectedTopicData, CollectedPixelArtData 추가

수집된 도감과 복원이 완료된 픽셀 아트의 경우 기존 TopicData와 PixelArtData의 데이터들이 모두 필요가 없기 때문에 수집된 정보를 별도로 관리 할 CollectedTopicData, CollectedPixelArtData 클래스를 추가 하였습니다.

 

  • CollectedTopicData
    • ID : 수집 토픽 데이터의 식별 값
    • TItle: 수집 토픽의 타이틀
    • Description: 수집 토픽의 설명
    • TotalPixelArtDataCount : 수집 토픽의 전체 픽셀 아트 데이터 수
    • CollectedPixelArtDataList : 복구 완료 된 픽셀 아트 데이터 리스트
[FirestoreData, Serializable]
public class CollectedTopicData
{
    [FirestoreProperty] public string ID { get; private set; }
    [FirestoreProperty] public string Title { get; private set; }
    [FirestoreProperty] public string Description { get; private set; }
    [FirestoreProperty] public int TotalPixelArtDataCount { get; private set; }
    [FirestoreProperty] public List<CollectedPixelArtData> CollectedPixelArtDataList { get; private set; }

    public CollectedTopicData()
    {
        ID = "";
        Title = "";
        Description = "";
        TotalPixelArtDataCount = 0;
        CollectedPixelArtDataList = new List<CollectedPixelArtData>();
    }

public CollectedTopicData(string id, string title, string description, int totalPixelArtDataCount)
    {
        ID = id;
        Title = title;
        Description = description;
        TotalPixelArtDataCount = totalPixelArtDataCount;
        CollectedPixelArtDataList = new List<CollectedPixelArtData>();
    }
}
  • CollectedPixelArtData
    • ID : 복구 완료 픽셀 아트 데이터의 식별 값
    • ThumbnailData: 픽셀 아트의 썸네일 데이터
    • Description : 픽셀 아트 설명
    • ThumbnailSize : 썸네일 사이즈
[FirestoreData, Serializable]
public class CollectedPixelArtData
{
    [FirestoreProperty] public string Title { get; private set; }
    [FirestoreProperty] public string ThumbnailData { get; private set; }
    [FirestoreProperty] public string Description { get; private set; }
    [FirestoreProperty] public int ThumbnailSize { get; private set; }

    public CollectedPixelArtData()
    {
        Title = "";
        ThumbnailData = "";
        Description = "";
        ThumbnailSize = 0;
    }
    public CollectedPixelArtData(string title, string thumbnailData, string description, int thumbnailSize)
    {
        Title = title;
        ThumbnailData = thumbnailData;
        Description = description;
        ThumbnailSize = thumbnailSize;
    }
}

 

| 복원이 완료 된 PixelArtData 도감 반영

FillRandomPixel 메서드에 남은 픽셀 (복원 픽셀 수)가 없는 경우 완료 처리 하는 로직을 추가하였습니다.

// ColorMatchSystem.cs

private void FillRandomPixel()
{
	...
    
    if (_pixelArtData.PixelColorData.RemainingPixels == 0)
    {
        _pixelArtData.IsCompleted = true;
    }
}

 

전반적인 게임 진행을 관리하는 GameManager 클래스를 추가 후

게임 플레이가 끝났을 때 픽셀 아트의 완료 유무를 체크 후 CollectedTopic의 CollectedPixelArtDataList 에 추가하는 CollectPixelArt 메서드를 구현하여 최종적으로 도감 데이터에 추가 하였습니다.

// GameManager.cs

private IEnumerator Playing()
{
    while (!colorMatchSystem.IsGameOver())
    {
        yield return _timerDelay;
        _playTime++;
        playUI.UpdatePlayTime(_playTime);
    }
    playUI.HideUI();

    if (_selectPixelArtData.IsCompleted)
    {
        CollectPixelArt();
    }

    SaveData();
}

private void CollectPixelArt()
{
    _selectTopicData.CompleteCount++;

    if (_selectTopicData.CompleteCount == _selectTopicData.TotalCount)
    {
        _selectTopicData.Complete = true;
    }

    _selectPixelArtData.PlayTime = _playTime;

    CollectedTopicData curCollectTopicData =  DataManager.userData.CollectedTopicDataList.Find((collectTopic) => collectTopic.ID == _selectTopicData.ID);
    CollectedPixelArtData newCollectPixelArtData = new CollectedPixelArtData(_selectPixelArtData.Title, _selectPixelArtData.ThumbnailData, _selectPixelArtData.Description, _selectPixelArtData.Size);
    curCollectTopicData.CollectedPixelArtDataList.Add(newCollectPixelArtData);
}

 

| 도감 씬 내 수집 토픽, 픽셀 아트 슬롯 생성

도감씬의 UI를 담당할 CollectUI 클래스를 추가한 후 수집된 토픽 데이터 슬롯과 픽셀 아트 슬롯들을 생성 하는 SetSlot메서드를 구현 하였습니다.

public class CollectUI : BodyUI
{
    [SerializeField] private Transform genCollectTopicSlotParent;
    [SerializeField] private CollectTopicSlot collectTopicSlotPrefab;
    [SerializeField] private CollectPixelArtSlot collectPixelArtSlotPrefab;
	...
    
    private void SetSlot()
    {
        List<CollectedTopicData> curCollectTopicDataList = DataManager.userData.CollectedTopicDataList;

        foreach (CollectedTopicData collectedTopicData in curCollectTopicDataList)
        {
            CollectTopicSlot newCollectTopicSlot = Instantiate(collectTopicSlotPrefab, genCollectTopicSlotParent);
            newCollectTopicSlot.SetSlot(collectedTopicData);

            foreach (CollectedPixelArtData collectedPixelArtData in collectedTopicData.CollectedPixelArtDataList)
            {
                CollectPixelArtSlot newCollectPixelArtSlot = Instantiate(collectPixelArtSlotPrefab, newCollectTopicSlot.GetCollectPixelArtParent());
                newCollectPixelArtSlot.SetSlot(collectedPixelArtData);
                newCollectPixelArtSlot.OnClick += CollectPixelArtSlotClickHandler;
            }
        }
    }
}

 

| 수집 픽셀 아트 데이터 정보 팝업 UI 업데이트 구현

이제 도감 씬 내에 생성된 픽셀 아트 슬롯을 눌렀을 때 해당 픽셀 아트의 정보를 팝업 UI에 업데이트 시키기 위해

CollectPixelArtSlot 클래스 내에 아래와 같은 작업을 진행 하였습니다.

  • CollectPixelArtSlotClickHandle 델리게이트 추가
  • CollectPixelArtSlotClickHandle OnClick 이벤트 추가
  • OnSlotClick() 메서드 구현
public class CollectPixelArtSlot : MonoBehaviour
{
	...
    
    public delegate void CollectPixelArtSlotClickHandler(CollectedPixelArtData collectedPixelArtData);
    public event CollectPixelArtSlotClickHandler OnClick;

    public void OnSlotClick()
    {
        OnClick?.Invoke(_collectPixelArtData);
    }
    public void SetSlot(CollectedPixelArtData collectedPixelArtData)
    {
        _collectPixelArtData = collectedPixelArtData;
        titleText.text = $"{_collectPixelArtData.Title}";
        thumbnailImage.sprite = PixelArtHelper.MakeThumbnail(_collectPixelArtData.ThumbnailData, _collectPixelArtData.ThumbnailSize);
    }
}

 

마지막으로 CollectUI 클래스 내에 CollectPixelArtSlotClickHandler 구현을 통해 전체적인 기능 구현을 마무리 했습니다.

public class CollectUI : BodyUI
{
    [SerializeField] private CollectPixelArtDetailPopup collectPixelArtDetailPopup;
    
	...

    private void CollectPixelArtSlotClickHandler(CollectedPixelArtData collectedPixelArtData)
    {
        collectPixelArtDetailPopup.SetPopup(collectedPixelArtData);
        collectPixelArtDetailPopup.gameObject.SetActive(true);
    }
}

 

| 이어하기 기능 구현

메인 씬의 플레이 버튼을 통해 이어하기를 진행 할 수 있도록 MainPlayButton 클래스를 아래와 같이 구현 하였습니다.

간단하게 동작을 우선하여 구현한 코드라 다소 난잡하긴 합니다.

public class MainPlayButton : MonoBehaviour
{
    [SerializeField] private Button playButton;
    [SerializeField] private Image buttonImage;
    [SerializeField] private TMP_Text buttonText;

    private TopicData _lastTopicData;
    private PixelArtData _lastPixelArtData;
    
    private async void Start()
    {
        LoadData();
        ConfigurePlayButton();
        await UpdateUserDataOnFirebase();
    }

    private void LoadData()
    {
        var selectTopicID = DataManager.userData.SelectTopicID;
        if (string.IsNullOrEmpty(selectTopicID))
        {
            SetButtonInactive("토픽 선택");
            return;
        }

        _lastTopicData = DataManager.LoadJsonData<TopicData>(DataPath.GalleryDataPath, selectTopicID);
        _lastPixelArtData = _lastTopicData?.PixelArtDatas.Find(pixelArtData => pixelArtData.ID == DataManager.userData.SelectPixelArtID);
    }

    private async Task UpdateUserDataOnFirebase()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        string FUID = FirebaseManager.ins.FireAuth.FUID;
#else
        string FUID = "Test";
#endif
        await FirebaseManager.ins.Firestore.UpdateData(FirestoreCollections.UserData, FUID, DataManager.userData);
    }

    private void ConfigurePlayButton()
    {
        if (_lastTopicData == null)
        {
            playButton.interactable = false;
            return;
        }

        if (_lastPixelArtData.IsCompleted)
        {
            DataManager.userData.SelectPixelArtID = string.Empty;
            ConfigurePlayButtonForCompletedPixelArt();
        }
        else
        {
            ConfigurePlayButtonForIncompletePixelArt();
        }
    }

    private void ConfigurePlayButtonForCompletedPixelArt()
    {
        if (_lastTopicData.Complete)
        {
            DataManager.userData.SelectTopicID = string.Empty;
            SetButtonInactive("토픽 선택");
        }
        else
        {
            buttonText.text = _lastTopicData.Title;
            buttonImage.gameObject.SetActive(false);
            SetButtonActive(SceneManager.LoadScene, SceneNames.GalleryScene);
        }
    }

    private void ConfigurePlayButtonForIncompletePixelArt()
    {
        buttonImage.sprite = PixelArtHelper.MakeThumbnail(_lastPixelArtData.ThumbnailData, _lastPixelArtData.Size);
        buttonText.text = "이어하기";
        SetButtonActive(SceneManager.LoadScene, SceneNames.PlayScene);
    }

    private void SetButtonActive(Action<string> action, string argument)
    {
        playButton.interactable = true;
        playButton.onClick.AddListener(() => action(argument));
    }

    private void SetButtonInactive(string text)
    {
        buttonImage.gameObject.SetActive(false);
        buttonText.text = text;
        playButton.interactable = false;
    }
}

| 작업 결과 ✅

도감-시스템-+-이어하기-구현한-결과-GIF
도감 시스템 + 이어하기 구현한 결과 GIF


| 다음에 작업할 내용 🕟︎

지금 까지 다양한 데이터 클래스들을 Firestore와 로컬을 통해 관리, 핸들링을 하면서 문제점과 좀 더 구조를 개선 시켜야 할 필요성을 느끼게 되어 다음엔 이를 작업할 예정입니다.


저작자표시 비영리 변경금지 (새창열림)
'유니티/작업' 카테고리의 다른 글
  • [유니티/프로젝트] ProjectA 프로젝트 퍼즐 로직 리팩토링 작업..
  • [유니티/개발일지] 픽셀 리마인드(PixelRemind) - 중간 점검 및 계획
  • [유니티/개발일지] 픽셀 리마인드(PixelRemind) - Firestore를 활용 토픽 데이터 업데이트 구현하기 - 완료
  • [유니티/개발일지] 픽셀 리마인드(PixelRemind) - Firestore를 활용 토픽 데이터 업데이트 구현하기 - 2
프로포폴
프로포폴
Unity 클라이언트 개발자가 운영 하는 개발 블로그 입니다
  • 프로포폴
    약파는개발자
    프로포폴
  • 전체
    오늘
    어제
    • 분류 전체보기 (67)
      • 유니티 (21)
        • 공부 (0)
        • 튜토리얼 (0)
        • 에셋 (3)
        • 문제 해결 (2)
        • 작업 (16)
      • 언리얼(Unreal) (1)
        • 문제 해결 (1)
      • 백준 (20)
        • 브론즈5 (Bronze V) (20)
      • 프로그래머스(Programmers) (23)
        • C# 풀이 (23)
      • C# (0)
        • 팁 (Tip) (0)
      • 컴퓨터 과학(CS) (2)
        • 알고리즘 (1)
        • 자료구조 (1)
  • 블로그 메뉴

    • 홈
    • 글쓰기
    • 태그
    • 유니티
    • 알고리즘
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    Bronze V
    개발일지
    백준
    프로그래머스_C#
    탐색(Search)
    컴퓨터과학
    작업
    유니티
    Unity
    projecta
    CS
    알고리즘
    픽셀리마인드
    프로그래머스_Lv.0
    SHA
    브론즈5
    프로그래머스
    c#
    C++
    baekjoon
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
프로포폴
[유니티/개발일지] 픽셀 리마인드(PixelRemind) - 도감 시스템 및 이어하기 구현하기
상단으로

티스토리툴바