
빌드파일 내부가 아니라, 외부의 경로에서 게임의 데이터를 관리하는 방법을 알아본다. 파일로 만들어 데이터베이스에 보관하기 위해서 쓰는 CSV, JSON을 알아보자
CSV, JSON로 데이터베이스 구성과 게임 저장 기능 구현을 해보자TSV)주의점
- 게임 자체가 원본데이터를 들고 있지 않는다. 해킹, 핵 등에 악용될 수 있기 때문
- 게임 속 대사 같은것들을
CSV로 다룬다 했을때, 대사에 쉼표가 들어가면 쉼포를 기준으로 나뉜다. "대사"와 같이 큰따옴표 안에 넣으면 나뉘지 않음

엑셀 또는 VS코드로 작성)
CSV 파일 형식을 지정하자CSV 구조를 위한 클래스다. CSV Table과 CSV Dictionary를 위한 파일 경로, 구분 기호를 처리하기 위한 공통적인 속성과 메서드를 가진다. MonoBehaviour를 상속받지 않는다[SerializeField] private string _filePath;
public string FilePath => Path.Combine(CsvReader.BasePath, _filePath);
[field: SerializeField] public char SplitSymbol { get; private set; }
// 생성자를 통해 생성 가능
protected Csv(string path, char splitSymbol)
{
_filePath = path;
SplitSymbol = splitSymbol;
}
CSV 파일을 읽어서 정해진 데이터 구조(현재는 이차원 배열 또는 Dictionary 자료구조)로 로드를 한다CSV Table 또는 CSV Dictionary 와 같이 CSV 클래스를 상속받은 클래스의 인스턴스를 생성Read()를 통해 CSV파일에서 데이터를 로드// 전달받은 Csv 객체를 기준으로 CSV 파일을 읽고 데이터 구조에 맞게 로드하는 메서드
public static void Read(Csv csv)
{
// 파일 경로 유효성 검사 및 내용이 비어있는지 검사, 실패하면 리턴
if (!IsValidPath(csv) ||
!IsValidEmpty(csv, out string[] lines))
return;
bool isReadSuccessful; // 로드 성공 여부 저장용 변수
// Csv 객체의 실제 타입에 따라 다르게 처리
switch (csv)
{
case CsvTable table: // CsvTable 타입인 경우
isReadSuccessful = ReadToTable(table, lines); // 2D 배열 형태로 로드
break;
case CsvDictionary dictionary: // CsvDictionary 타입인 경우
isReadSuccessful = ReadToDictionary(dictionary, lines); // Dictionary 형태로 로드
break;
default: // 그 외의 타입은 실패 처리
isReadSuccessful = false;
break;
}
}
public string GetData(Vector2Int vector2)
{
(int, int) rc = GetClampTableIndex(vector2.y, vector2.x);
return Table[rc.Item1, rc.Item2];
}
public string GetData(Vector2Int vector2)
{
(int row, int col) rc = GetClampTableIndex(vector2.y, vector2.x);
return Table[rc.row, rc.col];
}
C# 7 버전 이상에서만 가능Tuple types
- MSDN - Tuple types
- 가벼운 방식으로 데이터를 표기하는 방법
System.Serializable
Attribute중 하나다. 사용하면, 시스템에 의해 직접적으로 데이터가 직렬화 될 수 있음을 나타낸다. 에디터 상에서 데이터를 직접 편집 할 수 있다.Class나Struct위에 써준다
딕셔너리 자료구조 방식으로 데이터를 불러온다public string GetData(string row, string column)
{
if (Dict.TryGetValue(row, out var columns) && columns.TryGetValue(column, out var value))
{
return value;
}
Debug.LogError($"Data not found for row '{row}' and column '{column}'.");
return null;
}
딕셔너리에 담겨있는 자료 데이터를 불러오는 함수다. 조건문 if 안쪽에서 순서대로 보자Dict 딕셔너리에서 row 키를 넣어서 나오는 값을 columns에 담는다. 여기선 string으로 담긴다 없으면 에러 로그를 띄우고 null 반환&& 논리 연산자 이후, columns 딕셔너리에서 column 키 값을 넣어서 나오는 값을 value에 담는다. 없으면 에러 로그를 띄우고 null반환value가 반환된다CSV 파일을 가지고 테스트를 진행해본다public class Tester : MonoBehaviour
{
// 유니티 인스펙터 및 생성자에서 초기화
[SerializeField] private CsvTable _tableCSV;
[SerializeField] private CsvDictionary _dictCsv = new("DataTable/CsvTemp.csv", ',');
public Vector2Int dataTablePos;
private void Start()
{
// 읽기
CsvReader.Read(_dictCsv);
CsvReader.Read(_tableCSV);
// Table 구조는 int타입 및 Vector2Int를 사용해 데이터 참조
Debug.Log(_tableCSV.GetData(2, 3));
Debug.Log(_tableCSV.GetData(dataTablePos));
// Dictionary 구조는 string 두 개를 사용해 행, 열 데이터 참조
Debug.Log(_dictCsv.GetData("주황버섯", "설명"));
// 두 형태 모두 하나의 행을 통째로 string배열로 받아오는 GetLine기능 지원
string[] strArr1 = _tableCSV.GetLine(4);
string[] strArr2 = _dictCsv.GetLine("옥토퍼스");
}
}

File Path : Assets 폴더를 기준으로 기입하면 된다Split Symbol : 각 데이터를 쪼개는 기준 부호. CSV는 ,을 기준으로 쪼갠다Data Table Pos : 찾고자 하는 데이터의 열, 행을 입력한다| ID | 레벨 | 공격력 | 설명 | |
|---|---|---|---|---|
| 파란슬라임 | 1 | 15 | 70 | 젤리같이 생겼다. 탄산젤리 |
| 빨간달팽이 | 2 | 6 | 10 | 느리다. 느림. 모험가들에게 좋은 경제 수급원이다 |
| 주황버섯 | 3 | 8 | 15 | 평생을 뛰어다닌다. 그 고행을 이겨내면 머쉬맘이 된다는 전설이 있다 |
| 옥토퍼스 | 4 | 10 | 25 | 공중에 떠있는것 같지만, 사실 다리로 걸어다닌다 |
| 핑크빈 | 5 | 150 | 2000 | 귀여운 외모와 달리 최강 |
CSV 자료 데이터를 가지고 수행한다
Table로 된 CSV만 수행했다dataTablePos -> 클램프 되어서 15를 넣어도 최대가 5가 된다 -> (y = 5, x = 2) -> 150주의
- 만약에 대사나 설명 안에
,가 있을 경우 그 뒤의 내용은 짤린다. 이번에는 옥토퍼스의 설명 뒷내용이 짤렸다

CSV 내용들만 수행했다. string을 키 값으로 데이터를 불러온다는 것만 다르다#if UNITY_EDITOR // 유니티 에디터 환경인 경우
return Application.dataPath + Path.DirectorySeparatorChar; // Assets 폴더 경로 반환
#else // 빌드된 애플리케이션 환경인 경우
return Application.persistentDataPath + Path.DirectorySeparatorChar; // 해당 플랫폼 저장 경로 반환
#endif
JsonUtility의 ToJson, FromJson이 메인이 되는 기능들이고, 이들을 통해 세이브와 로드를 구현한다ToJson: json 파일로 바꿔준 다는 의미다.FromJson: json 파일을 불러와 사용할 수 있게 파싱한다.json 확장자명으로 지정한 경로에 저장된다. 데이터들이 직렬화 되어 저장된다

public override void Save<T>(T target)
{
Directory.CreateDirectory(BasePath);
string filePath = GetFilePath(target.FileName);
string jsonString = JsonUtility.ToJson(target);
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
byte[] bytes = Encoding.Default.GetBytes(jsonString);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, bytes);
}
IsFileAccessible(filePath);
}
왜 using을 쓰는가?
- 파일, 네트워크, 데이터베이스 연결 등 외부 리소스는 자동으로 정리하지 않음
using으로 묶어서 쓰면, 구문을 벗어날 때 자동으로Dispose()를 해서 메모리 해제를 함 -> 메모리 누수 방지
CSV는 DB와 같이 데이터들을 한데 모아놓고 뽑아 쓸 때 쓰기 좋다JSON은 세이브파일과 같이 파일 형태로 데이터들을 모아두고 가져다 쓸 때 좋다실시간 저장
- 다크소울, 항아리 게임과 같이 게임의 상태가 실시간으로 자동 저장되는 기능
- 일정한 시간마다
Json에 저장하거나, 특정한 동작에 옵저버 패턴으로 저장하는 기능 수행을 하면 가능- 강제 종료시에도 저장되는 것은 모르겠음