본문 바로가기
카테고리 없음

[BalancingLibra] DataTableManager 만들기

by mazayong 2026. 1. 22.

UserDataManager을 만든 후, 유저 데이터가 아닌 게임 내 아이템과 같은 기획에서 구현하는 밸런스 관련 데이터를 정리할 매니저가 필요하다고 생각했다. 그래서 UserDataManager 다음으로 DataTableManager을 구현하게 되었다.

 

 

1. 목적

2. 구현 코드

2.1. IDataTable

2.2. CSVReader.cs

2.3. DataTableManager

2.4. 테스트

3. 후기

 

 

 

 

 

1. 목적

유저 데이터와 같이 변동성이 있는 게 아닌 게임 내에서는 불변의 상태가 유지되는 값의 테이블들을 관리한다. 

기획자의 엑셀 파일에서 해당 내용을 읽어와야 하고, 확장이 쉬운 CSV 데이터셋을 사용할 예정이기 때문에 CSVReader도 추가로 제작한다.

 

 

 

2. 구현 코드

우선 데이터 테이블의 규격을 정의하는 인터페이스와 해당 인터페이스를 관리하는 매니저, CSV Reader을 사용할 예정이다.

데이터 테이블 종류가 아이템 종류, 미션 종류, 스테이지 종류와 같이 다양할 수 있기 때문에 데이터테이블 인터페이스를 추가했다.

 

2.1. IDataTable

namespace BalancingLibra.Data
{
    public interface IDataTable
    {
        void Load(string path);
    }
}

 

 

 

2.2. CSVReader.cs

using System.Collections.Generic;
using System.Text.RegularExpressions;

public static class CSVReader
{
    //CSV 1줄 파싱으로 문자열 배열로 리턴
    public static string[] ParseLine(string line)
    {
        return line.Split(',');
    }

    //CSV 전체 텍스트 파싱하여 줄 단위 리스트로 리턴 (헤더 제외)
    public static List<string[]> ParseCSV(string csvText)
    {
        List<string[]> list = new List<string[]>();
        string[] lines = csvText.Split('\n');

        for (int i = 1; i < lines.Length; i++)
        {
            if(string.IsNullOrWhiteSpace(lines[i]))
                continue;
            list.Add(ParseLine(lines[i]));
        }
        return list;
    }
}

 

 

 

2.3. DataTableManager.cs

해당 코드는 게임 내에서 변하지 않는 모든 데이터 테이블을 관리하는 매니저이다.

이 때, 게임 내에서 필요한 테이블의 데이터들을 로드하는 기능이 있어야 한다.

using System;
using System.Collections.Generic;
using BalancingLibra.Data;
using UnityEngine;

public class DataTableManager : SingletonBehaviour<DataTableManager>
{
    //모든 테이블 타입 key-value로 설정해서 보관
    private Dictionary<Type, IDataTable> _tables = new Dictionary<Type, IDataTable>();

    protected override void Init()
    {
        base.Init();
        LoadAllTables();
    }

    public void LoadAllTables()
    {
        //게임에 필요한 모든 데이터 테이블 등록 및 로드
        //LoadTable<ItemTable>("Data/ItemTable");

        Logger.Log("DataTableManager : All tables loaded");
    }

    //제네릭 로드 함수: 테이블 객체 생성 후 Load 호출, 딕셔너리에 등록
    private void LoadTable<T>(string path) where T : IDataTable, new()
    {
        T table = new T();
        table.Load(path);

        if(_tables.ContainsKey(typeof(T)))
            _tables[typeof(T)] = table;
        else
            _tables.Add(typeof(T), table);
    }

    //외부에서 특정 테이블 가져오는 함수
    public T Get<T>() where T : class, IDataTable
    {
        if(_tables.TryGetValue(typeof(T), out IDataTable table))
        {
            return table as T;
        }

        Logger.Log($"DataTableManager : Failed to get table {typeof(T).Name}");
        return null;
    }

}

* 키를 문자열로 선언하는 게 아닌 타입으로 선언했다. 해당 부분에 대해 잠시 살펴보자.

- 딕셔너리의 키인 Type은 C#의 System namespace 안에 속한 public class Type{..}의 클래스이다. 

- 해당 Type class는 클래스의 설명서로, 어떤 클래스 형식, 인터페이스 형식, 제네릭 형식 등등에 대한 형식 선언으로, typeof 연산자를 타내는 string 개체를 type으로 가져오는 데 주로 사용된다. 

(구체적인 설명은 아래 공식 문서 링크를 참고하길 추천한다.)

https://learn.microsoft.com/ko-kr/dotnet/api/system.type?view=net-8.0

- LoadTable 함수에서 typeof(T)를 적용할 경우, T에 대한 정보가 담긴 Type 객체를 리턴한다.

- 그래서 T에 대한 정보가 담긴 Type 객체를 키로, 실제 T 객체는 값으로 설정하는 것이다.

- 결론적으로 Type으로 구분한 이유는 클래스 이름 자체를 써서 안전하게 사용하기 위해서이다. 

 

하지만 해당 방법에 대해 고민할 부분이 있다. 

* 서버와 통신할 때는 어떻게 해야 할까?

- 서버나 외부 설정 파일에서 문자열로 데이터가 전송될 경우에 처리가 어렵다는 것이다.

-> 그럴 경우에는 if문을 사용하거나 리플렉션을 사용해야 한다. 

 

* 모드로 인한 확장은 어떻게 해야 하지?

- 루아 같은 스크립트 언어는 C#의 Type 개념이 없기 때문에 중간에 브릿지 코드를 작성해 놓아야 한다.

 

* 자동 등록하면 게임이 켜질 때 느려진다. 

-  LoadTable을 할 때 수동으로 모든 줄을 적는 것이 아닌 자동화를 하면 리플렉션으로 프로젝트의 모든 코드를 찾아야 한다. 그러면 로딩창 시간이 명시적으로 길어지게 된다.

- 이 문제를 해결하기 위해 게임을 만들 때 Code Generation을 구현해 에디터가 리플렉션을 돌려서 코드를 대신 짜주게 구성해야 한다. 이 부분은 지금 해결하는 것이 아닌 추후 구현해보도록 하겠다.

 

 

 

2.4. 테스트

이건 cursor을 이용해서 작업했다. 추후 다른 게시글로 작성할 예정이다.

 

 

3. 후기

딕셔너리에서 키를 문자열이 아니라 타입으로 선언하는 건 처음이었는데, 왜 타입으로 선언해도 되는지에 대해 깊게 고민하다 보니 다양한 고민들을 많이 하게 되어서 특정 게임 상황 시나리오도 고민해 이를 해결하는 경험을 하게 되었다. 역시 하나에 대해 깊게 생각해보면 다른 것과 연결되고, 또 다른 예외상황이 생각나고 그 상황에 대한 해결방법까지 고민하게 되므로 현재 공부하는 입장에서는 좋은 모습이라고 생각한다. 작업을 하는 동시에 많이 배워서 작업과 함께 빠르게 성장하는 개발자가 되고 싶다.