🐦 Flutter

Flutter Unity 통합하기

ji-hyun 2024. 12. 1. 20:25

Flutter에서 AR 화면 띄우기: Unity 통합 과정

Flutter 애플리케이션에 AR 화면을 구현하기 위해 Unity 통합을 구현하였다.

이번 글은 Flutter 와 유니티를 통합하며 진행했던 과정을 다시끔 되짚어보고자 한다.

 

(flutter_unity_widget 이라는 라이브러리를 사용)

 

 

 

 

 

Unity 통합을 위한 Android 프로젝트 설정하기

Unity를 통합한 Android 프로젝트를 생성하려면 여러 설정 파일을 수정해야 한다.

아래에서 설정 파일을 수정하는 과정을 알아보자.

 

# 1. 멀티 모듈 설정을 위한 settings.gradle 구성

MyProject/
├── app/
├── unityLibrary/
└── settings.gradle

 

위와 같이 유니티 모듈(unityLibrary)을 포함해서 멀티 모듈 프로젝트로 구성해줘야 하는데

그러기 위해선 settings/gradle 파일을 다음과 같이 수정해야 한다.

 

 

// 메인 앱 모듈 포함
include ':app'

// Unity 모듈 포함
include ':unityLibrary'
project(':unityLibrary').projectDir = file('./unityLibrary')

// (선택적) XR 기능이 필요한 경우
include ':unityLibrary:xrmanifest.androidlib'

 

settings.gradle 파일에 위처럼 각 프로젝트 디렉토리를 설정해준다.

  • project(":unityLibrary").projectDir = file("./unityLibrary")
    • 이 부분은 유니티 모듈의 디렉토리 위치를 정하는 부분이다.

  • include ":unityLibrary:xrmanifest.androidlib"
    • 이 부분은 Unity 의 XR(확장 현실) 관련 기능을 제공하는 xrmanifest.androidlib 라는 서브모듈을 포함한다.
      즉, xrmanifest.androidlib 는 Unity 에서 제공하는 증강 현실(AR)이나 가상 현실(VR)과 같은 기능을 구현하는 라이브러리이다. (나는 AR 기능이 필요해서 작성하였다.)

 

 

 

 

위 설정에 따라 전체 디렉토리 구조는 아래와 같게 된다.

project_root/
│
├── app/                          # 메인 앱 모듈
│   └── build.gradle              # 메인 앱의 Gradle 설정 파일
│
├── unityLibrary/                 # Unity에서 내보낸 라이브러리 모듈
│   ├── build.gradle              # Unity 모듈의 Gradle 설정 파일
│   └── xrmanifest.androidlib/    # XR 관련 서브 모듈
│       └── build.gradle          # XR 기능 관련 Gradle 설정 파일
│
└── settings.gradle                # 멀티 모듈 정의 파일

 

 

 

 

 

 

 

 

 

# 2. Android 프로젝트에 Unity 통합하기 (app/build.gradle)

 

Unity 를 Android 앱에 통합하려면 app/build.gradle 파일을 다음과 같이 수정해야 한다.

android {
	ndkVersion "21.3.6528147"
	defaultConfig {
		minSdk = 24
	}
}
dependencies {
    implementation project(':unityLibrary')
    implementation project(':flutter_unity_widget')
}

 

  • implementation project(':unityLibrary')
    • Unity에서 내보낸 모듈 (unityLibrary)을 프로젝트에 포함시킨다.
      이 모듈은 Unity에서 사용하는 모든 네이티브 라이브러리와 Unity 기능을 포함한다.

  • implementation project(':flutter_unity_widget')
    • Flutter Unity Widget은 Flutter와 Unity 간의 통신을 지원하는 라이브러리이다.
      이 라이브러리를 포함시키면 Flutter 코드에서 Unity를 제어할 수 있는 기능이 제공된다.

 

 

참고로 Unity에서 내보낸 unityLibrary는 Unity 프로젝트를 빌드할 때 생성된다.

Unity와 Android 프로젝트가 동기화되도록 Unity 프로젝트를 수정한 후에는 반드시 Unity 프로젝트를 다시 빌드하여 unityLibrary를 최신 상태로 유지해야 한다.

 

 

 

 

 

 

 

 

 

# 3. build.gradle 에서 AAR 파일 가져오기

 

Unity 를 빌드하고 나면 자동으로 안드로이드에서 사용할 수 있도록 여러 네이티브 라이브러리를 .aar 형식으로 제공한다.

따라서 flutter_unity_widget 문서에서는 이 aar 파일들을 포함시키기 위해 flatDir 방식을 사용해야 한다고 말한다.

 

allprojects {
    repositories {
        flatDir {
            dirs "${project(':unityLibrary').projectDir}/libs" // 'libs' 디렉토리를 로컬 라이브러리 경로로 등록
        }
        google()
        mavenCentral()
    }
}

 

위처럼  Gradle이 unityLibrary/libs 폴더 안에 있는 .aar 파일들을 인식하고 빌드 과정에서 포함하도록 작성해준다.

 

 

 

✋ flatDir 이란?

 

flatDir: 로컬 디렉토리에서 .aar 또는 .jar 파일을 읽어오도록 설정한다.

  • Unity 에서 내보낸 unityLibrary 의 libs 폴더에는 Unity 엔진과 관련된 .aar 파일들이 포함되어 있다.
  • flatDir 을 통해 이 디렉터리를 Gradle 이 종속성 검색 경로로 추가한다.

 

 

 

 

flatDir 방식의 주요 특징

 

  1. 로컬 라이브러리 참조
    로컬 디렉토리에 저장된 .jar 또는 .aar 파일을 직접 종속성으로 추가한다.
  2. 저장소 등록
    Gradle에서 repositories 블록에 flatDir을 사용해 로컬 경로를 명시적으로 등록해야 한다.
  3. 수동 관리 필요
    라이브러리를 직접 관리해야 하며, 의존성 버전 관리가 자동화되지 않는다.

 

 

 

 

 

유니티 빌드 후 아래와 같이 구성되어 있음을 확인한다.

 

 

libs 아래 aar 파일 확인

 

 

 

 

 

 

 

 

# 4. FlutterActivity → FlutterUnityAcitivty 로 변경 

 

MainActivity.kt 파일에서 FlutterActivity 를 FlutterUnityActivity 로 변경해준다.

+ import com.xraph.plugin.flutter_unity_widget.FlutterUnityActivity;

+ class MainActivity: FlutterUnityActivity() {
- class MainActivity: FlutterActivity() {

 

 

안드로이드 세팅은 끝났다.

이제 최종적으로 플러터를 빌드할 수 있다.

 

 

 

정리

android/settings.gradle Unity 모듈(unityLibrary) 포함 Gradle이 Unity 모듈을 프로젝트의 일부로 인식하게 만듦.
android/app/build.gradle Unity 모듈을 앱 모듈의 의존성으로 추가 메인 앱에서 Unity의 기능을 사용하기 위해 의존성으로 추가.
android/build.gradle Unity의 .aar 파일이 있는 디렉터리 추가 Unity 엔진 관련 파일들을 Gradle이 인식하고 빌드 과정에 포함할 수 있도록 설정.

 

 

 

 

 

 

 

 

 

 

duplicate class a.a.a found in modules jetified-arcore_client-runtime (:arcore_client:) and jetified-installreferrer-2.2-runtime (com.android.installreferrer:installreferrer:2.2)

 

 

플러터를 최종적으로 빌드한 후 나 같은 경우 installreferrer 라는 라이브러리와 충돌이 일어났다.

찾아보니 두 패키지 모두 난독화를 거쳐서 a.a.a 라는 클래스가 생겼고 installreferrer 와 arcore_client 는 a.a.a 동일한 클래스로 인해 충돌이 일어나는 것 같다. (추측임)

 

 

flutter_unity_widget 깃헙 이슈를 뒤져보았지만 해결 사례가 없었고 아래 문서에 따르면 arcore_client 는 1.26 에 아래 이슈를 해결했다는 기록이 있었다.

https://github.com/google-ar/arcore-android-sdk/issues/1140

 

 

 

android > unityLibrary > build.gradle 을 다음과 같이 수정해보자.

기존 arcore_client 를 주석처리하고 아래 코드로 바꿔 처리했다.

 

// implementation(name: 'arcore_client', ext:'aar')
implementation 'com.google.ar:core:1.26.0'

 

 

빌드되고 아무 문제가 없었다.

위 방법을 사용하고 있다.

 

 

 

 

 


Unity 통합을 위한 iOS 프로젝트 설정하기

먼저 유니티에서 export iOS Release 를 하여 내보내기를 해준다.

유니티에서 빌드하고 나면 아래와 같이 산출물이 생기는데 그 중 네모 친 두 개를 사용해 줄 것 이다.

 

 

 

 

이제 iOS 에서 Unity 통합을 진행해보자.

 

 

 

 

 

 

 

 

# 1. 하나의 Workspace 에 두 프로젝트 결합하기
(= Unity XcodeProject 를 workspace 로 끌어오기)

 

아래처럼 작업창에서 우클릭 후 "Add Files to Runner" 을 선택한다.

 

 

 

 

 

 

 

아래처럼 iOS -> UnityLibrary -> Unity-iPhone.xcodeproj 를 선택하고 오른쪽 하단 Add 를 클릭한다.

 

 

 

 

 

결과

 

위와 같이 Workspace 에 Unity Project 도 추가되었다.

 

 

 

 

 

 

 

 

 

 

# 2. UnityFramework 추가하기

 

Data 폴더에서 Target Membership 바꿔야 한다.

이는 아래와 이유로 문서에서 권장하고 있다.

 

By default Data folder is part of Unity-iPhone target, we change that to make everything encapsulated in one single framework file.

 

 

먼저 Data 폴더 클릭한다

 

 

 

우측 창에서 Target Membership 에서 Unity-iPhone 에 체크되어 있던걸 해제하고 UnityFramework 에 체크한다.

 

 

 

 

 

 

Runner 를 클릭해보자.

 

 

 

 

General → Framworks, Libraries, and Embedded Content 섹션 안 [+] 클릭하고

 

+ 클릭

 

 

 

Workspace → Unity-iPhone → UnityFramework.framework 선택 후 우측 하단 [Add] 클릭한다.

 

 

 

이렇게 Unity Framework 도 성공적으로 추가하였다.

 

참고로 UnityFramework 는 동적 프레임워크이므로
General → Framworks, Libraries, and Embedded Content 섹션 안 [+] 클릭을Link Binary With Libraries에서는 삭제해야 한다.

 

 

 

이제 iOS 도 최종 세팅이 끝났다.

플러터를 빌드하면 된다.

 

 

 

 

 

 

 

value of type unityappcontroller has no member unitymessagehandler

 

iOS 를 빌드하고 나면 위와 같은 오류를 마주친다.

다행히 위 오류는 겪은 사람들이 많아 해결 방법도 쉽게 찾을 수 있었다.

 

https://github.com/juicycleff/flutter-unity-view-widget/issues/486

 

  1. ios → UnityLibrary → Classes -> UnityAppController.mm 이동
  2. (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString ... 밑 코드 추가
    extern "C" void OnUnityMessage(const char* message)
    {
        if (GetAppController().unityMessageHandler) {
            GetAppController().unityMessageHandler(message);
        }
    }
    
    extern "C" void OnUnitySceneLoaded(const char* name, const int* buildIndex, const bool* isLoaded, const bool* IsValid)
    {
        if (GetAppController().unitySceneLoadedHandler) {
            GetAppController().unitySceneLoadedHandler(name, buildIndex, isLoaded, IsValid);
        }
    }

 

 

  1. ios → UnityLibrary → Classes -> UnityAppController.h 이동
  2. @property (nonatomic, copy) void (^quitHandler)(void); 밑 코드 추가
   @property (nonatomic, copy)                    void(^unitySceneLoadedHandler)(const char* name, const int* buildIndex, const bool* isLoaded, const bool* IsValid);
    @property (nonatomic, copy)                    void(^unityMessageHandler)(const char* message);

 

 

 

 

 

빌드는 성공적으로 되었다!

하지만 로딩 화면에서 멈추는 오류를 발견했다.

 

이것도 해결 방법을 쉽게 찾을 수 있었다.

 

 

  1. Xcode 열기
  2. Product > Scheme > Edit Scheme
  3. 창이 뜬 후 왼쪽 Run 클릭 후 Diagnostics 탭 확인
  4. Runtime API Checking 에서 Thread Performance Checker 체크 해제

 

 

 

 


Flutter Unity 통합 후 주의할 점

  • 유니티 제약 사항

https://docs.unity3d.com/Manual/UnityasaLibrary.html

You can’t load more than one instance of the Unity runtime, or integrate more than one Unity runtime.

 

 

유니티를 연동하고 나서

유니티 화면을 벗어나고 다른 유니티 화면으로 이동하면 기존 유니티 화면이 재생되는 오류가 있었다.

위의 공식문서와 같이 여러 유니티 인스턴스를 사용할 수 없다는 제약사항 때문인 것 같다..

 

 

그래서 여러 유니티 인스턴스를 사용하려면 기존 유니티 화면을 unload 하는 것이 최선의 선택일 것 같다.

하지만 막상 테스트해보니 이것마저 시원치 않았다.

 

  • 안드로이드
    unload 후 다시 재진입 시, 앱이 강제 종료됨

  • iOS
    WIP 작업중이라고 써있음 (일부 작동은 함, 하지만 찝찝한 느낌..)

 

결국 궁리하다가 플러터에서 restart 라는 메시지를 보내면 유니티에서 진행중이던 로직을 취소하는 것으로 처리해두었다.

 

 

 

 

 

 

이번 글은 Flutter와 Unity를 통합하는 이 여정을 통해 얻은 여러 경험과 해결 과정을 공유해보았다.

비슷한 문제를 겪는 개발자들에게 도움이 되었길 바라며 이 글을 마친다!