Tutorial (9) 

UGUI 기초

 

작성 기준 버전 :: 2019.2

 

이번에는 유니티 엔진의 GUI에 대해서 알아보자.

 

[본 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

게임에서 플레이어에게 게임 내의 정보를 전달하는 매개체를 유저 인터페이스, 줄여서 UI라고 부른다.

 

GUI
CUI

 

그리고 이 UI는 크게 문자로 보여주고 사용자 역시 문자를 입력해서 상호작용해야하는 Character User Interface, CUI와 이미지와 문자의 혼합된 형태로 보여지고 마우스를 이용해서 상호작용할 수 있는 Graphic User Interface, GUI로 나누어진다.

 

문자나 글자로만 상호작용하는 CUI는 컴퓨터의 성능이 모자라서 그래픽으로 UI를 보여주기 힘들던 옛날 게임에서나 볼 수 있는 방식의 UI다. 최근에 와서는 옛날 게임의 감성을 되살리고자 하는 게임에서 이 CUI 방식을 일부 차용하기도 한다.

 

하지만 컴퓨터의 성능이 충분히 올라온 지금은 대부분의 게임에서 GUI를 사용한다.

 

유니티 엔진의 GUI 시스템, UGUI

 

캔버스

 

유니티 엔진에서 사용되는 GUI 시스템을 유니티 GUI 줄여서 UGUI라고 부른다. 그럼 이제 에디터에서 UGUI의 기본부터 차근차근 살펴보자.

 

 

 

하이어라키 뷰에 우클릭 해보면 생성할 수 있는 게임 오브젝트의 종류를 볼 수 있는데 그 중에 UI 항목이 있다.

 

글자를 표현하는 Text, 그림을 표현하는 Image, 클릭할 수 있는 Button 등 UI로 사용할 수 있는 여러가지 형태의 게임 오브젝트들을 볼 수 있다.

 

 

우선 UI 게임 오브젝트 중에서 Canvas를 생성해보자. Canvas를 생성하면 씬 뷰에 하얀 선으로 직사각형이 표시되는 것을 볼 수 있다.

 

Canvas는 유니티 엔진에서 UI를 배치하기 위한 영억으로 모든 UI가 화면에 표시되기 위해서는 이 Canvas 컴포넌트가 부착된 게임 오브젝트의 자식 게임 오브젝트여야 한다.

 

 

Canvas의 설정은 용도에 따라서 여러가지가 있지만, 방금 생성한 Canvas처럼 Render Mode를 [Screen Space - Overlay]로 된 것을 기본적으로 많이 사용한다. 이 설정은 게임 해상도로 표현되는 스크린 스페이스에 UI를 그리는 설정이다.

 

이 설정에서 Canvas의 해상도는 게임의 해상도를 따른다.

 

 

게임 뷰를 보면 지금 해상도가 1920x1080으로 설정되어 있는 것을 볼 수 있는데 거기에 맞춰서 Canvas의 width와 height도 1920x1080인 것을 볼 수 있다. 게임 뷰의 해상도를 바꿔보면 Canvas의 해상도 역시 자동으로 바뀌는 것을 볼 수 있다.

 

[Screen Space - Overlay]는 일반적인 평면 UI에서 주로 사용되는 설정입니다.

 

Rect Transform 컴포넌트

 

일반 게임 오브젝트의 Transform
UI 게임 오브젝트의 Rect Transform

 

일반 게임 오브젝트와 UI 게임 오브젝트의 차이점으로는 일반 게임 오브젝트의 경우에는 Transform 컴포넌트로 씬 안에서의 위치를 표현하지만, UI 게임 오브젝트들은 Rect Transform 컴포넌트로 위치를 표현한다.

 

메인 메뉴 만들어 보기

 

간단하게 게임의 메인 메뉴 형태로 UI들을 만들어 보자.

 

2D 작업 모드

 

그 전에 씬 뷰에서 이렇게 원근감이 있는 상태로는 이동도 어렵고 UI 작업이 불편하기 때문에 키보드의 숫자 '2' 버튼을 눌러서 씬 뷰를 2D 모드로 만든다. 2D 모드는 UI 작업이나 2D 게임 작업을 위한 모드로 마우스 휠을 돌려서 확대/축소하고 휠 클릭으로 화면의 위치를 이동시킬 수 있다.

 

 

캔버스에 Button과 Text, Image를 이용해서 위의 이미지와 같이 UI를 구성해보자.

 

 

중간의 꾸미는 모양이 이미지는 이 그림을 다운로드 받아서 사용하면 된다.

 

늘 강조하던 내용이지만, 하이어라키 뷰에서 게임 오브젝트의 이름이 생성된 초기 이름 그대로이면 나중에 필요한 오브젝트를 찾기가 어려워지기 때문에 버튼 이름도 적절하게 바꿔주도록 한다.

 

참고로 유니티 엔진에서 어떤 UI가 더 위에 그려지느냐 하는 우선 순위는 하이어라키 뷰에서의 순서로 결정된다. 지금 하이어라키 뷰를 보면 "Background" Image가 다른 Text나 Button보다 하이어라키 뷰에서 상단에 있는 것을 볼 수 있다. 하지만 씬 뷰나 게임 뷰에서는 제일 뒤에 그려지고 있다.

 

이 "Background" 이미지를 조금씩 아래로 내려보면 다른 버튼과 텍스트를 하이어라키 뷰의 순서에 따라서 가리기 시작하는 것을 볼 수 있다.

 

이렇게 다른 UI 보다 앞에 나오길 바라는 UI는 하이어라키 뷰에서 아래로 옮기고, 뒤에 나오길 바라는 UI는 하이어라키 뷰에서 위로 옮겨서 UI의 순서를 조정할 수 있다.

 

이번에는 간단하게 방금 만든 메인 메뉴에 기능을 추가해보도록 하자. 먼저 MainMenu라는 이름으로 C# 스크립트를 생성한다.

 

public class MainMenu : MonoBehaviour
{
    // 새 게임 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickNewGame()
    {
        Debug.Log("새 게임");
    }

    // 불러오기 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickLoad()
    {
        Debug.Log("불러오기");
    }

    // 옵션 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickOption()
    {
        Debug.Log("옵션");
    }

    // 종료 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickQuit()
    {
#if UNITY_EDITOR // 에디터에서만 실행되는 코드
        UnityEditor.EditorApplication.isPlaying = false;    // 에디터의 플레이 모드를 중단
#else   // 빌드된 게임에서 실행되는 코드
        Application.Quit(); // 실행되고 있는 게임 프로그램을 종료
#endif
    }
}

 

코드를 저장하고 에디터로 돌아와서 "Main Menu Canvas"에 MainMenu 컴포넌트를 추가해준다.

 

 

그 다음에 각 버튼의 On Click 이벤트에 [+] 버튼을 누른 뒤, MainMenu 컴포넌트를 붙인 게임 오브젝트를 할당하고 각 버튼에 맞는 함수를 호출하도록 만든다.

 

그리고 플레이 버튼을 눌러 게임을 실행하고 각 버튼을 눌러보면 함수에 넣어둔 로그가 출력되고 마지막으로 종료 버튼을 누르면 플레이 상태가 종료되는 것을 알 수 있다.

 

이렇게 유니티에서 제공하는 UI 관련 컴포넌트들을 잘 응용하면 거의 모든 UI 기능들을 구현할 수 있다.

반응형

UI 비법서 (5) 

캔버스의 분할


작성 기준 버전 :: 2019.2


유니티에서 모든 UI는 캔버스(Canvas) 위에서 그려진다. 아직 유니티 엔진을 이용한 개발에 익숙하지 못한 개발자들은 모든 UI를 한 캔버스에 만드는 경우가 많다.


하지만 모든 UI를 한 캔버스로 몰아넣으면 모든 UI를 그리는 과정에서 불필요한 낭비가 발생하게 된다.


[그림 1]

 

[그림 1]을 보면 하나의 캔버스 안에 여러 개의 이미지가 포함되어 있는 것을 볼 수 있다. Checker 오브젝트는 흰 색과 검은 색으로 이루어진 이미지이고, 그 뒤에 Background Slide라는 이름의 회색 이미지가 크게 배치되어 있다.


[그림 2]

 

우선 이 상태에서 UI는 어떤 과정을 통해서 그려지는지 확인하기 위해서 프레임 디버거(Frame Debugger)를 실행해보자. 프레임 디버거는 게임에서 각 프레임이 그려질 때 어떤 과정을 거쳐서 그려지는지 보여주는 디버깅 툴이다. 이것을 통해서 어떤 렌더링 과정에서 시간을 소모하는지를 확인할 수 있는 좋은 도구이다. 프레임 디버거를 실행하기 위해서는 유니티 상단 메뉴에서 [Windows > Analysis > Frame Debugger]를 선택하면 된다.


[그림 3]


그러면 [그림 3]과 같은 프레임 디버거 창이 열린다.


[그림 4]

 

프레임 디버거 창을 띄운 후, 플레이를 시작하고 프레임 디버거 창의 Enable 버튼을 누르면 한 프레임이 그려지는데 어떤 과정으로 몇 단계나 거쳐서 실행되는지 확인할 수 있다.


[그림 5]

 

이 상태에서 7 of 7 옆에 있는 넘기기 버튼을 눌러서 확인해보면 총 7단계를 거쳐서 화면이 그려지고 있고 그 중에 UI는 6, 7단계 두 단계를 거쳐서 먼저 회색 바탕이 그려지고 그 위에 체크 무늬 이미지가 그려지는 것을 볼 수 있다.


using UnityEngine;

using UnityEngine.UI;


public class FillingImage : MonoBehaviour

{

    private Image image;


    void Start()

    {

        image = GetComponent<Image>();

    }


    bool isFill = false;

    float timer = 0f;


    void Update()

    {

        if(timer >= 1f)

        {

            timer = 0f;

            isFill = !isFill;

        }

        timer += Time.deltaTime;

        image.fillAmount = isFill ? timer : 1f - timer; 

    }

}

 

그렇다면 이번에는 회색 바탕의 이미지인 Background Slide 오브젝트에 위와 같은 코드를 추가해서 이미지가 계속해서 채워졌다가 사라지는 동작을 반복하도록 만들어보자.


[그림 6]


코드를 모두 작성했다면 Background Slide 게임 오브젝트에 컴포넌트로 붙여준다.


[그림 7]

 

이것을 실행해보면 [그림 7]와 같이 보여진다. 하지만 단순히 겉으로만 보이는 상황으로는 어떤 낭비가 발생하는지 알 수 없다. 그러면 어떤 낭비가 발생하는지 확인하기 위해서 다시 프레임 디버거로 살펴보자.


[그림 8]

 

프레임마다 분명 7단계였던 렌더링 과정이 회색 이미지가 체커와 겹치게 되면서 8단계로 늘어나는 것을 볼 수 있다.


[그림 9]

 

이 8단계로 늘어난 렌더링 과정을 살펴보면 7단계일 때는 분명 회색 바탕을 먼저 그리고 체크 무늬 이미지를 그렸던 과정이 회색 바탕과 겹치지 않은 체크 무늬 이미지를 그리는 과정, 회색 바탕 이미지를 그리는 과정 그리고 회색 바탕과 겹친 체크 무늬 이미지를 그리는 과정으로 나누어진 것을 볼 수 있다. 이것은 렌더링 과정이 아주 작게 늘어난 예시로, UI 캔버스의 구조가 복잡해지면 복잡해질 수록 어떤 성능의 낭비를 가져올지 알 수 없게 된다.


이것을 해결하기 위한 방법이 바로 이번 글의 주제인 캔버스의 분할이다.


[그림 10]

 

[그림 10]을 보면 매 프레임 변동이 발생하는 Background Slide와 늘 고정되어 있는 Checker를 다른 캔버스로 나누어 배치한 것을 볼 수 있다.


[그림 11]

 

이렇게 캔버스를 분할한 뒤 다시 실행해서 매 프레임의 렌더링 단계를 살펴보면 다시 7단계로 줄어든 것을 확인할 수 있다.


[그림 12]

 

렌더링 단계 역시 살펴보면 회색 바탕을 먼저 그리고 회색 바탕과 체크 무늬 이미지가 겹치는 것과 상관없이 한꺼번에 체크 무늬 이미지를 그리는 것을 볼 수 있다.


이렇게 캔버스를 분할하는 것 역시 렌더링 최적화를 위한 하나의 방법이 될 수 있다. 가능하면 변동되는 타이밍이 비슷한 UI끼리 그룹을 지어서 캔버스로 묶는 것이 좋다.

반응형

+ Recent posts