회사 게임의 iOS 버전 포팅을 마무리하다.
Unity 2017.1 로 만들어진 회사 게임 ( RPG 클리커)은 1년이 넘었고 나름대로 흥행 성공했지만 ,
iOS 버전 출시를 하지 못해왔다고 한다. 그래서 내가 이 프로젝트에 대한 어느정도 숙지가 끝나자마자
iOS 를 굽는 임무에 투입되었다 !
다양한 이슈들을 만나고 ( 블로그 포스팅을 올리지 못했던 일주일 동안 ㅎㅎ 힘들었다 ..)
Unity iOS 포팅은 처음은 아니고 전 회사와 개인 작도 경험했지만,
개인 작은 출시를 하지 못했고 , 전 회사 플젝은 어드벤쳐 게임이고 인앱이 1개고 클라우드 데이터도 단순해서 큰 문제가 되지 않았었다 .
가장 먼저 맞닥뜨린 iOS에서만 등장하는 버그
게임은 전진하며 몬스터를 사냥하고 새로운 몬스터를 만나는 방식으로 게임 진행이 이루어지는데 ,
iOS 버전과 MAC , PC 버전에서만 한마리를 잡고나면 다음 몬스터 리젠이 안되는 크리티컬한 버그가 발생했다.
1시간 정도 헤메고
로그도 좀 찍어본 결과 원인이 게임 내 로컬라이징 관련 정보들을 파싱해올 때 경로 문제인 것을 파악했다.
그래서 보니, 기존 코드에서 해당 경로를 플랫폼에 따라 분기를 나누는 과정에서 오류가 있었다. ( 아마 예제코드 부터 잘못되었던 것 같다 . )
그래서 해결 !
날로 먹은 게임 센터, 별거 아닌 줄 알았는데 헤멘 아이 클라우드 저장
게임 센터는 예상과 달리 유니티 내장 Social 라이브러리로 쉽게 해결 가능했다 .
허나 iCloud는 조금 고생했다 .
처음에 깃 허브에서 줏은 오픈소스 라이브러리를 사용했는데 , 정상적으로 잘 작동했지만
GPGS와 달리 세이브와 로드할 때 게임이 멎었다 .
( 내 예상으로는 GPGS는 다른 쓰레드에서 처리하도록 하는 것 같은데 , 이 소스는 그게 안되어 있던 것 같다. )
나름 유저 경험에 크리티컬한 이슈라
회사에 말해서 Prime 31 iCloud Plugin 이라는 유니티 에셋을 구입하여 사용했다.
마법처럼 될 것 같았지만..
유저 정보 -> JSON -> Byte Array -> Key-Value 로 배열 사이즈와 배열내용을 하나씩 저장 (Data_1 , Data_2 하는 식으로 )
저장은 했지만 로드를 하지 못하는 상태로 하루를 꼬박 소비했다.
에셋 설명을 아무리 읽어도 해결책을 못찾다가 .
Apple 공식 문서인 iCloud Fundamental의 Key-Value 설명을 보고 답을 알아냈다.
Key-Value Store 는 총 1MB 의 키 - 밸류 쌍을 저장할 수 있으며 ,
그 쌍은 총 1024개만 허용된다.
1MB가 작아보이지만, 왠만한 게임의 유저 데이터는 저 안에서 다 저장 가능하다 . ( 보통 큰 게임도 30KB 정도에서 끝나는 듯 )
그래서 기존의 바이트 어레이로 (11235 개) ( 이 프로젝트의 유저 정보 자체를 한번 정리 할 필요가 있을 것 같기도하다 .. ) 하던 걸
그냥 JSON 하나로 키 - 밸류로 저장했다 .
얻은 교훈은
에셋을 과신하지 말자 .
해당 Native에 가까운 문서를 가장 열심히 보고 그 후에야 에셋이나 플러그인의 해당 문서를 보는 것이 좋을 것 같다.
된통 깨진 경험 ㅎㅎ.
그밖에 잡스러운 iOS 대응들
노치 디자인이라던가 , 아직 iOS 용 서버가 개발이 끝나지는 않아서 랭킹 부분을 막아두는 등의 자잘한 업데이트도 진행했다 .
In APP Localized Price 할당 ( 국가별 인앱 상품 가격 텍스트 설정 )
Unity IAP 의 내장 API 중에 , 구글 스토어나 애플스토어에 이 게임의 상품 ID 를 던지면
결제 유저의 통화에 맞게 가격 스트링을 던져주는 기능이 있다. (MetaData.LocalizedPriceString? )
이 것을 이용해서 유저에 따라서 스트링을 뿌려주는 것을 만들어야 하는데 ,
기왕 하는 김에 좀 더 생산성이 좋고 쾌적한 방식이 없을까 고민하다가 다음과 같은 방식으로 구현했다.
가격 텍스트에는 “InAppCostText” 라는 Tag 를 설정해준다 . ( 커스텀 클래스를 짜넣어서 컴포넌트를 써도 되지만, 굳이 낭비하기 싫었다 )
그리고 InAppCostManager 라는 MonoBehaviour 를 상속 받는 ( 컴포넌트 ) 객체를 하나 만들고 ,
이 클래스의 Custom Edior (Inspector) 도 하나 만들어준다 .
이 에디터에는 버튼이 하나 달려있고 , 그걸 누르면 씬내의 모든 Tag == “InAppCostText” 인 Text 를 검출하여
딕셔너리로 들고 있는다. Key 는 Text의 GameObject.name 으로 하고 , Value 는 Text 컴포넌트 로 넣는다 .
그리고 그 딕셔너리 갯수와 키값들도 아래로 죽 늘어놓아 쉽게 확인 가능하게 한다 .
그리고 게임이 시작되면 IAP Store Controller != Null 일때 까지 대기하다가 IAP 초기화에 성공한 후에
현지화된 가격을 Text 에 뿌려준다 .
이 간단한 구현에도 두가지 이슈가 발생했다 . ( 홓 )
첫째로는 씬 내의 해당 태그를 가진 오브젝트를 검출하는 함수가 InActive 상태인 오브젝트는 검출하지 못했다 .
그래서 Resources.FindObjectWithType? 였나 하는 ( 왜 Resources에 있는지 외국 유니티 개발자들도 전부 의문인듯 했다 . )
함수로 Text 컴포넌트를 다 긁은 뒤에 거기서 Tag 를 비교해 인앱 텍스트만 가려서 딕셔너리에 저장했다 .
두번째로는 딕셔너리 자체가 시리얼라이즈 되지 않기 때문에 씬의 컴포넌트 상태로 제대로 저장되지 않는다는 점이었다. (SetDirty 를 해줘도 안됨 )
그래서 해결책으로 배열 두개를 만들어 딕셔너리를 흉내내서 박아놓고 게임 시작하면 이 배열로 다시 딕셔너리를 구축하는 방식을 사용했다.