개요

이전에 구현한 데이터 드리븐 은 개발자가 직접 엑셀 파일로 들어가서, CSV 파일로 저장한 다음 유니티 에디터에 직접 넣었어야 했다.

그러나 매번 시트에서 Unity로 가져오는 과정은 번거롭다.

이러한 과정을 버튼 한 번으로 자동으로 CSV 파일을 내려받아 에디터에 저장해주는 시스템을 구축할 것이다.

먼저 전체 코드를 살펴보자.

GoogleSheetsSync.cs

// Assets/Editor/GoogleSheetsSync.cs
#if UNITY_EDITOR 
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;

public static class GoogleSheetsSync
{
    // 카드·몬스터 두 문서를 하나의 배열로 관리
    private static readonly SheetDoc[] Docs =
    {
        // 데이터 테이블1
        new(
            "비공개ID",      // 문서 ID
            new (string Sheet, string File)[]
            {
                ("시트1",          "00.데이터1"),
                ("시트2",      "01.데이터2")
            }
        ),

        // 데이터 테이블2
        new(
            "비공개",
            new (string Sheet, string File)[]
            {
                ("유닛",            "10.유닛 테이블"),
                ("탱커",                    "11.몬스터 - 탱커"),
                ("러셔",                    "12.몬스터 - 러셔"),
                ("전사",                    "13.몬스터 - 전사"),

            }
        )
    };

    // 메뉴 클릭으로 동기화
    [MenuItem("Tools/Sync Sheets")]
    private static void SyncSheets()
    {
        foreach (var doc in Docs)
            foreach (var pair in doc.Pairs)
                DownloadCsv(doc.DocId, pair.Sheet, pair.File);

        AssetDatabase.Refresh();
        Debug.Log("<color=lime>[SheetSync] 모든 시트 동기화 완료</color>");
    }

 
    #region 내부 구현

    private static void DownloadCsv(string docId, string sheetName, string fileName)
    {
        var url = $"<https://docs.google.com/spreadsheets/d/{docId}/gviz/tq?tqx=out:csv>" +
                  $"&sheet={UnityWebRequest.EscapeURL(sheetName)}";

        var req = UnityWebRequest.Get(url);
        req.SendWebRequest();
        while (!req.isDone) { }

        if (req.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"[SheetSync] <b>{sheetName}</b> 실패 ▶ {req.error}");
            return;
        }

        const string folder = "Assets/Resources/CSV";
        if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);

        string clean = CleanCsv(req.downloadHandler.text);
        File.WriteAllText(Path.Combine(folder, $"{fileName}.csv"), clean, Encoding.UTF8);

        Debug.Log($"[SheetSync] {sheetName} → {fileName}.csv 저장");
    }

    private readonly struct SheetDoc
    {
        public readonly string DocId;
        public readonly (string Sheet, string File)[] Pairs;
        public SheetDoc(string docId, (string Sheet, string File)[] pairs)
        { DocId = docId; Pairs = pairs; }
    }
    private static string CleanCsv(string raw)
    {
        var sb = new StringBuilder();
        bool firstLine = true;
        StringBuilder names = null, types = null;

        foreach (var line0 in raw.Split('\\n'))
        {
            var line = line0.TrimEnd('\\r', '\\n');
            if (string.IsNullOrEmpty(line)) continue;

            // 1) 따옴표·끝 콤마 정리
            if (line.StartsWith("\\"") && line.EndsWith("\\""))
                line = line[1..^1];
            line = line.Replace("\\",\\"", ",");
            line = Regex.Replace(line, ",+$", "");

            // 2) 첫 줄이면 ‘이름 타입’ 분리 작업
            if (firstLine)
            {
                names = new StringBuilder();
                types = new StringBuilder();

                foreach (var cell in line.Split(','))
                {
                    var parts = cell.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
                    names.Append(parts[0]).Append(',');
                    types.Append((parts.Length > 1 ? parts[1] : "")).Append(',');
                }

                sb.AppendLine(Regex.Replace(names.ToString(), ",+$", ""));
                sb.AppendLine(Regex.Replace(types.ToString(), ",+$", ""));
                firstLine = false;
            }
            else         // 데이터 행은 그대로
                sb.AppendLine(line);
        }
        return sb.ToString();
    }
    #endregion
}
#endif

이제 코드에 대해서 자세하게 설명한다.

Docs 배열 구조와 Google Sheets 문서 ID 및 시트-파일 매핑의 의미

// 스프레드시트 한 개 문서에 대한 정보 구조체
public struct SheetDoc {
    public string docId;         // 구글 문서 ID
    public string[] sheetNames;  // 가져올 시트 이름들
    public string[] fileNames;   // 각 시트를 저장할 CSV 파일명들
}

// 동기화 대상 Google Sheets 문서 및 시트 설정
static SheetDoc[] Docs = new SheetDoc[] {
    new SheetDoc {
        docId = "1AbCdEFg...XYZ",  // 예시: Google 스프레드시트 문서 ID
        sheetNames = new string[] { "Enemies", "Items" },
        fileNames  = new string[] { "Enemies.csv", "Items.csv" }
    }
    // 필요한 만큼 SheetDoc 추가 가능
};

먼저, 동기화할 Google 스프레드시트 정보는 코드 내의 Docs 배열에 정의되어 있다.

이 배열은 동기화 대상이 되는 여러 개의 Google 스프레드시트 문서와 각 문서에 속한 시트들을 나타낸다.

각 항목은 커스텀 구조체 SheetDoc 타입으로 구성되어 있으며, 문서 ID, 시트 이름 목록, 그리고 각 시트를 저장할 CSV 파일 이름 목록을 포함한다.

위 코드를 보면, SheetsDoc 구조체는 하나의 스프레드시트 문서를 나타낸다.

docId 필드는 해당 문서의 고유 ID로

문서 ID 는 스프레드시트 URL에서 확인할 수 있는 문자열이다.

예를들어,