우선 실시간이 아닌 이유: 시트 내용을 불러올 때마다 셀의 값이 아닌 시트 전체의 값을 불러오기 때문에 하나하나 체리피킹해서 원할 때 사용하기엔 무리가 있음
게임을 만들다 보면 특정 수치들을 제어하는 데이터시트를 만들고 싶어질 때가 있습니다.
단순 수치 조정뿐만이 아니더라도 ScriptableObject로 만지기에는 너무나 복잡하고 많은 양의 데이터라던가,
언어 팩 등등 유니티 내부에서 하나하나 수정하기에는 껄끄러운 종류의 데이터 종류도 한두가지가 아닐 것입니다.




본인의 경우 이벤트의 ID,등장 위치, 등장 조건 및 내용물과 아이템의 내용, 그리고 각종 텍스트의 ID와 언어별(한글,영어) 값, 그리고 게임 내부 수치 등을 구글 스프레드세트를 사용해 나름이나마 좀 더 수월하게 편집할 수 있었습니다.
이러한 경우에서 활용할 수 있도록 유니티 프로젝트와 구글의 SpreadSheet를 연동한 예시를 작성해보겠습니다.

우선 적당한 시트를 하나 생성합니다.
내용은 아무렇게나 집어넣어 보겠습니다.
private const string SeetURL_Text = "https://docs.google.com/spreadsheets/d/1CKRY3Ld80zOMUp8geRr55ilJQqIHnfGQ_mxIQuKcGVc/export?format=tsv&gid=0";
위 스크립트에 삽입된 링크를 보면 스프레드시트의 웹 링크인
https://docs.google.com/spreadsheets/d/1CKRY3Ld80zOMUp8geRr55ilJQqIHnfGQ_mxIQuKcGVc/edit#gid=0
과는 무언가 다른 점을 확인하셨을 것입니다.
스프레드시트의 ID 부분 이후 원 링크는 "/edit#grid=0"으로 종결되어 있는 반면에 스크립트의 링크는
"export?format=tsv&grid=0"으로 종결되어있습니다.
그 이유는 해당 링크의 스프레드시트를 'tsv' 형태로 받아오기 위함입니다.

tsv가 무엇인가?
tsv는 해당 시트의 셀 별 내용물이 탭(\t)로 나뉘어져 있는 형식입니다.
csv는 쉼표로 나뉘어져 있습니다.


좌측은 메모장으로 열은 tsv, 우측은 csv입니다.
csv의 경우 셀 내용에 쉼표가 있을 경우 큰따옴표로 표시를 해주기는 합니다.
그러나 역시 문장을 쓰다보면 쉼표를 쓸 일이 없지야 않기 때문에 좀 더 가시성이 좋고 일괄성 있는 tsv로 작업하는게 좋을 것이라 생각합니다.
public class test : MonoBehaviour
{
private const string SeetURL_Text = "https://docs.google.com/spreadsheets/d/1CKRY3Ld80zOMUp8geRr55ilJQqIHnfGQ_mxIQuKcGVc/export?format=tsv&gid=0";
[SerializeField] private TextMeshProUGUI MyTMP_text = null;
private IEnumerator HolySheet()
{
UnityWebRequest _sheet_text = UnityWebRequest.Get(SeetURL_Text);
yield return _sheet_text.SendWebRequest();
MyTMP_text.text = _sheet_text.downloadHandler.text;
StopAllCoroutines();
}
[SerializeField] private TextMeshProUGUI MyTMP_time = null;
private IEnumerator counttime()
{
float _time = 0.0f;
while (true)
{
_time+= Time.deltaTime;
MyTMP_time.text = _time.ToString("n2");
yield return null;
}
}
private void Awake()
{
StartCoroutine(counttime());
StartCoroutine(HolySheet());
}
}
아무튼 tsv로 불러와서 화면에 출력해보는 테스트용 스크립트를 실행해보겠습니다.
코루틴에서 UnityWebRequest로 링크에서 tsv를 받아옵니다.
약간의 지연 시간이 있으므로 완전히 다운로드가 끝날 때까지 yield return을 사용합니다.

스프레드시트의 공유 설정을 '링크가 있는 모든 사용자'로 설정해야 합니다.

생각보다 빨리 출력됐네요.
그럼 이 tsv를 먹기 좋게 나눠보겠습니다.
public class test : MonoBehaviour
{
private const string SeetURL_Text = "https://docs.google.com/spreadsheets/d/1CKRY3Ld80zOMUp8geRr55ilJQqIHnfGQ_mxIQuKcGVc/export?format=tsv&gid=0";
[SerializeField] private TextMeshProUGUI MyTMP_text = null;
private IEnumerator HolySheet()
{
UnityWebRequest _sheet_text = UnityWebRequest.Get(SeetURL_Text);
yield return _sheet_text.SendWebRequest();
string[] _texts= _sheet_text.downloadHandler.text.Split('\n');
StringBuilder _stb = new StringBuilder();
int _count = 1;
string[] _temp = null;
for(int i = 0; i < _texts.Length; i++)
{
_temp = _texts[i].Split('\t');
foreach(var _text in _temp)
{
_stb.Append(_count + ": ");
_stb.Append(_text);
_stb.Append(" ");
_count++;
}
if (i < _texts.Length - 1) _stb.Append("<br>");
}
MyTMP_text.text= _stb.ToString();
}
private void Awake()
{
StartCoroutine(HolySheet());
}
}
행을 Split('\n')으로 분리하고
열을 Split('\t')으로 분리하기만 하면 됩니다.

하나하나 나뉘어진 값들을 어떻게 활용하는지는 사용할 목적에 따라 다릅니다.
public enum TestSkillEnum {None, Skill1,Skill2,Skill3};
public enum TestTypeEnum {None, Type1,Type2,Type3}
public class TestCharacterClass
{
private TestSkillEnum MySkill = TestSkillEnum.None;
private TestTypeEnum MyType = TestTypeEnum.None;
public TestCharacterClass(string[] datas)
{
MySkill = (TestSkillEnum)int.Parse(datas[0]);
MyType = (TestTypeEnum)int.Parse(datas[1]);
}
}
public class test : MonoBehaviour
{
private List<TestCharacterClass> MyCharacterDatas=new List<TestCharacterClass>();
private const string SeetURL_Text = "https://docs.google.com/spreadsheets/d/1CKRY3Ld80zOMUp8geRr55ilJQqIHnfGQ_mxIQuKcGVc/export?format=tsv&gid=0";
private IEnumerator HolySheet()
{
UnityWebRequest _sheet_text = UnityWebRequest.Get(SeetURL_Text);
yield return _sheet_text.SendWebRequest();
string[] _texts= _sheet_text.downloadHandler.text.Split('\n');
for(int i = 0; i < _texts.Length; i++)
{
MyCharacterDatas.Add(new TestCharacterClass(_texts[i].Split('\t')));
}
}
private void Awake()
{
StartCoroutine(HolySheet());
}
}
본인의 경우 게임 시작 시 불러온 값으로 내부 데이터들을 구성하는데 사용했습니다.
위 스크립트처럼 시트에서 불러온 string 값들을 int로 변환시켜 특정 배열로 형변환시키는 등 활용 자체가 어려울 일은 없을 것입니다.

물론 경우에 따라 처음 변환 알고리즘을 짜는데 약간 복잡할 수도 있지만 한번 만들어두면 몹시 편한 기능이 될 것이라고 장담합니다.
public class Textdata
{
public string ID = "";
public string kor = "";
public string en = "";
public string Text
{
//국가코드별로 kor,en,혹은 추후에 추가될 언어 string 반환
}
}
스프레드시트를 이용한 언어 팩은 텍스트 ID,언어별 값을 담은 Textdata 클래스를 만들었습니다.
private IEnumerator textdataupdate()
{
int _previewcount=TextDatas.Count;
UnityWebRequest _sheet_text = UnityWebRequest.Get(SeetURL_Text);
yield return _sheet_text.SendWebRequest();
string[] _textrow = _sheet_text.downloadHandler.text.Split('\n');
TextDatas.Clear();
for (int i = 1; i < _textrow.Length; i++)
{
string[] _data = _textrow[i].Split("\t");
if (_data.Length == 0 || _data[0] == "") continue;
Textdata _textdata = new Textdata();
_textdata.ID = _data[0];
_textdata.kor = _data[1].Contains("\r")?_data[1].Replace("\r",""):_data[1];
_textdata.en = _data[2].Contains("\r") ? _data[2].Replace("\r", "") : _data[2];
TextDatas.Add(_textdata);
}
yield return null;
}

그리고 업데이트할 때마다 해당 시트에서 ID, 값들을 불러와 새로 대입해 필요할 때마다 싱글톤(GameManager)에서 ID를 통해 문장을 불러오는 방식을 사용했습니다.
public string GetTextData(string _id)
{
foreach(var _data in TextDatas)
{
if (string.Compare(_id, _data.ID, true) == 0)
return _data.Text;
}
Debug.Log($"{_id} 없음");
return NullText;
}
이 아래는 그닥 좋다고 말하긴 힘든 예시를 작성해봤습니다.

해당 스크린샷은 기존에 정적 클래스로 온 스크립트에서 불러졌던 수치 저장용 클래스입니다.
특정 UI 이동 속도라던가, 플레이어 체력, 골드 증감값이라던가 등등 여러 스크립트에서 사용할 일이 많아 아예 정적 클래스로 만들었었습니다.
개발 도중 차라리 이것도 시트로 만들어버리면 어떨까? 싶어 통째로 시트를 만들고 값을 하나하나 다 옮겨 아래와 같은 시트를 완성했습니다.

보기도 좋고 수정도 편합니다.
하지만 이 시트를 다시 프로젝트에서 불러오는 과정에서 무엇인가 기이함을 느꼈습니다.

이게 맞나?
굴러가는데는 별 지장이 없었지만 정적 클래스 시절과는 다르게 편집이 상당히 까다로워졌습니다.
중간에 더 이상 사용하지 않게 되는 변수를 지우려면 그 아래 변수들의 대입 순서까지 전부 다 수정해야 하니까요.
아무튼 총체적으로 문제가 생길 일은 없었지만 그래도 도중에 사용하지 않는 변수를 지우지 못하고 쓸데없는 찌꺼기가 계속 누적된다는게 그닥 마음이 편치는 않았습니다.
'C#,Unity' 카테고리의 다른 글
| 스크립트로 UI 제어하기 - Selectable (0) | 2024.09.19 |
|---|---|
| Unity - 오브젝트 클릭 이벤트 만들기(IPointer,RayCast) (1) | 2024.05.14 |
| Unity - 부드럽게 순차적으로 이동시키기 (0) | 2024.05.13 |
| Unity - 순차적으로 진행되는 코루틴 대기열 만들기(Queue 활용) (0) | 2024.05.09 |
| Unity - Coroutine을 변수로 사용하기 (0) | 2024.05.09 |