아이템이 1,000개 들어 있는 인벤토리를 스크롤하면 아이콘이 위로 흘러가고 아래에서 새 아이콘이 올라옵니다. 보기에는 아이템 1,000개가 늘어선 긴 목록을 작은 창으로 내려다보는 것 같습니다.
실제로 화면에 만들어 둔 칸은 보이는 만큼인 35개뿐이고, 스크롤하면 그 칸들이 돌아가며 그림만 바꿔 끼웁니다. 위로 빠져나간 줄은 지워지지 않고 맨 아래로 옮겨져 다음 아이템을 보여 줍니다. 이렇게 보이는 만큼만 만들어 돌려 쓰는 방식을 리스트 가상화(list virtualization)라고 부릅니다.
영상판 — 5열 × 6줄 창에 아이템 1,000개를 넣은 인벤토리를 파이썬으로 직접 짜서 그렸습니다. 아이콘은 CC0 도트 아이콘(DB32)과 AI 로 생성한 아이콘을 함께 썼습니다.
보이는 7줄만 칸으로 둡니다
설명용으로 짠 인벤토리는 창에 6줄이 보이지만, 스크롤하는 동안에는 맨 위와 맨 아래 줄이 반쯤씩 걸쳐 7줄이 보이므로 칸을 35개 둡니다. 그림 1 아래 판의 왼쪽 번호는 칸마다 붙은 고정 번호(칸 1~35)이고, 오른쪽 번호는 그 줄이 지금 보여 주는 위치 인덱스(인벤토리에서 몇 번째 자리인지)입니다(그림 1).
맨 위에서 맨 아래까지 내리는 동안 칸 줄이 맨 아래로 옮겨 가 새 위치를 맡는 일은 195번 일어나고, 그때마다 칸 번호는 그대로인 채 위치 인덱스만 바뀝니다.

엔진의 리스트 뷰도 같은 방식입니다
언리얼 엔진의 리스트 뷰(ListView)는 목록이 다루는 데이터를 아이템(item), 화면에 만든 위젯(이 글의 칸)을 엔트리(entry)라고 나눠 부르고, 소스의 클래스 설명에 이렇게 적습니다.
"The list itself is based on a list of n items, but only creates as many entry widgets as can fit on screen. For example, a scrolling ListView of 200 items with 5 currently visible will only have created 5 entry widgets."
목록은 아이템 n개로 이뤄지지만 엔트리 위젯은 화면에 들어갈 만큼만 만든다는 뜻입니다. 아이템 200개 가운데 5개가 보이는 리스트 뷰는 엔트리 위젯을 5개만 만듭니다.
화면에서 빠진 엔트리 위젯은 위젯 풀로 돌아갔다가 다른 아이템을 맡을 때 다시 꺼내 씁니다.
유니티 UI Toolkit 의 ListView 도 한 페이지를 채울 만큼만 만들어 두고, 스크롤하면 그것들을 다른 데이터에 다시 바인딩합니다.
칸은 ID 로 테이블을 읽습니다
설명용 인벤토리는 이름, 아이콘, 등급, 최대 개수처럼 같은 종류의 아이템이면 모두 같은 정보를 아이템 테이블에 한 번만 두고, 내가 가진 아이템은 위치 인덱스와 그 테이블의 ID, 개수만으로 나타냈습니다. 언리얼 엔진의 데이터 테이블(DataTable)이 이런 테이블을 담는 기능입니다.
그러면 칸이 새 아이템을 맡을 때 하는 일은 그 위치의 ID 로 테이블 한 줄을 찾아 아이콘과 등급 색을 읽어 오는 것뿐이고, 이름이 필요한 곳도 같은 줄에서 읽습니다(그림 2). 테이블 한 줄을 고치면 그 종류의 아이템이 전부 함께 바뀝니다.

한 줄씩 넘기기와 부드럽게 미끄러지기
설명용 인벤토리에서 한 줄씩 넘길 때는 칸 30개를 제자리에 두고, 스크롤할 때마다 첫 칸이 어느 위치 인덱스부터 보여 줄지만 다섯씩 바꿉니다.
부드럽게 미끄러질 때는 칸 줄 전체를 함께 밀어 올리는데, 민 거리가 한 줄 높이(이 인벤토리에서는 124px)에 닿으면 맨 위 줄을 맨 아래로 옮겨 다음 위치 다섯 개를 채우고 민 거리를 다시 0부터 셉니다. 걸치는 7번째 줄이 필요한 것도 이 때문입니다(그림 3).

칸 크기가 같아야 계산이 쉽습니다
보이는 칸만 만들려면 지금 스크롤 위치에서 어느 아이템이 보이는지를 알아야 합니다. 언리얼 엔진의 타일 뷰(TileView)는 격자로 늘어놓는 리스트 뷰인데, 클래스 설명부터 모든 타일이 같은 크기라고 적고 엔트리마다 같은 너비와 높이를 줍니다. 칸 크기가 모두 같으면 지금 위치에서 몇 번째 줄이 보이는지를 줄 높이 하나로 셀 수 있습니다.
그래서 칸이 고르게 늘어선 격자 인벤토리에 줄 하나를 통째로 쓰는 버튼 같은 칸을 넣으려면(예를 들어 인벤토리 칸을 늘리는 버튼), 같은 크기의 타일만 늘어놓는 타일 뷰로는 바로 만들 수 없습니다.
리스트 뷰는 엔트리마다 높이가 달라도 되는 대신, 스크롤 위치를 몇 번째 아이템까지 내려왔는지로 적어 두고 화면에 들어오는 엔트리를 그때 만들어 크기를 잽니다.
유니티 ListView 문서는 모든 아이템을 같은 높이로 두는 FixedHeight 가 기본이고 이쪽이 더 빠르다고 적습니다.
저장할 때도 위치 인덱스 · ID · 개수만 남깁니다
설명용 인벤토리의 저장 함수도 아이템마다 위치 인덱스와 ID, 개수 한 줄만 남기고 빈자리는 아예 적지 않습니다(그림 4). 위치 1,010개 가운데 빈자리 10개를 뺀 1,000줄입니다.
아이템을 다른 위치로 옮기면 그 아이템 줄의 위치 인덱스만 바뀝니다.

참고 자료
- 언리얼 엔진 5.8 소스, Runtime/UMG 의 ListView.h(가상화된 목록 · 아이템과 엔트리 · 200개 가운데 5개) · TileView.h(같은 크기 타일로 늘어놓는 리스트 뷰 · 엔트리 너비와 높이) · ListViewBase.h(엔트리 위젯 풀 · 스크롤 위치는 아이템 단위) · Runtime/Slate 의 STileView.h(모든 타일이 같은 크기) · SListView.h(엔트리마다 높이가 다를 수 있음) · Runtime/Engine 의 DataTable.h https://github.com/EpicGames/UnrealEngine (에픽 계정을 깃허브에 연결해야 열림)
- Unity, ListView (UI Toolkit 매뉴얼). 한 페이지만큼 만들고 다시 바인딩 · FixedHeight 가 기본이고 더 빠르다 https://docs.unity3d.com/Manual/UIE-uxml-element-ListView.html
- ARoachIFoundOnMyPillow, 16x16 RPG Items (DB32) (OpenGameArt · CC0). 도트 아이콘 https://opengameart.org/content/16x16-rpg-items-db32
'게임개발뜯어보기' 카테고리의 다른 글
| 이 RTS 유닛 수백 기는 지도 한 장을 같이 읽는다 - 플로우 필드 (0) | 2026.10.30 |
|---|---|
| 벽에서 먼저 나온 쪽이 유리한 이유 - 피커스 어드밴티지 (0) | 2026.10.29 |
| 게임에서 문을 현실처럼 열게 하면 맞출 게 너무 많다 - IK 와 물리 문 (0) | 2026.10.27 |
| 이 연막은 그림이 아니라 공간을 채운 복셀이다 - 복셀과 레이 마칭 (0) | 2026.10.26 |
| 게임 속 계단현상, 이렇게 지워왔다 - 슈퍼샘플링부터 DLSS까지 (1) | 2026.10.24 |