iOS에서 Unity를 라이브러리로 사용하기

Derrick
더핑크퐁컴퍼니 기술 블로그
18 min readJan 14, 2022
Baby Shark World

안녕하세요. 더핑크퐁컴퍼니에서 Software Engineer로 근무하고 있는 그루트 입니다.

2021년 12월, 전 세계 아이들에게 즐거움과 다양한 경험을 선사할 수 있는 새로운 서비스, Baby Shark World 앱을 소프트 런칭했습니다. Baby Shark World에는 놀이와 학습을 모두 아우르는 다양한 토픽의 영상과 게임이 포함되어 있습니다.

아기상어, 핑크퐁, 공룡, 자동차 등 다양한 캐릭터와 아이의 고른 성장을 돕는 새로운 게임 및 영상들이 업데이트될 예정입니다. 많은 관심 부탁드립니다.

Baby Shark World 서비스는 iOS Native 모바일 App 내부에 Unity를 라이브러리로 사용해서 개발되었습니다. Unity를 사용해서 다양한 게임들을 개발하였으며, 현재 iOS AppStore에서 다운로드 가능합니다.

Baby Shark World 앱을 개발하는 과정에서 예기치 못한 문제들을 만났고, 이를 기술적으로 해결하는 과정 및 경험, Tip들을 공유하려고 합니다. Unity 및 iOS Native 기술을 통합해서 서비스를 만들거나 준비하시는 분들에게 조금이나 도움이 되었으면 좋겠습니다.

Unity As A Library 도입 배경

Baby Shark World 는 한 앱에서 많은 영상과 게임이 포함되어있고 지속적으로 새로운 게임들을 업데이트 및 추가 되어야하는 요구사항이 있었습니다.

주요 기능들

  • Video Streaming Playback
  • 게임 개발
  • UI 작업

Video Streaming Playback, 게임 개발, 각종 UI 작업 등, 해당 주요 기능들을 효율적으로 구현하기 위해서 적절한 개발 방법을 찾기 위해 고민을 많이 했습니다. 게임 개발 같은 경우에는 iOS Native에서 Game Kit API을 사용하거나 Unity를 사용하는 방법이 있습니다. 저희는 많은 게임들을 Unity를 사용해서 만들었던 경험을 가지고 있고, 기존 게임들을 개발하면서 사용 되었던 많은 리소스들을 재사용 하기 위해서 Unity를 사용하기로 결정했습니다.

Unity and iOS

UI 작업 및 Video Streaming Playback 같은 경우에는 iOS Native로 개발된 커스텀 UI Component Opensource들이 많이 존재하고, UI Rendering 속도 측면에서도 Unity를 통해 UI를 Rendering하는 것보다 iOS Native 기술을 사용해서 UI를 개발하면 더 빠르고 쉽게 개발할 수 있다고 판단했습니다.

Add Unity-powered features to your app using Unity as a Library

iOS앱에 Unity를 사용하기 위해서 기술 조사를 진행한 결과 Unity 2019.4 버전부터 일반 Android 및 iOS Native 앱에서 Unity를 라이브러리로 사용하기에 구조적인 변화가 있었다는 점을 확인했습니다.

Unity에서는 Unity 2019.4 버전부터 Unity As A Library 기능을 통해 iOS Native 애플리케이션 내에서 Unity Runtime Library를 Load/Active/Unload하는 시기와 방법을 관리할 수 있는 컨트롤을 제공하기 시작했습니다. 이 기능을 이용하면, Native 모바일 앱 내부에 게임을 통합하고 iOS 앱 로직에서 게임별 로직을 분리할 수 있기 때문에 저희에게 적합한 솔루션이라고 생각했습니다.

개발 진행에 앞서 iOS 화면에서 Unity 화면으로의 전환 속도(Load/Unload), 메모리양, Third parties 호환 가능성 등을 기술적으로 검증하였습니다. 검증 결과 기술적으로 가능하다고 판단하여, iOS 앱에 Unity를 라이브러리로 사용해서 개발하기로 하였습니다.

Integrating Unity as a library into standard iOS app

Unity를 일반 iOS Xcode 프로젝트에 통합하려면 다음의 작업이 필요합니다.

  1. Xcode 프로젝트(iOS Xcode 프로젝트와 Unity에서 생성하는 Xcode 프로젝트 모두)를 단일 Workspace 작업 공간에 결합합니다.
Xcode project 결합 과정

2. UnityFramework.framework 파일을 iOS Xcode 프로젝트에 대한 Application 타겟의 Embedded Binaries 섹션에 추가합니다.

3. iOS Xcode 프로젝트에서 Build Phases Tab에 포함된 UnityFramework.framework 파일을 제거해줍니다.

4. Unity-iPhone 프로젝트에서 Data 폴더를 선택하고, 오른쪽 패널에서 Target membership을 UnityFramework로 변경해줍니다.

5. Unity-iPhone 프로젝트에서 NativeCallProxy.h를 선택합니다. 오른쪽 패널에 Target membership을 Public으로 변경해줍니다.

이렇게 하면 UnityFramework 클래스를 사용하여 iOS Native에서 Unity 런타임을 제어할 수 있습니다.

Unity Load, Unload 방법

iOS Xcode 프로젝트와 Unity에서 생성하는 Xcode 프로젝트 모두를 단일 Workspace 작업 공간에 결합하는 과정이 완료되면 UnityFramework.framework 가 제공하는 API들을 iOS Appdelegate file에서 사용할 수 있습니다.

Unity를 Runtime에 Load하는 방법과 Unload하는 방법은 간단합니다.
Unity를 Load하고 싶을 때는 Init Unity API를 호출하고 Unity를 Unload하고 싶을 때는 Unload Application API를 호출하면 됩니다. Unload가 성공적으로 수행되면 iOS notification을 통해 Unloaded된 시점을 전달받을 수도 있습니다.

Init Unity

Init Unity 안에 수행되는 과정들은 bundle 안에 있는 Unity framework를 가져와서, Unity framework의 상태를 알 수 있는 Listener 등을 등록하고, Unity 런타임이 Data 폴더를 찾기 위해 번들 ID를 설정합니다.

Data 폴더 안에는 App의 직렬화된 에셋과, NET 어셈블리(.dll 또는.dat 파일)가 등 메타데이터 등이 저장되어있습니다.

setDataBudleID API runEmbeddedWithArgc를 호출하기 전에 호출되어야 합니다.

마지막으로 runEmbeddedWithArgc API를 통해 Unity를 Load하게 됩니다. Unity가 Unload되고 다시 Load가 되려면 Init Unity API를 다시 호출해주시면 됩니다.

Init Unity

UnloadApplication

UnloadApplication API는 Unity가 차지하고 있는 대부분의 메모리를 해제합니다. Unload 후에는 다시 Init 과정을 거쳐서 Unity를 다시 실행할 수 있습니다.

Unload Unity

QuitApplication

UnloadApplication API 대신에 QuitApplication API를 사용하면 Unity는 모든 메모리를 해제하고, 프로세스를 종료하게 됩니다. 같은 프로세스로 Unity를 다시 실행할 수 없고 앱을 종료시킬 때 사용됩니다.

Unity Init,Unload Demo

Unity에서 제공하는 Uaal Example Repository와 Unity 공식 문서에는 Unity를 Xcode 프로젝트에 통합하는 방법을 설명하는 예제 프로젝트와 상세한 API가이드 문서를 제공합니다. 자세한 해당 Document를 참고하시면 쉽게 따라 하실 수 있습니다.

Unity with Swift

현재 저희 iOS 개발팀에서는 Baby Shark World 프로젝트에서 Swift 언어를 주 언어로 사용해서 개발하고 있습니다. Swift 언어로 구성된 개발 환경에서 Unity를 라이브러리로 사용하려고 하니 문제가 있었습니다. Unity 공식 문서 및 Uaal Example Github Repository에도 Swift 언어 지원에 대한 구체적인 내용은 없었습니다.

In some scenario developers using native platform technologies (like Android/Java & iOS/Objective C) want to include in their apps/games features powered by Unity for 3D/2D Real Time Rendering, AR experience, interaction with 3D models, 2D mini games and more.

여러 방안을 찾던 중에 Unity framework API가 사용되는 iOS Appdelegate파일과 Unity framework API를 호출할 수 있게 포함된 Object C Code 부분을 Swift로 Converting하기로 했습니다.

또한 Swift bridge header를 생성해서 Unity framework API를 Globally 사용할 수 있게 하고, Unity에서 iOS Native에 Callback API 전달할 때 사용되는 NativeProxy.h 파일을 Define 했습니다.

Appdelegate
Swift Header

위와 같이 Converting 작업을 진행하고 나니 정상적으로 Swift로 구성된 iOS 앱에서 Unity API를 사용할 수 있었습니다.

Object C로 구성된 Uaal Example을 Swift로 Converting한 Sample 코드는 이곳에서 확인할 수 있습니다.

iOS Native와의 조화

Game 화면 UI Buttons

Baby Shark World — Game

Baby Shark World에 포함된 게임별 화면에서는 사용자가 Next, Previous, back button 등을 이용해 이전 게임, 다음 콘텐츠 또는 메인화면으로 돌아갈 수 있습니다. iOS Native 와 Unity와 결합해서 개발을 진행하다 보니, 두 플랫폼의 경계가 되는 부분에서 UI Event handling 처리가 쉽지 않았습니다. UI Component 들을 iOS Native에서 구현해야 할지, Unity에서 구현해야 할지 고민되었습니다.

Unity Framework에서 제공하는 API들을 확인해본 결과, appController API를 사용하면, Unity가 렌더링 되는 UIViewController의 View를 사용할 수 있다는 사실을 확인했습니다.

UnityFramework Appcontroller가 가지고 있는 RootView에 iOS Native에서 UIButton들을 포함한 UI View를 Addsubview하니, 게임이 렌더링되는 화면 위에 iOS Native에 생성한 UI View를 배치할 수 있었습니다.
이렇게 구성하니 iOS Native에서 Load/Unload등 Unity Framework API들을 쉽게 호출할 수 있었습니다.

Unity → iOS, Buffering Case

iOS Native 화면에서 Unity 화면으로 전환될 때 화면상의 어색함을 없애기 위해서 Loading buffing UI를 포함했습니다.

Buffing UI는 Unity가 Load 되는 시점에 UI를 없애기 위해서 Native Plugin API를 이용해 iOS Native에 callback을 전달했습니다.

Native Plugin API를 사용하기 위해서는 Unity Editor에서 Assets/Plugins/iOS 위치에 NativeCallProxy.mm파일과 NativeCallRoxy.h를 추가해줍니다. 해당 파일들 포함해 Unity를 빌드하면, Unity에서 생성하는 Xcode 프로젝트에 자동으로 Dependency가 구성됩니다.

Swift로 구성된 iOS 앱에서는 위에 언급한 것처럼 Swift bridge header에 NativeCallProxy.h도 Define되어야 합니다.

NativeCallProxy.h
NativeCallProxy.mm

Unity에 Scene에 붙어 있는 Game Object가 Start API가 불릴 때, 이 시점에 맞추어서 Unity에서 iOS Native에게 Callback Event를 주고, 이 타이밍에 맟추어서 iOS Native에서 Buffing UI를 Hide 처리하니 알맞게 구성할 수 있었습니다.

iOS → Unity Data 전달

BabySharkWorld에 포함된 게임들은 Addressable asset system을 이용해 서버에서 asset bundle을 내려받은 후 게임을 Load하는 과정을 가지고 있습니다. 메인화면에서 게임으로 진입하는 시점에 토픽에 알맞은 게임을 다운받아야 하므로 iOS Native에서 Unity가 Load되는 시점에 다운로드에 필요한 정보를 같이 전달해야 했습니다.

Unity Framework에서 제공하는 sendMessageToGOWithName API를 사용하면 iOS Native에서 Unity에 사용되는 Game Object에 Data 전달이 가능합니다.

하지만 오직 하나의 문자열 값만 전달할 수 있고, iOS Native에서 Unity로 전달하는 값들이 점차 확장될 것으로 예상되어서, iOS Native와 Unity에서 모두 해석할 수 있는 포맷을 지정하고 데이터를 직렬화하는 방법이 필요하다고 생각했습니다. 따라서 이해하기 쉽고 iOS Native와 Unity 양쪽에서 완벽하게 지원할 수 있는 JSON Format을 선택하였습니다.

iOS Native에서 전달할 Data를 JSON 형태의 Data로 String을 구성하고, Unity에 sendMessageToGOWithName API를 이용해 값을 전달하였습니다. Unity에서는 전달받은 String값을 다시 JSON 형태로 변환 후 값을 사용했습니다. 이렇게 Data 전달 방식을 구성하니, 다양한 데이터를 쉽게 전달할 수 있었습니다.

Tips

Unity와 iOS Native를 결합해서 개발하다 보니 몇 가지 새로운 점을 발견하게 되었습니다. 소소한 Tip들이지만 개발하시는데 조금이나마 도움이 되었으면 합니다.

Cocoapods

iOS Xcode 프로젝트와 Unity에서 생성하는 Xcode 프로젝트 모두를 단일 Workspace 파일형태로 결합이 이루어지게 됩니다. Cocoapods 또한 여러 3rd parties 라이브러리들을 Workspace로 파일들을 관리하게 됩니다.

Unity와 Cocoapods을 같이 사용하기 위해서는 우선 cocoa pod를 통해 Workspace 파일을 먼저 생성하고, Unity를 통해 생성된 Xcode 프로젝트를 해당 Workspace에 결합하면 Cocoapods 기반에 iOS Xcode project에서 Unity를 같이 사용할 수 있습니다.

Unity iOS Simulator support

Unity Editor에서 다음과 같이 설정을 해주면 iOS Simulator에서 Unity를 실행할 수 있습니다.(Burst compiler 모드에서는 지원되지 않습니다.)

iOS Player Settings → Player → Target SDK

iOS Simulator도 지원을 하다 보니 iOS 개발자분들과 협업 측면에서 많은 생산성을 가져올 수 있습니다.

Unity As A Library 제약 사항

Unity As A Library 기능은 다음의 제약사항을 가지고 있습니다.

  • Unity As A Library는 FullScreen 렌더링만 지원하며, 화면 일부의 렌더링은 지원하지 않습니다.
  • 두 개 이상의 Unity 런타임 인스턴스를 로드할 수 없습니다.
  • Unity 런타임에서 동작하도록 만들기 위해 타사 Third parties (네이티브관리되는 플러그인 모두)을 조정해야 할 수 있습니다.
  • Application.Unload를 호출한 이후 Unity가 Unload된 상태면 동일한 프로세스로 즉시 재전환한 후 실행하기 위해 약간의 메모리(100Mb 이하)를 유지해야합니다.

Baby Shark World 성공적 소프트 런칭

Baby Shark World - Games

iOS Native와 Unity 결합 개발 과정을 통해 21년 12월 성공적으로 서비스를 런칭하였습니다. 총 26개의 게임이 포함되었고, iOS Native 앱 화면에서 자유롭게 Unity로 만들어진 다양한 게임들을 이용할 수 있게 되었습니다. Unity를 라이브러리로 사용하니 간편하게 Unity로 만들어진 게임들을 사용할 수 있었고 기존에 만들어진 게임들을 앱에 포함할 수 있어서 높은 생산성을 가져왔습니다.

더불어 iOS 및 Unity 개발자들 사이에서도 서로의 기술을 이해하고 플랫폼별 특성을 학습할 수 있는 계기가 되어서 좋은 경험이었습니다.

What’s the Next?

서비스를 계속해서 확장하고 고도화할수록 풀어야 할 숙제가 많습니다. 22년 3월 하드 런칭에는 Baby Shark World Android 버전도 출시를 앞두고 있고, 다양하고 새로운 기능과 게임을 추가할 계획입니다.

Android 플랫폼에서도 Unity를 결합해서 개발 진행 예정이고, 이 과정에서 생기는 다양한 문제 및 해결 사례도 공유하려고 합니다.

다음 포스팅도 기대해주세요:) 긴 글 읽어 주셔서 감사합니다.

더핑크퐁컴퍼니에서는 많은 개발자들을 기다리고 있습니다.

더핑크퐁컴퍼니 개발자 지원하기👉 https://fong.kr/apply/

Reference

--

--

Derrick
더핑크퐁컴퍼니 기술 블로그

Trying to be an awesome software engineer who can help people and public welfare services😎 https://www.linkedin.com/in/sensational/