안녕하세요~ Halcy 입니다.


저번시간에는 디테일 커스텀을 하기전에 필요한 에디터 모듈을 제작하여 추가하는 것 까지 하였습니다!


오늘은 본격적으로 디테일 커스텀을 해볼 예정입니다. 


본 튜토리얼은 언리얼 문서의 디테일 패널 커스터마이징을 기본적으로 따라하며, 거기에 + - 가 있을 예정입니다!


그럼 본격적으로 시작해 보겠습니다.


(사진 1-1) 셋업 절차


언리얼 문서의 디테일 패널 커스터마이징을 보면 가장 먼저 나오는 것이 프로퍼티를 커스터마이징해 넣을 클래스를 생성합니다. ILayoutDetails 를 상속해야 합니다. 라고 합니다. 


그럼 여기서 ILayoutDetails 가 무엇일까요? 

저희가 코딩할때 항상 하듯이 저것이 무슨 역할의 클래스인지를 먼저 찾아보겠습니다. 언리얼 API에서 말이죠!


(사진 1-2) ILayoutDetails 검색결과


두둥..! ILayoutDetails라는 클래스는 존재하지도 않네요...? 접두사로 I가 붙었으니 인터페이스 인것은 알겠는데 언리얼 공식 다큐먼트에서 사용하라고 하는 클래스가 엔진내부에 존재하지조차 않는다...!! 


이것이 과거에 존재 했다가 없어진 것인지... 아니면 처음부터 존재하지 않은 것인지... 저희는 알 길이없습니다.. 하지만 저희는 디테일 탭을 커스텀하고 싶다구요... ! 


그래서 제가 찾아본 결과 그리고 언리얼 엔진 내부를 뜯어본 결과..!


(사진 1-3) StaticMeshComponentDetails.h 파일내부


스태틱 메쉬 컴폰넌트의 디테일탭을 구성하는 헤더파일을 뜯어보니 IDetailCustomization 이라는 클래스를 사용하더군요..! 


네 맞습니다. 이게 과거 언젠가 업데이트에 바뀐 것인지 모르지만 저 다큐먼트에서 말하는 클래스는 ILayoutDetails가 아닌 IDetailCustomization 입니다. 


자! 이제 IDetailCustomization 클래스가 무슨 클래스인지 알아보겠습니다.


(사진 1-4) IDetailCustomization 출처( http://api.unrealengine.com/INT/API/Editor/PropertyEditor/IDetailCustomization/index.html )언리얼엔진 공식문서


언리얼엔진 API에서 찾아보면 위 사진처럼 나옵니다. 위 클래스의 설명을 번역해보면


"특정 클래스에 대한 세부 정보를 배치하는 모든 클래스의 인터페이스"


라고 합니다. 한마디로 특정 클래스의 디테일탭의 배치를 담당하는 클래스 인터페이스 정도로 이해 할 수 있을 것입니다.


또한 이 인터페이스가 가지고있는 함수의 설명을 보면 디테일들을 커스텀 할때 불러야만 하는 함수라고 정의 되어있습니다.


이러한 설명들을 보았을 때 현재 저희가 딱 필요로 하는 클래스라는 것을 알 수 있을 것입니다.


이제 이것을 상속하는 클래스를 하나 만들겠습니다.


(사진 1-5) IDetailCustomization 상속하는 클래스 생성(DetailCustomization)


사진 1-5와 같이 IDetailCustomization 을 상속하는 클래스 DetailCustomization을 생성 하였습니다.


(사진 1-6) IDetailCustomization 헤더파일


헤더파일은 사진 1-4에서 나온 CustomizeDetails 함수를 오버라이드 해줍니다. 저희는 디테일을 커스텀 할것이기 때문에 디테일을 커스텀 할때 쓰라는 함수를 안쓰면 안되겠죠!?


그리고 Static 함수로 공유 레퍼런스(TSaredRef) 로 IDetailCustomization 을 반환하는 MakeInstance함수를 하나 정의 해줍니다. 이것은 추후에 모듈에 추가할때 필요한 부분입니다. 스마트 포인터 또한 Slate 튜토리얼이 끝나고 다룰 수 있으면 다뤄 보겠습니다....


(사진 1-7) IDetailCustomization CPP 파일


그 후 CPP 파일에서는 헤더에서 선언한 함수들을 구현해 줍니다. MakeInstance함수는 이름에서 알 수 있듯이 MakeShareable 매크로를 이용하여 C++의 포인터로 초기화한 해당 클래스를 공유 포인터로 초기화 시켜 반환합니다.


그리고 이제 CustomizeDetails 함수에서 본격적으로 디테일 탭을 커스텀 할 것 입니다.


(사진 1-8) 언리얼 공식문서 출처(http://api.unrealengine.com/KOR/Programming/Slate/DetailsCustomization/ )언리얼엔진 공식문서


언리얼 공식문서에서 Lighting탭을 커스텀하였으므로 저희도 그대로 따라 해보도록 하겠습니다.

아니나 다를까 IDetailCategory 라는 클래스는 없군요...

그래서 제가 또 찾아보았습니다.


(사진 1-9) IDetailCategoryBuilder 출처( 언리얼 공식문서)


찾아보니 IDetailCategoryBuilder 라는 것이 나오더군요. 일단 이름은 비슷해 보이는데 맞는지는 모르겠습니다.


그래서 고민을 하다가 DetailBuilder.EditCategory 의 반환자가 IDetailCategoryBuilder라는 것을 알고 이것이 맞다는 것을 깨달았습니다. 이것 역시 과거 어느 순간에 바뀐것 같군요.. ㅠㅠ


(사진 1-10) DetailCustomization.cpp에 추가


공식문서를 그대로 가져다 쓰면 위의 TEXT("OptionalLocalizedDisplayName")에서 에러가 나더라구요. 그래서 해당 부분을 직접 FText로 변환해주니 에러가 없어졌습니다.


(사진 1-11) 언리얼 공식문서 출처(http://api.unrealengine.com/KOR/Programming/Slate/DetailsCustomization/ )언리얼엔진 공식문서


언리얼 공식문서 튜토리얼을 따라하면서 가장 큰 문제가 되는부분이 여기였습니다. 공식문서 튜토리얼에선 AddProperty 를 저런식으로 사용하였는데..!


(사진 1-12) AddProperty 출처(언리얼공식문서)


네... 보시는 바와 같이 단순 스트링이 두개 들어가는 AddProperty는 없습니다... 그래서 저 형식에 맞추어 만들어 보려했는데 제 역량이 부족하여 만족스러운 결과가 나오지 않아... 저희는 여기서 슬레이트형식으로 만들어 보겠습니다. 그리고... !


(사진 1-13) 프로퍼티를 추가하는 방법 2가지


언리얼 공식문서에서 보면 프로퍼티를 추가하는 방법은 두 가지가 있는데 그 중 AddProperty 를 사용 하는것은 멀티박스 스타일 레이아웃을 사용하는 방법으로, 프로퍼티 재배치에 빠르다고 합니다. 한 마디로 이것은 기존의 만들어둔 프로퍼티를 '재배치' 하는 것 이라고 이해 할 수 있을 것입니다... !!( 저는 그렇게 이해했는데 아닐 수도.. ) 다른 한가지 방법은 Slate 문법을 사용하는 방법으로, 가장 '확실한' 커스터마이징 옵션이 제공됩니다. 라고 하니 저희는 역시 가장 '확실한' 방법으로 커스텀해보겠습니다..! (절대 제가 AddProperty를 사용못해서 그런건 아닙니다...!)


그럼 이제 Slate를 사용해서 프로퍼티를 추가하려면 어떻게 해야하는가..! 에 대하여 알아보도록 하겠습니다.


멀티 박스 스타일은 AddProperty 를 사용 하는데 Slate를 사용하기 위해선 무슨 함수를 써야 하는가 찾아보았습니다.


(사진 1-14) AddCustomRow 출처(언리얼공식문서)


찾아보면 Slate를 사용할 수있는 방법이 2가지 정도 있습니다. 위 AddCustomRow함수를 사용하는 방법과 HeaderContent 함수를 사용 하는 방법입니다. 


두 가지 방법의 차이는 AddCustomRow는 FDetailWidgetRow라는 클래스를 반환하는데 여기에 NameContent라는 함수가 있습니다. 이 함수는 컨텐츠를 이름 슬롯에 배정하는 함수로 이것을 활용하여 Slate를 사용하여 만든 컨텐츠를 슬롯에 배정할 수 있습니다. 


이름 슬롯이란 기본적으로 디테일탭은 SSplitter를 이용하여 두가지 영역으로 구분되어있습니다. 한쪽이 NameSlot 다른쪽이 ValueSlot입니다. NameSlot에 배치하기위해선 NameContent()함수를, ValueSlot에 배치하기위해선 ValueContent()를 이용하면 됩니다.


HeaderCotent 는 직접 SWidget을 상속하는 클래스를 만들어 이것을 통째로 카테고리에 추가 할 수 있는 함수 입니다. 


저희는 현재 매우 큰 Slate판을 만드는 것이 아닌 간단한 수정만 할 것이므로 AddCustomRow를 활용하여 만들어 보도록 하겠습니다.


(사진 1-15) Slate첫 추가


가장 기본적인 STextBlock을 이용하여 MyCustom이라는 스트링을 Lighting 탭에 추가 하여보겠습니다. 사진 1-15처럼 추가 해주시면 됩니다. AddCustomRow의 첫 인자는 저희가 디테일탭에서 검색을 할때 사용할 필터를 입력해주시면 됩니다.


이렇게 작성하시고 컴파일을 하고 확인을 하려 했으나.... 그 어느 디테일 탭의 Lighting탭에도 MyCustom이라는 글자는 없습니다.... 


네 아직 끝난게 아닙니다.


저희는 현재 변경할 디테일탭이 어느 디테일탭인지 정하지 않았습니다..! Lighting디테일 탭은 스켈레톤에도 스태틱 메쉬에도 여러군데에 존재하는데 저희는 그 어떤것인지 정하지 않았죠..!! 


좀 더 컴퓨터 처럼 생각해보자면..! 지금 저희가 만든 FDetailCustomization 이라는 클래스가 어느 타이밍에 만들어지고 어느 타이밍에 작동을 시작하는지 조차 모르겠습니다... !!


이제 모듈을 따로 만든 목적이 나옵니다. 해당 클래스를 모듈에서 작동을 시작시켜야죠!!


(사진 1-16) 저번에 추가한 모듈의 헤더


저번 시간에 추가한 모듈의 헤더에 위와 같이 함수 두개를 오버라이드 해줍니다.


해당 함수는 모듈이 시작할 때와 모듈이 끝날때 실행되는 함수 들입니다. 


디테일 탭을 커스텀 하려면 엔진이 켜질때 한마디로 모듈이 불려와 질때 디테일탭에 관한 함수들도 다 불려와 있어야 엔진이 진짜 디테일탭을 생성할 때 바로바로 불려올 수 있을 것입니다.


(사진 1-17) 모듈 cpp파일


이제 모듈이 시작할 때 우리가 만든 FDetailCustomization 클래스도 같이 불러와있어!! 라는 명령을 지정해야 할 것입니다. 


사진 1-17을 보시면 PropertyModule 이라는 변수를 만들고 그 변수를 LoadModuleChecked로 FPropertyEditorModule인 "PropertyEditor" 모듈을 불러왔나 체크하고 불러왔으면 이것을 PropertyModule에 넣어주도록 되어있습니다.


말이 어려운데 간단히 말하면 FPropertyEditorModule 클래스의 이름이 PropertyEidtor인 모듈이 불려왔냐를 물어보는 것이고 불려 왔다면 변수에 넣어라 입니다.


그 후 PropertyModule에 저희가 만든 클래스의 인스턴스도 지정해줍니다. 그리고 저는 UStaticMeshComponent의 Lighting 탭을 커스텀 할 것이므로 위 처럼 구성하여 주었습니다. 


마찬가지로 모듈에 등록을 했으면 해제도 해주어야겠지요. ShutdownModule에 위와 같이 구성하였습니다.


이제 다된 것 같습니다 !! 다시 실행해보죠!!


..... 역시나 안되죠..? 분명 스태틱메쉬 컴포넌트의 라이팅 탭을 조절하라고 했는데 여전히 그대로일 것입니다... 왜일까요... 고민을 한참 해보다 문득 이 모듈(MyEditorModule)이 로드되는 타이밍의 문제가 아닐까.. 생각했습니다.



(사진 1-18) uproject변경



그리고 Uproject파일에서 모듈 부분의 LoadingPhase 부분을 엔진 초기화 후로 바꾸어주었습니다. 생각해보면 저희가 커스텀한 디테일탭이 엔진 초기화 전에 있었다면, 그 후 다시 엔진은 원래의 상태로 디테일 탭을 다시 재 초기화 할 것입니다. 그러면 저희가 커스텀한 디테일탭은 원래의 것으로 덮어씌워지고 저희가 확인 할 수 없을 것 입니다. 그러므로 디테일탭을 커스텀 하기위해선 엔진 이후에 모듈을 불러와야 합니다..!


그리고 확인해보면...!


(사진 1-19) 좌측은 스켈레톤의 Lighting 탭 우측은 스태틱메쉬컴포넌트의 라이팅탭


위와 같이 저희가 원하던 결과가 나오는 것을 알 수 있습니다..!!


여기까지가 이번시간에 준비한 튜토리얼입니다. 


다쓰고 보니 설명이 조금 복잡한 감이 있지만.... 이해 안가시는 부분은 언제든 알려주시면 최대한 도움드리도록 하겠습니다.. ㅠㅠ


그럼 여기까지 읽어주셔서 감사하고 다음시간엔 Slate 중 하나인 SComboBox 를 사용하여 언리얼 공식문서에 나오는 것과는 조금다르지만 Dynamic과 Static등을 선택할 수 있도록 해보겠습니다!!


감사합니다!





+ Recent posts