오늘 한 일
- 유니티 심화 주차 강의 진도 나가기
- 동적 생성 특강 듣기
- JSON 복습하기
JSON은 JavaScript 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 기반 표준 포맷으로 웹이나 네트워크에서 서버와 클라이언트 사이에 데이터를 주고 받을 때 주로 사용된다.
- JSON의 데이터는 Key와 Value의 쌍으로 이루어진 데이터로 저장된다.
{ "이름": "홍길동", "나이": 25, "성별": "남성", "직업": "개발자", "주소": { "도시": "서울", "우편번호": "12345" }, "취미": ["독서", "음악감상", "여행"] }
- 다른 언어에서도 JSON을 읽고 쓸 수 있는 기능이 제공된다.
(JSON이 언어로부터 독립적인 데이터 포맷이기 때문)// 사용 가능한 언어 JavaScript, Python, Java, C#, PHP, Ruby, Swift ... 그외 등등
- 주석을 넣을 수 없다
장점
- 텍스트를 사용하기 때문에 사람이 이해하기 쉽다.
- 직렬화와 비직렬화 함수를 통해서 변환이 편하다.
단점
- 작은 문법 오류에도 민감하게 반응한다.
// 기본적인 데이터 구조와 리스트, Dictionary public class JsonTestClass { public string name; public int health; public float speed; public List<string> itemList = new List<string>(); public Dictionary<string, float> potionList = new Dictionary<string, float>(); public JsonTestClass() { name = "르탄"; health = 100; speed = 5f; itemList.Add("스파르타의 창"); itemList.Add("스파르타의 방패"); potionList.Add("회복 물약", 4); potionList.Add("마나 물약", 3); } public void Print() { Debug.Log($"이름: {name}"); Debug.Log($"체력: {health}"); Debug.Log($"속도: {speed}"); foreach (string item in itemList) { Debug.Log($"소지한 아이템 {item}"); } foreach (var data in potionList) { Debug.Log($"포션 목록 - {data.Key} : {data.Value}"); } } } // Vector3 public class JsonVector { public Vector3 vector3 = new Vector3(1f, 1f, 1f); } // MonoBehaviour public class TestMono : MonoBehaviour { public int i = 10; public Vector3 v3 = new Vector3(2f, 2f, 2f); }
코드
// 기본 데이터 구조 직렬화 JsonTestClass jTest1 = new JsonTestClass(); string jsonData = JsonConvert.SerializeObject(jTest1); // 오브젝트를 매개변수로 전달 Debug.Log(jsonData); // 기본 데이터 구조 역직렬화 JsonTestClass jTest2 = JsonConvert.DeserializeObject<JsonTestClass>(jsonData); jTest2.Print(); // MonoBehaviour 직렬화 GameObject obj = new GameObject(); obj.AddComponent<TestMono>(); Debug.Log(JsonConvert.SerializeObject(obj)); // Vector3 직렬화 JsonVector jVector = new JsonVector(); Debug.Log(JsonConvert.SerializeObject(jVector));
결과
- 기본 데이터 구조 직렬화
- 기본 데이터 구조 역직렬화
- MonoBehaviour 클래스 자기 참조 루프
- MonoBehaviour를 상속받는 클래스를 직렬화할 때에 순환 구조를 해결해도 다른 예외는 해결책이 없다. 그래서 상속받지 않는 클래스가 대신 값을 받아오거나 다른 매개 변수를 통해서 직렬화해줘야한다.
Vector3 normalized 루프
- Vector3를 어거지로 정상 직렬화하고 싶다면..
JsonVector jVector = new JsonVector(); JsonSerializerSettings setting = new JsonSerializerSettings(); // setting을 건들여준다. setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; Debug.Log(JsonConvert.SerializeObject(jVector));
- 위와 같이 Loop 설정을 바꿔서 직렬화할 수 있지만, 문제는 좌표가 아닌 다른 것들도 다 딸려서 저장되기 때문에 사용하지 않는 편이다.
- 그래서 MonoBehaviour를 상속받는 클래스처럼 따로 특정값만 받아서 직렬화해주는 방식을 사용한다.
코드
// 기본적인 데이터 구조 직렬화 JsonTestClass jTest1 = new JsonTestClass(); string jsonData = JsonUtility.ToJson(jTest1); Debug.Log(jsonData); // 기본적인 데이터 구조 역직렬화 JsonTestClass jTest2 = JsonUtility.FromJson<JsonTestClass>(jsonData); jTest2.Print(); // Vector3 직렬화 JsonVector jVector = new JsonVector(); string jsonVector = JsonUtility.ToJson(jVector); Debug.Log(jsonVector); // Mono 직렬화 GameObject obj = new GameObject(); var test1 = obj.AddComponent<TestMono>(); test1.i = 100; test1.v3 = new Vector3(3f, 3f, 3f); /// 직렬화 할 때는 게임 오브젝트가 아니라 해당 클래스 컴포넌트를 직접적으로 가져와줘야한다. string jsonData2 = JsonUtility.ToJson(obj.GetComponent<TestMono>()); Debug.Log(jsonData2); // Mono 역직렬화 GameObject obj2 = new GameObject(); // 기존 역직렬화와 다르게 Overwrite를 사용한다 JsonUtility.FromJsonOverwrite(jsonData, obj2.AddComponent<TestMono>()); // FromJsonOverwrite - 새로운 오브젝트를 만들지 않고, 기존에 있는 오브젝트에 클래스의 변수값을 덮어씌우는 처리를 한다.
결과
- 보면은 Dictionary가 Serialize 되지 않은 모습을 볼 수 있다.
(Json Utility는 Dictionary와 클래스 Serialize를 지원하지 않는다.)
-> 클래스는 [System.Serializable]로 해결 가능하지만,
-> Dictionary는 아예 지원하지 않는다. (외부 라이브러리를 사용해야된다;)- Vector3나 MonoBehaviour를 상속받는 클래스의 오브젝트를 직렬화할 때 정상적으로 직렬화된다.
파일을 만들어서 저장하기
// 파일 경로 지정 (File 모드 읽거나 만들거나) FileStream saveStream = new FileStream(Application.dataPath + "/test.json", FileMode.OpenOrCreate); // 데이터를 만들어서 직렬화 JsonTestClass jTest1 = new JsonTestClass(); string jsonSaveData = JsonConvert.SerializeObject(jTest1); // 문자열인 Json 데이터를 인코딩 UTF8의 GetBytes 함수로 Byte 배열로 만들어준다. (파일 저장 때문에) byte[] saveData = Encoding.UTF8.GetBytes(jsonSaveData); // 쓰고 닫기 saveStream.Write(saveData, 0, saveData.Length); saveStream.Close();
저장된 결과
- 잘 저장된 모습을 볼 수 있다. (Application.dataPath면 Assets 파일에다 저장될 것이다.)
만든 파일을 읽어오기
// 지정된 파일 열기 (FileMode.Open) FileStream loadStream = new FileStream(Application.dataPath + "/test.json", FileMode.Open); // 생성된 파일 stream에서 Read로 데이터를 읽어드린 다음 읽은 데이터를 반대로 String으로 인코딩해준다. byte[] loadData = new byte[loadStream.Length]; loadStream.Read(loadData, 0, loadData.Length); loadStream.Close(); string jsonLoadData = Encoding.UTF8.GetString(loadData); // 그 다음에 이 문자열을 DeSerialize해준다. JsonTestClass jTest2 = JsonConvert.DeserializeObject<JsonTestClass>(jsonLoadData); jTest2.Print();
불러서 읽은 결과
- 기본적인 데이터 구조들이 불러와진 걸 볼 수 있다.
이와 같이 게임 데이터를 로컬에 저장하고 불러오는 세이브 기능을 구현해볼 수 있다.
Json 데이터를 클래스로 만드는 방법이 있는데
Json 2 C#를 검색하면 Json 데이터를 읽고 자동으로 클래스로 만들어주는 사이트들이 있다.
베르의 게임 개발 유튜브
JSON으로 작업하기
저번에 Visual Studio로만 하는 JSON 활용법과 Unity에서 하는 JSON 활용법이 달라서 신기했다. 추후에 가능하다면 JSON을 통한 멀티 플레이어 기능 구현을 하는 방법을 알아둬야겠다.