기존 구글 api를 활용한 시크릿 코드 기반의 데이터 불러오기 방식은 서버를 필요로 한 방식으로 빌드에서 데이터 로드 오류가 발생하여 다시 통신 방식으로 바꾸게 되었다.
구글 api방식을 사용하게 된 계기 자체는 단순 읽기만이 아닌 쓰기의 기능도 필요했기 때문이다.
그런데 post방식
으로 구글 앱스 스크립트를 활용하여 원하는 기능을 구현할 수 있다는 방법을 알게되어 사용한 방식의 구현을 정리해보려고 한다.
먼저 google apps script를 하나 생성해준다.
작업할 파일을 id로 불러와 변수에 저장해준다. 이때 하나의 파일에 여러 개의 sheet가 존재한다면 배열을 사용하여 개별적으로 접근이 가능하다.
id는 url에서 https://docs.google.com/spreadsheets/d/여기 부분/
이다.
var SheetId = SpreadsheetApp.openById("id");
var sheet_0 = dialogSheetId.getSheets()[0];
var sheet_1 = dialogSheetId.getSheets()[1];
🔗GoogleAppsScript-WebApp documentation
post 통신이 오면 해당 스크립트에서는 처리 함수인 doPost(e)
를 실행합니다.
따라서 이 함수에 파라미터로 들어온 e
의 정보에 맞춰 하고자 하는 기능을 개발해주면 됩니다.
function doPost(e)
{
var p = e.parameter;
if(!regCheck(p.order)) // 데이터 유효성 검사
return ContentService.createTextOutput("");
// unity에서 요청한 주문에 맞춰 원하는 함수를 실행해줍니다.
switch(p.order)
{
case "setValue": setValue(p.sheetType, p.values); return responseSet();
case "getValue": getValue(p.sheetType, p.range); return responseGet();
}
function getValue(sheetType, range)
{
switch(sheetType){
case "case1":
value = sheet0.getRange(range).getValues();
break;
case "case2":
value = sheet1.getRange(range).getValues();
break;
}
}
getValues()는 2차원 배열로 데이터를 반환한다. 이를 유니티에 json
으로 보내기 위해 형태를 보정해준다.
function responseGet()
{
var jsonData = [];
var headers = value[0]; // column의 count가 동일하다는 전제
for (var i = 1; i < value.length; i++) { // 0번째 row는 스키마이므로 실제 데이터는 1번째 부터.
var row = value[i];
var rowObject = {};
// 각 행의 데이터를 저장한다.
for (var j = 0; j < headers.length; j++) {
rowObject[headers[j]] = row[j];
}
jsonData.push(rowObject);
}
// 유니티에서 배열 형태의 json 파싱을 위한 형식으로 key를 추가하고
// 그 다음으로 보내고자하는 데이터를 value로 담는다.
jsonData = "{\"data\":"+JSON.stringify(jsonData)+"}";
return ContentService.createTextOutput(jsonData).setMimeType(ContentService.MimeType.JSON);
}
이에 대한 안내는 위에 첨부한 도큐먼트에 나와있다.
참고로 범위를 그냥 열로만 지정할 경우도 있을 것이다. (ex. A:E)
이때는 존재하는 공백 칸인 데이터도 긁어서 보내주게 된다. 따라서 필요없는 행과 열 칸은 삭제하자.
function setValue(sheetType, datas)
{
var curSheet;
var jsonArray = JSON.parse(datas); // 유니티에서 보내온 1차원 배열의 json 데이터
switch (sheetType){
case "case1":
curSheet = sheet_0; // 위에서 불러온 시트
break;
case "case2":
curSheet = sheet_1;
break;
}
curSheet.clear();
curSheet.appendRow(["A","B","C","D","E"]); // 보내온 클래스 데이터의 스키마(변수명) 추가
for(var i = 0; i < jsonArray.data.length; i++){
var A = jsonArray.data[i].A;
var B = jsonArray.data[i].B;
var C = jsonArray.data[i].C;
var D = jsonArray.data[i].D;
var E = jsonArray.data[i].E;
curSheet.appendRow([A, B, C, D, E]);
}
}
전체적인 흐름은 아래와 같다.
DataManager라는 스크립트를 singleton으로 생성한 뒤 위에서 만든 google script의 url를 통신을 위해 리터럴로 저장해준다.
통신을 통한 데이터 반환을 위해 유니티의 async와 await라는 비동기 시스템을 활용해줬다.
왜 코루틴을 사용하지 않았는가?
코루틴
을 사용하게 되면 코루틴 코드 내부적으로는 통신을 기다려 데이터를 받아올 수 있지만,
유니티의 전체적인 흐름은 동기적이기 때문에 이때 코루틴 자체적으로 기다려서 받은 데이터를 다른 스크립트에서 사용하기 까지 흐름을 보장받지 못한다.
-> 즉 동기적인 흐름상, 데이터를 사용할 때null
일 수도 있다는 의미이다.- 그렇다면
delegate
는?
데이터를 통신을 통해 받은 뒤 delegate를 활용해 콜백을 사용해줄 수 잇었지만 받은 데이터의 클래스가 어떤 것인지 하나의 함수에 결정하기 어렵다.
상황별로 어떤 데이터이므로 어떤 자료형으로 반환되는 코드를 짜줘야한다는 말이다.
(이전 포스트에서는 대리자를 통해 데이터를 받았고 그를 위해 모든 자료형에 대해 split함수를 생성해주는 불편한 상황이 발생했었다)
이런 상황을 방지하기 위해 아래의 이유를 포함하여generic
을 사용했고 자료 반환을 추구했다.- DataManager 스크립트는 싱글톤 구조이기에 함수를 호출하는 코드의 위치들이 다 제각각이다.
따라서 해당 함수의 사용과 같이 반환으로 데이터를 저장하고 싶었다.
하지만 코루틴은 자료 반환을 지원하지 않기에 반환 받을 수 있는 async를 사용했다.
그렇게 탄생한 getValues 함수.
public async Task<T[]> GetValues<T>(SheetType sheetType, string range)
{ // 통신 형식은 wwwform를 활용했다.
WWWForm form = new WWWForm();
form.AddField("order", "getValue");
form.AddField("sheetType", sheetType.ToString());
form.AddField("range", range);
string data = await PostGetRequestAsync(form);
// 요청시 받아온 자료형에 맞춰 바로 json을 파싱해준다.
return JsonHelper.FromJson<T>(data);
}
async Task<string> PostGetRequestAsync(WWWForm form)
{
UnityWebRequest www = UnityWebRequest.Post(url, form);
var operation = www.SendWebRequest();
while (!operation.isDone) {
await Task.Yield();
}
string output = "";
if (www.result == UnityWebRequest.Result.Success) {
Debug.Log("응답: " + www.downloadHandler.text);
output = www.downloadHandler.text;
}
else {
Debug.Log("에러: " + www.error);
}
www.Dispose();
return output;
}
SetValues 함수는 range 대신에 넘길 데이터를 json형식으로 필드에 저장해준다.
public async Task SetValues<T>(SheetType sheetType, T[] datas) {
WWWForm form = new WWWForm();
form.AddField("order", "setValue");
form.AddField("sheetType", sheetType.ToString());
string jsonData = JsonHelper.ToJson<T>(datas);
form.AddField("values", jsonData);
await PostSetRequestAsync(form);
}