원문 : Optimizing UI Controls

주의 : 번역이 개판이므로 이상하면 원문을 참조하십시오.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.



Optimizing UI Controls


확인 완료한 버전: 5.3 - 난이도: 고급


이 섹션은 특정 유형의 UI 컨트롤들을 제출하는 것에 초점을 맞춘 최적화 가이드의 섹션입니다. 대부분의 컨트롤들은 성능 관점에서 상대적으로 유사하지만, 두 가지는 게임이 출시 가능한 상태에 가까워졌을 때 많은 성능 이슈를 일으키므로 특별합니다.


UI Text


유니티의 내장 Text 컴포넌트는 래스터화된 텍스트 글리프( glyph )를 UI 내부에 디스플레이하기 위한 편한 방법입니다. 그러나 일반적으로 알지 못하는 여러 가지 동작이 존재하며, 아직까지 주로 성능 핫스팟이 됩니다. UI 에 텍스트를 추가할 때, 텍스트 글리프는 실제로는 글자 하나당 개별적인 쿼드( quad )로 렌더링된다는 점을 기억해야 합니다. 이러한 쿼드들은 글리프를 둘러싼 빈 공간의 많은 부분을 차지하는 경향이 있습니다. 이는 그것의 모양에 의존하며, 원치않게 다른 UI 요소들의 배칭을 저해하는 텍스트가 배치되는 경우가 많습니다.


Text mesh rebuilds


한 가지 주요 이슈는 UI 텍스트 메쉬를 리빌드하는 것입니다. UI 텍스트 컴포넌트가 변경될 때마다, 그 텍스트 컴포넌트는 실제 텍스트를 디스플레이하기 위해서 사용되는 폴리곤들을 재계산해야 합니다. 이 재계산은 텍스트가 변하지 않더라도 텍스트 컴포넌트나 그것의 부모 게임 오브젝트들이 단순히 disable 되거나 다시 enable 될 때도 발생합니다.


이러한 동작은 점수판이나 통계를 위해 많은 개수의 레이블을 디스플레이하는 UI 에서 문제가 됩니다. 유니티 UI 를 가리거나 보여주는 가장 일반적인 방법은 UI 를 포함하는 게임오브젝트를 enable/disable 시키는 것이며, 많은 개수의 텍스트 컴포넌트를 가지고 있는 UI 는 그것들이 디스플레이될 때마다 원치않는 프레임율 스파이크를 일으키게 될 것입니다.


이 이슈에 대한 잠재적인 우회책을 원한다면, 다음 챕터의 Disabling Canvas Renderes 를 참고하시기 바랍니다.


Dynamic fonts and font atlases


동적 폰트는 전체 문자들의 집합이 매우 많거나 런타임에 앞서 알려지지 았을 때 텍스트를 렌더링하기 편한 방법을 제공합니다. 유니티 구현에서는, 이러한 폰트들은 UI Text 컴포넌트 내에 문자들에 기반해 런타임에 글리프 아틀라스를 빌드합니다.


로드된 각각의 개별 폰트 오브젝트는 자신만의 텍스쳐 아틀라스를 유지할 것입니다. 심지어는 그것이 다른 폰트들과 동일한 폰트 패밀리를 사용한다고 해도 말이죠. 예를 들어, 한 컨트롤 내에서 굵은( bold ) 텍스트를 가진 가진 Arial 을 사용하고, 다른 컨트롤에서 Arial Bold 를 사용하면 동일한 결과를 산출하지만, 유니티는 두 개를 개별적인 텍스쳐 아틀라스에 유지할 것입니다 - 하나는 Arial 을 위해 다른 하나는 Arial Bold 를 위해( 역주 : 하나는 Arial 폰트를 사용하고 텍스트에 굵기 옵션을 준 것이고, 다른 하나는 Arial Bold 폰트를 사용한 것을 의미하는 듯 ).


성능 관점에서 볼 때, 유니티 UI 의 동적 폰트가 개별 크기, 스타일, 문자를 위해 폰트 텍스쳐 아틀라스에 하나의 글리프를 유지한다는 것을 이해하는 것이 중요합니다. 즉, 만약 UI 가 두 개의 텍스트 컴포넌트를 가지고 있고, 둘다 'A' 라는 문자를 디스플레이한다고 하면:


    • 만약 두 Text 컴포넌트가 같은 크기를 공유한다면, 폰트 아틀라스는 그것 안에 하나의 글리프를 가지게 될 것입니다.
    • 만약 두 Text 컴포넌트가 같은 크기를 공유하지 않는다면( 예를 들어 하나는 16 포인트이고 다른 하나는 24 포인트라면 ), 폰트 아틀라스는 서로 다른 크기의 문자 'A' 를 위해 두 개의 복사본을 가지게 될 것입니다.
    • 만약 하나의 Text 컴포넌트는 bold 이고 다른 하나는 그렇지 않다면, 폰트 아틀라스는 굵은 'A' 와 보통 'A' 를 포함하게 될 것입니다.


동적 폰트를 가진 UI Text 오브젝트에 아직 폰트 텍스쳐로 래스터화되지 않은 글리프가 있다면, 폰트 텍스쳐 아틀라스는 반드시 리빌드되어야만 합니다. 새로운 글리프가 현재 아틀라스에 맞다면, 그것이 추가되고 그 아틀라스는 그래픽 디바이스에 다시 업로드될 것입니다. 그러나 현재 아틀라스가 너무 작다면, 시스템은 아틀라스를 리빌드할 것입니다. 이는 두 개의 단계로 수행됩니다.


첫째, 아틀라스는 같은 크기로 리빌드됩니다. 이 때 현재 활성화된 UI Text 컴포넌트에 의해 보이고 있는 글리프만이 사용됩니다. 만약 시스템이 현재 사용중인 글리프들을 새로운 아틀라스에 끼워 맞추는 것에 성공한다면, 그것은 그 아틀라스를 래스터화하고 다음 단계로 진행하지 않습니다.


둘째, 만약 현재 사용중인 글리프 집합이 현재 아틀라스와 같은 크기의 아틀라스에 끼워 맞춰질 수 없다면, 해상도가 더 낮은 쪽을 두 배로 늘린 더 큰 아틀라스가 생성됩니다. 예를 들어 512x512 아틀라스는 512x1024 아틀라스로 확장됩니다.


위의 알고리즘 때문에, 동적 폰트 아틀라스는 한 번 생성되고 나면 크기가 늘어나기만 합니다. Given the cost of rebuilding the texture atlases, 리빌드 동안 반드시 최소화해야 합니다. 이는 두 가지 방식으로 수행될 수 있습니다:


가능할 때마다, 원하는 글리프 셋을 위해서 비-동적 폰트들과 preconfigure support 를 사용하십시오. 이는 일반적으로 제약이 잘 된 Latin/ASCII 문자들과 같은 적은 범위의 크기를 가지는 문자 집합을 사용하는 UI 들을 위해 잘 동작합니다.


만약 전체 유니코드 셋과 같은 극단적으로 큰 범위를 가진 문자들이 지원되어야만 한다면, 폰트는 Dynamic 으로 설정되어야만 합니다. 예측할 수 있는 성능 문제들을 피하기 위해서는, 시작시에 폰트 글리프 아틀라스를 Font.RequestCharactersInTexture 를 통해 적절한 문자들의 집합을 가지도록 미리 준비시키기 바랍니다.


폰트 아틀라스 리빌드는 변경되는 각 UI Text 컴포넌트들을 위해서 개별적으로 발동된다는 것에 주의하십시오. 극단적으로 많은 개수의 Text 컴포넌트들을 띄울 때는, Text 컴포넌트의 내용에서 유일한 문자들을 수집하고 폰트 아틀라스를 미리 준비해야 이득입니다. 이는 글리프 아틀라스가 새로운 글리프가 나타날 때마다 한 번씩 리빌드되지 않고 한 번만 리빌드될 수 있도록 보장해 줄 것입니다.


폰트 아틀라스가 리빌드될 때, 활성화된 UI Text 컴포넌트에 현재 포함되어 있지 않은 모든 문자들은, 그것들이 Font.RequestCharactersInTexture 호출의 결과로서 원래부터 아틀라스에 추가되어 있는 경우라 할지라도, 새로운 아틀라스에 제출되지 않는다는 것에도 주의하시기 바랍니다. 이러한 제한을 우회하기 위해서는, 모든 원하는 문자들이 준비되어 있는 상태로 남아 있도록 하기 위해 Font.textureRebuilt 델리케이트를 구독하고 Font.characterInfo 를 질의하십시오.


Font.textureRebuilt 델리게이트는 현재 문서화되어 있지 않습니다. 그것은 단일 인자를 가진 유니티 이벤트입니다. 이 인자는 그것의 텍스쳐가 리빌드되어 있는 폰트입니다. 이 이벤트의 구독자는 다음 구문을 따라야 합니다:



Specialized glyph renderes


각 글리프 사이에 상대적으로 고정된 위치를 가지고 있는 잘 알려져 있는 글리프의 경우에는, 그들의 글리프를 디스플레이하는 커스텀 컴포넌트를 작성하는 것이 이득입니다. 이것의 예는 스코어 디스플레이가 있습니다.


점수를 위해, 디스플레이 가능한 문자들은 잘 알려진 글리프 셋( 숫자 0 ~ 9 )으로부터 그려지며, 지역에 따라 변하지 않고, 서로 고정된 거리로 보여집니다. 정수를 그것의 숫자로 분리하고 적절한 숫자 스프라이트를 렌더링하는 것은 상대적으로 단순합니다. This sort of specialized digit-display system can be built in a manner that is both allocationless and considerably faster to calculate, animate and display than the Canvas-driven UI Text component.


Fallback fonts and memory usage


큰 문자 셋을 지원해야만 하는 애플리케이션을 위해서는, 폰트 임포터의 "Font Names" 필드에서 많은 폰트들을 리스팅하고자 하는 마음이 듭니다. "Font Names" 필드에 리스팅된 모든 폰트들은 글리프가 주요 폰트 내에 배치될 수 없는 상황에서 대안으로 사용될 수 있을 것입니다. 폴백의 순서는 "Font Names" 필드에서 리스팅된 순서에 의해서 결정됩니다.


그러나 이러한 동작을 지원하기 위해서는 유니티는 "Font Names" 필드에서 리스팅된 모든 폰트들을 메모리에 로드해서 유지할 것입니다. 만약 폰트 문자 셋이 매우 크다면, 폴백 폰트에 의해 소비되는 메모리의 양이 매우 커질 것입니다. 이는 일본어 한자나 중국 문자들과 같은 그림 문자( pictographical ) 폰트를 포함할 때 자주 보이는 현상입니다.


Best Fit and performance


일반적으로, UI Text 컴포넌트의 Best Fit 설정은 절대 사용되어서는 안 됩니다.


"Best Fit" 는 동적으로, 이는 Text 컴포넌트의 바운딩 박스 내에 오우버플로우 없이 디스플레이될 수 있으며 구성 가능한 최소/최대 포인트 크기로 잘린, 가장 큰 정수 포인트 크기로 폰트의 크기를 조정합니다. 그러나 유니티는 디스플레이되고 있는 개별 문자의 크기별로 개별 글리프를 폰트 아틀라스에 렌더링하기 때문에, Best Fit 를 사용하면 다양한 크기의 글리프를 사용하는 아틀라스의 크기를 급격히 넘어서게 될 것입니다.


유니티 5.3 에서는, Best Fit 에 의해 사용되는 크기 검출이 선택적이지 않습니다. 그것은 It generates glyphs in the font atlas for each size increment tested, which further increases the amount of time required to generate font atlases. 이는 아틀라스 오우버플로우를 발생시키는 경향이 있는데, 이는 오래된 글리프가 아틀라스에서 제거되도록 만듭니다. Best Fit 계산을 위한 많은 횟수의 테스트 때문에, 이는 종종 다른 Text 컴포넌트에 의해 사용중인 글리프들을 퇴거시킬 것이고, 적절한 폰트 사이즈가 계산된 후에 적어도 한 번 더 폰트 아틀라스를 리빌드시킬 것입니다. 이러한 특정 이슈는 유니티 5.4 에서 교정되었습니다. 그리고 Best Fit 는 폰트 텍스쳐 아틀라스를 불필요하게 확장하지 않을 것이지만, 여전히 정적으로 크기가 정해진 텍스트보다는 훨씬 더 느립니다.


자주 사용하는 폰트 아틀라스의 리빌드는 런타임 성능을 급격히 떨어뜨릴 것이고 메모리 단편화를 일으킬 것입니다. Best Fit 로 설정된 Text 컴포넌트의 양이 많아질 수록, 이 문제가 악화될 것입니다.


Scroll Views


Fill-rate 문제 다음으로, 유니티 UI 의 스크롤 뷰는 일반적으로 두 번째 가는 런타임 성능 이슈입니다. 스크롤 뷰는 일반적으로 매우 많은 개수의 UI 요소들을 사용해 자신의 칸텐츠를 표현할 것을 요구합니다. 스크롤 뷰를 띄우는 기본적인 두 가지 접근법이 있습니다:


    • 모든 스크롤 뷰 칸텐츠를 표현하기 위해서 충분한 모든 요소들을 채웁니다.
    • 요소들을 풀링하여, 보여지는 칸텐츠를 표현하는 데 필요한 만큼 위치를 재조정합니다.


둘 다 문제를 가진 해결책들입니다.


첫 번째 해결책은 모든 UI 요소들을 인스턴스화기 위해서 필요한 시간을 증가시킵니다. 왜냐하면 표현해야 할 아이템이 늘어날 수록 스크롤 뷰를 리빌드하는 시간이 늘어나기 때문입니다. 한줌의 Text 컴포넌트만을 디스플레이할 필요가 있는 스크롤 뷰처럼, 스크롤 뷰 내에 요구되는 요소의 개수가 적다면, 단순함을 위해 이 기법이 선호됩니다.


두 번째 해결책은 현재 UI 와 레이아웃 시스템 하에서 올바르게 구현하기 위한 코드의 양이 너무 많습니다. 아래에서 나중에 두 가지 가능한 기법에 대해서 논의하도록 하겠습니다. 매우 복잡한 스크롤링 UI 에 대해서, 성능 문제를 피하기 위해서는 몇 가지 종류의 풀링 접근법이 필요합니다.


이러한 이슈들에도 불구하고, 모든 접근법들은 RectMask2D 컴포넌트를 스크롤 뷰에 추가함으로써 개선될 수 있습니다. 이 컴포넌트는 스크롤 뷰 뷰포트 외부에 존재하는 스크롤 뷰 요소들이, Canvas 가 리빌딩될 때 지오메트리가 생성되고 정렬되고 분석되어야 하는, drawable 요소의 리스트에 포함되지 않도록 보장해 줍니다.


Simple Scroll View element pooling


유니티 내장 Scroll View 컴포넌트를 사용하는 것의 자연스러운 이점의 대부분을 보존하면서 스크롤 뷰를 사용하는 오브젝트 풀링을 구현하기 위한 가장 단순한 방법은 하이브리드 접근법을 사용하는 것입니다:


UI 에 요소들을 놓기 위해서, 보이는 UI 요소들을 위한 "placeholder" 로서 Layout Element 컴포넌트를 게임오브젝트와 함께 사용하십시오. 이는 레이아웃 시스템이 스크롤 뷰의 칸텐트를 적절히 계산하도록 해 주고 스크롤바가 적절히 기능하게 해 줄 것입니다.


그리고 나서, 스크롤 뷰의 가시 영역의 보이는 부분에 충분히 맞게 보이는 UI 요소들에 대한 풀을 인스턴스화하고, 이것들을 위치 placeholder 들의 부모로 붙이십시오. 스크롤 뷰가 스크롤링되면, UI 요소들을 재사용해서 뷰에 스크롤된 칸텐트들을 디스플레이하십시오.


이는 배칭되어야 하는 UI 요소들의 개수를 지속적으로 줄이게 될 것입니다. 왜냐하면 배칭 비용은 RectTransform 의 개수가 아니라 Canvas 내의 CanvasRendere 의 개수에 기반해서만 증가할 것이기 때문입니다.


Problems with the simple approach


현재는, UI 요소의 부모가 변경되거나 이웃의 순서가 변경될 때마다, 그 요소와 그것의 하위 요소들은 갱신된 것으로 표시되며 그것들의 Canvas 가 강제로 리빌드됩니다.


그 이유는 유니티가 transform 의 부모를 변경하는 콜백과 이웃의 순서를 수정하기 위한 콜백을 분리하지 않았기 때문입니다. 이 이벤트들은 둘 다 OnTransformParentChanged 콜백을 부릅니다. 유니티 UI 의 Graphic 클래스의 소스( Graphics.cs )에서, 그 콜백이 구현되며 SetAllDirty 메서드를 호출합니다. Graphic 을 갱신함으로써, 시스템은 Graphic 이 다음 프레임에 렌더링되기 전에 그것의 레이아웃과 버텍스들을 리빌드할 수 있도록 만듭니다.


캔버스들에 스크롤 뷰 내의 각 요소에 대한 루트 RectTransform 을 할당하는 것이 가능합니다. 그러면 스크롤 뷰의 전체 칸텐츠가 아니라 부모가 변경된 요소들에 국한해서 리빌드를 할 것입니다. 그러나 이것은 스크롤 뷰를 렌더링하는 데 필요한 드로콜의 개수를 증가시키는 경향이 있습니다. 더우기, 스크롤 뷰 내부의 개별 요소들이 복잡하고 수십개의 Graphic 컴포넌트로 구성되어 있고, 특히 각 요소에 Layout 컴포넌트들이 매우 많다면, 그것들을 리빌드하는 비용은 저사양 디바이스에서 프레임율을 급격하게 떨어뜨리게 될 것입니다.


만약 스크롤 뷰 요소가 가변 크기를 가지지 않는다면, 레이아웃과 버텍스들에 대한 완전한 재계산은 불필요합니다. 그러나 이 동작을 피하는 것은 부모 변화나 이웃 순서 변화 대신에 위치 변화에 기반한 오브젝트 풀링 솔루션을 구현할 것을 요구합니다.


Position-based Scroll View Pools


위에서 언급한 문제들을 피하기 위해서, 오브젝트가 포함된 UI 요소들의 RectTransform 들을 단순하게 이동시킴으로써 오브젝트를 풀링하는 스크롤 뷰를 생성하는 것이 가능합니다. 움직여진 RectTransform 의 크기가 변경되지 않았다면 내용은 다시 빌드될 필요가 없으며, 이는 스크롤 뷰의 성능을 매우 개선하게 됩니다.


일반적으로는 이를 위해 ScrollView 의 커스텀 서브클래스를 작성하거나 커스텀 LayoutGroup 컴포넌트를 작성하는 것이 최선입니다. 후자는 일반적으로 더 단순한 솔루션이며, 유니티 UI 의 LayoutGroup 추상 기저 클래스의 서브클래스를 구현함으로써 성취할 수 있습니다.


커스텀 LayoutGroup 은 밑에 있는 소스 데이터들을 분석해 얼마나 많은 데이터 요소들이 디스플레이되어야 하고 스크롤 뷰의 Content RectTransform 이 적절히 리사이징될 수 있는지 판단합니다. 그리고 나서 그것은 ScrollView change event 들을 구독하고 이를 사용해 그것의 가시적 요소를 적절하게 재배치할 수 있습니다.

+ Recent posts