개발단에 가입하여 베르의 게임 개발 유튜브를 후원해주세요!

 

베르의 게임 개발 유튜브

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다! 게임 개발에 도움이 되는 강좌들을 올리는 채널입니다! [투네이션 후원] https://toon.at/donate/637735212761460238 [유니티 어필리에이트

www.youtube.com

 

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!

이번에는 유니티에서 JSON을 사용하는 방법에 대해서 알아봅시다.

 

사용 엔진 버전 : 2020.3

 

타임라인

0:00 인트로

0:10 JSON이란?

1:04 JSON 데이터의 기본구조

1:50 JSON 데이터 검사기

2:23 Newtonsoft JSON 라이브러리

3:15 유니티 엔진 .NET 세팅

3:56 오브젝트를 JSON 데이터로

6:50 JSON 데이터를 오브젝트로

7:45 유니티에서 JSON 사용시 주의점

10:11 유니티의 JSONUtility

11:39 Vector3 시리얼라이즈

12:44 모노비헤이비어를 상속받는 클래스의 오브젝트 시리얼라이즈

14:27 JSON 데이터 파일로 저장하기

15:47 파일로 저장한 JSON 데이터 불러오기

16:23 JSON 2 C sharp

17:16 아웃트로

 

[예제]

https://drive.google.com/file/d/1G2e87vkxyG2cFRM0YhUp9mpOATCVBkB1/view?usp=sharing

 

스크립트

인트로

안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.

이번 영상에서는 유니티에서 JSON을 사용하는 방법에 대해서 알아보도록 하겠습니다.

JSON이란?

JSON을 사용하는 방법에 대해서 알아보기 전에 JSON이 무엇인지 알아보도록 하겠습니다.

JSON은 웹이나 네트워크에서 서버와 클라이언트 사이에 데이터를 주고 받을 때 사용하는 개방형 표준 포맷입니다.

좀 더 쉽게 말해보자면 어떻게 서버와 클라이언트 사이에 데이터를 주고 받을지에 대한 약속 같은 겁니다.

이 JSON이라는 포맷은 텍스트를 사용하기 때문에 사람이 이해하기 쉽다는 장점을 가지고 있습니다.

그래서 유니티에서도 상당히 많이 사용되며, 네트워크 게임을 개발할 때 게임에 필요한 데이터를 주고 받거나, 게임 진행 상황이나 게임 설정을 저장하는 식으로 사용됩니다.

XML이라는 것도 사용 범위가 거의 일치하지만 XML은 JSON에 비해서 가독성이 떨어지고 데이터를 넣거나 꺼내기 위해서 파싱하는 과정이 까다로운데 반해서, JSON은 XML에 비해서 가독성이 좋고 직렬화와 비직렬화 함수를 통해서 오브젝트에서 JSON 데이터로, JSON 데이터에서 오브젝트로 편하게 변환할 수 있다는 장점이 있습니다.

JSON 데이터의 기본 구조

앞서 말했다시피 JSON 데이터는 텍스트로 구성되며 이런 형태의 구조를 가지고 있습니다.

찬찬히 보시면 아시겠지만 JSON의 데이터는 key와 value의 쌍으로 이루어진 데이터로 저장하는데, items와 같이 배열로 된 데이터 역시 저장이 가능하고 객체 안에 객체를 넣는 것도 가능합니다.

그리고 데이터 내용이 문자열로 되어 있기 때문에 사람이 알아보기 매우 쉽습니다.

JSON 데이터에서 중괄호는 객체를 의미하고, 대괄호는 순서가 있는 배열을 나타냅니다.

그리고 JSON은 정수, 실수, 문자열, 불리언, null 등의 데이터 타입을 지원합니다.

추가로 JSON은 주석을 지원하지 않기 때문에, JSON 파일을 사람이 읽고 수정할 수 있도록 할 계획이라면 key의 이름을 명확하게 정해서 이 값이 무엇을 의미하는지 알려주는게 좋습니다.

JSON 데이터 검사기

이런 JSON의 단점으로는 작은 문법 오류에도 민감하게 반응한다는 점입니다.

중간에 중괄호나 대괄호, 콜론, 쉼표가 하나라도 빠지면 JSON 파일이 깨져버리고 문자열 형태인 JSON 데이터를 원하는 데이터로 변환할 수 없게 됩니다.

이런 문제 때문에 구글에서 JSON 검사기를 검색하면 JSON 데이터가 유효한지 검사해주는 웹 페이지들이 많습니다.

JSON 데이터를 작성하고 난 뒤에는, JSON 데이터 파일의 깨짐으로 인한 버그를 막기 위해서 이런 JSON 검사기로 검사하고 사용하는 것이 좋습니다.

Newtonsoft JSON 라이브러리

그럼 이제 유니티에서 JSON을 사용하는 방법을 알아보겠습니다.

유니티에서 JSON을 사용하는 방법은 여러 가지가 있는데 그 중 첫 번째는 Newtonsoft의 라이브러리를 사용하는 것입니다.

먼저 구글에서 Newtonsoft JSON을 검색하면 첫 페이지에서 Json.NET - Newtonsoft 페이지를 찾을 수 있습니다.

이 페이지에 접속하고 Download 버튼을 누른 뒤 아래 쪽에 있는 Json.NET 버튼을 클릭합니다.

그러면 Newtonsoft의 깃허브 페이지로 이동하게 됩니다.

이 릴리즈 페이지에서 최신 버전의 압축 파일을 다운로드 받아줍니다.

다운로드가 완료된 다음에는 받은 파일의 압축을 해제하고 Bin 폴더로 들어가서 이중에 필요한 .NET 버전을 선택합니다.

보통은 .NET 4.5 버전을 선택하면 됩니다.

여기 있는 DLL 파일을 JSON을 사용할 프로젝트 폴더 안으로 옮겨줍니다.

.NET 4.X 이상 세팅

단, 여기서 놓치지 말아야 할 주의 사항이 있습니다.

우리가 임포트한 DLL은 .NET 4버전 이상에서 사용되는 것인데 기본적으로 설정을 건드리지 않은 유니티 엔진은 .NET 2.0을 사용합니다.

그래서 에디터 상태에서는 DLL이 잘 동작하는데 막상 게임을 빌드하면 DLL이 동작하지 않게 됩니다.

이 문제를 수정하기 위해서는 먼저 상단 메뉴 바에서 [Edit > Project Settings] 항목을 선택해서 프로젝트 세팅 창을 열어줍니다.

그리고 Player 항목을 선택하고 Other Settings 카테고리를 보면 Api Compatibility Level 프로퍼티를 볼 수 있습니다.

이 프로퍼티를 .NET 4.X로 변경해줍니다.

오브젝트를 JSON 데이터로

모든 세팅을 마친 다음에는 본격적으로 Newtonsoft의 라이브러리를 사용해보겠습니다.

먼저 해볼 작업은 C# 클래스를 기본으로 생성된 오브젝트에 담긴 데이터를 JSON 데이터로 변환하는 것입니다.

NewtonsoftJsonExample이라는 이름으로 새 스크립트를 생성하고 스크립트 에디터로 이동합니다.

스크립트에서 Newtonsoft JSON 라이브러리를 사용하기 위해서 스크립트의 상단에 Newtonsoft.Json 네임스페이스를 using 선언해줍니다.

이제 Newtonsoft의 JSON 라이브러리를 사용할 준비가 끝났습니다.

본격적인 테스트 이전에 테스트 용도로 사용할 클래스를 하나 정의하겠습니다.

이 클래스에는 JSON에 담을 수 있는 대부분의 타입을 멤버변수로 하나씩 넣도록 하겠습니다.

int, float, bool, string처럼 가장 기본적인 데이터 타입부터 배열, 리스트, 딕셔너리같은 컨테이너 타입, 그리고 사용자가 직접 정의한 클래스 형식도 한 클래스에 묶어서 JSON 데이터로 만들 수 있습니다.

멤버 변수 선언이 끝난 다음에는 생성자에서 각 값을 자동으로 초기화하게 만들어주고 Print 함수를 만들어서 모든 값을 하나씩 출력하는 코드도 작성해줍니다.

JsonTestClass 작성이 끝난 다음에는 NewtonsoftJsonExample클래스의 Start 함수로 이동합니다.

먼저 JsonTestClass의 오브젝트를 생성해서 JSON 데이터로 만들어보겠습니다.

하나의 오브젝트를 문자열인 JSON 데이터로 만드는 과정은 아주 간단합니다.

JsonConvert의 SerializeObject 함수를 호출해서 오브젝트를 매개변수로 넣어주기만 하면 됩니다.

이 과정을 직렬화라고 부릅니다.

이렇게 JSON 데이터로 바뀐 문자열을 출력하게 만든 뒤 코드를 저장하고 에디터로 돌아가서 테스트해보겠습니다.

에디터로 돌아와서는 새 게임 오브젝트를 하나 만들고 방금 만든 컴포넌트를 붙여줍니다.

그리고 게임을 플레이시켜보면 우리가 만든 JsonTestClass의 오브젝트가 문자열로 바뀐 것을 볼 수 있습니다.

문자열로 바뀐 JSON 데이터를 찬찬히 뜯어보면 우리가 클래스의 멤버 변수로 선언해주고 값으로 넣어준 것들이 제대로 문자열로 바뀌어 담긴 것을 확인할 수 있습니다.

JSON 데이터를 오브젝트로

그럼 이제 반대로 문자열로 된 JSON 데이터를 오브젝트로 변환해보겠습니다.

다시 NewtonsoftJsonExample의 Start 함수로 돌아가서 jTest2 변수를 만들고 JsonConvert의 DeserializeObject 함수를 호출해줍니다.

JSON 데이터를 다시 오브젝트로 바꿀 때는 JSON 데이터를 어떤 오브젝트로 변환하는지 명시적으로 함수에 알려줘야합니다.

만약 JSON 데이터가 가진 구조가 함수에 알려준 클래스의 구조와 다르다면 변환 도중에 에러가 발생하기 때문에 잘 확인하고 진행해야 합니다.

JSON 데이터를 다시 오브젝트로 만든 다음에는 변환 확인을 위해서 jTest2의 Print 함수를 호출해줍니다.

코드 작성이 끝나면 코드를 저장하고 에디터로 돌아갑니다.

그리고 게임을 플레이시켜보면 JSON 데이터로 바뀌었던 jTest1의 내용이 정상적으로 JsonTestClass로 바뀌어서 출력되는 것을 볼 수 있습니다.

유니티에서 JSON 사용시 주의점

이번에는 유니티에서 JSON을 사용할 때 주의해야 할 점을 알아보겠습니다.

에디터에서 TestMono라는 이름으로 C# 스크립트를 생성하고 스크립트를 열어줍니다.

그리고 int 타입 멤버 변수만 하나 만들어주고 NewtonsoftJsonExample스크립트로 이동합니다.

이렇게 새 게임 오브젝트를 생성하고 여기에 TestMono를 붙인 다음, 이 TestMono를 가져와서 JSON 데이터로 변환해서 출력하는 코드를 작성합니다.

이 코드의 기본 의도는 클래스에 들어있는 int 타입의 i를 JSON 데이터로 만들어서 저장하려는 것입니다.

하지만 이 코드를 저장하고 에디터로 돌아가서 플레이해보면 전혀 의도대로 동작하지 않고 JsonSerializationException을 발생시킵니다.

로그를 읽어보면 Self Referencing loop, 즉 자기 참조 루프에 빠진 것을 알 수 있습니다.

이 예외는 gameObject에서 gameObject를 호출할 수 있는 순환구조 때문에 생기는 것인데, 이 문제를 해결할 방법은 있지만, 이후에도 다른 예외가 많이 발생하고 몇몇 문제는 해결책이 없기 때문에 Newtonsoft의 JSON 라이브러리로는 모노비헤이비어를 상속받는 클래스의 오브젝트를 JSON 데이터로 시리얼라이즈할 수는 없습니다.

그렇기 때문에 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈하는 대신에 스크립트가 가지고 있는 프로퍼티 중에서 필요한 프로퍼티를 클래스로 묶어서 해당 클래스만 시리얼라이즈 하거나 뒤에서 설명할 유니티가 기본 제공하는 JsonUtility 기능을 사용해서 시리얼라이즈하는 것을 추천합니다.

다음 문제는 Vector3를 시리얼라이즈 하는 문제입니다.

Vector3 역시 간단한 벡터를 담은 클래스를 만들고 시리얼라이즈 하려고 하면 모노비헤이비어를 상속 받은 클래스의 오브젝트를 시리얼라이즈하려고 할 때처럼 Self Referencing loop 문제가 발생합니다.

이것 역시 Vector3의 프로퍼티인 normalized에서 다시 normalized를 호출할 수 있기 때문에 발생하는 문제입니다.

이 문제를 해결하기 위해서는 이렇게 JsonSerializerSetting 오브젝트를 만들고 ReferenceLoopHandling을 Ignore로 설정하고 시리얼라이즈를 해야합니다.

하지만 이런 식으로 레퍼런스 반복을 무시하게 만들어도 normalized 벡터나 벡터의 길이 등의 불필요한 값들이 시리얼라이즈되기 때문에 쓸데없이 JSON 데이터의 길이나 용량이 늘어나는 문제가 생깁니다.

외부 라이브러리를 이용해서 Vector3에서 좌표 값만을 JSON 데이터로 시리얼라이즈하기를 원한다면 이렇게 별도의 시리얼라이즈용 Vector 클래스를 만들어서 시리얼라이즈해야 할 겁니다.

유니티의 JsonUtility

외부 라이브러리인 Newtonsoft의 Json 라이브러리를 알아보았으니 이제 유니티에서 기본 제공되는 JsonUtility를 알아보겠습니다.

그리고 시리얼라이즈와 디시리얼라이즈에 대해서는 앞에서 다 설명했으니 여기서는 한꺼번에 가겠습니다.

먼저 JsonUtilityExample이라는 이름으로 새 C# 스크립트를 생성합니다.

그리고 아까 전에 만들어둔 JsonTestClass 클래스를 이용해서 오브젝트에서 JSON 데이터로 만들겠습니다.

JsonUtility의 ToJson 함수를 사용하면 됩니다.

반대로 JSON 데이터에서 오브젝트로 만들 때는 JsonUtility의 FromJson 함수를 사용하면 됩니다.

나머지는 전부 동일합니다.

다만 JsonUtility의 단점은 기본적인 데이터 타입과 배열, 리스트에 대한 시리얼라이즈만 지원합니다.

그래서 JSON 데이터로 변경된 결과물을 보면 데이터 내에 딕셔너리와 직접 생성한 클래스가 보이지 않는 것을 알 수 있습니다.

직접 생성한 클래스의 경우에는 [System.Serializable] 어트리뷰트를 붙여줘야만 JSON 데이터로 함께 변환되며, 딕셔너리는 아예 지원하지 않습니다.

때문에 딕셔너리를 시리얼라이즈하려면 외부 라이브러리를 사용해야 합니다.

Vector3 시리얼라이즈

이제 유니티의 JsonUtility가 제공하는 특수 기능을 알아보겠습니다.

앞에서도 보았다시피 외부 라이브러리로는 유니티 내장 클래스를 시리얼라이즈할 때 자기 참조 문제가 발생해서 시리얼라이즈가 제대로 이루어지지 않습니다.

특히 Vector3 타입은 유니티에 내장된 클래스로써 위치나 방향을 표시하는데 자주 쓰입니다

그렇기 때문에 이전에 접속한 캐릭터의 마지막 위치 같은 데이터로 저장될 수 있습니다.

하지만 Vector3는 유니티의 JsonUtility나 유니티의 특성에 맞춘 라이브러리가 아닌 일반 JSON 라이브러리를 사용해서 시리얼라이즈를 하면 normalized 프로퍼티로 인해서 Self Reference loop 문제를 발생시킵니다.

이 문제를 해결해도 저장하려고한 좌표값 외에 벡터의 길이와, 제곱 그리고 정규화된 벡터와 그 길이같은 필요하지 않은 정보를 많이 포함하게 됩니다.

하지만 JsonUtility를 사용해서 앞에서 만든 벡터를 담은 클래스로 테스트 해보면 불필요한 값을 제외하고 필요한 좌표만 저장되는 것을 확인할 수 있습니다.

모노비헤이비어를 상속받는 클래스의 오브젝트 시리얼라이즈

그리고 유니티의 JsonUtility를 사용하면 모노비헤이비어를 상속받는 클래스의 오브젝트 역시 시리얼라이즈가 가능합니다.

앞에서 사용한 TestMono 클래스에 간단하게 Vector3 타입의 멤버 변수를 추가하고 JsonUtilityExample의 Start 함수에서 새 게임 오브젝트를 생성하고 거기에 TestMono 컴포넌트를 붙인 뒤 JsonUtility로 시리얼라이즈하는 코드를 작성합니다.

여기서 주의할 점은 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈 할 때는 클래스가 컴포넌트로 붙어있는 게임 오브젝트가 아니라 GetComponent등으로 직접 가져온 클래스의 오브젝트로 시리얼라이즈해야 합니다.

코드를 저장하고 에디터로 돌아가서 테스트해보면 JsonUtility로 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈 했을 때 문제 없이 깔끔하게 오브젝트가 JSON 데이터로 변환되는 모습을 볼 수 있습니다.

그럼 다시 이 데이터를 오브젝트로 변환해보겠습니다.

코드로 돌아가서 FromJson 함수를 사용해서 디시리얼라이즈하는 코드를 작성하고 테스트합니다.

그러면 이렇게 새로운 인스턴스를 생성하지 못했다고 에러가 발생하고 디시리얼라이즈에 실패합니다.

이 문제를 해결하기 위해서는 FromJson 함수 대신에 FromJsonOvewrite 함수를 사용해야 합니다.

이 함수는 JSON 데이터를 오브젝트로 변환할 때 새로운 오브젝트를 만들지 않고 기존에 있는 오브젝트에 클래스의 변수 값을 덮어씌우는 처리를 합니다.

한 마디로 모노비헤이비어를 상속받는 오브젝트의 JSON 데이터를 다시 오브젝트로 만들려면 같은 형태를 가진 오브젝트를 미리 생성한 뒤에 디시리얼라이즈해야 한다는 뜻입니다.

이 기능을 이용하면 게임 내의 오브젝트를 JSON 데이터로 저장해서 게임 진행 상황을 저장하고 다시 불러와서 게임 진행 상황을 원래대로 세팅하는 세이브&로드 기능을 구현할 수 있습니다.

JSON 데이터 파일로 저장하기

그럼 일종의 세이브 파일을 만들기 위해 JSON 데이터로 변환된 정보를 파일로 저장하는 방법을 알아봅시다.

먼저 JsonSaveLoader라는 이름으로 C# 스크립트를 생성합니다.

스크립트 생성이 완료되고 나면 파일 입출력과 문자열 처리를 하기 위해서 스크립트의 상단에 System.IO 네임스페이스와 System.Text 네임스페이스를 using 선언해줍니다.

그리고 Json 기능을 사용하기 위해서 Newtonsoft.Json 네임스페이스도 using 선언합니다.

그리고 Start 함수에서 파일을 저장할 경로와 파일 이름으로 FileStream을 만들어 줍니다.

보통 json 파일은 .json이라는 확장자를 가집니다.

그 다음 문자열인 JSON 데이터를 Encoding.UTF8의 GetBytes 함수로 byte 배열로 만들어줍니다.

그리고 이렇게 만든 데이터를 fileStream에 써줍니다.

코드 작성을 마친 후에 저장하고 에디터로 돌아가서 플레이해보면 지정한 경로에 json 파일이 생성되고 그 내용이 정상적으로 저장된 모습을 볼 수 있습니다.

파일로 저장한 JSON 데이터 불러오기

그 다음은 이렇게 저장한 json 파일을 읽어 들일 차례입니다.

역시 이번에도 FileStream을 생성하되 이번에는 FileMode를 Open으로 설정합니다.

그리고 생성된 fileStream에서 Read로 데이터를 읽어들인 다음, 읽은 데이터를 반대로 string으로 인코딩 해줍니다.

그 다음에 이 문자열을 디시리얼라이즈 해줍니다.

코드를 저장하고 에디터에서 플레이해보면 정상적으로 파일의 JSON 데이터가 로드되어서 오브젝트로 변환되고 오브젝트의 내용이 로그로 출력되는 것을 볼 수 있습니다.

JSON 2 C Sharp

마지막으로 유용한 사이트를 하나 더 추천해드리겠습니다.

보통 서버 단 작업을 하는 개발자와 협업을 하다보면 보통 주고 받는 데이터 타입을 테이블이나 표로 만들어서 주고 받겠지만, 작업이 빠르게 진행되어야 하는 경우에는 그냥 JSON 데이터로 던지고 클라이언트에서 이 JSON 데이터의 구조만 보고 직접 클래스를 짜야하는 경우도 왕왕 발생합니다.

물론 받은 JSON 데이터를 일일이 분석해서 클래스를 짜도 되겠지만 훨씬 간단하고 원터치로 JSON데이터를 클래스로 만드는 방법이 있습니다.

구글에서 JSON 2 C sharp이라고 검색하면 JSON을 바로 C# 클래스로 변환해주는 사이트들이 나옵니다.

이중에서 적당한 사이트를 골라서 접속하고 JSON 데이터를 입력한 뒤 Convert 버튼을 눌러주면 JSON 데이터 구조를 분석해서 자동으로 C# 클래스로 만들어줍니다.

이 코드를 복사해서 클래스 이름만 원하는 대로 바꿔서 사용하면 됩니다.

아웃트로

이번 영상에서는 유니티에서 JSON을 다루는 방법을 알아보았습니다.

이 강좌는 시청자 여러분들의 시청과 후원으로 제작되었습니다.

이상 베르의 게임 개발 유튜브였습니다. 감사합니다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

'Unity3D > Programming' 카테고리의 다른 글

[Unity] JSON  (0) 2022.04.05
[Unity] Time 클래스 통합본  (0) 2021.12.27
[Unity] Time.time & Time.realtimeSinceStartup & Time.timeSinceLevelLoad  (0) 2021.12.07
[Unity] timeScale과 unscaledDeltaTime  (0) 2021.11.29
[Unity] Time.deltaTime  (0) 2021.11.22
[Unity] 인보크(Invoke)  (0) 2021.07.12

유니티에서 JSON 사용하기(Newtonsoft JSON)

작성 기준 버전 :: 2018.3.1f1

 

JSON은 웹이나 네트워크에서 서버와 클라이언트 사이에서 데이터를 주고 받을 때 사용하는 개방형 표준 포멧으로, 텍스트를 사용하기 때문에 사람이 이해하기 쉽다는 장점이 있다.

 

이런 JSON 포멧을 유니티에서도 많이 사용하는 편이다. 네트워크 게임을 개발할 때 게임에 필요한 데이터를 주고 받거나, 게임 진행 상황을 저장하거나, 게임 설정을 저장하는 방식으로도 사용할 수 있다.

 

유니티에서 XML을 사용하는 것과 사용 범위가 거의 일치하는데, XML은 가독성이 매우 떨어지고 데이터를 넣거나 꺼내기 위해 파싱(Parsing)하는 과정이 까다로운데 반해서, JSON은 XML에 비해서 가독성이 좋고 직렬화(Serialize)와 비직렬화(Deserialize) 함수를 통해서 데이터에서 JSON 데이터로, JSON 데이터에서 데이터로 편하게 변환할 수 있다는 장점을 가지고 있다.

 

Newtonsoft의 JSON 라이브러리는 다양한 전체 기능을 제공하는 라이브러리로 시리얼라이즈 및 디시리얼라이즈하는 컴팩트한 기능만을 사용하기를 원한다면 유니티 엔진에 내장된 JsonUtility를 사용할 것을 권장한다.

 

 

JSON 라이브러리 다운로드 및 프로젝트에 임포트(Download JSON & Import JSON to project)

 

우선 JSON 라이브러리를 다운로드받기 위해 아래 링크에 접속한다.

 

Newtonsoft JSON Library

 

그리고 릴리즈된 애셋 중에 원하는 버전의 Json(버전).zip 파일을 다운로드받는다.

 

 

다운로드 받은 파일을 압축을 해제하고 폴더를 열어보면 위와 같은 폴더와 파일들이 보일텐데 그 중에서 Bin 폴더를 연다.

 

 

 

Bin 폴더 안에는 사용하는 .NET 버전에 따라 라이브러리 파일들이 폴더에 나눠져 담겨있는데, 일반적으로는 net35 폴더 안에 있는 dll을 사용하면 되지만, 유니티에서 .NET 4.x 기능을 사용하거나 최신 버전의 기능이 필요하다면 net45 폴더 안에 있는 dll을 사용해도 된다. 이번 섹션에서는 간단하게 JSON 사용법을 익힐 것이기 때문에 net35 버전을 사용한다.

 

 

net35 폴더 안에서 Newtonsoft.Json.dll 파일을 프로젝트 창에 드래그해서 프로젝트에 포함시킨다.

 

 

JSON의 기본구조

 

기본적인 JSON 데이터의 구조는 다음과 같다.

 

{

    "id":"wergia",

    "level":10,

    "exp":33.3,

    "hp":400,

    "items":

    [

        "Sword",

        "Armor",

        "Hp Potion",

        "Hp Potion",

        "Hp Potion"

    ]

}

 

JSON의 데이터는 키(Key)와 값(Value) 쌍(Pair)로 이루어진 데이터를 저장하는데, items와 같이 배열로 된 데이터 역시 저장이 가능하고 객체 안에 객체를 넣는 것도 가능하며 위의 데이터 내용이 문자열로 이루어져 있기 때문에 사람이 알아보기가 매우 쉽다.

 

JSON 데이터에서 { } 는 객체를 의미하고, [ ] 는 순서가 있는 배열을 나타낸다. 그리고 JSON은 정수, 실수, 문자열, 불리언, null 타입의 데이터 타입을 지원한다.

 

JSON은 주석을 지원하지 않기 때문에, JSON 파일을 사람이 읽고 수정할 수 있도록 할 예정이라면, 키의 이름을 명확하게 정해서 이 값이 무엇을 의미하는지 확실히 표현하는게 좋다.

 

 

JSON의 단점은 작은 문법 오류에도 매우 민감하다는 점이다. 중간에 중괄호나 대괄호, 콜론, 쉼표가 하나라도 빠지면 JSON 파일이 깨져버리고 파일을 읽어들일 수 없게 된다. 이런 문제 때문에 구글에서 JSON 검사기를 검색하면 JSON 데이터가 유효한지 검사해주는 웹페이지들이 많다. JSON 데이터를 작성하고 난 뒤에는 JSON 데이터 파일의 깨짐으로 인한 버그를 막기 위해서 이런 JSON 검사기로 검사하고 사용하는 것이 좋다.

 

 

 

 

 

유니티에서 JSON 사용하기

 

JSON에 대해서 간단하게 알아보았으니 이제 유니티에서 JSON을 사용하는 방법에 대해서 알아보자.

 

기본적인 JSON <-> Object 변환하기

 

우선 Json 예제를 작성할 JsonExample 클래스를 하나 생성한다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class JsonExample : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
       
    }
}

 

JSON과 관련된 기능을 사용하기 위해서 상단의 using 지시문 파트에 다음 using 지시문을 추가한다.

 

using Newtonsoft.Json;

 

using 지시문을 추가하지 않아도 기능을 사용할 수는 있지만, 그렇게 하면 Newtonsoft.Json 네임스페이스를 계속해서 타이핑해야하기 때문에 using 지시문을 추가한다.

 

그리고 JSON 데이터와 오브젝트 간에 시리얼라이즈, 디시리얼라이즈 테스트를 위해 다음과 같은 클래스를 정의한다.

 

public class JTestClass
{
    public int i;
    public float f;
    public bool b;
    public string str;
    public int[] iArray;
    public List<int> iList = new List<int>();
    public Dictionary<string, float> fDictionary = new Dictionary<string, float>();


    public JTestClass() { }

 

    public JTestClass(bool isSet)
    {

        if (isSet)

        {
            i = 10;
            f = 99.9f;
            b = true;
            str = "JSON Test String";
            iArray = new int[] { 1, 1, 3, 5, 8, 13, 21, 34, 55 };

            for (int idx = 0; idx < 5; idx++)
            {
                iList.Add(2 * idx);
            }

 

            fDictionary.Add("PIE", Mathf.PI);
            fDictionary.Add("Epsilon", Mathf.Epsilon);
            fDictionary.Add("Sqrt(2)", Mathf.Sqrt(2));

        }
    }


    public void Print()
    {
        Debug.Log("i = " + i);
        Debug.Log("f = " + f);
        Debug.Log("b = " + b);
        Debug.Log("str = " + str);

        for (int idx = 0; idx < iArray.Length; idx++)
        {
            Debug.Log(string.Format("iArray[{0}] = {1}", idx, iArray[idx]));
        }

        for (int idx = 0; idx < iList.Count; idx++)
        {
            Debug.Log(string.Format("iList[{0}] = {1}", idx, iList[idx]));
        }

        foreach(var data in fDictionary)
        {
            Debug.Log(string.Format("iDictionary[{0}] = {1}", data.Key, data.Value));
        }
    }
}

 

여러 가지 타입과 배열, 리스트, 딕셔너리를 가지고 있는 클래스이기 때문에 오브젝트를 JSON 데이터로 변환하기에 좋은 클래스이다.

 

JSON 테스트용 클래스를 모두 작성했으면 JsonExample 클래스에 다음 함수 두 개를 구현한다.

 

string ObjectToJson(object obj)
{
    return JsonConvert.SerializeObject(obj);
}

T JsonToOject<T>(string jsonData)
{
    return JsonConvert.DeserializeObject<T>(jsonData);
}

 

ObjectToJson() 함수는 JsonConvert 클래스의 SerializeObject() 함수를 이용해서 오브젝트를 문자열로 된 JSON 데이터로 변환하여 반환하는 처리를 하고 JsonToObject() 함수는 DeserializeObject() 함수를 이용해서 문자열로 된 JSON 데이터를 받아서 원하는 타입의 객체로 반환하는 처리를 한다.

 

함수들을 모두 작성했다면 Start() 함수에 우선 ObjectToJson() 함수를 테스트하는 코드를 작성한다.

 

void Start()
{
    JTestClass jtc = new JTestClass(true);
    string jsonData = ObjectToJson(jtc);
    Debug.Log(jsonData);
}

 

코드를 저장한 뒤 에디터로 돌아가서 JsonExample을 게임 오브젝트에 붙이고 플레이 버튼을 눌러보면 JTestClass 객체가 JSON 데이터로 변환되어 로그로 출력되는 것을 확인할 수 있다.

 

 

그 다음에는 Start() 함수 아래에 JsonToObject() 함수를 테스트하는 다음 코드를 작성한다.

 

var jtc2 = JsonToOject<JTestClass>(jsonData);
jtc2.Print();

 

그리고 코드를 저장하고 에디터로 가서 플레이 버튼을 눌러보면 문자열인 JSON 데이터가 JTestClass 객체로 변환되어 정상적으로 작동하는 것을 확인할 수 있다.

 

 

 

JSON 데이터 파일로 저장하고 불러오기

 

JSON 데이터를 파일로 저장하거나 파일에 저장된 JSON 데이터 파일을 불러올 일이 있을 수 있다. 이번에는 이것에 대해서 배워보자.

 

우선은 문자열로 만든 JSON 데이터를 파일로 저장하는 코드의 예시는 다음과 같다.

 

void CreateJsonFile(string createPath, string fileName, string jsonData)
{
    FileStream fileStream = new FileStream(string.Format("{0}/{1}.json", createPath, fileName), FileMode.Create);
    byte[] data = Encoding.UTF8.GetBytes(jsonData);
    fileStream.Write(data, 0, data.Length);
    fileStream.Close();
}

 

CreateJsonFile() 함수를 작성한 뒤 Start() 함수를 아래와 같이 CreateJsonFile() 함수를 호출하도록 수정한다.

 

void Start()
{
    JTestClass jtc = new JTestClass(true);
    string jsonData = ObjectToJson(jtc);
    CreateJsonFile(Application.dataPath, "JTestClass", jsonData);
}

 

코드를 저장하고 에디터에서 플레이 해보면 dataPath인 Assets 폴더 안에 JTestClass.json 파일이 생성되고 그 내용이 제대로 쓰여져 있는 것을 확인할 수 있다.

 

 

이번에는 방금 저장한 JSON 파일을 읽어들여서 오브젝트로 변환하는 코드를 작성한다. 예시 코드는 아래와 같다.

 

T LoadJsonFile<T>(string loadPath, string fileName)
{
    FileStream fileStream = new FileStream(string.Format("{0}/{1}.json", loadPath, fileName), FileMode.Open);
    byte[] data = new byte[fileStream.Length];
    fileStream.Read(data, 0, data.Length);
    fileStream.Close();
    string jsonData = Encoding.UTF8.GetString(data);
    return JsonConvert.DeserializeObject<T>(jsonData);
}

 

LoadJsonFile() 함수를 모두 작성했으면 Start() 함수를 다음과 같이 수정한다.

 

void Start()
{
    var jtc2 = LoadJsonFile<JTestClass>(Application.dataPath, "JTestClass");
    jtc2.Print();
}

 

코드를 저장하고 에디터에서 플레이해보면 정상적으로 파일의 JSON 데이터가 로드되어서 오브젝트로 변환되어 로그가 출력된 것을 확인할 수 있다.

 

 

 

 

 

 

유니티에서 JSON 사용시 주의점

 

유니티에서 JSON을 사용할 때, 몇가지 주의점이 있다.

 

우선 유니티에서 클래스를 만들 때, 일반적으로 대다수의 클래스는 모노비헤이비어(Monobehaviour)를 상속받는다.

 

public class JsonExample : MonoBehaviour
{
    void Start()
    {
        GameObject obj = new GameObject();
        obj.AddComponent<TestMono>();
        Debug.Log(JsonConvert.SerializeObject(obj.GetComponent<TestMono>()));
    }

}

 

위의 예시 코드에 TestMono 클래스는 int 타입 변수 하나를 가지고 모노비헤이비어를 상속받는 클래스이다. 빈 게임 오브젝트에 TestMono 클래스를 컴포넌트로 붙여서 JSON데이터로 시리얼라이즈해서 로그로 출력하는 테스트인데 이를 플레이해서 테스트해보면 아래와 같이 에러가 발생한다.

 

 

 

이 예외는 gameObject에서 gameObject를 호출할 수 있는 순환구조 때문에 생기는 것인데 이것을 해결할 수는 있지만, 이후에도 다른 예외를 많이 발생시키기고 몇몇 문제는 해결책이 없기 때문에 Newtonsoft의 JSON 라이브러리로는 모노비헤이비어를 상속받는 클래스의 오브젝트를 JSON 데이터로 시리얼라이즈할 수는 없다. 그렇기 때문에 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈하는 대신에 스크립트가 가지고 있는 프로퍼티를 클래스로 묶어서 해당 클래스만 시리얼라이즈하거나 유니티가 제공하는 JsonUntility 기능을 사용해서 시리얼라이즈하는 것을 추천한다.

 

다음은 Vector3를 시리얼라이즈하는 문제인데, Vector3를 그냥 시리얼라이즈 하려고 하면 모노비헤이비어를 시리얼라이즈하려고 할 때처럼 Self referencing loop 문제가 발생한다. 이것은 Vector3의 프로퍼티인 normalized에서 다시 normalized를 호출할 수 있기 때문에 발생하는 문제이다.

 

public class UJsonTester
{
    public Vector3 v3;

    public UJsonTester() { }

    public UJsonTester(float f)
    {
        v3 = new Vector3(f, f, f);
    }
}

 

public class JsonExample : MonoBehaviour
{
    void Start()
    {
        JsonSerializerSettings setting = new JsonSerializerSettings(); ;
        setting.Formatting = Formatting.Indented;
        setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

        UJsonTester jt = new UJsonTester(3f);
        Debug.Log(JsonConvert.SerializeObject(jt, setting));
    }

}

 

이를 해결하기 위해서는 위의 코드처럼 JsonSerializerSetting을 만들어서 ReferenceLoopHandling을 Ignore로 설정하고 시리얼라이즈를 해야한다.

 

 

하지만 이런 방식으로 레퍼런스 반복을 무시하게 만들어도 normalized 벡터나 벡터의 길이 등의 불필요한 값들이 시리얼라이즈되기 때문에, 불필요하게 JSON 데이터의 길이가 늘어나는 문제가 발생한다.

 

public class JVector3
{
    [JsonProperty("x")]
    public float x;
    [JsonProperty("y")]
    public float y;
    [JsonProperty("z")]
    public float z;

    public JVector3()
    {
        x = y = z = 0f;
    }

    public JVector3(Vector3 v)
    {
        x = v.x;
        y = v.y;
        z = v.z;
    }

    public JVector3(float f)
    {
        x = y = z = f;
    }
}

public class UJsonTester
{
    public JVector3 v3;

    public UJsonTester() { }

    public UJsonTester(float f)
    {
        v3 = new JVector3(f);
    }

    public UJsonTester(Vector3 v)
    {
        v3 = new JVector3(v);
    }
}

public class JsonExample : MonoBehaviour
{
    void Start()
    {
        UJsonTester jt = new UJsonTester(transform.position);
        Debug.Log(JsonConvert.SerializeObject(jt));
    }

}

 

외부 라이브러리를 이용해서 Vector3 중에서 x, y, z 좌표값만을 JSON 데이터로 시리얼라이즈하기를 원한다면 위의 예시 코드와 같이 별도의 시리얼라이즈용 Vector 클래스를 만들어서 시리얼라이즈를 해야한다.

 

 

이러한 번거로운 과정이 불편하다면, 유니티가 기본 제공하는 JsonUtility를 혼용해서 사용하는 방법도 있다. 유니티가 제공하는 JsonUtility로 Vector3를 시리얼라이즈하면 x, y, z 좌표값만을 JSON 데이터로 변환한다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형
  1. 인디 2020.10.19 11:26

    고맙습니다 도움 많이 됐어요.

유니티에서 JSON 사용하기(Unity JSON Utility)

 

작성 기준 버전 :: 2018.3.1f1

 

JSON은 웹이나 네트워크에서 서버와 클라이언트 사이에서 데이터를 주고 받을 때 사용하는 개방형 표준 포멧으로, 텍스트를 사용하기 때문에 사람이 이해하기 쉽다는 장점이 있다.

 

이런 JSON 포멧을 유니티에서도 많이 사용하는 편이다. 네트워크 게임을 개발할 때 게임에 필요한 데이터를 주고 받거나, 게임 진행 상황을 저장하거나, 게임 설정을 저장하는 방식으로도 사용할 수 있다.

 

유니티에서 XML을 사용하는 것과 사용 범위가 거의 일치하는데, XML은 가독성이 매우 떨어지고 데이터를 넣거나 꺼내기 위해 파싱(Parsing)하는 과정이 까다로운데 반해서, JSON은 XML에 비해서 가독성이 좋고 직렬화(Serialize)와 비직렬화(Deserialize) 함수를 통해서 데이터에서 JSON 데이터로, JSON 데이터에서 데이터로 편하게 변환할 수 있다는 장점을 가지고 있다.

 

유니티에서 기본 제공하는 JsonUtility는 컴팩트한 최소한의 기능만을 제공하기 때문에 JSON 라이브러리의 모든 기능을 사용하고 싶다면 다른 JSON 라이브러리를 사용할 것을 권장한다.

 

 

JSON의 기본구조

 

기본적인 JSON 데이터의 구조는 다음과 같다.

 

{

    "id":"wergia",

    "level":10,

    "exp":33.3,

    "hp":400,

    "items":

    [

        "Sword",

        "Armor",

        "Hp Potion",

        "Hp Potion",

        "Hp Potion"

    ]

}

 

JSON의 데이터는 키(Key)와 값(Value) 쌍(Pair)로 이루어진 데이터를 저장하는데, items와 같이 배열로 된 데이터 역시 저장이 가능하고 객체 안에 객체를 넣는 것도 가능하며 위의 데이터 내용이 문자열로 이루어져 있기 때문에 사람이 알아보기가 매우 쉽다.

 

JSON 데이터에서 { } 는 객체를 의미하고, [ ] 는 순서가 있는 배열을 나타낸다. 그리고 JSON은 정수, 실수, 문자열, 불리언, null 타입의 데이터 타입을 지원한다.

 

JSON은 주석을 지원하지 않기 때문에, JSON 파일을 사람이 읽고 수정할 수 있도록 할 예정이라면, 키의 이름을 명확하게 정해서 이 값이 무엇을 의미하는 지 하는게 좋다.

 

 

 

JSON의 단점은 작은 문법 오류에도 매우 민감하다는 점이다. 중간에 중괄호나 대괄호, 콜론, 쉼표가 하나라도 빠지면 JSON 파일이 깨져버리고 파일을 읽어들일 수 없게 된다. 이런 문제 때문에 구글에서 JSON 검사기를 검색하면 JSON 데이터가 유효한지 검사해주는 웹페이지들이 많다. JSON 데이터를 작성하고 난 뒤에는 JSON 데이터 파일의 깨짐으로 인한 버그를 막기 위해서 이런 JSON 검사기로 검사하고 사용하는 것이 좋다.

 

 

유니티에서 JSON 사용하기

 

JSON에 대해서 간단하게 알아보았으니 이제 유니티에서 JSON을 사용하는 방법에 대해서 알아보자.

 

기본적인 JSON <-> Object 변환하기

 

우선 Json 예제를 작성할 JsonExample 클래스를 하나 생성한다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class JsonExample : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
       
    }
}

 

유니티가 제공하는 JsonUtility는 UnityEngine 네임스페이스에 포함되어 있기 때문에, 다른 using 지시문을 추가할 필요가 없다.

그리고 JSON 데이터와 오브젝트 간에 시리얼라이즈, 디시리얼라이즈 테스트를 위해 다음과 같은 클래스를 정의한다.

 

[System.Serializable]

public class JTestClass
{
    public int i;
    public float f;
    public bool b;

    public Vector3 v;
    public string str;
    public int[] iArray;
    public List<int> iList = new List<int>();

 

    public JTestClass() { }

 

    public JTestClass(bool isSet)
    {

        if (isSet)

        {
            i = 10;
            f = 99.9f;
            b = true;

            v = new Vector3(39.56f, 21.2f, 6.4f);
            str = "JSON Test String";
            iArray = new int[] { 1, 1, 3, 5, 8, 13, 21, 34, 55 };

 

            for (int idx = 0; idx < 5; idx++)
            {
                iList.Add(2 * idx);
            }
        }
    }


    public void Print()
    {
        Debug.Log("i = " + i);
        Debug.Log("f = " + f);
        Debug.Log("b = " + b);

        Debug.Log("v = " + v);
        Debug.Log("str = " + str);

        for (int idx = 0; idx < iArray.Length; idx++)
        {
            Debug.Log(string.Format("iArray[{0}] = {1}", idx, iArray[idx]));
        }

        for (int idx = 0; idx < iList.Count; idx++)
        {
            Debug.Log(string.Format("iList[{0}] = {1}", idx, iList[idx]));
        }
    }
}

 

여러 가지 타입과 배열, 리스트를 가지고 있는 클래스이기 때문에 오브젝트를 JSON 데이터로 변환하기에 좋은 클래스이다.

 

JSON 테스트용 클래스를 모두 작성했으면 JsonExample 클래스에 다음 함수 두 개를 구현한다.

 

string ObjectToJson(object obj)
{
    return JsonUtility.ToJson(obj);
}

T JsonToOject<T>(string jsonData)
{
    return JsonUtility.FromJson<T>(jsonData);
}

 

ObjectToJson() 함수는 JsonUtility 클래스의 ToJson() 함수를 이용해서 오브젝트를 문자열로 된 JSON 데이터로 변환하여 반환하는 처리를 하고 JsonToObject() 함수는 FromJson() 함수를 이용해서 문자열로 된 JSON 데이터를 받아서 원하는 타입의 객체로 반환하는 처리를 한다.

 

함수들을 모두 작성했다면 Start() 함수에 우선 ObjectToJson() 함수를 테스트하는 코드를 작성한다.

 

void Start()
{
    JTestClass jtc = new JTestClass(true);
    string jsonData = ObjectToJson(jtc);
    Debug.Log(jsonData);
}

 

코드를 저장한 뒤 에디터로 돌아가서 JsonExample을 게임 오브젝트에 붙이고 플레이 버튼을 눌러보면 JTestClass 객체가 JSON 데이터로 변환되어 로그로 출력되는 것을 확인할 수 있다.

 

 

그 다음에는 Start() 함수 아래에 JsonToObject() 함수를 테스트하는 다음 코드를 작성한다.

 

var jtc2 = JsonToOject<JTestClass>(jsonData);
jtc2.Print();

 

그리고 코드를 저장하고 에디터로 가서 플레이 버튼을 눌러보면 문자열인 JSON 데이터가 JTestClass 객체로 변환되어 정상적으로 작동하는 것을 확인할 수 있다.

 

 

 

 

 

 

JSON 데이터 파일로 저장하고 불러오기

 

JSON 데이터를 파일로 저장하거나 파일에 저장된 JSON 데이터 파일을 불러올 일이 있을 수 있다. 이번에는 이것에 대해서 배워보자.

 

우선은 문자열로 만든 JSON 데이터를 파일로 저장하는 코드의 예시는 다음과 같다.

 

void CreateJsonFile(string createPath, string fileName, string jsonData)
{
    FileStream fileStream = new FileStream(string.Format("{0}/{1}.json", createPath, fileName), FileMode.Create);
    byte[] data = Encoding.UTF8.GetBytes(jsonData);
    fileStream.Write(data, 0, data.Length);
    fileStream.Close();
}

 

CreateJsonFile() 함수를 작성한 뒤 Start() 함수를 아래와 같이 CreateJsonFile() 함수를 호출하도록 수정한다.

 

void Start()
{
    JTestClass jtc = new JTestClass(true);
    string jsonData = ObjectToJson(jtc);
    CreateJsonFile(Application.dataPath, "JTestClass", jsonData);
}

 

코드를 저장하고 에디터에서 플레이 해보면 dataPath인 Assets 폴더 안에 JTestClass.json 파일이 생성되고 그 내용이 제대로 쓰여져 있는 것을 확인할 수 있다.

 

 

이번에는 방금 저장한 JSON 파일을 읽어들여서 오브젝트로 변환하는 코드를 작성한다. 예시 코드는 아래와 같다.

 

T LoadJsonFile<T>(string loadPath, string fileName)
{
    FileStream fileStream = new FileStream(string.Format("{0}/{1}.json", loadPath, fileName), FileMode.Open);
    byte[] data = new byte[fileStream.Length];
    fileStream.Read(data, 0, data.Length);
    fileStream.Close();
    string jsonData = Encoding.UTF8.GetString(data);
    return JsonUtility.FromJson<T>(jsonData);
}

 

LoadJsonFile() 함수를 모두 작성했으면 Start() 함수를 다음과 같이 수정한다.

 

void Start()
{
    var jtc2 = LoadJsonFile<JTestClass>(Application.dataPath, "JTestClass");
    jtc2.Print();
}

 

코드를 저장하고 에디터에서 플레이해보면 정상적으로 파일의 JSON 데이터가 로드되어서 오브젝트로 변환되어 로그가 출력된 것을 확인할 수 있다.

 

 

 

유니티의 JsonUtility가 제공하는 특수한 기능들

 

유니티의 JsonUtility는 유니티 엔진을 위한 특수한 기능들을 몇 가지 제공한다.

 

Vector3 시리얼라이즈

 

Vector3는 유니티에 내장된 클래스로써 위치나 방향을 표시하는데 자주 사용되는 클래스이다. 그렇기 때문에 이전에 접속했을 때의 캐릭터의 마지막 위치같은 데이터로 저장될 수 있다. 하지만 Vector3는 유니티의 JsonUtility가 아닌 다른 JSON 라이브러리를 사용해서 시리얼라이즈를 하면 normalized 프로퍼티로 인해서 Self reference loop 문제를 발생시키고 이 문제를 해결해도 아래의 이미지와 같이 x, y, z 좌표값 이외에 정규화된 벡터와 그 길이와 길이의 제곱등 불필요한 정보들을 많이 포함하게 된다.

 

 

이런 문제는 Vector3를 시리얼라이즈할 때 JsonUtility를 사용하면 간단하게 해결된다.

 

[System.Serializable]
public class UJsonTester
{
    public Vector3 v3;

    public UJsonTester() { }

    public UJsonTester(float f)
    {
        v3 = new Vector3(f, f, f);
    }

    public UJsonTester(Vector3 v)
    {
        v3 = v;
    }
}

public class JsonExample : MonoBehaviour
{
    void Start()
    {
        UJsonTester jt = new UJsonTester(transform.position);
        Debug.Log(JsonUtility.ToJson(jt));
    }

}

 

테스트 코드를 저장하고 테스트해보면 불필요한 값 없이 x, y, z 좌표값만 저장된 것을 확인할 수 있다.

 

 

 

모노비헤이비어를 상속받는 클래스의 오브젝트 시리얼라이즈

 

다른 JSON 라이브러리를 사용해서 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈하려고 하면 여러 문제가 발생하며 시리얼라이즈가 되지 않는다.

 

[System.Serializable]
public class TestMono : MonoBehaviour
{
    public int i = 10;
    public Vector3 pos = new Vector3(1f, 2f, 3f);
}

 

모노비헤이비어를 상속받는 TestMono 클래스를 선언했으면 JsonUtility로 시리얼라이즈하는 코드를 작성한다. 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈할 때, 주의할 점은 반드시 클래스가 컴포넌트로 붙어있는 게임 오브젝트가 아니라 GetComponent() 등으로 직접 가져온 클래스로 시리얼라이즈를 해야한다.

 

GameObject obj = new GameObject();
obj.name = "TestMono 01";
var jd = JsonUtility.ToJson(obj.GetComponent<TestMono>());
Debug.Log(jd);

 

JsonUtility로 모노비헤이비어를 상속받는 클래스의 오브젝트를 시리얼라이즈하면 문제없이 깔끔하게 오브젝트가 JSON 데이터로 변환되는 것을 확인할 수 있다.

 

 

 

이렇게 JSON 데이터로 변환한 모노비헤이비어를 상속받는 클래스의 오브젝트는 디시리얼라이즈를 할 때 FromJson() 함수를 사용하면 아래와 같이 새로운 인스턴스를 생성하지 못했다고 에러가 발생하고 시리얼라이즈에 실패한다.

 

 

이런 문제를 해결하기 위해서는 FromJson() 함수 대신에 FromJsonOverwrite() 함수를 사용해야 한다. 이 함수는 JSON 데이터를 오브젝트로 변환할 때, 새로운 오브젝트를 만들지 않고 기존에 있는 오브젝트에 클래스의 변수 값을 덮어씌우는 처리를 한다.

 

FromJsonOverwrite() 함수의 테스트를 위해서 Start() 함수의 내용을 아래와 같이 수정한다.

 

GameObject obj = new GameObject();
obj.name = "TestMono 01";
var t = obj.AddComponent<TestMono>();
t.i = 333;
t.pos = new Vector3(-939, -33, -22);
var jd = JsonUtility.ToJson(obj.GetComponent<TestMono>());
Debug.Log(jd);

GameObject obj2 = new GameObject();
obj2.name = "TestMono 02";
var t2 = obj2.AddComponent<TestMono>();
JsonUtility.FromJsonOverwrite(jd, t2);

 

에디터로 가서 테스트를 진행해보면 TestMono 02 오브젝트가 생성되고, 이 오브젝트가 가진 TestMono 컴포넌트의 프로퍼티의 값이 TestMono 01 오브젝트를 JSON 데이터로 변환한 값이 덮어씌워져 있는 것을 확인할 수 있다.

 

 

 

 

 

 

JsonUtility와 딕셔너리(Dictionary)

 

유니티에 내장된 JsonUtility를 통해서 JSON을 다룰 때 알아두어야 할 점은 JsonUtility는 딕셔너리에 대한 시리얼라이즈 및 디시리얼라이즈를 지원하지 않는다는 것이다. JsonUtility는 JSON을 다루기 위한 가장 최소한의 기능만 제공하기 때문에 딕셔너리를 JSON으로 다루려면 Newtonsoft나 다른 JSON 라이브러리를 사용하거나 이에 대한 기능을 직접 구현해야 할 것이다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형
  1. 익명 2019.06.16 18:43

    비밀댓글입니다

    • wergia 2019.07.22 11:37 신고

      답변은 좀 더 보시기 편하게 글로 올렸습니다.

      확인해주세요.

      https://wergia.tistory.com/174

  2. 오션스8 2020.05.15 16:12

    포스팅 잘 보고 갑니다.

    • wergia 2020.05.29 19:43 신고

      네! ㅎㅎ 다음에도 유용한 포스트로 찾아뵙겠습니다.

  3. hsan 2020.11.17 16:42

    좋은 포스팅 감사합니다.
    유니티와 json을 처음 접할때에 좋은 자료인 것 같습니다!

  4. 익명 2021.06.01 22:50

    비밀댓글입니다

    • 익명 2021.06.03 10:01

      비밀댓글입니다

  5. 클라요 2021.06.21 16:07

    큰 도움이 되었습니다. 좋은글 감사합니다.

  6. 익명 2021.07.09 19:14

    비밀댓글입니다

  7. csw 2022.01.06 16:42

    모노비헤이비어를 상속받는 클래스의 오브젝트 시리얼라이즈

    두 번째 사진에서 게임 오브젝트를 만들고, 이름을 지었는데 해당 게임 오브젝트에 컴포넌트를 추가하는 코드가 빠진 것 같습니다.
    GameObject obj = new GameObject();
    obj.name = "TestMono 01";
    obj.gameobject.AddComponent<TestMono>(); <--- 이부분
    var jd = JsonUtility.ToJson(obj.GetComponent<TestMono>());
    Debug.Log(jd);

데이터 테이블(Data Table) 사용하기

 

작성 기준 버전 :: 4.21.1

 

게임을 제작할 때 레벨업에 필요한 경험치량이나 스킬의 계수 등 추후에 밸런스 수정 작업이 필요한 값들은 함부로 코드에 상수로 넣어서는 안된다. 이런 부분은 기획자가 손쉽게 접근이 가능해야 하기 때문에, 기획자들이 주로 사용하는 엑셀이나 스프레드시트의 데이터를 언리얼 엔진으로 임포트해서 사용하는 방식을 지원한다. 이것을 데이터 주도형 접근법이라고 한다.

 

언리얼 엔진에서는 기획자들이 주로 사용하는 엑셀이나 스프레드시트에서 손쉽게 만들어낼 수 있는 .CSV 파일이나 서버 프로그램에서 주로 사용되는 JSON 파일을 손쉽게 임포트하는 기능을 제공한다.

 

 

데이터 테이블 임포트
 

데이터 테이블은 유용한 방식으로 짜여진 표를 의미한다. .CSV 파일을 임포트하기 위해서는 우선 프로그래머가 데이터를 엔진이 인식할 수 있게 Row 컨테이너를 만들어서 엔진에 데이터 해석 방식을 알려줘야 한다.

 

우리가 예시로 사용할 .CSV 파일은 다음 레벨업까지 필요한 경험치의 양에 대한 것이고 그 내용은 다음과 같다.

 

Name,ExpToNextLevel,TotalExp
1,0,0
2,100,100
3,200,300
4,300,500
5,400,700
6,500,900
7,600,1100
8,700,1300
9,800,1500
10,1600,2400

 

LevelUpTable.csv
다운로드

 

이런 컨테이너를 만드는 방법은 두 가지가 있는데 블루프린트를 이용하는 방식과 C++ 코드를 통해 만드는 방식이 있다.

 

 

블루프린트

 

데이터 테이블 로우를 만들기 위해서는 구조체를 생성해야 한다. 구조체의 이름은 BP_LevelUpTableRow로 한다.

 

 

블루프린트 구조체가 생성되면 더블클릭해서 블루프린트 구조체 에디터를 열고 변수를 추가한다. 추가하는 변수의 이름은 ExpToNextLevel과 TotalExp로 각 열의 이름과 순서가 일치해야 한다. 제일 첫 열인 Name은 게임 내에게 각 행에 접근하는 이름이 되는 것으로 따로 변수를 추가하지 않아도 된다.

 

 

변수를 모두 추가한 뒤에는 구조체를 저장하고 에디터를 닫는다. 그리고 콘텐츠 브라우저 패널에서 파일 창에 우클릭하여 /Game에 임포트... 를 선택한다.

 

 

CSV 파일을 임포트한다.

 

 

데이터 테이블 옵션 창이 뜨면 데이터 테이블 행 유형 선택을 방금 추가한 구조체로 설정하고 확인을 누른다.

 

 

추가된 데이터 테이블을 열어보면 .CSV 파일의 내용이 훌륭하게 임포트된 것을 확인할 수 있다.

 

 

 

C++ 코드

 

행 컨테이너를 블루프린트 구조체로 만들 경우, C++ 코드에서는 사용할 수 없다는 단점이 있다. C++ 코드에서 사용하기 위해서는 USTRUCT로 만들어야 되는데 언리얼 구조에 대한 설명은 C++ / USTRUCT 사용자 정의 구조체 만들기 문서에서 참고할 수 있다.

 

우선 Actor 클래스를 상속받아서 CustomDataTables라는 더미 클래스를 생성한다.

 

 

클래스가 생성되면 전처리기와 클래스 선언 사이에 구조체를 선언하는 코드를 추가해준다. 행 컨테이너로 사용되는 구조체는 FTableRowBase를 상속받아야만 한다.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/DataTable.h"
#include "CustomDataTables.generated.h"

USTRUCT(BlueprintType)
struct FLevelUpTableRow : public FTableRowBase
{
    GENERATED_BODY()

public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LevelUp")
        int32 ExpToNextLevel;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LevelUp")
        int32 TotalExp;
};

UCLASS()
class DATATABLETEST_API ACustomDataTables : public AActor
{
    GENERATED_BODY()
   
};

 

코드를 완성하고 프로젝트를 빌드한 뒤, 에디터로 돌아가서 .CSV 파일을 임포트해서 데이터 테이블 옵션의 데이터 테이블 행 유형 선택 드롭다운 메뉴를 열어보면 우리가 방금 추가한 LevelUpTableRow가 있는 것을 확인할 수 있다.

 

 

이를 통해서 .CSV 파일을 임포트하면 아까 블루프린트 구조체를 통해서 임포트했을 때와 동일하게 DataTable이 생성되는 것을 볼 수 있다.

 

 

 

 

 

데이터 테이블 사용하기

 

이번 파트에서는 작성한 데이터 테이블을 사용하는 방법에 대해서 알아보자.

 

블루프린트에서 데이터 테이블 사용하기

 

블루프린트에서 데이터 테이블을 사용하기 위해서는 블루프린트 그래프의 빈 자리에 우클릭해서 컨텍스트 메뉴를 열고 "데이터 테이블 행 구하기"를 검색해서 이 노드를 배치하면 된다.

 

 

GameModeBase를 상속받는 블루프린트 클래스를 하나 생성한다. 이벤트 그래프의 BeginPlay 이벤트로부터 다음과 같이 블루프린트 그래프를 구성하자.

 

 

위 그래프는 LevelUpTable에서 각 행을 가져와서 Total Exp 값을 화면에 출력하는 역할을 한다.

 

블루프린트를 저장하고 에디터로 가서 월드 세팅의 Game Mode를 방금 추가한 게임 모드로 바꿔준다.

 

 

그 다음 에디터에서 플레이 버튼을 눌러보면 화면에 각 레벨의 Total Exp가 연속으로 출력되는 것을 볼 수 있다.

 

 

 

C++ 코드에서 데이터 테이블 사용하기

 

C++ 코드에서 데이터 테이블을 사용하는 과정은 블루프린트에서 노드 하나만 생성하면 되는 것에 비해서는 조금 복잡하다.

 

우선 프로젝트에 DataTableTestGameModeBase라는 이름으로 새 게임 모드 클래스를 만들고 헤더에 다음 멤버 변수와 함수를 추가한다.

 

public:
    ADataTableTestGameModeBase();

    virtual void BeginPlay() override;

private:
    class UDataTable* LevelUpDataTable;

 

그리고 DataTableTestGameModeBase라는 .cpp로 가서 다음 전처리기를 추가한다.

 

#include "CustomDataTables.h"
#include "UObject/ConstructorHelpers.h"

 

ADataTableTestGameModeBase::ADataTableTestGameModeBase() 생성자 함수와 BeginPlay() 함수를 다음과 같이 구현한다.

 

ADataTableTestGameModeBase::ADataTableTestGameModeBase()
{
    static ConstructorHelpers::FObjectFinder<UDataTable> DataTable(TEXT("/Game/LevelUpTable"));
    if (DataTable.Succeeded())
    {
        LevelUpDataTable = DataTable.Object;
    }
}

void ADataTableTestGameModeBase::BeginPlay()
{
    Super::BeginPlay();

    if (LevelUpDataTable != nullptr)
    {
        for (int32 i = 1; i <= 10; i++)
        {
            FLevelUpTableRow* LevelUpTableRow = LevelUpDataTable->FindRow<FLevelUpTableRow>(FName(*(FString::FormatAsNumber(i))), FString(""));
            UE_LOG(LogTemp, Log, TEXT("Lv.%d :: ExpToNextLevel(%d) TotalExp(%d)"), i, (*LevelUpTableRow).ExpToNextLevel, (*LevelUpTableRow).TotalExp);
        }
    }
}

 

코드 작성이 완료되면 프로젝트를 빌드하고 에디터로 넘어간다.

 

그 다음 월드 세팅에서 게임 모드를 방금 만든 것으로 교체한다.

 

 

플레이 버튼을 눌러보면 게임이 시작되면서 데이터 테이블의 값들을 가져와서 로그를 출력하는 것을 확인할 수 있다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형
  1. Game 2021.02.17 13:38

    감사합니다

  2. 익명 2022.05.11 07:32

    비밀댓글입니다

    • 익명 2022.05.18 08:51

      비밀댓글입니다

+ Recent posts