호우동의 개발일지

Today :

article thumbnail

GUI 레이아웃 시스템을 이해함으로써,
Layout을 좀 더 세밀하게 다룰 수 있다.

예를 들어, EditorGUILayout.Label() 함수를 사용하여
여러 Label을 GUI로 만들면 세로로 Label들이 배치될 것이다.

하지만 LayoutSystem을 이해하고
지금부터 배우는 새로운 함수를 사용하면 좀 더 복잡한 배치가 가능해진다.

 

 


가로/세로 배치

// Layout을 가로로 배치한다.
EditorGUILayout.BeginHorizontal();
{ 
     ..  원하는 GUI 배치 내용물 입력
} 
EditorGUILayout.EndHorizontal();

// Layout을 세로로 배치한다.
EditorGUILayout.BeginVertical();
{
     ..  원하는 GUI 배치 내용물 입력
}
EditorGUILayout.EndVertical();

EditorGUILayout 클래스에 있는 Horizontal 함수를 사용하면 가로로 배치할 수 있다.

여기서 중요하게 봐야 할 점은 항상
가로로 배치할 GUI 내용물들을 Begin과 end 사이에 넣어야 한다는 것이다.

EditorGUILayout.EndHorizontal();을 넘어가는 순간
다시 레이아웃은 기본값인 세로로 돌아오게 된다.

세로 배치 또한 가로배치 함수와 이름만 다를 뿐 똑같은 규칙을 따른다.

다른 점은 레이아웃 시스템의 기본 값이 세로이기 때문에
End를 넘어가도 세로로 유지된다는 점 정도?

 

테스트

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

public class TestEditorWindow : EditorWindow
{

    [MenuItem("MyTool/OpenTool %g")]
    static void Open()
    {
        var window = GetWindow<TestEditorWindow>();
        window.title = "myTool";
        Debug.Log(window.GetType());
    }
    private void OnGUI()
    {
        EditorGUILayout.BeginHorizontal();
        {
	// Horizontal 라벨 10번 반복
            for (int i = 0; i < 10; i++)
                EditorGUILayout.LabelField("Horizontal");
        }
        EditorGUILayout.EndHorizontal();
		
        // Vertical 라벨 10번 반복
        for (int i = 0; i < 10; i++)
            EditorGUILayout.LabelField("Vertical");
    }
}

해당 스크립트를 실행하고 OpenTool을 클릭하면

 

테스트 결과

EndHorizontal() 전까지는 가로로 배치되고,
그 후에는 다시 세로 배치로 돌아와서 세로로 배치되는 것을 확인할 수 있다.

 


가로 세로 섞어서 사용

    private void OnGUI()
    {
        for (int i = 0; i < 5; i++)
        {	
        	// 세로 레이아웃으로 전환
            EditorGUILayout.BeginVertical();
            {
                EditorGUILayout.LabelField("Title :" + i);
                
                // 기존의 필드의 너비와 라벨의 너비를 백업해둠
                var backup1 = EditorGUIUtility.fieldWidth;
                var backup2 = EditorGUIUtility.labelWidth;
				
                //라벨의 너비와 필드의 너비를 50으로 고정
                EditorGUIUtility.fieldWidth = 50;
                EditorGUIUtility.labelWidth = 50;
				
                // 가로 레이아웃으로 전환
                EditorGUILayout.BeginHorizontal();
                {
                    for (int j = 0; j < 3; j++)
                    {
                        EditorGUILayout.TextField(j.ToString(),"put data here");
						// 마지막 가로 요소일때만 제외
                        if (j != 2) EditorGUILayout.Space(10);
                    }
                }
                EditorGUILayout.EndHorizontal();
          		// 세로 레이아웃 전환
				
                // 백업해뒀던 너비와 라벨 값을 다시 넣음
                EditorGUIUtility.fieldWidth = backup1;
                EditorGUIUtility.labelWidth = backup2;
            }
            EditorGUILayout.EndVertical();
            EditorGUILayout.Space(10);
			
            // 세로 5번째 요소가 아닐 때
            if (i < 5)
            {	
            	// 가로 GUI 요소들을 균일한 너비를 가지게 함
                EditorGUILayout.LabelField("", GUI.skin.horizontalScrollbar);
            }
        }
    }

위 코드는 가로 레이아웃과 세로 레이아웃을 혼합하여 GUI를 구성하는 코드를 만든 것이다.

여기서 중간 backup1, backup2를 만든 이유는,
여기서 설정해 뒀던 라벨과 필드의 너비값이 다른 GUI의 영향을 주지 않기 위해 초기화한 것이다.

그리고 GUI.skin.horizontalScrollbar는
그냥 가로 GUI를 균일화 작업한 것만 알고 있으면 된다.

결과는 아래와 같이 나온다.

 

균일화 작업 결과

 

 


레이아웃 영역 지정


기초 사용

GUILayout.BeginArea(Rect rtArea, GUI.skin.window);
{
  // GUI 구성요소..
}
GUILayout.EndArea();

레이아웃 영역 지정이란,
쉽게 말해서 레이아웃 안에 또 다른 레이아웃(블록)을 만드는 것이다.

이 또한 Begin ~ End 형식을 따르는데,
이 사이에 있는 GUI 요소들은 모두 여기 생성한 레이아웃에 배치된다.

블록을 생성하는 데는 위치값과 크기값이 필요하기 때문에 Rect값이 필요하다.

그리고 GUI.skin.window 부분(두 번째 인자)은
해당 영역을 어떻게 그릴 것인가?를 표현한다.

여기서 GUI.skin.window는 윈도 창처럼 표현하겠다고 명시한 것이다.

테스트

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

public class TestEditorWindow : EditorWindow
{



    [MenuItem("MyTool/OpenTool %g")]
    static void Open()
    {
        var window = GetWindow<TestEditorWindow>();
        window.title = "myTool";
        Debug.Log(window.GetType());
    }
    private void OnGUI()
    {
        Vector2 offset = new Vector2(20, 40);
        Rect rtArea = new Rect(offset.x,offset.y, position.width - offset.x * 2, 200);
        GUILayout.BeginArea(rtArea, GUI.skin.window);
        {
            EditorGUILayout.BeginHorizontal();
            {
                for(int i = 0; i < 5; i++) EditorGUILayout.LabelField("TEST");
            }
            EditorGUILayout.EndHorizontal();
        }
        GUILayout.EndArea();
    }
}

결과

윈도 형식으로 레이아웃 안에 새로운 레이아웃이 하나 더 생겼다.


스크롤 뷰

평상시에 볼 수 있는 스크롤링이 가능한 뷰를 만드는 법

EditGUILayout.BeginScrollView(Vector2 scrollbar);
{
  // GUI 구성요소..
}
GUILayout.EndScrollView();

인자로 Vector2인 변수가 하나 들어간다.
이 변수에 현재 스크롤 위치를 저장한다.

Begin ~ end 사이에 있는 GUI 들은 모두 Scroll 뷰에 배치된다.

 

스크롤 뷰와 다중 영역 지정을 이용한 예시

해당 예시는 하나의 영역을 또 다른 영역으로 나누어(Sub layout)
독립적인 공간으로 나누는 multi layout을 구현한다.

private void OnGUI()
{

    Vector2 offset = new Vector2(20, 40);
    Rect rtArea = new Rect(offset.x,offset.y, position.width - offset.x * 2, 200);

    // 틀이 되는 가장 큰 영역
    GUILayout.BeginArea(rtArea, GUI.skin.window);
    {
        Rect rtSub = rtArea;

        // 너비를 절반으로 줄임
        rtSub.Set(0, 0, rtSub.width * 0.5f, rtSub.height);

        // 왼쪽 Sublayout(왼쪽 영역)
        GUILayout.BeginArea(rtSub, "Sub01", GUI.skin.window);
        {
            //스크롤 뷰
            scrollPos1 = EditorGUILayout.BeginScrollView(scrollPos1);
            {
                // 수직 레이아웃
                EditorGUILayout.BeginVertical();
                {
                    for (int i = 0; i < 30; i++) EditorGUILayout.LabelField("Vertical!!");
                }
                EditorGUILayout.EndVertical();

            }
            EditorGUILayout.EndScrollView();
        }
        GUILayout.EndArea();

        rtSub.x += rtSub.width; // 나머지 절반의 너비를 더해 오른쪽으로 이동

        // 오른쪽 Sublayout(오른쪽 영역)
        GUILayout.BeginArea(rtSub, "Sub02", GUI.skin.window);
        {
            //스크롤 뷰
            scrollPos2 = EditorGUILayout.BeginScrollView(scrollPos2);
            {
                // 수평 배치
                EditorGUILayout.BeginHorizontal();
                {
                    for (int i = 0; i < 30; i++) EditorGUILayout.LabelField("Horizontal");
                }
                EditorGUILayout.EndHorizontal();

            }
            EditorGUILayout.EndScrollView();
        }
        GUILayout.EndArea();
    }
    GUILayout.EndArea();
}

큰 영역 안에 왼쪽 서브 영역과 오른쪽 서브 영역으로 나누었다.



왼쪽 서브 영역은 수직 레이아웃을,
오른쪽 서브 영역을 수평 레이아웃으로 했다.

두 쪽 다 스크롤뷰로 만들었다.

스크롤 뷰

 

 


Begin~End 말고 Scope로 표현

위에서는 계속 Begin으로 시작해서 End로 끝나는 형태의 프로그래밍을 해왔는데,
사실 이게 길기도 하고 여간 귀찮은 게 아니다.

그래서 이거보단 편한 방식이 있다.

using(var scope = new EditorGUILayout.HorizontalScope())
{
    GUI 요소들..
}

이렇게 표현하면
EditorGUILayout.BeginHorizontal() ~ EditorGUILayout.EndHorizontal()
과 동일한 효과를 가진다.

여기서 scope를 이용해서 프로그래밍을 조금 해보자.

private void OnGUI()
{
    using (var scope = new EditorGUILayout.HorizontalScope())
    {
        if (GUI.Button(scope.rect, GUIContent.none)) Debug.Log("Clicked");

        for (int i = 0; i < 5; i++)
        {
            GUILayout.Label("TestLabel");
            GUILayout.Box(EditorGUIUtility.FindTexture("BuildSettings.Editor"));
        }
    }
}

여기서 scope.rect는 Horizontal Scope에 그려지는 GUI의 전체이다.
즉, 여기서는 모든 버튼들을 뜻하게 된다.

모든 버튼들이 한 번에 눌러질 것이다..

한번에 눌려지는 버튼

위와 같이 정상적으로 가로 배열이 되고 버튼을 누르면
모든 버튼이 한 번에 눌려지는 것을 확인할 수 있다.