Abstract
이 페이지는 데이터 테이블의 코어 쪽 구조 — 행이 어떻게 저장되고 조회 API 가 무엇을 제공하는지 — 를 다룹니다. 텍스트 파일을 만들어 실제로 게임에 붙이는 전 과정은 Unity 쪽
데이터 테이블 (Data Tables) 페이지에 ToyboxNightmare 의 적 스탯을 예로 단계별로 적어 두었습니다. 여기서는 "로드한 뒤 어떻게 꺼내 쓰는가"에 집중합니다.
테이블은 Dictionary<int, 행> 이다
내부는 단순합니다. Id 를 키로 하는 딕셔너리 하나, 그리고 가장 작은 Id 와 가장 큰 Id 의 행을 따로 기억해 둡니다. 여기서 조회 방법별 비용이 갈립니다.
조회 | 비용 | 쓰는 때 |
GetDataRow(id) | 즉시 (딕셔너리 조회) | 기본. 가능하면 항상 이것 |
MinIdDataRow / MaxIdDataRow | 즉시 | "1레벨 데이터", "최고 레벨 데이터" |
GetDataRow(조건) / GetDataRows(조건) | 전체 순회 | 가끔. 매 프레임 부르면 안 된다 |
GetDataRows(정렬) | 전체 복사 후 정렬 | 목록 화면을 열 때 한 번 |
Id 설계가 곧 성능이다
조건 검색이 느리다는 것은, 자주 찾는 기준을 Id 에 담으라는 뜻입니다. ToyboxNightmare 의 적을 테이블로 옮긴다고 하면, 지금은 주소 문자열("Zombunny")로 스탯을 찾고 있으므로 매번 조건 검색이 됩니다. 두 가지 해법이 있습니다.
// 방법 1: 로드 직후 한 번만 순회해서 별도 색인을 만든다
private readonly Dictionary<string, EnemyRow> mByAssetName = new Dictionary<string, EnemyRow>();
private void BuildIndex(IDataTable<EnemyRow> table)
{
EnemyRow[] rows = table.GetAllDataRows();
for (int i = 0; i < rows.Length; i++)
{
mByAssetName.Add(rows[i].AssetName, rows[i]);
}
}
// 방법 2: 게임 코드가 처음부터 Id 로 적을 가리킨다 (스포너 테이블에 주소 대신 적 Id 를 적는다)
EnemyRow row = table.GetDataRow(enemyTypeId);
C#
복사
RPG 처럼 테이블이 많은 게임은 Id 에 규칙을 넣어 묶기도 합니다 — 10000번대는 무기, 20000번대는 방어구, 10203 은 "무기 / 2등급 / 3번" 같은 식입니다.
가비지를 만들지 않는 조회
배열을 돌려주는 메서드는 부를 때마다 새 배열을 만듭니다. 자주 부르는 곳에서는 결과를 받을 리스트를 넘기는 쪽을 씁니다.
private readonly List<EnemyRow> mRowBuffer = new List<EnemyRow>();
table.GetAllDataRows(mRowBuffer); // mRowBuffer 를 비우고 채워 준다. 새 할당 없음
C#
복사
런타임 수정 — 어디에 쓰나
AddDataRow(문자열) / RemoveDataRow(id) 로 로드된 테이블을 고칠 수 있습니다. 쓰임새는 두 가지입니다: 서버가 내려준 이벤트 데이터를 기존 테이블에 덧붙이기, 그리고 테스트 코드에서 파일 없이 테이블 채우기.
이 에디션에서 다른 점 · 본문을 읽을 때 주의할 점
•
본문 예제는 행 클래스가 IDataRow 를 직접 구현합니다. 그대로도 동작하지만, 게임 코드에서는 Unity 쪽이 제공하는 DataRowBase 를 상속하는 것이 관례입니다(Id 를 override, 필요한 ParseDataRow 만 override).
•
LoadDataTable<T>(...) 라는 메서드는 없습니다. CreateDataTable<T>() 로 만든 뒤 DataTableBase 로 캐스팅해 ReadData(주소) 를 부릅니다. 본문 예제도 고쳐 두었습니다.
•
같은 행 타입으로 테이블을 두 번 만들면 Already exist data table 예외입니다. 한 타입으로 여러 테이블이 필요하면(예: 스테이지별 스폰 표) CreateDataTable<T>("Stage01") 처럼 이름을 붙입니다.
•
본문 "리소스 시스템 연동"의 세 가지 소스(에셋 / 디스크 바이너리 / 가상 파일 시스템) 중 이 에디션에서 동작하는 것은 에셋(Addressables 의 TextAsset) 하나입니다.
•
float.Parse 는 기기 언어에 따라 소수점 해석이 달라집니다. 행을 파싱할 때 CultureInfo.InvariantCulture 를 넘기세요. 본문 예제의 float.Parse(columnTexts[index++]) 는 독일어·프랑스어 등으로 설정된 기기에서 0.5 를 잘못 읽습니다.
데이터 테이블(Data Tables)
Game Framework의 Data Table 시스템은 게임 내의 표 형태 데이터를 로드, 파싱, 접근할 수 있도록 구조화된 방식을 제공합니다.
이 시스템은 아이템 속성, 캐릭터 능력치, 레벨 설정 등과 같이 테이블 형태로 관리하면 효율적인 게임 데이터를 다루는 데 적합합니다.
설정 데이터(Configuration)나 다국어(Localization) 시스템 등 다른 데이터 관리 기능은 별도의 문서를 참고하세요.
개요
Data Table 시스템을 사용하면 다음과 같은 작업이 가능합니다.
•
텍스트 파일, 바이너리 파일 등 다양한 소스에서 표 형태 데이터 로드 및 파싱
•
ID 또는 사용자 정의 조건으로 데이터 행(Row) 접근
•
데이터 행을 열거, 필터링, 정렬
•
런타임에 데이터 행 추가/제거
•
테이블 내 최소/최대 ID 값 빠르게 조회
핵심 구성 요소
1. Data Table Manager
•
모든 데이터 테이블을 관리하는 핵심 컴포넌트
•
GameFrameworkModule을 상속
•
데이터 테이블 생성, 가져오기, 파괴 담당
2. Data Table
•
DataTable<T> 인스턴스로 표현되며, T는 IDataRow 인터페이스 구현 타입
•
각 테이블은 고유 ID로 인덱싱된 데이터 행 집합을 저장
3. Data Row
•
IDataRow를 구현한 커스텀 클래스
•
각 행은 고유 ID를 가진 단일 레코드
•
문자열 또는 바이너리 데이터를 구조화된 형태로 변환하는 ParseDataRow 메서드 구현
구현 세부사항
데이터 저장 방식
•
내부적으로 Dictionary<int, T> 사용
→ O(1) 속도로 ID 기반 조회 가능
•
최소 ID 행과 최대 ID 행을 캐싱하여 빠른 접근 지원
// 이 에디션의 소스에서는 mDataSet / mMinIdDataRow / mMaxIdDataRow
private readonly Dictionary<int, T> m_DataSet;
private T m_MinIdDataRow;
private T m_MaxIdDataRow;
C#
복사
데이터 로딩
•
DataProvider 시스템을 사용해 리소스 로드 및 파싱
•
에셋에서 읽는 ReadData 는 비동기, 이미 메모리에 있는 문자열·바이트를 넣는 ParseData 는 동기
•
텍스트, 바이너리, 커스텀 포맷 가능
데이터 파싱
•
각 IDataRow 클래스는 다음 메서드 중 필요한 것을 구현
bool ParseDataRow(string dataRowString, object userData);
bool ParseDataRow(byte[] dataRowBytes, int startIndex, int length, object userData);
C#
복사
사용 예시
데이터 행 클래스 정의
public class PlayerLevelData : IDataRow
{
public int Id { get; private set; }
public string Name { get; private set; }
public int ExpRequired { get; private set; }
public float AttackBonus { get; private set; }
public bool ParseDataRow(string dataRowString, object userData)
{
string[] columnTexts = dataRowString.Split('\t');
int index = 0;
Id = int.Parse(columnTexts[index++]);
Name = columnTexts[index++];
ExpRequired = int.Parse(columnTexts[index++]);
AttackBonus = float.Parse(columnTexts[index++]);
return true;
}
public bool ParseDataRow(byte[] dataRowBytes, int startIndex, int length, object userData)
{
// 바이너리 파싱 구현
return true;
}
}
C#
복사
데이터 테이블 로드 및 접근
// 매니저 가져오기
IDataTableManager dataTableManager = GameFrameworkEntry.GetModule<IDataTableManager>();
// 생성 + 로드 (LoadDataTable 이라는 메서드는 없다. 만든 뒤 DataTableBase.ReadData 를 부른다. 비동기)
IDataTable<PlayerLevelData> created = dataTableManager.CreateDataTable<PlayerLevelData>();
((DataTableBase)created).ReadData("PlayerLevel");
// ↓ ReadDataSuccess 이후
// 가져오기
IDataTable<PlayerLevelData> playerLevelTable = dataTableManager.GetDataTable<PlayerLevelData>();
// ID로 행 조회
PlayerLevelData level5 = playerLevelTable.GetDataRow(5);
// 조건으로 조회
PlayerLevelData[] highLevels = playerLevelTable.GetDataRows(row => row.ExpRequired > 1000);
// 정렬
PlayerLevelData[] sortedLevels = playerLevelTable.GetDataRows((a, b) => a.ExpRequired - b.ExpRequired);
C#
복사
주요 기능
•
ID 조회
T GetDataRow(int id)
C#
복사
•
조건 검색
T[] GetDataRows(Predicate<T> condition)
C#
복사
•
정렬
T[] GetDataRows(Comparison<T> comparison)
C#
복사
•
조건 + 정렬
T[] GetDataRows(Predicate<T> condition, Comparison<T> comparison)
C#
복사
런타임 수정 기능
•
행 추가
bool AddDataRow(string dataRowString, object userData);
bool AddDataRow(byte[] dataRowBytes, int startIndex, int length, object userData);
C#
복사
•
행 삭제
bool RemoveDataRow(int id);
C#
복사
•
전체 삭제
void RemoveAllDataRows();
C#
복사
리소스 시스템 연동
•
ResourceManager와 통합하여 다양한 소스에서 로드 가능
◦
애셋 파일(텍스트, 바이너리, 커스텀)
◦
디스크 바이너리 파일
◦
가상 파일 시스템(VFS)
•
DataProvider가 실제 리소스 로드와 파싱 담당
메모리 효율성
•
Dictionary 기반 O(1) 조회
•
최소/최대 ID 캐싱
•
행 객체는 로드 시 new T() 로 한 번 만들어져 테이블이 파괴될 때까지 유지 (풀링은 하지 않는다. 조회 중 할당이 없다는 뜻)
•
결과 리스트 재사용 메서드 제공
void GetDataRows(Predicate<T> condition, List<T> results)
void GetDataRows(Comparison<T> comparison, List<T> results)
void GetAllDataRows(List<T> results)
C#
복사
결론
Game Framework의 Data Table 시스템은 구조화된 데이터를 효율적으로 관리하고, 다양한 소스에서 유연하게 로드할 수 있도록 설계되었습니다.
IDataRow 인터페이스를 구현하면, ID 기반 빠른 조회, 조건 검색, 정렬, 런타임 수정까지 가능하며, ResourceManager와의 연동으로 프로젝트 전반에서 활용성이 높습니다.







