게임 로고 이미지

UI는 인게임의 로직을 구현하는 것 보다 쉽게 때문에 많은 사람들이 별다른 설계 없이 기능을 구현하기도 하는데요. 이렇게 설계 없이 구현하다 보면 어느 순간 기능 하나를 구현하기위해 많은 시간이 들곤 합니다. 예를 들어서 여러 개의 팝업이 화면에 띄워져 있는데 모두 한번에 닫고 싶을 때 처음부터 설계를 잘 해 놓지 않고 나중에 구현 하려고 하면 쉽지 않습니다. 그렇기에 UI Manager를 만들어서 관리를 해주는 것이 필요합니다. 이번 시간에는 UI Manager의 구현과 가장 많이 쓰이는 기능들을 구현해 보겠습니다.


[UI Manager가 하는 일]

UI Manager는 UI를 관리 해주는 역할을 합니다. 구체적으로는 현재 열려 있는 팝업들을 모두 닫거나, 열린 팝업들을 기억하여 뒤로 가기를 눌렀을 때 과거에 열렸던 팝업들을 차례로 열어주는 역할을 하기도 합니다. 또한 UI를 예약하여 현재 열려 있는 팝업이 닫히면 예약된 팝업을 띄워 주기도 합니다.


[UI Manager의 필요성]

프로젝트에 UI가 많지 않아서 필요 없다고 생각 하실 수도 있지만 그렇지 않습니다. 프로젝트는 항상 변화하기 마련이고 그에 따라 UI도 추가 될 확률이 높습니다. 초반에 UIManager를 설계해서 추가하는 것은 시간이 그리 걸리지 않지만 역순으로 UI를 만든 후에 구현하려면 시간이 더욱 많이 들 수 밖에 없습니다. 그렇기에 초기에 설계를 해 놓는 것은 생산성과 직결된 부분이라고 할 수 있습니다.

또한 전체 닫기와 뒤로 가기, 예약 기능의 필요성을 궁금해 하시는 분들도 있을 겁니다. 하나씩 필요성을 설명해 드리겠습니다.

먼저, 전체 닫기는 인벤토리와 장비를 설명해주는 팝업이 열려 있다고 가정해 봅시다. 이 상황에서 홈버튼을 눌러서 메인 화면으로 가고 싶다면 인벤토리 창과 장비 설명 팝업을 닫아줘야 합니다. 이럴 때 필요한 것이 전체 닫기 입니다. 현재 열려 있는 UI를 모두 닫고 메인 화면을 보여주는 것이죠.

두 번째로 뒤로 가기는 팝업을 계속 타고 들어갈 때 필요합니다. 예를 들어서 장비 창 팝업 -> 장비 강화 팝업 -> 재화 구매 팝업 순으로 팝업을 열었다고 했을 때 뒤로 가기 버튼을 눌렀다면 역순으로 팝업이 닫혀야 할 것 입니다. 재화 구매 팝업 -> 장비 강화 팝업 -> 장비 창 팝업 순으로 말이죠.

세 번째로 예약 기능은 특가 판매를 하는 상품의 팝업을 몬스터를 10마리 잡았을 때와 20마리 잡았을 때 띄우고 싶다고 가정해 봅시다. 만약 10마리를 잡은 시점에 팝업1이 떴고 닫기를 누르지 않은 상태에서 20마리를 잡게 되었다면 예약을 미리 해 둬서 팝업1이 닫히는 시점에 팝업2를 띄워주면 되는 것 입니다.


[UIPopup 구현]

using UnityEngine;

public class UIPopup : MonoBehaviour
{
    [SerializeField] private GameObject popupCanvas; // 팝업 창의 캔버스
    [SerializeField] private Animator popupAnimator; // 팝업 창의 애니메이터

    // 애니메이션 이벤트에서 호출할 메서드
    public void OnCloseAnimationFinished()
    {
        // 애니메이션 완료 후 팝업 캔버스를 비활성화합니다.
        UIUtilities.SetUIActive(popupCanvas, false);
    }

    // 팝업이 열릴 때 호출됩니다.
    public void Open()
    {
        if (popupCanvas != null)
        {
            // 팝업 캔버스를 활성화합니다.
            UIUtilities.SetUIActive(popupCanvas, true);

            // 팝업 애니메이션 재생 (애니메이션 이벤트로 OnCloseAnimationFinished 호출)
            if (popupAnimator != null)
            {
                popupAnimator.SetTrigger("Open");
            }
        }
    }

    // 팝업이 닫힐 때 호출됩니다.
    public void Close()
    {
        if (popupCanvas != null)
        {
            // 팝업 애니메이션 재생 (애니메이션 이벤트로 OnCloseAnimationFinished 호출)
            if (popupAnimator != null)
            {
                popupAnimator.SetTrigger("Close");
            }
            else
            {
                // 애니메이션을 사용하지 않는 경우 즉시 팝업 캔버스를 비활성화합니다.
                UIUtilities.SetUIActive(popupCanvas, false);
            }
        }
    }

    // 팝업 내에서 어떤 동작을 수행할 때 호출될 메서드들을 추가할 수 있습니다.
    public void OnButtonClicked()
    {
        // 팝업 내 버튼이 클릭되었을 때 실행할 동작을 여기에 작성합니다.
    }
}
using UnityEngine;

public static class UIUtilities
{
    // 게임 오브젝트의 활성화 상태를 설정하는 유틸리티 함수
    public static void SetUIActive(GameObject uiObject, bool isActive)
    {
        if (uiObject != null)
        {
            uiObject.SetActive(isActive);
        }
    }
}

UIPopup 클래스는 팝업을 열고 닫는데 기본이 되는 클래스 입니다. 또한 팝업 애니메이션을 만들어서 열고 닫을 때 애니메이션을 재생 해줍니다. 닫힐 때는 애니메이션이 마무리 된 후 닫혀야 하기 때문에 애니메이션이 다 실행된 후 OnCloseAnimationFinished를 호출해 주시면 됩니다.

좀 더 여러가지 기능을 넣을 수 있지만, 가장 기본이 되는 기능만 넣었습니다. 이유는 기본이 기능만 들어 있을 경우 이해하기도 쉽고, 자신의 원하는 팝업으로 확장 시키기도 쉽기 때문에 입니다.

UIUtilities은 UI를 사용하는데 공통으로 자주 사용되는 기능을 모아 놓은 클래스 입니다. 유틸 클래스를 만들어 중복된 코드 사용을 방지하며 생산성을 늘리는 것 입니다.


[UIManager 구현]

using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour
{
    private static UIManager instance;

    public static UIManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<UIManager>();
                if (instance == null)
                {
                    Debug.LogError("UIManager가 씬에 존재하지 않습니다.");
                }
            }
            return instance;
        }
    }

    private Stack<UIPopup> openPopups = new Stack<UIPanel>();
    private Queue<UIPopup> pendingPopups = new Queue<UIPanel>(); // 예약된 팝업을 위한 큐

    private void Update()
    {
        // 뒤로가기 키를 누르면 가장 최근에 열린 팝업을 닫습니다.
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            CloseLastOpenedPopup();
        }
    }

    // 팝업을 엽니다.
    public void OpenPopup(UIPanel popup)
    {
        if (popup != null)
        {      
            // 새로운 팝업을 엽니다.
            UIUtilities.SetUIActive(popup.gameObject, true);
            openPopups.Push(popup);
        }
    }

    // 팝업을 닫습니다.
    public void ClosePopup(UIPanel popup)
    {
        if (popup != null && openPopups.Contains(popup))
        {
            // 팝업을 닫습니다.
            UIUtilities.SetUIActive(popup.gameObject, false);
            openPopups.Pop();

            // 팝업이 닫힌 후 예약된 팝업이 있다면 엽니다.
            if (pendingPopups.Count > 0)
            {
                OpenPopup(pendingPopups.Dequeue());
            }
        }
    }

    // 가장 최근에 열린 팝업을 닫습니다.
    public void CloseLastOpenedPopup()
    {
        if (openPopups.Count > 0)
        {
            ClosePopup(openPopups.Peek());
        }
    }

    // 모든 열린 팝업을 닫습니다.
    public void CloseAllOpenPopups()
    {
        while (openPopups.Count > 0)
        {
            ClosePopup(openPopups.Peek());
        }
    }

    // 예약된 팝업을 추가합니다.
    public void ReservePopup(UIPanel popup)
    {
        if (popup != null)
        {
            pendingPopups.Enqueue(popup);
        }
    }
}

팝업을 열 때 Stack에 저장 합니다. 이유는 뒤로 가기 기능 시 열려 있는 팝업 중 가장 최근 팝업부터 차례로 닫아야 하기 때문에 그렇습니다. 팝업을 닫을 때 팝업을 비활성화 시켜 주고 만약 예약된 팝업이 있고 열려 있는 팝업이 없다면 예약된 팝업을 열어줍니다.

위 코드를 참고 하셔서 본인의 프로젝트에 맞게 사용하시면 될 것 같습니다.

[UI 관련 기능 제작 노하우]

UI 기능을 처음에 만들려면 어려움을 많이 겪으실 겁니다. 물론 간단하게 열고 닫는 기능은 상관 없지만 뒤로 가기나 팝업을 예약 하는 기능 등 각 프로젝트에 맞는 요구사항이 있기에 시간을 많이 할애 합니다.

하지만 UI는 어떤 프로젝트 든 존재하며 비슷한 기능이 있기 마련입니다. 그렇기에 시간을 절약하기 위해서는 자신만의 관련 기능을 가진 클래스들을 가지고 계시는 것이 중요 합니다. 만약 그러한 것이 없다면 제가 제공해 드린 코드를 베이스로 조금 씩 확장 시키시는 것도 좋은 방법이 될 것 입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다