1. 초기 화면 세팅
2. UIPageStage(Image) 생성 후, 크기와 색상 조정

***UIPageStage는 UIStageController, 이전,다음 페이지 버튼, 페이지 쪽 수를 관리

3. UIStageController(image) 생성 후, Stage의 크기 만큼 크기 조절

***UIStageController에서는 UIStage들을 관리

 

4. grid(Create Empty) 생성 후, 앵커 프리셋을 좌상단으로 조정 (Alt+Shift)
5. grid에 Grid Layout Group 컴포넌트 부착
6. Image를 생성 후, 좌 상단으로 앵커 프리셋 조정(Alt+shift)
7. grid의 Grid Laydout Group의 Padding과 Cell Size, Spacing의 수치를 변경하고, Contraint를 Fixed Coulumn Count로 변경 후, 6으로 설정(한 줄의 Stage 개수)
8. Ctrl + D를 통해 복사를 해서 위치가 정상적으로 들어가는지 확인
9. Image를 하나만 남기고 제거 후, UIStage로 이름 변경 및 알파 값 수정
10. UIStage를 구성해주고, 프리팹으로 생성 (프리팹으로 만든 뒤, Hierarchy창의 UIStage는 다시 Unpack)
11. 이전과 같이 Ctrl+D를 이용해 복사 후, 위치 확인
12. 원본 가이드 씬에서 이전, 다음 버튼을 프리팹으로 생성 후, 기존 버튼들은 다시 Unpack
13. 버튼 프리팹들을 UIPageStage의 자식 객체로 드래그 앤 드랍
14. 기본적인 Stage 완성
15. UIStage의 상태를 관리 하기 위한 Test04UIStage 스크립트 생성
16. Test04UIStage 스크립트 작성
17. 기존의 UIStage를 하나만 남겨 놓고 지운 후, 작성한 스크립트를 부착
18. 복사를 통해 다시 UIStage 배치 (Ctrl+D)
19. UIStage들의 Image 컴포넌트 비활성화(배경 처리)
20. Test04UIMain, Test04UIStageController, UIPageStage 스크립트 생성 후, 각 오브젝트에 연결

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

public class Test04UIStageController : MonoBehaviour
{
    [SerializeField]
    private Test04UIStage[] uiStages;

    private Dictionary<int, Test04UIStage.eState> dic = new Dictionary<int, Test04UIStage.eState>();

    private int totalStages;

    public event UnityAction onMoveNextEvent;
    private UIPageStage.Page page;

    public void Init(int totalStages, UIPageStage.Page page)
    {
        this.page = page;

        Debug.LogFormat("Init :  {0} ~ {1}, total : {2}", this.page.start, this.page.end, this.page.Total);

        this.totalStages = totalStages;

        for (int i = 0; i < this.totalStages; i++)
        {
            var stageNum = i + 1;
            if (stageNum == 1)
            {
                dic.Add(stageNum, Test04UIStage.eState.Open);
            }
            else
            {
                dic.Add(stageNum, Test04UIStage.eState.Lock);
            }

        }
        this.Clear(); //총 Stage를 넘어간 Stage는 비활성화

        //이벤트 등록 
        for (int i = 0; i < this.page.Total; i++)
        {
            var uiStage = this.uiStages[i];
            int idx = i;
            uiStage.onClick = (stageNum, state) =>
            {

                Debug.LogFormat("stageNum: {0}, state: {1}", stageNum, state);

                if (state != Test04UIStage.eState.Open) return;//오픈 상태에서만 동작

                this.CompleteStage(uiStage);
                int nextStageNum = stageNum + 1;
                if (nextStageNum <= this.totalStages)
                {
                    Debug.LogFormat("{0} ~ {1}, total : {2}", this.page.start, this.page.end, this.page.Total);

                    Debug.LogFormat("다음 스테이지 오픈 : {0}, page.end: {1}", nextStageNum, this.page.end);
                    //다음 UIStage가 현재 페이지에 있는지 확인 
                    if (nextStageNum > this.page.end)
                    {
                        Debug.Log("다음 스테이지가 현재 페이지에 없음");
                        //데이터만 변경 
                        this.dic[nextStageNum] = Test04UIStage.eState.Open;
                        //다음 페이지로 이동 버튼 눌렀을때 로직 실행 
                        this.onMoveNextEvent();
                    }
                    else
                    {
                        Debug.Log("다음 스테이지가 현재 페이지에 있음");
                        Test04UIStage nextUIStage = this.uiStages[idx + 1];
                        Debug.LogFormat("nextUIStage: {0}", nextUIStage);
                        //데이터를 변경
                        this.dic[nextStageNum] = Test04UIStage.eState.Open;
                        //UI를 업데이트
                        nextUIStage.ChangeState(Test04UIStage.eState.Open);
                        //모든 스테이지들의 상태를 출력 
                        for (int i = 0; i < this.totalStages; i++)
                        {
                            var num = i + 1;
                            Debug.LogFormat("{0} : {1}", num, this.dic[num]);
                        }
                    }
                }
                else
                {
                    Debug.Log("마지막 스테이지");
                }
            };

            int stageNum = page.start + i;
            uiStage.Init(stageNum, this.dic[stageNum]);
        }
    }

    private void CompleteStage(Test04UIStage uiStage)
    {

        if (uiStage.State == Test04UIStage.eState.Open)
        {
            this.dic[uiStage.StageNum] = Test04UIStage.eState.Complete;
            //해당 스테이지 찾아 업데이트 
            uiStage.ChangeState(Test04UIStage.eState.Complete);
        }
    }

    public void UpdateUI(UIPageStage.Page page)
    {
        this.page = page;

        Debug.LogFormat("UpdateUI: {0} ~ {1}, total : {2}", this.page.start, this.page.end, this.page.Total);

        for (int i = 0; i < this.totalStages; i++)
        {
            var stageNum = i + 1;
            Debug.LogFormat("{0} : {1}", stageNum, this.dic[stageNum]);
        }

        this.Clear();

        for (int i = 0; i < page.Total; i++)
        {
            var uiStage = this.uiStages[i];
            int stageNum = page.start + i;
            uiStage.UpdateUI(stageNum, this.dic[stageNum]);
        }
    }

    private void Clear()
    {
        foreach (Test04UIStage uiStage in this.uiStages)
        {
            uiStage.Hide();
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIPageStage : MonoBehaviour
{
    public struct Page
    {
        public int start;
        public int end;
        public int Total
        {
            get
            {
                return this.end - this.start + 1; 
            }
        }
        public Page(int start, int end)
        {
            this.start = start;
            this.end = end;
        }
    }
    private const int MAX_DISPLAY_STAGES = 18;  //한페이지에 보여줄수있는 최대 UIStage 개수 

    [SerializeField] private Button btnPrev; //이전 페이지 버튼 
    [SerializeField] private Button btnNext; //다음 페이지 버튼 
    [SerializeField] private int totalStages = 28;   //최대 UIStage 개수 
    [SerializeField] private Test04UIStageController stageController; //UIStage들을 관리 하는 컴포넌트 

    private int currentPage = 1; //현재 페이지 숫자
    private int totalPage = -1;

    public void Init()
    {
        //계산하기 
        this.totalPage = Mathf.CeilToInt((float)this.totalStages / MAX_DISPLAY_STAGES);

        Page page = this.CalcStartEnd();

        this.stageController.onMoveNextEvent += OnMoveNextEventHandler;
        this.stageController.Init(totalStages, page);    //초기화 

        this.btnNext.onClick.AddListener(() => {
            this.MoveNext();
        });

        this.btnPrev.onClick.AddListener(() => {
            this.MovePrev();
        });
    }

    private void OnMoveNextEventHandler()
    {
        this.MoveNext();
    }

    private void OnDisable()
    {
        this.stageController.onMoveNextEvent -= OnMoveNextEventHandler;
    }

    private void MovePrev()
    {
        if (this.currentPage == 1)
        {
            Debug.Log("첫 페이지");
            return;
        }
        this.currentPage -= 1;
        Debug.LogFormat("currentPage: {0}", this.currentPage);
        Page page = this.CalcStartEnd();
        this.stageController.UpdateUI(page); //초기화 
    }

    private void MoveNext()
    {
        if (this.currentPage == this.totalPage)
        {
            Debug.Log("마지막 페이지");
            return;
        }

        this.currentPage += 1;
        Debug.LogFormat("currentPage: {0}", this.currentPage);
        Page page = this.CalcStartEnd();

        Debug.LogFormat("<color=red>{0} ~ {1}, Total: {2}</color>", page.start, page.end, page.Total);

        this.stageController.UpdateUI(page);    //초기화 
    }

    private Page CalcStartEnd()
    {
        Debug.LogFormat("총 스테이지 : {0}", this.totalStages);
        Debug.LogFormat("마지막 페이지 : {0}", this.totalPage);
        Debug.LogFormat("현재 페이지 : {0}", this.currentPage);
        int end = this.currentPage * MAX_DISPLAY_STAGES;
        //currentPage : 1
        //end : 18 
        //18 - (18-1)
        //start : 1
        int start = end - (MAX_DISPLAY_STAGES - 1);


        if (end >= this.totalStages)
        {
            end = this.totalStages;
        }

        return new Page(start, end);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Test04UIMain : MonoBehaviour
{
    [SerializeField]
    private UIPageStage uiPageStage;

    void Start()
    {
        this.uiPageStage.Init();
    }
}
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class Test04UIStage : MonoBehaviour
{
    public enum eState
    {
        Lock, Open, Complete
    }

    public TMP_Text[] arrTxtStageNum;


    public GameObject[] arrStateGo;    //0: lock, 1: Open, 2: complete

    public eState state;

    public eState State
    {
        get
        {
            return this.state;
        }
    }

    private Button btn;
    public System.Action<int, eState> onClick;
    private int stageNum;
    public int StageNum
    {
        get
        {
            return this.stageNum;
        }
    }
    public void Init(int stageNum, eState state)
    {
        this.stageNum = stageNum;
        Debug.Log(stageNum);

        this.btn = this.GetComponent<Button>();

        foreach (var tmpText in this.arrTxtStageNum)
        {
            tmpText.text = stageNum.ToString();
        }

        this.btn.onClick.AddListener(() =>
        {
            this.onClick(this.stageNum, this.state);
        });

        this.state = state;
        this.ChangeState(this.state);

        this.gameObject.SetActive(true);

    }


    public void ChangeState(eState state)
    {
        this.InActiveAll();
        this.state = state;
        int index = (int)this.state;
        this.arrStateGo[index].SetActive(true);
    }

    private void InActiveAll()
    {
        foreach (var go in this.arrStateGo)
        {
            go.SetActive(false);
        }
    }

    public void Show()
    {
        this.gameObject.SetActive(true);
    }
    public void Hide()
    {
        this.gameObject.SetActive(false);
    }

    public void UpdateUI(int stageNum, eState state)
    {
        this.stageNum = stageNum;
        this.state = state;

        foreach (var tmpText in this.arrTxtStageNum)
        {
            tmpText.text = stageNum.ToString();
        }

        this.ChangeState(this.state);
        this.gameObject.SetActive(true);
    }

}

21. 스크립트 작성 및 수정

22. Stage 동작 확인

 

***유의할 점

1. UIStage 오브젝트에 버튼 컴포넌트가 있는지 꼭 확인하기 -> 없으면 null 오류 발생

2. 보통 Canvas 생성시, EventSystem이 자동 생성되는데, 만약 Canvas만 복사해서 가져올 경우 EventSystem이 없을수도 있다. => 버튼 및 이벤트 트리거 동작 안함

 

+ Recent posts