킹머핀의 제작 일지
UNT) 커스텀 에디터 만들기 본문
졸작을 위해 유니티 커스텀 에디터를 제작하려 한다. 원래는 커스텀 창을 먼저 만드려 했지만, 게임오브젝트 컴포넌트 값을 제어할 수 있어야 하고, 창 자동 스크롤 바를 만드는 것부터 못 하겠더라. ㅎㅎㅎ 그건 나중에 필요하면 하자.
위의 매뉴얼에서 볼 수 있듯이, GUILayout과 EditorGUILayout 클래스로 GUI를 추가할 수 있다. 그런데, 이 둘은 무슨 차이지? 게다가, GUI도 있고 EditorGUI도 있다. 이 넷은 무슨 차이지?? 대충 알아봤다.
GUI / GUILayout / EditorGUI / EditorGUILayout
우선 GUI와 GUILayout 클래스의 차이는 레이아웃 자동 생성 여부다. 그렇다면 EditorGUI와 EditorGUILayout도 마찬가지일 터. 에디터 창을 만들 때는 UI의 크기와 범위가 일정할 수록 가시성이 좋으므로 GUILayout이나 EditorGUILayout을 더 자주 사용하면 편할 것이다.
GUILayout과 EditorGUILayout 클래스의 차이는 에디터 최적화 여부다. 아래 이미지를 보면 EditorGUILayout으로 생성한 토글 버튼의 위치는 정확히 에디터 창 폭(가로 길이)의 절반에 걸쳐 정렬된다.
그런데 토글 버튼처럼 Lable과 Content로 된 GUI를 제외한 나머지는 둘이 차이가 있는지 모르겠다.. (있으니까 따로 만든걸까?)
그런데 EditorGUILayout에는 없는 GUI가 GUILayout에는 있는 경우가 있다. 예를 들면, 버튼이다. 그래서 GUILayout만 사용하거나 둘 다 사용해서 에디터 창을 만들 수 있을 것이다. 나는 둘 다 사용하기로 했다.
이 블로그를 참고하면서 시작하면 좋다.
이 글은 전문적인 글의 번역본이며 양도 많아서 활용법을 배우기 좋다.
연결한 스크립트의 필드 가져오기
나는 에디터(개발자)와 런타임(사용자)가 조작할 수 있는 옵션을 만드려 한다. 그래서 에디터에서만 작동하는 에디터 상속 스크립트에 모든 필드를 넣을 수는 없다. 그렇다면 Monobehaviour을 상속받는 스크립트를 게임오브젝트에 장착하고, 그 필드를 에디터 상속 스크립트에서 제어할 필요가 있다.
다른 스크립트에서 필드를 가져올 방법은 크게 두 가지다.
1. 아래 코드는 맨 위의 출처에서 가져옴. 게임오브젝트에 장착한 스크립트에서 선언한 필드의 이름을 가져온다.
장점은, SerializedProperty and SerializedObject are classes for editing properties on objects in a completely generic way that automatically handles undo and styling UI for Prefabs.
단점은, 필드 하나 당 SerializedProperty 객체를 하나씩 일일이 할당해야 한다는 점.
[CustomEditor(typeof(LookAtPoint))]
//[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
SerializedProperty lookAtPoint;
void OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
...
2. 아래 코드는 이 출처에서 가져옴.
장점은, 해당 스크립트 객체 하나만 생성해주면 모든 공용 필드를 가져올 수 있다.
단점은, SerializedProperty의 장점이 없다.
[CustomEditor (typeof(MyPlayerAlternative))]
public class MyPlayerEditorAlternative : Editor
{
public override void OnInspectorGUI()
{
MyPlayerAlternative mp = (MyPlayerAlternative)target;
mp.damage = EditorGUILayout.IntSlider ("Damage", mp.damage, 0, 100);
...
나는 Prefab overrides가 당장은 필요하지만, 그것도 나중에 미리 정의한 프로파일을 불러오는 기능을 만들 것이기 때문에 2번 방법을 사용하기로 했다.
조작한 GUI 값을 적용하기
커스텀 에디터의 GUI를 조작하면 연결한 다른 스크립트의 변수 값을 변경하는 코드를 작성하려 한다.
인스펙터를 조작할 때 매 프레임마다 OnInspecterGUI 함수가 호출되는데, 이 때 GUI에 변경사항이 있는지 확인할 수 있다. 예를 들어, 버튼을 눌렀는지를 다음과 같이 확인할 수 있다.
if (GUILayout.Button("This, is, BUTTon."))
{
DoSomething();
}
이렇게 개별 GUI에 대응하지 않아도 되는 방법도 있다. 아래의 두 ChangeCheck 함수 사이에서 어떠한 변경사항이라도 있다면 일괄 적용하는 방식이다.
// 출처 : docs.unity3d.com/kr/2020.3/ScriptReference/EditorGUI.BeginChangeCheck.html
EditorGUILayout.LabelField("New value", labelText);
// Start a code block to check for GUI changes
EditorGUI.BeginChangeCheck();
sliderValue = EditorGUILayout.Slider(sliderValue, 0, 1);
// End the code block and update the label if a change occurred
if (EditorGUI.EndChangeCheck())
{
labelText = sliderValue.ToString();
}
그래서 난 이 둘 중 한 방법으로 모든 GUI를 조작하면 연결한 스크립트의 함수를 호출해서 다른 원하는 스크립트의 변수 값을 변경하려 했다.
근데, 생각해보니 위 코드처럼 슬라이더를 조작한 값을 저장하기 위해 변수를 사용한다면, 변수 대신 속성(Property)을 사용하면, 번거롭게 함수를 일일이 정의하지도 호출하지도 않아도 된다!
(사실 속성이 정확히 어떤 용도인지는 아직도 잘 모르겠으나 훨씬 간편해지므로 사용하기로 했다. 내가 아는 것은 캡슐화가 간편하고 함수를 대체할 수 있다는 정도다.)
근데 문제가 생겼다. 해당 스크립트와 연결된 게임오브젝트와 아직 연결되지 않았다면 OnInspecterGUI가 업데이트될 때마다 오류가 나타난다. 표시와 저장이 동시에 이루어지기 때문이다. 해결 방법은?
- 조건문으로 인스펙터에 표시만 하고 값을 적용하지는 않는다. (쉽고 무식한 방법)
- 번거롭게 함수를 일일이 정의하고 ChangeCheck와 조건문을 이용해서 호출한다.
- 아예 표시하지 않는다. (가장 쉽고 간단하지만 덜 맘에 드는 방법)
1번도 나쁘지 않지만, 역시 쉬운 방법으로 가자. 3번 결정.
막상 적용하고 나니 오히려 좋아보인다. ㅎㅎ&
(나는 이제 다음으로 XR Interacter Toolkit의 Locomotion System을 이해해야 한다.)
'Unity' 카테고리의 다른 글
VR) 유니티 XR Interaction Toolkit 응용 탐구 (0) | 2021.05.06 |
---|---|
VR) 유니티 XR Interaction Toolkit 클래스 탐구 (0) | 2021.04.19 |
VR) 유니티 XR Interaction Toolkit 기본 탐구 (0) | 2021.04.05 |
VR) 유니티에서 오큘러스 Intergration 샘플 씬 탐구 (feat. MacOS' tears) (2) | 2021.04.02 |
VR) 유니티에서 오큘러스 퀘스트 사용하기 우여곡절 (0) | 2021.03.30 |