180bpm

게임 테이블을 binary file로 불러오기 본문

Develop/Unity

게임 테이블을 binary file로 불러오기

powdersnow 2014. 10. 16. 18:53

1. 서론

지금 하는 프로젝트에서는 게임 테이블을 XML로 불러서 사용하고 있다.

처음엔 XML 파싱하는데만 120초 이상 나와서 서버에서 JSON으로 받아서 파싱하기도 하는식으로 어느정도 파일을 분리해서 10~30초 정도로 줄였는데 퍼블리셔에선 이것도 느리다고 하더라.


그래서 방법이 없을까 고민하던차에 전전회사 TD님이 "왜 파싱을 하냐며, 바이너리로 올리면 되지 않냐" 하셨다.


XML은 파일 읽기 -> 파싱 -> 구조체 생성 -> 구조체에 값 밀어넣기 인데

바이너리는 파일 읽기 -> 메모리에 올려버리기 의 느낌으로 끝난다고 한다.


그래서 생각했던게 C++ 처럼

http://stackoverflow.com/questions/5506645/how-to-read-write-a-struct-in-binary-files 

typedef struct student
{
    char name[10];
    int age;
    vector<int> grades;
}student_t;

0~9 - 이름, 10~13 나이..

이런식으로 메모리에 올린뒤 다시 메모리값을 읽어서 파일로 써야하는건줄 알았는데 C#에서는 직렬화라고 해서 쉽게 만들어주는게 있더라. 그래서 생각보다 작업이 어렵지도 않았고 빨리 결과를 확인했다.


2. 목적

XML 파싱을 없앤다 - > 파싱하는데 시간 많이 걸림. XML 클래스를 쓰면 Unity 빌드옵션중 .Net 2.0 Subset으로 빌드 못함.



3. 작업

직렬화 작업(바이너리화)

참고 문서


http://answers.unity3d.com/questions/48316/uploading-custom-filetype-to-mysql-database.html

http://www.dotnetperls.com/serialize-list

StreamWriter sWriter = new StreamWriter(Application.persistentDataPath + "/data.bin");
        BinaryFormatter bin = new BinaryFormatter();
        bin.Serialize(sWriter.BaseStream, SkillList);
        sWriter.Close();

테스트는 유니티 어플 안에서  하려고 했기 때문에 File.Open 함수로는 경로를 찍을 수 없어서 StreamWriter를 이용해 파일을 생성했다.

파일 생성 -> BinaryFormatter에 바이너리화 시킬 데이터 (클래스의 리스트를 넣었다) 넣고 직렬화->파일 닫기 끝.


파일 읽기

StreamReader sr = new StreamReader(Application.persistentDataPath + "/data.bin");
        BinaryFormatter bin = new BinaryFormatter();
        serialSkillList = (List<SkillData>)bin.Deserialize(sr.BaseStream);
        sr.Close();

파일 읽어서 원하는 데이터형으로 역직렬화. 끝.

생각보다 너무 쉽게 되었다.


4. 주의점

[Serializable] public class SkillData {    ... }

클래스 앞에 [Serializable] 이라고 붙여야 직렬화 대상 클래스가 된다. 역직렬화만 할때도 넣어야한다. 그리고 이게 Unity3D에서 어떤 영향을 줄지도 아직 확인은 못했다.

using System;
 
using Random = UnityEngine.Random;
using Debug = UnityEngine.Debug;

system 클래스 import 하면 UnityEngine의 각종 기능과 충돌난다. 그래서 이렇게 using으로 또 지정을 해준다.


5. 결과

테스트는 지금 게임에서 쓰고 있는 테이블중 가장 용량이 큰 테이블로 했다.

파일 정보-

용량 : 약 400KB

테이블에서 컬럼 수 : 32개

테이블에서 열 수 : 320개

XML : 40만자, 11,000줄

int, float, string, enum이 복합적으로 들어간 클래스.


어플이 한번 구동되면 반복테스트가 의미가 없는게, 일단 파일 리딩을 하려고 하면 메모리에 얹어버려서 그 다음부터는 리딩하는 과정이 없어지는듯 하다. Flush를 했음에도 불구하고.

로그 순서는 XML 파싱 -> 데이터 바이너리화 -> 바이너리화 된 데이터 불러오기


PC :

334ms -> 32ms

 10~30배 빨라짐.


모바일 : 

621ms -> 158ms

4배 빨라짐.(노트2)

단일파일만 봤을때 유의미한 결과를 얻음.

내가 이해하는게 맞다면 파싱하는게 없으니 폰이 느리면 느릴수록 효과는 더 증대되지 않을까 싶다.


그래서 저사양 폰에서도 테스트를 해봤다.

베가레이서1 :1145ms -> 316ms 3.6배

갤럭시S:2182ms -> 331ms 6.5배


생각대로 나온건가..


6. 더 해야할것

잠수함패치 이야기가 나와서, 몬스터 데이터 같은건 앞으로도 서버에서 받고 싶다고 한다.

그래서 CDN을 통한 패칭이 아니고 매번 접속때마다 DB에서 받고 싶다는건데..

MySQL엔 blob 이라는 자료형이 있어서  이걸 WWW 클래스로 받아서 처리하는법을 찾고 있음.


유니티에서 사용할때 제약이 없는지(아이폰은 안된다던가..등등)

그리고 내부적으로 어떻게 돌아가는지 대충 감을 잡고, C++에서도 해보기로.


7. 문제점

1. 실데이터는 양이 많아서 확실히 차이를 보였는데, 샘플코드 만들려고 양을 줄였더니 종종 더 느려지는 현상이 생겼다.


샘플코드 데이터 : 약 45,000자 , 약 2200줄.

PC  : 15ms -> 16ms

이건 왜 그런가..;


2. 서버 개발자분이 blob 테스트를 해주셨다. 실데이터를 바이너리로 뽑은 파일을 보냈다.

기존엔 테이블을 CSV로 export -> mysql의 myadmin을 이용해서 csv import -> 요청시 테이블 훑어서 -> json으로 생성-> post 형식으로 쓰고 있다.

근데 바뀐걸 테스트 헤봤더니.서버에서 처리하는데 200ms~4000ms가 나오더라.

이러면 쓸 일이 없다. 원인을 찾던가, 서버 값은 계속 Json으로 받아오던가.


샘플코드 : 



Comments