Unity 개발일지

[C#] Newtonsoft.Json 을 이용하여 콘솔창에서 Save/Load 기능 만들기 본문

C#

[C#] Newtonsoft.Json 을 이용하여 콘솔창에서 Save/Load 기능 만들기

아머르 2024. 5. 2. 21:10

진행하기 전에 데이터 직렬화(Serialization), 역직렬화(Deserialization)는 무엇이고 왜 필요한지 알아보자.

 

직렬화 ( Serialization ) 란?

메모리를 디스크에 저장하거나 네트워크 통신에 사용하기 위한 형식으로 변환하는 것

 

역직렬화 ( Deserialization ) 란?

디스크에 저장한 데이터를 읽거나, 네트워크 통신으로 받은 데이터를 메모리에 쓸 수 있도록 다시 변환하는 것

 

직렬화는 왜 필요한가?

메모리에 대해 이해하기 위해서는 힙 영역과 스택영역에 대한 기본적인 지식이 있어야한다.

 

1. 값 형식 데이터(Value Type)

  우리가 흔히 선언해서 사용하는 int, string, char 등 값 형식의 데이터들은 스택에 메모리가 쌓이고 직접 접근이 가능하다.

 

2. 참조 형식 데이터(Reference Type)

  C#에서 Object타입 혹은 C++에서 포인터 변수들이 여기에 해당된다.

  해당 혁식의 변수를 선언하면 힙에 메모리가 할당되고, 스택에서는 이 힙 메모리를 참조하는 구조로 되어있다.

 

이 두가지 데이터 중 디스크에 저장하거나 통신에 이용하는 데이터는 값 형식 데이터이다.

참조형식 데이터는 실제 데이터 값이 아닌 힙에 할당되어있는 메모리 번지 주소를 가지고 있기 떄문에 저장, 통신에 사용할 수 없다.

 

따라서 데이터 직렬화를 하게 되면 각 주소값들이 가지는 데이터들을 전부 값 형식 데이터로 변환해주고, 직렬화가 된 데이터들은 언어에 따라서 텍스트 또는 바이너리 등의 형태가 되는데, 이러한 형태가 되었을 때 저장하거나 통신 시 파싱이 가능한 유의미한 데이터가 되는 것이다.

 

직렬화가 왜 필요한지 알았다면 이제 저장 기능을 구현해 보자.

우선 Newtonsoft.Json 패키지를 받으면 되겠다.

첫번째 방법

상단의 프로젝트 탭 - NuGet 패키지 관리 - 찾아보기 - Newtonsoft.Json 입력 후 다운로드

이미 Newtonsoft.Json을 받았다면 설치됨에서 확인할 수 있다.

 

두번째 방법

상단의 도구 탭 - NuGet 패키지 관리자 - 패키지 관리자 콘솔을 클릭한다.

PM> 부분에 Install-Package Newtonsoft.Json을 작성 후 엔터를 치면 패키지가 받아진다.

 

이 패키지를 사용하기 위해서는 Save/Load 함수를 입력할 cs파일에 using Newtonsoft.Json;을 선언하는것을 잊지말자.

using Newtonsoft.Json; 썻을 때 빨간 밑줄이 뜬다면 두번째 방법으로 패키지를 다시 받아보자!

여기까지 하면 Json을 사용하기 위한 기초 작업은 끝났다.

 

저장할 값들을 PlayerData.cs를 만들어 클래스를 선언해주고 나열하였다.

 

그리고 PlayerData 생성자를 만들어 player에 있는 값들을 받아 저장파일을 생성할 것이다.

 

JsonSerialize.cs 파일을 만든 후 우리가 사용할 변수들을 선언해준다.

주석으로 처리되어있는 부분은 Player와 Item데이터를 사용하기 위해 만들어주었는지만,

여기에 선언해봤자 Program.cs의 GameManager랑 다른 객체로 인식을 하기때문에 오류가 발생했다.

따라서 삭제 후 Save또는 Road를 할 때 함수의 매개변수로 받아서 사용하게 되었다.

 

SaveData함수를 선언한 수 Player를 매개변수로 받는다.

이 함수는 GameManager에서 실행하므로 GameManager의 Player를 받아올 것이다.

 

저장할 파일명, 경로를 지정하고,

playerData 변수를 선언하고 생성자를 통해 값을 전달하고 직렬화를 해준다.

정상적으로 작동했다면 다음과 같은 파일이 생성될 것이다.

 

SaveData를 만드는 것 까지는 무난(?)하게 작업했지만

LoadData를 하는 과정에서 무수히 많은 오류가 터져나왔다...

 

cs별로 분업하고 합치는 과정에서 발생한 오류들을 갓튜터님들과 해결해가며 무사히 작성을 완료했다.

GameManager의 작동 원리를 보면 InitializeGame 함수를 실행하여 기본적인 오브젝트들을 생성해준다.

이 오브젝트들이 다른데에서 또 선언되거나 제대로 참조가 되지 않으면 null 이 발생하니 주의해주자.

Initialize의 위치 또한 제일 먼저 생성시켜줘야한다. 역시 서순이 매우 중요하다.

 

다음은 역직렬화 파트로 넘어가보자.

처음 기획은  역직렬화 후 Player 생성자에 playerData를 매개변수로 넣어 생성하려고 했지만

여기서 또 버그발생! Player를 생성하는것보다는 함수를 이용해 Player에게 playerData를 넘겨주기로 했다.

SetPlayer 함수를 만들어주고 playerData를 매개변수로 넣었다.

 

LoadData 함수에 불러올 파일명, 경로를 설정한 뒤

저장 데이터가 없다면 gameManager의 PlayerName 함수로 이동하도록 설정해주었고,

데이터가 있다면 playerData에 플레이어 데이터를 역직렬화 해줘서 SetPlayer 함수에 넣어 Player로 데이터를

넘겨주었다.

playerData.json에 잘 저장된 모습이다.

 

저장기능 확인

 

로드기능 확인

 

콘솔창에서도 작동이 잘 되었다.

 

아이템 파트도 구현하려고 해봤지만 추가 작업이 이루어지고 있었고,

플레이어도 중간에 팀원들이 추가적인 내용을 넣어서 나중에 또 수정한건 덤.. ㅎ

 

<추가 - 아이템 및 포션 저장기능 구현>

다음날에는 이어서 아이템 저장기능을 구현했다. Item은 플레이어와 다르게 List를 가져와야한다.

먼저 Item에 있는 리스트들을 참조하였다. 그리고 빈 생성자를 만들고(만들지 않으면 null이 출력됨) 

ItemData(Item item) 생성자에 Item.cs의 값들을 저장할 수 있게 만들었다.

처음에 생성자 내부에 List<Item> ItemIndex = item.ItemIndex; 로 작성해서 생성 간

클래스에 처음 정의 한 ItemIndex와 이름이 같은 다른 데이터를 만들어 null값이 출력되었다...

 

SaveData 함수에 변경점은 JsonSerialize 클래스의 전역변수로 선언했던 것들을 함수 안의 지역변수로 넣어주었다.

저장, 로드할때만 쓰는 변수들을 굳이 전역, 정적 변수로 선언할 필요가 없기 때문!

저장, 로드하면 변수들이 다시 생성되므로 지역변수로 선언해주었다.

그리하여 완성된 Save기능!

 

로드 기능은 앞서 작성한 LoadData 함수에 이어서 작성해주었다.

로드하던 중 저장 데이터에는 값이 제대로 들어가있는데 상점에 들어갈 때 오류가 발생했다.

이는 기존에 Item 클래스의 프로퍼티에 get; 만 설정되어있었는데 set;도 설정해주니 잘 작동되었다.

set; 이 없으니 데이터를 넣어주지 못했던것!

 

 

포션 저장, 불러오기 기능은 아이템과 동일하므로 작성 생략~!

 

 

이로서 3일간의 장황한 기능구현이 끝났다...

SaveData로 생성된 데이터를 들어가보면 값을 수정할 수 있는데 이는 치트플레이어를 생성할 수 있다.

공부를 더 해서 플레이어데이터를 암호화 하여 저장하는 것도 포스팅 해야겠다.

 

 

참조

 

 

데이터 직렬화(serialization)는 무엇이고 왜 필요한가?

우선 이 글은 구글링에서 나오는 여러 가지 직렬화에 대한 글들과 설명들을 읽고 제 나름대로 한번 더 이해하기 편하도록 정리한 글입니다. 데이터 직렬화(serialization), 역직렬화(deserialization)는

hub1234.tistory.com

 

데이터 직렬화 형식 (JSON, YAML, XML, TOML), 왜 필요하고 언제 사용하나?

오늘은 슬랙 봇 API를 이용해봤는데, 결국 원하는대로 구현이 되기는 했지만 생각보다 구현 속도가 너무 오래 걸렸다.결론적으로 말하자면, slack bot API에 대한 이해가 부족했던 탓과, JSON에 대한

velog.io

반응형