2025.04.08 (화)

윤혜진·2025년 4월 8일

📍오늘의 학습 키워드

  • 최종 프로젝트 3일차
    • Firebase 시작하기
    • 구글 스프레드 시트 → json 변환
    • 트러블 슈팅

📍작업 내용

  • 오늘 작업 내용 요약 :

    • 구글 시트 연결하려 했으나 튜터님께 파이어베이스를 추천받음
    • 참고한 영상: https://youtu.be/A6du3DUTIPI?si=mRirXN_pSGE3ANPI
    • 처음 생각한 그림은 구글 스프레드시트에서 작성한 데이터 테이블을 바로 서버와 연결해주는 것이었지만, 현재 나의 실력으로 둘을 바로 연결해줄만한 방법이 당장 떠오르지 않아 차선을 선택.
    • 선택한 방법: 구글스프레드시트→json 파일로 변환하고, 변환된 json파일을 파이어베이스의 서버에 올린 뒤, 서버에 있는 파일을 받아오는 방식을 이용하기로 함
    • 데이터테이블의 구조는 클래스 다이어그램에서 나온 각자의 ScriptableObject를 참고하여 작성했으나, ATM을 구현할 때와 달리 데이터의 구조가 깊어서 파싱이 쉽지 않았음.
  • 파이어베이스 시작하기

  • 구글 스프레드 시트 → json 변환

    • (시도1) https://youtu.be/XOUqdgnOK5c?si=sQnMmPJ9mcxAeRI-

    • 해당 영상을 보니 구글 시트의 확장 프로그램을 이용하면 시트의 데이터들을 json 파일로 변환이 가능한 것 같아서 시도해보았다.

    • 그러나 Sheets to JSON로는 내가 원하는 Key로 데이터들을 묶어줄 수 있는 방법이 없어서 다른 방법을 생각해보기로 했다.

    • (시도2) 다른 확장 프로그램을 사용해도 내가 원하는 구조로 딱 맞춰 변환해줄 수 있지는 않을 것 같아 Google Apps Script를 이용해 내가 원하는 기능을 만들어 사용해보기로 했다.

    • 이 기능을 사용하는 방법은 다음과 같다:

      1. 구글 스프레드시트 > 확장 프로그램 > Apps Script 선택

      2. 나타난 Apps Script 창에 gs 파일을 추가해 메서드를 직접 코딩하면 된다.

  • 이걸 내가 직접 프로그래밍 할 수 있었으면 정말 좋았겠지만... Apps Script는 자바스크립트 기반으로만 코딩이 가능하기 때문에 이 부분은 부득이하게 AI의 도움을 받았다.

  • AI에게 요구한 부분은 다음과 같음:
    - 시트의 탭에 적힌 이름으로 데이터를 묶어줄 것
    - 시트 첫번째 열에 있는 값을 키로 사용할 것
    - json 데이터로 변환 후 사용자가 json 파일을 저장할 수 있도록 할 것
    - 파일명은 현재 날짜를 붙여 생성되게 할 것
    - 만약 같은 이름의 파일이 있다면 이름 끝에 “(2)” 와 같은 식으로 숫자를 붙여줄 것
    version탭은 version으로 묶고, 나머지 탭들은 jsonData로 묶어줄 것 (버전관리를 위해 추가해주었다.)
    - 구글 시트에서 변환 작업을 바로 실행할 수 있도록 해당 기능의 UI를 추가해줄 것

  • 그 결과 다음과 같은 코드가 나왔고, 내가 원하는 구조대로 json 파일을 변환할 수 있었다!:

    function exportVersionedJSONtoDrive() {
     const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
     const sheets = spreadsheet.getSheets();
     let versionValue = "1.0"; // 기본값
    
     const result = {
       version: "",
       jsonData: {}
     };
    
     sheets.forEach(sheet => {
       const sheetName = sheet.getName();
       const data = sheet.getDataRange().getValues();
    
       if (sheetName.toLowerCase() === "version") {
         // 버전 정보 읽기: A1 = "version", B1 = 숫자
         if (data.length > 0 && data[0].length > 1) {
           versionValue = String(data[0][1]);
         }
         return; // version 시트는 jsonData에 포함하지 않음
       }
    
       if (data.length < 2) return;
    
       const headers = data[0];
       const sheetData = {};
    
       for (let i = 1; i < data.length; i++) {
         const row = data[i];
         const key = row[0]; // 첫 번째 셀 값이 key
    
         const rowData = {};
         for (let j = 1; j < headers.length; j++) {
           const header = headers[j];
           const keyName = header.charAt(0).toLowerCase() + header.slice(1);
           rowData[keyName] = row[j];
         }
    
         sheetData[key] = rowData;
       }
    
       result.jsonData[sheetName] = sheetData;
     });
    
     result.version = versionValue;
     const jsonString = JSON.stringify(result, null, 2);
    
     // 📅 날짜 구하기
     const today = new Date();
     const yyyy = today.getFullYear();
     const mm = String(today.getMonth() + 1).padStart(2, '0');
     const dd = String(today.getDate()).padStart(2, '0');
     const dateString = `${yyyy}-${mm}-${dd}`;
    
     // 📄 파일명 구성
     const baseFileName = `gameData_v${versionValue}_${dateString}`;
     const fileExtension = ".json";
    
     const folderPath = "17floor/DataTable";
     const targetFolder = getOrCreateFolderByPath(folderPath);
    
     // 동일한 이름 있으면 (2), (3)... 붙이기
     let fileName = baseFileName + fileExtension;
     let count = 2;
     while (folderHasFile(targetFolder, fileName)) {
       fileName = `${baseFileName}(${count})${fileExtension}`;
       count++;
     }
    
     const file = targetFolder.createFile(fileName, jsonString, "application/json");
     Logger.log("✅ JSON 파일 저장 완료: " + file.getUrl());
    }
    
    // 폴더 경로 생성 유틸
    function getOrCreateFolderByPath(path) {
     const parts = path.split('/');
     let folder = DriveApp.getRootFolder();
    
     for (let i = 0; i < parts.length; i++) {
       const name = parts[i];
       const folders = folder.getFoldersByName(name);
       folder = folders.hasNext() ? folders.next() : folder.createFolder(name);
     }
    
     return folder;
    }
    
    // 파일 존재 여부 확인
    function folderHasFile(folder, fileName) {
     const files = folder.getFilesByName(fileName);
     return files.hasNext();
    }
    
    // 메뉴에 커스텀 함수 등록
    function OnOpen() {
     const ui = SpreadsheetApp.getUi();
     ui.createMenu("📦 JSON 변환")
       .addItem("📁 17floor/DataTable에 저장", "exportVersionedJSONtoDrive")
       .addToUi();
    }
  • UI를 추가하는 방법은 다음과 같다:

    • 확장 프로그램 > 매크로 > 메서드 이름

    • 누르면 스크립트 실행중... 이 뜨는데, 조금만 더 기다리면 성공적으로 추가되는 것을 볼 수 있다.

    • 저장 탭을 누르면 현재 로그인 되어있는 구글 계정의 G드라이브에 저장된다.

    • 저장된 json 파일은 파이어베이스에서 JSON 가져오기를 통해 올릴 수 있다

  • 과정 GIF:

📍겪은 어려움

  • google-services.json 이슈

    • 코드를 짜고 실행하려는데 다음과 같은 오류가 발생함:

      • Unable to load Firebase app options ([A:/TeamSparta/on-the-17floor/Element_Tower_Defense/Assets/StreamingAssets\google-services-desktop.json, A:/TeamSparta/on-the-17floor/Element_Tower_Defense/Assets/StreamingAssets\google-services.json] are missing or malformed)
    • 오류 번역:

      • Firebase 앱 옵션을 로드할 수 없습니다 ([A:/TeamSparta/17층/Element_Tower_Defense/Assets/StreamingAssets\\google-services-desktop.json, A:/TeamSparta/17층/Element_Tower_Defense/Assets/StreamingAssets\\google-services.json]이 누락되었거나 잘못된 형식입니다)
    • 문제 원인:

      • Firebase SDK는 초기화 시 google-services.json(Android용) 혹은 google-services-desktop.json(유니티 에디터 또는 데스크탑 실행용) 파일을 StreamingAssets 폴더에서 찾는데, 이 파일들이 해당 폴더에 존재하지 않기 때문에 Firebase 앱 초기화에 실패한 거였음.
    • 해결한 방식:

      • 해당 파일들을 파이어베이스에서 다시 다운받아 StreamingAssets 폴더에 넣어주니 해당 오류는 일어나지 않음.
    • 또 다른 문제 발생:

      DatabaseException: Failed to get FirebaseDatabase instance:
      Specify DatabaseURL within FirebaseApp or from your GetInstance() call.
    • 원인:

      • google-services.json에 Realtime Database의 URL가 포함되지 않은 상태라 데이터를 받아오지 못하고 있었음.
    • 해결한 방식:

      • 파이어베이스에서 google-services.json을 다시 다운받아 넣어줌.
      • 새로 받은 파일에는 Realtime Database의 URL가 제대로 포함되어 있었다.
      • SDK를 추가할 때마다 google-services.json은 다시 받아서 넣어줘야 한다고 생각해도 무방할 듯.
  • JSON 데이터 파싱 이슈

    • 문제를 해결하고 실행하니 또 다른 오류가 발생함 :

      ArgumentException: JSON parse error: Invalid value.
      JsonUtility.FromJson<T>(json)
    • 오류 원인:

      • JsonUtility는 딕셔너리를 지원하지 않는데, 내가 짠 자료의 구조는 Key안의 List안의 List 구조여서 굉장히 깊음.
      • 때문에 래퍼 클래스를 통해 파싱해주려고 해도 현실적으로 쉽지 않았음.
      • 가장 현실적인 해결 방법은 JsonUtility대신 딕셔너리를 지원하는 Newtonsoft.Json를 사용하는 것인데, 유니티에서는 JsonUtility의 성능이 압도적으로 좋다고 알고 있기 때문에 고민이 됨.
      • 일단은 다른 방법이 있을지도 모르니 내일 튜터님께 찾아가서 조언을 구해보기로 함.

📍회고 및 반성

  • 내 나름대로는 기획자의 편의성을 챙긴다고 구글 스프레드 시트를 이용했지만, 역시 바로 구글 스프레드 시트를 연동해서 사용하는 방법보다는 번거로워 보여서 고민이다.
  • 구글 스프레드 시트는 보안 이슈가 있을 수도 있다고 하니 어쩔 수 없는 일이지만, 그래도 레벨 테스트를 진행할 때는 구글 스프레드 시트를 연결해서 즉각적으로 테스트를 할 수 있도록 하는 게 나을까? 고민이 됨.
  • 이 부분도 튜터님과 다른 팀원들과 이야기해 보면 좋을 것 같다.

0개의 댓글