Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(58일차) - 데이터 파싱 (2), API 활용

by 독기품은토끼 2025. 8. 10.
✅ 오늘의 학습 목표
1. 데이터 파싱 (2)
2. API 활용

1. Parser

1. Json

자바스크립트 객체 표기법

서버에서 데이터를 받아올 때나, 저장 데이터를 관리할 때 자주 사용한다.

 

Json을 사용하는 이유

  • 데이터를 key : value 형식으로 깔끔하게 저장
  • XML처럼 태그를 열고 닫는 거 없이 중괄호 {}와 대괄호[]로 구조 표현 가능
  • 유니티에서는 JsonUtility라는 내장 파서를 사용하면 바로 C# 객체로 변환 가능
{
  "characters": 
  [
    {
      "CharID": "C01",
      "Name": "Player",
      "HP": 100,
      "Attack": 15
    },
    {
      "CharID": "C02",
      "Name": "Goblin",
      "HP": 30,
      "Attack": 5
    },
    {
      "CharID": "C03",
      "Name": "Dragon",
      "HP": 500,
      "Attack": 50
    },
    {
      "CharID": "C04",
      "Name": "Wizard",
      "HP": 70,
      "Attack": 20
    }
  ]
}
[System.Serializable]
public class CharacterData
{
    public string CharID;
    public string Name;
    public string HP;
    public int Attack;
}

[System.Serializable]
public class CharacterListWrapper
{
    public List<CharacterData> characters;
}
  • JSON 구조에 맞춰 클래스 설계
  • JSON 최상위에 있는 "characters" 배열을 List로 받기 위해 CharacterListWrapper로 감싸줌
  • 주의: JSON 키 이름과 변수 이름(대소문자 포함)이 같아야 함
void Start()
{
    var dataFile = Resources.Load<TextAsset>("JsonData");
    var data = dataFile.text;

    // var data2 = File.ReadAllText(Application.dataPath + "/Resources/JsonData.json");

    ParsingCharacterJsonData(data);
}
  • Resources.Load<TextAsset>: Assets/Resources 폴더 안의 JSON 파일을 불러옴.
  • 확장자는 빼고 파일명만 입력
  • 다른 방법: File.ReadAllText(Application.dataPath + "/Resources/JsonData.json")
    → 파일 시스템 직접 접근 (플랫폼 호환성 주의)
구분 File.ReadAllText Resources.Load
방식 파일 시스템 접근 Unity 리소스 시스템 사용
경로 어디든 가능 반드시 Resources 폴더
확장자 포함해야 함 생략
빌드 후 수정 가능 불가(Read-only)

 

public List<CharacterData> characterDatas = new List<CharacterData>();

private void ParsingCharacterJsonData(string data)
{
    Debug.Log(data);
    CharacterListWrapper wrapper = JsonUtility.FromJson<CharacterListWrapper>(data);

    foreach (CharacterData cData in wrapper.characters)
    {
        characterDatas.Add(cData);
        Debug.Log($"{cData.CharID} / {cData.Name} / {cData.HP} / {cData.Attack}");
    }
}
  • JsonUtility.FromJson<T>(string json) : JSON 문자열을 T 타입 객체로 변환
  • wrapper.characters 안에 캐릭터 정보들이 리스트 형태로 들어옴

 

2. XML

XML은 태그 기반이라 구조를 어노테이션으로 정확히 맵핑해주어야 데이터가 깔끔하게 들어간다.

대신 한 번 맞춰두면 XmlSerializer가 알아서 객체로 쭉 뽑아준다.

<?xml version="1.0" encoding="UTF-8"?>
<Characters>
  <Character>
    <CharID>C01</CharID>
    <Name>Player</Name>
    <HP>100</HP>
    <Attack>15</Attack>
  </Character>
  <Character>
    <CharID>C02</CharID>
    <Name>Goblin</Name>
    <HP>30</HP>
    <Attack>5</Attack>
  </Character>
  <Character>
    <CharID>C03</CharID>
    <Name>Dragon</Name>
    <HP>500</HP>
    <Attack>50</Attack>
  </Character>
  <Character>
    <CharID>C04</CharID>
    <Name>Wizard</Name>
    <HP>70</HP>
    <Attack>20</Attack>
  </Character>
</Characters>
[System.Serializable]
public class CharacterData
{
    public string CharID;
    public string Name;
    public int HP;
    public int Attack;
}

[System.Serializable]
[XmlRoot("Characters")]
public class CharacterList
{
    [XmlElement("Character")]
    public List<CharacterData> characters;
}
  • [XmlRoot("Characters")] : XML 최상위 태그 지정
  • [XmlElement("Character")] : <Characters> 안에 반복되는 <Character> 태그를 매핑
  • List<CharacterData>로 모든 캐릭터 정보 저장
void Start()
{
    var dataFile = Resources.Load<TextAsset>("XmlData");
    string data = dataFile.text;

    ParsingCharacterXmlData(data);
}
  • Resources 폴더 안에 XmlData.xml이 있어야 함
  • 확장자는 생략하고 이름만 입력
private void ParsingCharacterXmlData(string data)
{
    Debug.Log(data);

    XmlSerializer serializer = new XmlSerializer(typeof(CharacterList));

    using (StringReader reader = new StringReader(data))
    {
        CharacterList loadedData = (CharacterList)serializer.Deserialize(reader);
        characterDatas = loadedData.characters;
    }

    foreach (CharacterData cData in characterDatas)
    {
        Debug.Log($"{cData.CharID} / {cData.Name} / {cData.HP} / {cData.Attack}");
    }
}
  • XmlSerializer : XML 데이터를 C# 객체로 변환
  • StringReader : 문자열(XML 텍스트)을 스트림처럼 읽도록 변환
  • Deserialize : XML → C# 객체로 역직렬화
  • 리스트에 담긴 캐릭터 정보들을 출력

 

JSON과 XML의 차이점

구분 JSON XML
구조 key-value 태그 기반
매핑 필드명 동일 [XmlRoot], [XmlElement] 필요
최상위 배열 바로 불가(래핑 필요) 루트 태그 필수
유니티 파서 JsonUtility XmlSerializer

 

3. 데이터 저장&불러오기

게임 진행 중 만들어지는 데이터를 JSON 파일로 저장하고 시작할 때 불러오는 로직을 붙여보자

using System;
using UnityEngine;
using System.IO;

[System.Serializable]
public class SaveData
{
    public string CharID = "C01";
    public string Name = "Player";
    public int HP = 100;
    public int Attack = 10;
    public int score;
}

public class SaveDataFile : MonoBehaviour
{
    private int score;
    private string savePath;

    void Start()
    {
        // Application.dataPath : Assets 폴더
        // Application.persistentDataPath : 플랫폼별로 안전하게 추천하는 로컬 저장소 경로
        savePath = Path.Combine(Application.persistentDataPath, "saveDataFile.json");

        Load();
        Debug.Log("Load Score : " + score);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            score++;
            Debug.Log("Score : " + score);

            Save();
        }
    }

    private void Save()
    {
        SaveData data = new SaveData();
        data.score = this.score;

        // string json = JsonUtility.ToJson(data);
        string json = JsonUtility.ToJson(data, true);

        File.WriteAllText(savePath, json);

        Debug.Log("Data saved to : " + savePath);
    }

    private void Load()
    {
        if (File.Exists(savePath))
        {
            string json = File.ReadAllText(savePath);
            SaveData data = JsonUtility.FromJson<SaveData>(json);
            this.score = data.score;
        }
        else
        {
            score = 0;
        }
    }

    void OnApplicationQuit()
    {
        Save();
    }
}

데이터 구조

  • SaveData 클래스에 캐릭터 기본값과 score를 포함.
  • 점수는 런타임 중 계속 변하니까 SaveData 인스턴스를 만들어 현재 점수를 담아 저장

저장 경로 선택

  • Application.persistentDataPath에 saveDataFile.json으로 저장
  • 이 경로는 플랫폼별 안전한 로컬 저장소라서 PC/모바일/WebGL 등 빌드 환경에서 일관성 있게 동작
  • Path.Combine으로 경로 조립(운영체제별 경로 구분자 이슈 자동 처리)

참고: Application.dataPath(Assets 폴더)는 에디터에서만 편하고 빌드 후엔 쓰기 제한이 걸릴 수 있다.
저장용이라면 persistentDataPath 고정이 마음 편함

 

실행 흐름

  1. Start() → Load()
    • 시작할 때 저장 파일이 있으면 File.ReadAllText(savePath)로 JSON 문자열을 읽고
      JsonUtility.FromJson<SaveData>(json)으로 역직렬화해서 score에 복원
    • 파일이 없으면 score = 0으로 초기화
  2. Update()에서 Space 입력 → Save()
    • Space를 누를 때마다 score++ 후 즉시 저장(Save() 호출)
    • Save()에서는 new SaveData()로 컨테이너 만들고 현재 점수를 세팅한 다음,
      JsonUtility.ToJson(data, true)로 보기 좋은 포맷(pretty print) JSON 생성 → File.WriteAllText.
  3. OnApplicationQuit()에서 최종 저장
    • 앱 종료 시 한 번 더 Save() 호출해 마지막 상태를 디스크에 보관

 

JsonUtility 포인트

  • ToJson(obj, true)의 true는 prettyPrint 옵션
    디버깅/사후 확인할 때 가독성이 좋아진다. (용량은 미세하게 늘 수 있음)
  • FromJson<T>는 필드명 기준 매핑이므로, JSON 키와 C# 필드명이 달라지면 값이 안 들어올 수 있다
    필드명 바꿀 땐 JSON도 같이 업데이트하거나, 별도 매핑용 클래스를 둔다

PlayerPrefs와의 비교

구분 PlayerPrefs JSON
구조 key-value (string/float/int) 자유로운 계층 구조(객체/리스트)
사용 난이도 매우 쉬움 중간(클래스/직렬화 필요)
데이터 크기 소/중 대용량도 OK
가시성 내부 저장 (레지스트리/플랫폼별) 파일로 저장 → 확인/전송 쉬움
보안 낮음 낮음(별도 암호화 필요)
권장 용도 옵션/간단한 숫자 세이브 슬롯, 인벤토리, 진행 상황 등

이번 케이스처럼 점수 + 메타 정보 같이 구조화된 데이터는 JSON 쪽이 훨씬 관리가 편하다.

 

2. API 활용

API(Application Programming Interface)이란 다른 시스템/서버의 기능이나 데이터를 URL로 요청해서 쓰는 방법을 뜻한다.

게임 클라이언트(유니티) ↔ 서버(백엔드) 구조에서 읽기(READ: GET), 쓰기(WRITE: POST)를 주로 쓴다.

  • 클라이언트 (Client) : 요청을 보내는 곳
  • 서버 (Server) : 응답을 보내는 곳
  • REST API : 클라이언트가 서버에 요청을 데이터로 전송 (서버가 이 클라이언트 입력을 사용하여 내부 함수를 시작하고 출력 데이터를 다시 클라이언트에 반환)
🥕 예행 작업
1. 데이터 활용 신청
 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

UnityWebRequest 기본 흐름

  1. URL 조립 (쿼리 파라미터 붙이기)
  2. UnityWebRequest.Get(url) 또는 UnityWebRequest.Post(url, form)
  3. yield return request.SendWebRequest()로 비동기 대기
  4. request.result로 성공/실패 확인
  5. request.downloadHandler.text로 응답 문자열 꺼내기 (보통 JSON/XML)
  6. HTTPS 권장. 꼭 HTTP를 써야 할 상황이면Player > Other Settings > Configuration > Allow downloads over HTTP = Always allowed.
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();

if (www.result != UnityWebRequest.Result.Success) {
    Debug.Log("Failed Data : " + www.error);
} else {
    string data = www.downloadHandler.text;
    Debug.Log(data);
}

 

 

1. 날씨 API

 

문서부터 읽기

  • 필수 파라미터(예: serviceKey, pageNo, numOfRows, dataType, base_date, base_time, nx, ny) 확인
  • 응답 스펙(각 필드 의미, 코드표: PTY/SKY 등) 확인

URL 만들기

URL += $"serviceKey={key}&numOfRows={numOfRows}&pageNo={pageNo}&dataType={dataType}" +
       $"&base_date={base_date}&base_time={base_time}&nx={nx}&ny={ny}";
  • 기본이 XML이면 &dataType=JSON을 붙이면 JSON으로 받을 수 있다
  • 날짜가 옛날이면 “최근 3일만 제공” 같은 오류가 나므로 날짜/시간 최신화 필수

 

응답 받기 & 로그

  • 데이터가 길면 콘솔에서 truncate로 잘려 보일 수 있음(데이터 자체는 제대로 들어옴)

JSON 파싱용 클래스 설계

[System.Serializable] public class Root     { public Response response; }
[System.Serializable] public class Response { public Header header; public Body body; }
[System.Serializable] public class Header   { public string resultCode; public string resultMsg; }
[System.Serializable] public class Body     { public string dataType; public Items items; public string pageNo, numOfRows, totalCount; }
[System.Serializable] public class Items    { public List<Item> item; }
[System.Serializable] public class Item     { public string category, fcstDate, fcstTime, fcstValue, nx, ny; }
  • 문서의 계층대로 C# 클래스를 만든다(루트→response→body→items→item)

JsonUtility로 역직렬화

var root = JsonUtility.FromJson<Root>(data);
foreach (var it in root.response.body.items.item) {
    // PTY, SKY 등 골라 쓰기
}

 

실제 활용 (날씨 상태 매핑 예시)

private void SetWeatherType()
{
    if (currentSKY == 1 && currentPTY == 0)
    {
        weatherType = WeatherType.Sun;
    }
    else if (currentSKY == 3 || currentSKY == 4)
    {
        weatherType = WeatherType.Cloud;
    }
    else if (currentPTY == 1 || currentPTY == 2 || currentPTY == 4)
    {
        weatherType = WeatherType.Rain;
    }
    else if (currentPTY == 3)
    {
        weatherType = WeatherType.Snow;
    }

    Debug.Log($"현재 날씨는 {weatherType}입니다.");
}
  • 카테고리별 값 분기해서 enum으로 게임 상태에 반영
  • PTY(강수형태), SKY(하늘상태) 코드표는 문서 참고해서 숫자→의미 매핑
  • 리스트에서 필요한 카테고리만 필터링해 현재 상태를 계산

 

2. 구글 스프레드 시트

읽기(GET) - CSV 바로 가져오기

  • 공유 권한을 링크가 있는 모든 사용자(보기)로 설정
  • URL 뒤에 export?format=csv로 붙이면 바로 CSV로 내려받는다
    • 특정 범위만: export?format=csv&range=A2:D5
    • 시트 지정: &gid=시트ID
  • CSV/TSV는 줄/쉼표/탭으로 나뉘므로, Split로 파싱해서 구조화

JSON으로 뽑기

  • (간단 방법) Apps Script로 웹 앱을 만들어 JSON으로 변환해서 내보내기
  • (정석) Google API 사용(인증 필요, 문턱은 더 높지만 안정적)

Apps Script로 GET/POST 받기

  • 시트 열고 확장 프로그램 → Apps Script에서 스크립트 작성 후 배포(웹 앱)
  • 접근 권한을 “모든 사용자”로 두면 유니티에서 호출 가능
function doGet(e)  { return ContentService.createTextOutput("Get"); }
function doPost(e) {
  var val = e.parameter.value; // Unity에서 보낸 값
  // sheet.getRange(row, col).setValue(val);
  return ContentService.createTextOutput("Post");
}
// GET
var www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
var read = www.downloadHandler.text;

// POST (신규 권장 방식)
WWWForm form = new WWWForm();
form.AddField("value", "123");
var www2 = UnityWebRequest.Post(url, form);
yield return www2.SendWebRequest();
var wrote = www2.downloadHandler.text;
  • UnityWebRequest.PostWwwForm은 구방식. 요즘은 Post(url, WWWForm) 사용
  • 민감정보/검증/인증은 백엔드에서 처리(클라에서 키/시크릿 하드코딩 금지)
  • Apps Script는 배포(버전) 개념이라 수정 후 새 버전 배포를 눌러야 반영된다(웹 앱 URL은 유지 가능)

 

 


 

 

🚨 복습 필요