본문 바로가기
개발 (Game)/Unity (General)

[Unity] TMP폰트 관련 메모리 최적화

by 진현개발일기 2025. 2. 1.

회사 프로젝트에서 다국어 작업 중 메모리 최적화를 진행한 적이 있었다.

플레이 후 게임 옵션창에서 모든 언어를 한 번씩 설정을 해놓으면 폰트 관련 개체 및 아틀라스 텍스처 등이 425.4MB의 메모리를 차지하고 있었다.

 

여러 RnD 끝에 동일 환경에서 9.4MB까지 최적화에 성공했다.

■ 기존 

 

· Total: 425.4MB

· Native Memory: 213.3 MB

· GPU Memory: 212.0 MB

· Managed: 108.5 KB

 

언어 변경 시 TMP 개체의 하위 개체로 Submesh UI개체가 생성된다. 이는 새로운 언어를 세팅한 뒤 렌더링 중인 문자가 기본 폰트에 포함되어 있지 않은 상황이라서 Fallback Font Asset들을 탐색하여 원하는 문자를 내포하고 있는 폰트 에셋의 Atlas 텍스처를 참조하기 위해 생성된 개체들이다.

 

해당 개체들은 생성된 이후에는 사라지지 않아서 (지울 수 있어도 유니티에서 권장하지를 않음) 다른 언어로 변경하였을 경우에도, 내가 사용하고 있지 않는, 이전 언어의 폰트 아틀라스를 참조하고 있기 때문에 메모리 해제가 일어나지 않아 많은 용량들을 차지하고 있게 된다.

 

■ 최적화 이후


· Total:
9.4MB

· Native Memory: 5.2 MB

· GPU Memory: 4.0 MB

· Managed: 238.0 KB

 

 과정

1. 메인이 되는 기본 폰트

[기존]

· Font Asset 세팅이 Dynamic인 상태로, 11499 (한글 + 영어 + 특수문자 + 숫자 등)개가 미리 맵핑되어 있었다.

[수정]

· Font Asset을 Static으로 세팅한 뒤, 대부분 언어에서 공통으로 사용할 요소 [특수문자 & 알파벳 & 숫자]들만 미리 맵핑해놨다. 이는 대략 397개까지 축소 되었다.

 

2. 언어별 폰트

[기존]

· Font Asset 세팅이 모두 Dynamic인 상태 및 언어별 모든 문자들이 미리 맵핑되어 있었고, 모두 기본 폰트의 Fallback Assets로 지정되어 있었다. 

[수정]

· 에디터에서 플레이 전 & 빌드 전 전처리로 기본 폰트 외 모든 언어의 데이터를 미리 해제해준다.

 

· 모든 언어들이 기본 폰트의 Fallback으로 지정되어 있고, Dynamic인 상태 또한 그대로이다.

 

· 게임 시작 시 유저 언어를 Fallback의 첫번째 요소로 지정한다. 언어 변경 시에도 동일 로직을 수행한다. 이유는 기본 폰트에서 없는 문자는 Fallback Font Asset들을 참조하면서 가져오려고 하는데 첫번째 요소부터 순서대로 탐색하기 때문이다.

 

· 변경하려는 언어를 로드하기 이전에 사용하고 있던 폰트가 있었다면 이의 CharacterTabel, GlyphTable 등이 차지하고 있는 메모리를 해제해줬다. (TMP_FontAsset 클래스의 ClearFontAssetData())

 

· 변경하려는 언어의 폰트 에셋을 로드한 뒤, 미리 맵핑해놨던, 프로젝트에서 사용 중인 해당 언어의 문자들을 로드한다.

(TMP_FontAsset 클래스의 TryAddCharacters(). 파라미터로는 uint배열의 유니코드 집합을 넘겨줬다.)

(해당 데이터는 각 언어마다 Dynamic 세팅을 한 뒤, 플레이하면서, 수집했던 문자들을 Json으로 맵핑해놨던 글자들이다.)

 

※ 이와 같이 미리 로드하는 이유는 Dynamic Font Asset이 플레이 도중에 필요한 문자를 그때 그때마다 추가하므로 컴퓨터 자원을 지속적으로 사용하기에 이를 최소화 함으로써 원활한 게임 진행을 도모하기 위해서이다. 첨언하자면, 사용 되어지는 문자들을 이미 알고 있으니 미리 로드하는 방식이 플레이 도중에 렉이 걸릴 수 있는 가능성을 남겨놓는 것보다는 나을 것 같다는 판단을 했다.

 

 

[기존: 폰트 에셋별 미리 로드된 문자 개수]

· 베트남어: 339개

· 인도네시아어: 495개

· 태국어: 75개

· 번체: 15383개

 

[수정 후: 폰트 에셋별 필수[프로젝트에 사용 중인] 문자 개수]

· 베트남어: 82개

· 인도네시아어: 0개 (알파벳과 동일. 기본 폰트에 포함됨)

· 태국어: 63개

· 번체: 1096개

 

3. 폰트 아틀라스 해상도

[기존]

· 기본 폰트 및 Fallback Font Asset의 Texture Resolution이 모두 4096으로 되어 있었다.

[수정]

· 1024, 2048로 수정. 

 복기

처음에는 문자 유니코드 데이터 외 Glyph 관련 구조체 데이터들 또한 맵핑한 뒤 로드하려고 했다. 데이터 형태가 복잡해질 것 같아 Json 포맷으로 작업하였으나, Unicode 데이터만 있어도 충분하다는 것을 작업 후반부에 알았다.

 

추후 시간이 될 때 아래와 같이 리팩토링을 해야겠다.

데이터 형태가 단순하면서 사람이 눈으로 확인할 필요가 없으므로 (역)직렬화가 비교적 느린 Json 대신에 좀 더 빠른 CSV로 데이터 포맷 교체 및 관련 로직 수정 예정

 

추가 (25. 02. 08)

[TMP_FontAsset 클래스의 TryAddCharacters(). 파라미터로는 uint배열의 유니코드 집합을 넘겨줬다] 부분에서 간헐적으로 유니티 에디터에서만 최초 1회 에러가 발생하는 경우가 있다. 이는 TMP 버전에 따라 안생길 수도 있으나, 현재 날짜 기준으로 비교적 최신 버전의 패키지를 사용하고 있지 않다면 발생하는 버그이다.

 

원인은 TMP 내부 코드에서 Unity Editor (전처리)일 경우 TryAddCharacters를 할 때 

AtlasTexture를 새로 세팅 후 Glyphs를 추가할려고 하는데 새로 만드는 Atlas Texture의 이름을 "FontAsset 기본 Atlas 이름 + 인덱스" 형태로 만들려고 한다. 이때 기본 atlas 변수가 할당이 안되어 있어서 생기는 이슈인데 아래와 같이 atlasTexture의 get 프로퍼티를 이용하여 초기화 시켜주면 해결 된다.

 

(예)

 

#if UNITY_EDITOR

    _ = fontAsset.atlasTexture;  <- 이 부분에서 get 프로퍼티를 통해 기본 atlas가 초기화 된다.

#endif

 

TryAddCharacters(유니코드 집합);