CSV파일을 로드하여 TEXT 출력
○ 구현목표
- 게임을 진행하는 도중 등장인물들간의 대화를 작성하게 되는경우는 거의 필수적으로 발생 - 각각의 대화를 NPC오브젝트에 직접 하드코딩을 하는 방법도 있으니 유지보수가 어렵고 코드가 지저분해진다 - 별도의 CSV파일을 작성하여 내용을 읽어들여 TEXT를 뿌려주는 기능을 구현한다 - Resources폴더를 사용하지 않는다 요구조건 - TextMeshPro에서 사용할 폰트는 이미 설치되어있다고 가정한다 |
○ 참고자료
https://gist.github.com/SiarheiPilat/de4688651f106e7993a7e2fdb743cac4
A lightweight CSV reader for Unity.
A lightweight CSV reader for Unity. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
※ 국내에서 찾아볼 수 있는 대부분의 자료는 위 링크를 참고하여 작성된 자료들이라고 생각됨
※ 이 포스트에서도 동일하게 위 자료를 참고하여 약간의 커스터마이징을 진행함
○ 대화에 사용될 CSV파일 준비 (Sample.csv)
※ 한글을 출력하고 싶다면 반드시 UTF-8 형식으로 작성되어야 함
○ 대화용 UI구성
※ 배경(Box)은 [ UI → panel ], 텍스트는 [ UI → Text - TextMeshPro]
○ CSV파일 임포트
※ 상기 참고자료의 코드를 그대로 사용하게 되면 반드시 Resources폴더를 사용해야 함
※ 구현 목표에 따라 별도의 폴더에 임포트 한다
○ CSV파일 로더 작성
① 로더 전용 오브젝트 작성 [ Create Empty ]를 통하여 비어있는 오브젝트 작성
② 프로젝트 윈도우에서 [ Create → MonoBehaviour Script ]를 통해 LoadCsv스크립트 작성 및 어태치
③ 스크립트 작성
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;
public class LoadCsv : MonoBehaviour
{
static string SPLIT_RE = @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))";
static string LINE_SPLIT_RE = @"\r\n|\n\r|\n|\r";
static char[] TRIM_CHARS = { '\"' };
public static List<Dictionary<string, object>> Read(TextAsset file)
//메소드에서 매개변수로 CSV를 직접 받아 오도록 작성
{
var list = new List<Dictionary<string, object>>();
//CSV파일을 직접 매개변수로 사용하기 때문에 Resources.Load는 필요 하지 않음
//Resources폴더를 사용하지 않음
var lines = Regex.Split(file.text, LINE_SPLIT_RE);
if (lines.Length <= 1) return list;
var header = Regex.Split(lines[0], SPLIT_RE);
for (var i = 1; i < lines.Length; i++)
{
var values = Regex.Split(lines[i], SPLIT_RE);
if (values.Length == 0 || values[0] == "") continue;
var entry = new Dictionary<string, object>();
for (var j = 0; j < header.Length && j < values.Length; j++)
{
string value = values[j];
value = value.TrimStart(TRIM_CHARS).TrimEnd(TRIM_CHARS).Replace("\\", "");
object finalvalue = value;
int n;
float f;
if (int.TryParse(value, out n))
{
finalvalue = n;
}
else if (float.TryParse(value, out f))
{
finalvalue = f;
}
entry[header[j]] = finalvalue;
}
list.Add(entry);
}
return list;
}
}
※ NPC오브젝트 등 해당 스크립트를 언제든지 불러와서 사용할 수 있도록 public static으로 작성
※ 위 메소드를 호출하는 기본 형
//메소드 호출
List<Dictionary<string, object>> data = LoadCsv.Read(filename);
//값 출력
Debug.Log(data.[int 행번호]["칼럼명"])
//앞 [] : int타입 행번호, 뒤 [] : string타입 칼럼명을 쌍따옴표("") 안에 직접 입력
○ NPC역할을 하게 될 오브젝트 생성 및 스크립트 작성
① Empty오브젝트에 스크립트(TextManager)만 추가하여 간단하게 진행
② 스크립 작성 - (1)
using TMPro;
using UnityEngine;
public class TextManager : MonoBehaviour
{
[SerializeField] TextAsset csvFile; //불러오게 될 CSV파일용 변수
[SerializeField] TextMeshProUGUI nameTextBox; //nameBox의 text 변수
[SerializeField] TextMeshProUGUI messageTextBox; //messageBox의 text 변수
}
③ 스크립트 작성 - (2) : 선언한 변수에 오브젝트 및 어셋 어태치
④ 스크립트 작성 - (3) : CSV파일을 읽어들이기 위한 구조체 작성 및 구조체 배열 선언
TextSturct[] myText; //② 구조체 배열 선언
//① 구조체 선언
struct TextSturct
{
public string nameText;
public string messageText;
}
※ 보통 CSV파일의 내용이 단 한줄만 존재하는 것은 아니기 때문에 구조체 배열을 선언 하여 진행
※ 이 포스팅의 내용처럼 단 한줄만 출력한다면 구조체 및 배열없이 직접 텍스트를 출력해도 무방함
⑤ 스크립트 작성 - (4) : LoadCSV 호출 메소드 작성
using System.Collections.Generic;
public class TextManager : MonoBehaviour
{
void GetText(TextAsset filename)
{
List<Dictionary<string, object>> data = LoadCsv.Read(filename);
//LoadCsv의 메소드를 호출하여 CSV파일을 list에 로딩
myText = new TextSturct[data.Count];
//CSV파일을 배열에 저장
for (int i = 0; i < data.Count; i++) //각각의 값을 구조체 배열에 저장
{
string tempName = data[i]["NAME"].ToString();
string tempText = data[i]["TEXT"].ToString();
myText[i].nameText = tempName;
myText[i].messageText = tempText;
}
}
}
⑥ 스크립트 작성 - (5) : Update 작성
void Update()
{
GetText(csvFile); //csvFile에 어태치한 CSV파일을 매개변수로 하여 메소드 호출
nameTextBox.text = myText[0].nameText; //nameText 출력
messageTextBox.text = myText[0].messageText; //messageText 출력
}
※ myText[0]만 출력한 이유는 샘플 CSV가 한줄짜리 데이터이기 때문임
※ 필요에 따라 for 또는 if 등등을 활용하여 커스텀 하여 사용
○ 실행 테스트
○ 스크립트 전문
① LoadCsv.cs
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;
public class LoadCsv : MonoBehaviour
{
static string SPLIT_RE = @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))";
static string LINE_SPLIT_RE = @"\r\n|\n\r|\n|\r";
static char[] TRIM_CHARS = { '\"' };
public static List<Dictionary<string, object>> Read(TextAsset file)
{
var list = new List<Dictionary<string, object>>();
var lines = Regex.Split(file.text, LINE_SPLIT_RE);
if (lines.Length <= 1) return list;
var header = Regex.Split(lines[0], SPLIT_RE);
for (var i = 1; i < lines.Length; i++)
{
var values = Regex.Split(lines[i], SPLIT_RE);
if (values.Length == 0 || values[0] == "") continue;
var entry = new Dictionary<string, object>();
for (var j = 0; j < header.Length && j < values.Length; j++)
{
string value = values[j];
value = value.TrimStart(TRIM_CHARS).TrimEnd(TRIM_CHARS).Replace("\\", "");
object finalvalue = value;
int n;
float f;
if (int.TryParse(value, out n))
{
finalvalue = n;
}
else if (float.TryParse(value, out f))
{
finalvalue = f;
}
entry[header[j]] = finalvalue;
}
list.Add(entry);
}
return list;
}
}
② TextManager.cs
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class TextManager : MonoBehaviour
{
[SerializeField] TextAsset csvFile;
[SerializeField] TextMeshProUGUI messageTextBox;
[SerializeField] TextMeshProUGUI nameTextBox;
TextSturct[] myText;
void Update()
{
GetText(csvFile);
nameTextBox.text = myText[0].nameText;
messageTextBox.text = myText[0].messageText;
}
void GetText(TextAsset filename)
{
List<Dictionary<string, object>> data = LoadCsv.Read(filename);
myText = new TextSturct[data.Count];
for (int i = 0; i < data.Count; i++)
{
string tempName = data[i]["NAME"].ToString();
string tempText = data[i]["TEXT"].ToString();
myText[i].nameText = tempName;
myText[i].messageText = tempText;
}
}
struct TextSturct
{
public string nameText;
public string messageText;
}
}