Using Unity as a Library in iOS
안녕하세요 iOS 개발하는 지니 입니다 (╹◡╹)
유알못에서 유니티 인간(?)이 되어가는 요즘,,,
iOS에 Unity를 통합해서 iOS App과 Unity가 서로 커뮤니케이션하는 것을 해보려고 합니다.
참고로 아래 두가지 말은 똑같은 의미이니 헷갈리지 마세요! (유니티 공식문서, 블로그, 깃헙에서 혼용되어 사용되고 있어요)
- Integrating Unity into iOS
- Using Unity as a Library in iOS
Unity 2019.4 LTS 부터 Unity를 Android(Java) 및 iOS(Objective-C) 앱으로 제어가능한 라이브러리로 활용할 수 있습니다. (참고: 유니티 블로그 > Unity를 라이브러리로 사용하여 앱에 Unity 기반 기능 추가하기 )
즉 기존 네이티브 앱에 유니티 기반 AR, 게임 등을 쉽게 추가하고 제어할 수 있게 된 것입니다.
이렇게 유니티를 라이브러리처럼 사용할 수 있다면
여러 프로젝트에 재사용도 가능하고 iOS, 안드로이드 네이티브 UI의 성능과 특성을 살리면서 유니티도 함께 사용할 수 있게 되는 것이죠!
유니티를 라이브러리로 사용한 두가지 예시를 살펴볼까요?
이케아 앱은 AR 화면만 Unity View 이고 나머지 화면들은 모두 Native View입니다.
핑크퐁 컴퍼니의 Bayby Shark World도
iOS Native App에 Unity를 통합해서 여러 재밌는 게임들을 제공하고 있습니다.
2021년 12월에 서비스를 런칭하고 작성해주신 따끈따끈한 글 읽어보시길 추천드려요
참고로
Unity as a Library는 전체화면 렌더링만 지원하며, 화면 일부의 렌더링은 지원하지 않습니다.
그리고 두 개 이상의 Unity 런타임 인스턴스를 로드할 수 없다고 합니다.
iOS에서 Unity를 라이브러리로 사용하게 되면 이런 그림이 나오게 됩니다.
Xcode workspace는 multiple projects 를 동시에 돌리고 그들의 products 를 결합할 수 있게 해주는데요, Xcode workspace에 두개의 proejcts 를 두는 방식을 사용해주는 것입니다.
- Integrating Unity as a library into standard iOS app
- Integrating Unity into native iOS applications
위의 두가지 개발 문서을 참고해서 실습을 해보겠습니다.
iOS Project와 Unity Project를 각각 만들어줍니다.
iOS Project 는 HostApp, Unity Project는 UnityGame 이라고 이름지어줄게요
네이티브 Xcode 프로젝트와 Unity에서 생성하는 Xcode 프로젝트를 하나의 Xcode workspace로 합쳐준 후, 몇가지 세팅을 해줄 것입니다.
To integrate Unity into another Xcode project, you need to combine both Xcode projects (the native one and the one Unity generates) into a single Xcode workspace, and add the UnityFramework.framework file to the Embedded Binaries section of the Application target for the native Xcode project. Once you do this, you can use the UnityFramework class to control the Unity runtime.
우선 Xcode workspace 부터 만들어줍시다.
HostApp을 열고 File > Save As Workspace 를 눌러주시면 됩니다.
(cocoapod을 사용하시거나 하여 이미 workspace가 있으신 경우. 생략하셔도 됩니다)
그리고 UntiyGame 프로젝트에서 iOS 플랫폼을 선택해서 빌드해줍시다.
빌드산출물 중, 네모 친 두개를 사용해줄 것 입니다.
이제 두개의 XcodeProject 와 하나의 WorkSpace가 준비완료되었으니 합쳐봅시다!
# 하나의 Workspace에 두 프로젝트 결합하기
Unity XcodeProject를 WorkSpace로 끌어와줍니다.
그리고 Location을 Relataive to Workspace로 선택해줍니다.
그럼 하나의 Workspace에 두개의 Project가 있도록 통합완료!
# UnityFramework 추가하기
그 다음, HostApp의 Embedded Content 부분에 + 를 누르고 UnityFramework를 추가해줍니다.
그러면 Build Phases > Link Binary With Libraries 에 UnityFramework이 자동으로 추가되는데, - 눌러서 제거 해주세요
# Data 폴더 Target Membership 바꾸기
Unity-iPhone의 Data folder의 Target Membership을 UnityFramework으로 바꿔주세요!
아래의 이유로 문서에서 권장하고 있습니다.
By default Data folder is part of Unity-iPhone target, we change that to make everything encapsulated in one single framework file.
여기까지 하시고 빌드 성공하는 지 확인해주시면 됩니다.
Unity-iPhone > UnityFramework > UnityFramework.h 은 이렇게 되어있는데요,
https://gist.github.com/eunjin3786/83552d7c2c615d67c799a25b8639c914
HostApp 쪽 코드에 UnityFramework 를 import하면 위의 인터페이스를 사용할 수 있습니다.
각 메소드에 대한 설명은 문서 를 참고해주세요!
iOS에서 Untiy를 로드해보겠습니다.
Untiy-iPhone > MainApp > main.mm에 있는 코드를 따라서 작성해주면 됩니다.
다만 Unity-iPhone을 빌드할 때는 유니티 앱을 빌드하는 것이고
우리는 유니티를 라이브러리로 사용해줄 것이니까 두번째 메소드로 바꿔서 사용해줘야합니다.
https://gist.github.com/eunjin3786/51aa0e230effca6748e05c007ee9ea46
블로그를 참고해서 Swift 버전으로 작성해줬습니다.
https://gist.github.com/eunjin3786/3dcb2216a7174da4acbda19cc7331a9b
그리고 대충 버튼 만들어서 launchUnity를 해보면
https://gist.github.com/eunjin3786/b0b7a97f2498727b2d3d6bc6364ff3ab
잘되는군요!
이번에는 반대로 유니티를 unload 해보겠습니다.
(void)unloadApplication
이 메서드를 호출하면 Unity를 언로드하고, 언로드가 완료된 후 UnityFrameworkListener에 대한 콜백을 수신할 수 있습니다. Unity는 차지하고 있는 대부분의 메모리를 해제합니다(전부는 아님). Unity를 다시 실행할 수 있습니다.
(void)quitApplication:(int)exitCode;
이 메서드를 호출하면 Unity를 완전히 언로드하고, Unity가 종료될 때 UnityFrameworkListener에 대한 콜백을 수신합니다. Unity는 모든 메모리를 해제합니다.
참고: 이 호출 이후에는 동일한 프로세스로 Unity를 다시 실행할 수 없습니다.
AppController에 대해 quitHandler를 설정하여 기본 프로세스 종료를 오버라이드할 수 있습니다.
위의 두 메소드 중, unloadApplication을 사용하겠습니다.
unloadApplication만 호출해서는 유니티 화면이 닫히지 않고 UnityFrameworkListener에 대한 콜백을 수신해서 Host App의 윈도우를 직접 보이게 해야하더라구요!
그래서 UnityManager 에 아래 코드를 추가해주겠습니다.
https://gist.github.com/eunjin3786/a802a4e9e7c384fec6b0090d92671259
그리고 SceneDelegate 또는 AppDelegate에서 window를 UnityManager 에 넘겨주는
코드를 작성해줍니다.
https://gist.github.com/eunjin3786/ae0c6945f4c697ed44ddf2c157cdcb78
버튼을 누르면 3초 후에 유니티 화면을 닫아주는 코드를 추가해서 잘되는 지 테스트 해봅니다.
https://gist.github.com/eunjin3786/270f537e530deb1d0c38ed74d8d45249
iOS에서 Untiy 한테 메세지를 보내봅시다.
# Unity
우선 유니티 쪽에 Text를 추가해줬고
Canvas에 CanavasController 라는 스크립트를 붙여준 후,
유니티 에디터에서 Text를 dynamicText 필드에 연결해줬습니다.
https://gist.github.com/eunjin3786/c6605160877f9a86b3a3e5c94595ef5b
# iOS
iOS 쪽에서는 아래 메소드를 사용해서 메세지를 보내면 됩니다.
(void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg;
이름을 기준으로 게임 오브젝트를 찾고, 단일 문자열 메시지 파라미터를 사용하여 functionName을 호출합니다.
UnityManager에 sendMessageToUnity 라는 메소드를 추가해주고
https://gist.github.com/eunjin3786/f94ebebd038867321f102e9a280c859a
Lauch Unity 버튼을 눌렀을 때, 메세지도 보내게 해봅니다.
- Script가 붙어있는 게임오브젝트 이름
- 호출할 함수 이름
- 파라미터로 전달할 메세지
위의 3가지로 UntiyMessage를 만들어주면 됩니다.
https://gist.github.com/eunjin3786/476443b5f0771c69df278a23bd0ba37d
그리고 돌려보면 메세지를 잘받는 것을 볼 수 있습니다.
But.. 하나의 string 값만 전달할 수 있어서 전달해야하는 데이터가 많은 경우
핑크퐁 팀 처럼 json string을 넘기면 좋을 것 같네요!
iOS Native에서 Unity로 전달하는 값들이 점차 확장될 것으로 예상되어서, iOS Native와 Unity에서 모두 해석할 수 있는 포맷을 지정하고 데이터를 직렬화하는 방법이 필요하다고 생각했습니다. 따라서 이해하기 쉽고 iOS Native와 Unity 양쪽에서 완벽하게 지원할 수 있는 JSON Format을 선택하였습니다.
iOS Native에서 전달할 Data를 JSON 형태의 Data로 String을 구성하고, Unity에 sendMessageToGOWithName API를 이용해 값을 전달하였습니다. Unity에서는 전달받은 String값을 다시 JSON 형태로 변환 후 값을 사용했습니다. 이렇게 Data 전달 방식을 구성하니, 다양한 데이터를 쉽게 전달할 수 있었습니다.
우선 Application 이 제공하는 메소드를 쓰면 굉장히 간단합니다.
닫기 버튼을 추가하고
클릭했을 때 Application.Unload 를 호출하면 끝!
https://gist.github.com/eunjin3786/0bc95f4aa6139e1d79724f734a2d9993
하지만, iOS에게 커스텀 message를 특별히 보내고 싶으면 별도 처리를 해줘야합니다.
이건 좀 복잡해서 iOS 앱에 Unity 통합하기 (2) 글을 따로 작성하였는데
참고해주세요!