게임엔진/개인 플젝

Flow Free 3 - UI

ShovelingLife 2024. 3. 27. 19:31

UI Manager - 모든 UI들을 관리 / 로직 포함

캔버스에 직접 스크립트 추가하여 나머지 UI들은 자동으로 연동 딱히 다른 클래스 만들어서 이를 상속 받아 팝업 형태 할 정도로 개수가 많지 않아서 단순하게 구현

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;

public class UI_Manager : SingletonLocal<UI_Manager>
{
    [SerializeField]
    GameObject GameUI_Prefab;

    public GameUI gameUI;

    public ResultUI ResultUI_Obj;
    int mLayer;
    bool isCreatedResUI = false;

    // 파이프 UI 용도
    public Canvas pipeUI_CanvasPrefab = null;
    Canvas pipeUI_Canvas;

    // 서브 UI 용도 (결과창)
    public Canvas subUI_CanvasPrefab = null;
    Canvas subUI_Canvas;

    // Start is called before the first frame update
    void Awake()
    {
        Init();
    }

    private void Update()
    {
    }

    public void Init()
    {
        mLayer = LayerMask.NameToLayer("UI");

        // 서브캔버스 결과 (UI 전용) 세팅 및 카메라 세팅
        pipeUI_Canvas = Instantiate(pipeUI_CanvasPrefab);
        pipeUI_Canvas.worldCamera = Global.GetUI_Camera();
        subUI_Canvas = Instantiate(subUI_CanvasPrefab);
        subUI_Canvas.worldCamera = Global.GetUI_Camera();
        gameUI = Instantiate(GameUI_Prefab, transform).GetComponent<GameUI>();
        gameUI.Init();
    }

    public void ResetLevel()
    {
        // Init();
        // GetComponent<Canvas>().worldCamera = Global.GetUI_Camera();
    }


    public Slot CheckForHoveredItem()
    {
        return CheckForHoveredItem(GetEventSystemRaycastResults());
    }

    public Slot CheckForHoveredItem(List<RaycastResult> listRaycastResult)
    {
        Slot tmpSlot = null;

        for (int index = 0; index < listRaycastResult.Count; index++)
        {
            RaycastResult res = listRaycastResult[index];

            if (res.gameObject.layer == mLayer)
                tmpSlot = res.gameObject.GetComponentInChildren<Slot>();
        }
        return tmpSlot;
    }

    List<RaycastResult> GetEventSystemRaycastResults()
    {
        PointerEventData eventData = new PointerEventData(EventSystem.current);
        eventData.position = Input.mousePosition;
        List<RaycastResult> listRes = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventData, listRes);
        return listRes;
    }

    public void ClearedGame()
    {
        if (isCreatedResUI ||
            subUI_Canvas == null)
            return;

        Instantiate(ResultUI_Obj, subUI_Canvas.transform);
        isCreatedResUI = true;
    }
}

Score Manager - 모든 스코어 관리

이중 구조체로 선언, 프로퍼티로 해서 모든 값 변경은 해당 매니저가 관리 외부로부터 임의로 값을 수정해선 안됨

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

// 오직 스코어 관련

public class ScoreManager : SingletonGlobal<ScoreManager>
{
    #region 스코어 관련

    public struct GameScore
    {
        public int curPipes, totalPipes, move, best, percent;
    }

    GameScore _gameScore;

    // 
    public GameScore gameScore
    {
        get => _gameScore;
    }

    #endregion

    public bool IsGameEnded() => gameScore.curPipes == gameScore.totalPipes;

    public string UpdateCurPipe(int val)
    {
        _gameScore.curPipes = val;
        return _gameScore.curPipes.ToString();
    }

    public string UpdateTotalPipe(int val)
    {
        _gameScore.totalPipes = val;
        return _gameScore.totalPipes.ToString();
    }

    public string UpdateMoveCnt() => (++_gameScore.move).ToString();

}

인게임

 

게임 진행상황을 관리해줄 GameUI.cs

아래는 각 버튼들을 가지고와서 자동으로 연동

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using static ScoreManager;

public class GameUI : MonoBehaviour
{
    // 메인 UI
    [SerializeField] Text mTxtLvl, mTxtSize;

    // 서브 UI
    [SerializeField] TextMeshProUGUI mTxtCur, mTxtMax, mTxtMove, mTxtBest, mTxtPercent;

    ScoreManager scoreManager;

    private void Awake()
    {
        scoreManager = ScoreManager.instance;
    }

    public void Init()
    {
        InitButtons();
        InitTexts();
    }

    // ------- 버튼 관련 -------
    void InitButtons()
    {
        // 버튼 초기화 O(n2)
        BtnEvent btnEvent = UI_Manager.instance.GetComponent<BtnEvent>();
        
        foreach (var button in GetComponentsInChildren<Button>())
        {
            UnityAction fn = null;
            var name = button.name;

            // 메뉴 버튼
            if      (name.Contains("Before"))
                     fn = btnEvent.GoToScreen;

            // 옵션 버튼
            else if (name.Contains("Option"))
                     fn = btnEvent.GoToOption;

            // 되돌리기 버튼
            else if (name.Contains("Undo"))
                     fn = btnEvent.Undo;

            // 초기화 버튼
            else if (name.Contains("Reset"))
                     fn = btnEvent.Reset;

            button.onClick.AddListener(fn);
        }
    }

    void InitTexts()
    {
        // 검색해서 할당
        foreach (var txt in GetComponentsInChildren<TextMeshProUGUI>())
        {
            var arrName = txt.name.Split(' ', 5);

            for (int i = 1; i < arrName.Length; i++)
            {

                if (arrName[i] == "Title")
                    break;

                switch (arrName[i])
                {
                    //case "Level":    mTxtLvl     = txt; break;
                    //case "Size":     mTxtSize    = txt; break;
                    case "Cur":      mTxtCur     = txt; break;
                    case "Max":      mTxtMax     = txt; break;
                    case "Movement": mTxtMove    = txt; break;
                    case "Best":     mTxtBest    = txt; break;
                    case "Percent":  mTxtPercent = txt; break;
                }
            }
        }
        UpdateTexts();
    }

    void UpdateTexts()
    {
        var gameScore = scoreManager.gameScore;

        mTxtCur.SetText(gameScore.curPipes.ToString());
        mTxtMax.SetText(gameScore.totalPipes.ToString());
        mTxtMove.SetText(gameScore.move.ToString());
        mTxtBest.SetText(gameScore.best.ToString());
        mTxtPercent.SetText(gameScore.percent.ToString());
    }

    public void UpdateMoveTxt() => mTxtMove.SetText(scoreManager.UpdateMoveCnt());

    public void UpdateCurPipeTxt(int val) => mTxtCur.SetText(scoreManager.UpdateCurPipe(val));

    public void UpdateTotalPipeTxt(int val) => mTxtMax.SetText(scoreManager.UpdateTotalPipe(val));
}

결과창

 

Result UI

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

public class ResultUI : MonoBehaviour
{
    public Text gradeTxt, movementsTxt;

    private void Awake()
    {
        InitButtons();
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        var gameScore = ScoreManager.instance.gameScore;
        int movements = gameScore.move;
        int max = gameScore.totalPipes;
        float res = (float)movements / max;
        string resMsg = "";

        if      (res <= 1.5f)
                 resMsg = "퍼펙트";

        else if (res > 1.5f &&
                 res <= 3.0f)
                 resMsg = "잘했어요";
        
        else
                 resMsg = "괜찮아요";

        gradeTxt.text = resMsg;
        movementsTxt.text = $"{movements}번의 무브로 레벨을 완료했습니다.";
    }

    // ------- 버튼 관련 -------
    void InitButtons()
    {
        // 버튼 초기화 O(n2)
        BtnEvent btnEvent = UI_Manager.instance.GetComponent<BtnEvent>();
        
        foreach (var button in GetComponentsInChildren<Button>())
        {
            UnityAction fn = null;
            var name = button.name;

            // 종료 버튼
            if      (name.Contains("Close"))
                     fn = btnEvent.Close;

            // 메인 화면 버튼
            else if (name.Contains("Next"))
                    fn = btnEvent.NextLevel;

            // 힌트 버튼
            else if (name.Contains("Hint"))
                     fn = btnEvent.FreeHint;

            // 공유 버튼
            else if (name.Contains("Share"))
                     fn = btnEvent.Share;


            button.onClick.AddListener(fn);
        }
    }
}