---
title: "동적으로 채우는 글자는 본 만큼 램에 쌓인다"
description: "채팅이나 유저 이름처럼 나올 글자를 미리 알 수 없는 화면은 글자를 실행 중에 굽습니다. 한글과 중국어에서 그 텍스처가 얼마나 커지는지, 왜 채팅만 Text 기반 컴포넌트로 만드는지를 그림으로 봅니다."
author: "유니얼"
pubDate: 2026-09-17
category: "game-dev/unity/ui"
tags: ["Unity", "UI", "uGUI"]
canonical: https://blog.unialgames.com/posts/unity-ui-dynamic-font-memory/
---
게임 안의 글자는 대부분 정해져 있습니다. 버튼 문구, 설명, 퀘스트 이름은 만드는 쪽이 정합니다. 그런데 **채팅, 유저 이름, 길드 이름, 외부에서 받아 오는 공지** 는 무슨 글자가 나올지 미리 알 수 없습니다.

Unity 는 이런 글자를 **실행 중에 굽습니다.** 화면에 처음 나온 글자를 그때 만들어서 글자 텍스처에 붙입니다. 영어권 게임에서는 별일이 아닙니다. 알파벳과 숫자, 기호를 다 합쳐도 백 자가 안 됩니다. 한글과 중국어에서는 이야기가 달라집니다.

## 새 글자가 들어오면 벌어지는 일

<figure class="diagram">
<svg viewBox="0 0 680 300" role="img" aria-label="채팅에 새 글자가 오면 그 자리에서 구워 아틀라스에 넣고, 자리가 없으면 장을 하나 더 만든다">
<text x="20" y="26" font-size="13" fill="var(--text)">채팅 한 줄이 들어왔을 때</text>
<rect x="20" y="42" width="180" height="46" rx="6" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="32" y="71" font-size="14" fill="var(--text)">반갑습니다 :)</text>
<line x1="210" y1="64" x2="239" y2="64" stroke="var(--text-muted)" stroke-width="1.5"></line><polygon points="239,59 248,64 239,69" fill="var(--text-muted)"></polygon>
<rect x="254" y="36" width="170" height="58" rx="6" fill="var(--bg)" stroke="var(--accent)"></rect>
<text x="266" y="58" font-size="12" fill="var(--text)">아틀라스에 있는 글자인가</text>
<text x="266" y="78" font-size="11" fill="var(--text-muted)">있으면 그대로 그립니다</text>
<line x1="434" y1="64" x2="463" y2="64" stroke="var(--text-muted)" stroke-width="1.5"></line><polygon points="463,59 472,64 463,69" fill="var(--text-muted)"></polygon>
<rect x="478" y="30" width="182" height="70" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"></rect>
<text x="490" y="52" font-size="12" fill="var(--text)">없으면 그 자리에서 굽고</text>
<text x="490" y="72" font-size="12" fill="var(--text)">빈 칸에 붙입니다</text>
<text x="490" y="90" font-size="11" fill="var(--text-muted)">그 프레임이 잠깐 걸립니다</text>
<text x="20" y="134" font-size="13" fill="var(--text)">빈 칸이 없으면</text>
<rect x="140" y="118" width="86" height="86" rx="5" fill="var(--bg)" stroke="var(--accent)"></rect>
<rect x="140" y="118" width="86" height="86" rx="5" fill="var(--accent)" fill-opacity="0.3"></rect>
<text x="183" y="166" font-size="11" text-anchor="middle" fill="var(--text)">가득 참</text>
<text x="140" y="220" font-size="11" fill="var(--text-muted)">1 장</text>
<rect x="260" y="118" width="86" height="86" rx="5" fill="var(--bg)" stroke="var(--accent)"></rect>
<rect x="260" y="118" width="86" height="86" rx="5" fill="var(--accent)" fill-opacity="0.3"></rect>
<text x="303" y="166" font-size="11" text-anchor="middle" fill="var(--text)">가득 참</text>
<text x="260" y="220" font-size="11" fill="var(--text-muted)">2 장</text>
<rect x="380" y="118" width="86" height="86" rx="5" fill="var(--bg)" stroke="var(--warn)"></rect>
<rect x="380" y="176" width="86" height="28" rx="4" fill="var(--warn)" fill-opacity="0.25"></rect>
<text x="423" y="150" font-size="11" text-anchor="middle" fill="var(--warn)">새 장</text>
<text x="380" y="220" font-size="11" fill="var(--text-muted)">3 장</text>
<text x="20" y="248" font-size="12" fill="var(--warn)">빈 칸이 다 차면 같은 크기의 장이 하나 더 생깁니다. 1024 × 1024 한 장이 1 MB 입니다.</text>
<text x="20" y="270" font-size="12" fill="var(--text)">한 번 들어간 글자는 화면에서 사라져도 그 자리에 남아 있습니다.</text>
<text x="20" y="290" font-size="12" fill="var(--text-muted)">지우는 것은 게임 쪽에서 따로 시켜야 합니다.</text>
</svg>
<figcaption>처음 보는 글자는 그 자리에서 구워 붙이고, 자리가 없으면 장을 하나 더 만듭니다.</figcaption>
</figure>

한 번 구운 글자는 텍스처 안에 자리를 하나 차지합니다. 자리가 다 차면 **같은 크기의 텍스처를 한 장 더** 만듭니다. 글자가 늘어난 만큼 텍스처도 늘어납니다.

- 처음 보는 글자가 나온 프레임에는 **굽는 시간이 듭니다.** 긴 문장이 한꺼번에 들어오면 그 프레임만 눈에 띄게 걸립니다
- 텍스처는 **한 픽셀에 한 바이트** 를 씁니다. 1024 × 1024 한 장이 1 MB 입니다
- 글자를 화면에서 치워도 **그 자리는 그대로 남습니다**

## 한글과 중국어는 자릿수가 다릅니다

<x-atlas>
<script type="application/json">
{
  "label": "아틀라스가 몇 장 필요한가",
  "note": "글자 모양과 자리 잡는 방식에 따라 달라지는 대략치입니다."
}
</script>
</x-atlas>

한글은 현대 글자를 다 세면 11,172 자이고, 많이 쓰는 완성형만 추려도 2,350 자입니다. 중국어는 표준 상용 한자가 3,500 자, 일본어 상용 한자가 2,136 자입니다. 채팅에 몇 백 명이 몇 시간 떠들면 **그 언어의 상용 글자는 거의 다 한 번씩 나옵니다.**

그러니까 채팅이 있는 게임에서 동적으로 굽는 방식은 "**그 언어의 글자를 거의 다 텍스처에 올려 두는 것**" 과 같은 말이 됩니다. 굽는 크기를 키울수록 한 장에 들어가는 글자가 확 줄어드는 것도 위에서 보이실 겁니다.

## 채팅창은 두 부분입니다

<figure class="diagram">
<svg viewBox="0 0 680 290" role="img" aria-label="채팅창은 지나간 대화가 쌓이는 목록과 직접 치는 입력칸 두 부분이고, 각각 다른 컴포넌트다">
<rect x="30" y="30" width="250" height="220" rx="10" fill="var(--bg)" stroke="var(--border)"></rect>
<text x="44" y="52" font-size="11" fill="var(--accent)">길드원1</text>
<text x="98" y="52" font-size="11" fill="var(--text)">같이 사냥 가실 분</text>
<text x="44" y="86" font-size="11" fill="var(--accent)">길드원2</text>
<text x="98" y="86" font-size="11" fill="var(--text)">저요 저요</text>
<text x="44" y="120" font-size="11" fill="var(--accent)">길드원3</text>
<text x="98" y="120" font-size="11" fill="var(--text)">5분만 기다려 주세요</text>
<text x="44" y="154" font-size="11" fill="var(--accent)">나</text>
<text x="98" y="154" font-size="11" fill="var(--text)">바로 갈게요</text>
<rect x="44" y="196" width="222" height="34" rx="5" fill="var(--bg-subtle)" stroke="var(--accent2)"></rect>
<text x="56" y="218" font-size="11" fill="var(--text-muted)">메시지를 입력하세요</text>
<line x1="286" y1="76" x2="330" y2="76" stroke="var(--accent)" stroke-dasharray="4 3"></line>
<line x1="286" y1="212" x2="330" y2="212" stroke="var(--accent2)" stroke-dasharray="4 3"></line>
<text x="340" y="60" font-size="13" fill="var(--accent)">1. 지나간 대화 목록</text>
<text x="340" y="82" font-size="12" fill="var(--text-muted)">글자를 그리는 컴포넌트가 줄마다 하나씩</text>
<text x="340" y="102" font-size="12" fill="var(--text-muted)">(Text 또는 TextMeshPro)</text>
<text x="340" y="122" font-size="12" fill="var(--text)">새 글자가 계속 들어오는 쪽입니다</text>
<text x="340" y="176" font-size="13" fill="var(--accent2)">2. 내가 치는 입력칸</text>
<text x="340" y="198" font-size="12" fill="var(--text-muted)">InputField 입니다. 안의 글자를</text>
<text x="340" y="218" font-size="12" fill="var(--text-muted)">Text 가 그리면 Text 기반,</text>
<text x="340" y="238" font-size="12" fill="var(--text-muted)">TextMeshPro 가 그리면 TMP_InputField</text>
<text x="30" y="276" font-size="12" fill="var(--warn)">둘이 같은 폰트를 쓰면 글자는 같은 텍스처에 쌓입니다. 한쪽만 바꾸면 반만 바뀝니다.</text>
</svg>
<figcaption>채팅창은 지나간 대화 목록과 입력칸 두 부분입니다.</figcaption>
</figure>

- **지나간 대화 목록** 은 글자를 그리는 컴포넌트가 줄마다 하나씩 놓인 것입니다. 새 글자가 계속 들어오는 쪽이 여기입니다
- **내가 치는 입력칸** 은 `InputField` 입니다. 안의 글자를 `Text` 가 그리면 **Text 기반 InputField**, TextMeshPro 가 그리면 **TMP_InputField** 입니다
- 둘이 **같은 폰트를 쓰면 글자도 같은 텍스처에 쌓입니다.** 목록만 Text 기반으로 바꾸고 입력칸을 TMP 로 두면, 사람이 직접 친 글자는 TMP 쪽에 그대로 남습니다
- 그래서 "채팅은 Text 기반으로" 라고 할 때는 **목록과 입력칸을 같이** 말하는 것입니다

## 쌓이는 쪽과 버리는 쪽

<x-tabs>
<figure class="diagram" data-tab="TextMeshPro 의 동적 아틀라스">
<svg viewBox="0 0 680 250" role="img" aria-label="TMP 는 본 글자를 계속 들고 있고">
<rect x="40" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="40" y="134" width="110" height="26" rx="4" fill="var(--accent)" fill-opacity="0.35"></rect>
<line x1="40" y1="134" x2="150" y2="134" stroke="var(--accent)" stroke-width="1.5"></line>
<text x="95" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">1분</text>
<rect x="200" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="200" y="108" width="110" height="52" rx="4" fill="var(--accent)" fill-opacity="0.35"></rect>
<line x1="200" y1="108" x2="310" y2="108" stroke="var(--accent)" stroke-width="1.5"></line>
<text x="255" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">5분</text>
<rect x="360" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="360" y="82" width="110" height="78" rx="4" fill="var(--warn)" fill-opacity="0.35"></rect>
<line x1="360" y1="82" x2="470" y2="82" stroke="var(--warn)" stroke-width="1.5"></line>
<text x="415" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">10분</text>
<rect x="520" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="520" y="56" width="110" height="104" rx="4" fill="var(--warn)" fill-opacity="0.35"></rect>
<line x1="520" y1="56" x2="630" y2="56" stroke="var(--warn)" stroke-width="1.5"></line>
<text x="575" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">20분</text>
<text x="40" y="28" font-size="12" fill="var(--text-muted)">채팅을 계속 보고 있을 때 글자 텍스처가 차지하는 자리</text>
<text x="40" y="212" font-size="12" fill="var(--warn)">본 글자가 전부 남아 있어서 계속 늘어납니다.</text>
<text x="40" y="232" font-size="12" fill="var(--text-muted)">비우라고 시키기 전까지는 줄지 않습니다. 비우면 그 뒤에 나오는 글자를 다시 굽습니다.</text>
</svg>
<figcaption>본 글자가 그대로 쌓입니다.</figcaption>
</figure>
<figure class="diagram" data-tab="일반 Text 의 동적 폰트">
<svg viewBox="0 0 680 250" role="img" aria-label="일반 Text 는 텍스처를 다시 만들 때 최근에 쓴 글자만 남긴다">
<rect x="40" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="40" y="134" width="110" height="26" rx="4" fill="var(--accent)" fill-opacity="0.35"></rect>
<line x1="40" y1="134" x2="150" y2="134" stroke="var(--accent)" stroke-width="1.5"></line>
<text x="95" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">1분</text>
<rect x="200" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="200" y="130" width="110" height="30" rx="4" fill="var(--accent)" fill-opacity="0.35"></rect>
<line x1="200" y1="130" x2="310" y2="130" stroke="var(--accent)" stroke-width="1.5"></line>
<text x="255" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">5분</text>
<rect x="360" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="360" y="132" width="110" height="28" rx="4" fill="var(--accent)" fill-opacity="0.35"></rect>
<line x1="360" y1="132" x2="470" y2="132" stroke="var(--accent)" stroke-width="1.5"></line>
<text x="415" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">10분</text>
<rect x="520" y="40" width="110" height="120" rx="6" fill="var(--bg)" stroke="var(--border)"></rect>
<rect x="520" y="128" width="110" height="32" rx="4" fill="var(--accent)" fill-opacity="0.35"></rect>
<line x1="520" y1="128" x2="630" y2="128" stroke="var(--accent)" stroke-width="1.5"></line>
<text x="575" y="180" font-size="12" text-anchor="middle" fill="var(--text-muted)">20분</text>
<text x="40" y="28" font-size="12" fill="var(--text-muted)">채팅을 계속 보고 있을 때 글자 텍스처가 차지하는 자리</text>
<text x="40" y="212" font-size="12" fill="var(--accent2)">텍스처를 다시 만들 때 최근에 쓴 글자만 남기고 버립니다.</text>
<text x="40" y="232" font-size="12" fill="var(--text-muted)">그래서 오래 켜 둬도 화면에 보이는 양만큼에서 맴돕니다. 대신 다시 만들 때 그 폰트의 글자를 전부 다시 그립니다.</text>
</svg>
<figcaption>다시 만들 때 최근에 쓴 글자만 남깁니다.</figcaption>
</figure>
</x-tabs>

여기서 두 컴포넌트가 갈립니다.

- **TextMeshPro** 로 그리면 동적 아틀라스가 **본 글자를 계속 들고 있습니다.** 비우라고 시키기 전까지 줄지 않습니다. 대신 한 번 구운 글자는 다시 구울 일이 없어서 그 뒤로는 빠릅니다
- **`Text` 컴포넌트**(TextMeshPro 가 나오기 전부터 있던 것)로 그리면, 그 폰트의 텍스처를 다시 만들 때 **최근에 쓴 글자만 남기고 버립니다.** 문서에도 "텍스처를 다시 만들면 지난 프레임에 쓰인 글자만 들어 있다" 고 적혀 있습니다. 그래서 오래 켜 둬도 화면에 보이는 양에서 맴돕니다
- 대신 `Text` 쪽은 텍스처를 다시 만들 때마다 **그 폰트를 쓰는 글자를 전부 다시 그립니다.** 채팅이 빠르게 흐르면 이쪽이 자주 일어납니다

**채팅만 Text 기반으로 만드는 이유가 이것입니다.** 대화 목록은 `Text` 로, 입력칸은 `Text` 기반 `InputField` 로 두면 화면에 보이는 몇 줄만 글자 텍스처에 남고 지나간 대화의 글자는 알아서 버려집니다. 나머지 화면은 TextMeshPro 로 두고 채팅만 다르게 가도 됩니다.

## 그대로 TextMeshPro 로 가려면

채팅까지 TextMeshPro 로 만들 수도 있습니다. 대신 쌓이는 것을 관리해야 합니다.

- **주기적으로 비웁니다.** 채팅창을 닫을 때, 마을을 벗어날 때처럼 끊기는 지점에서 동적으로 채운 글자를 지웁니다. 지운 뒤 처음 나오는 글자는 다시 굽습니다
- **굽는 크기를 작게 잡습니다.** 채팅 글자는 작으니 큰 크기로 구울 이유가 없습니다. 위의 그림에서 크기를 낮추면 한 장에 들어가는 글자가 몇 배로 늘어납니다
- **자주 쓰는 글자는 미리 구워 둡니다.** 한글 완성형처럼 흔한 범위를 미리 구운 폰트를 기본으로 두고, 거기 없는 글자만 동적으로 받게 합니다
- **빌드에 굽힌 글자가 딸려 가지 않게 합니다.** 에디터에서 테스트하며 채워진 글자가 그대로 빌드에 들어가면 쓰지도 않는 텍스처가 같이 나갑니다

## 장이 여러 개면 그리기도 갈립니다

글자 텍스처가 두 장, 세 장으로 늘어나면 **서로 다른 장에 있는 글자는 한 번에 못 그립니다.** 한 줄짜리 채팅에 장이 다른 글자가 섞여 있으면 그 줄을 나눠 그립니다. 메모리만 늘어나는 것이 아니라 그리는 횟수도 같이 늘어납니다.

빠져 있는 글자를 다른 폰트에서 찾아 오는 경우에도 같습니다. 그 폰트는 자기 텍스처를 따로 들고 있습니다. 이모지처럼 다른 폰트에서 오는 글자가 섞이면 그 줄은 나눠 그려집니다.

## 정할 것들

| 항목 | 정해 줄 것 |
| --- | --- |
| 지원 언어 | 한국어만인지, 중국어와 일본어도 받는지. 언어마다 글자 수가 다릅니다 |
| 채팅 표시 | 한 화면에 몇 줄까지 보이는지, 지난 대화를 얼마나 남기는지 |
| 이름 길이 | 유저 이름과 길드 이름의 최대 글자 수 |
| 글자 크기 | 채팅에 쓰는 크기가 몇 종류인지. 종류가 적을수록 텍스처가 작습니다 |
| 이모지 | 쓸 수 있는지, 쓴다면 어느 범위까지인지 |
| 비우는 지점 | 채팅창을 닫을 때처럼 글자 텍스처를 정리해도 되는 순간 |

## 정리

- 나올 글자를 미리 모르는 화면은 글자를 실행 중에 굽고, 그만큼 텍스처가 늘어납니다
- 한 픽셀에 한 바이트라 1024 × 1024 한 장이 1 MB 입니다. 자리가 차면 같은 크기로 한 장이 더 생깁니다
- 한글과 중국어는 상용 글자만 수천 자입니다. 채팅이 오래 흐르면 그 언어의 글자가 거의 다 올라옵니다
- 채팅창은 지나간 대화 목록과 입력칸 두 부분이고, 목록은 글자 컴포넌트, 입력칸은 `InputField` 입니다
- TextMeshPro 로 그리면 본 글자를 계속 들고 있고, `Text` 로 그리면 텍스처를 다시 만들 때 최근에 쓴 글자만 남깁니다
- 그래서 채팅은 목록과 입력칸을 같이 Text 기반으로 두거나, TextMeshPro 로 두되 비우는 지점과 굽는 크기를 정해 둡니다
- 텍스처가 여러 장이면 서로 다른 장의 글자는 한 번에 못 그립니다. 다른 폰트에서 받아 오는 글자도 마찬가지입니다