Unity as a LibraryをSwiftPM経由で導入してiOSビルド環境を改善した話

こんにちは!クラスター社でSoftware Engineerをしているizumiです。
今回はcluster iOSで行ったビルド改善について解説します。

メタバースプラットフォーム clusterのiOS版は仮想空間内の体験(通称:inroom)をUnity、仮想空間までのワールド検索やフレンド一覧、イベント一覧などの導入部(通称:outroom)をSwiftで書いており、Unity as a Libraryを用いてアプリケーションを構築しています。

Unity as a Libraryとは

Unity as a Libraryとは、Unityで構築したアプリケーションをネイティブアプリのライブラリとして扱うことのできる機能です。
具体的にiOSアプリケーションに組み込む例としては以下のような構成になります。

導入手順はざっくり以下のようになっています。

① Unity (C#)をiOS向けにビルドし、Unity-iPhoneというxcodeprojファイルを生成する
② ①で生成されたxcodeprojをアプリケーションのxcworkspaceに追加する
③ Unity-iPhone内のUnityFrameworkというFrameworkをメインターゲットのアプリケーションにlinkする

(詳細は公式のexample https://github.com/Unity-Technologies/uaal-example/blob/master/docs/ios.md を参照してください)

Unityのサンプルリポジトリもこの手法が扱われており、clusterでもこの手法をベースにビルドを行っていましたが、この手法には次に挙げる問題点がありました。

Unity as a Libraryの問題点

1.iOSのclean build時間の増加

アプリケーションのbuild時にUnityFrameworkもbuildする仕組みとなっているため、clean buildには大きな時間がかかっていました。
特にclusterのunityprojectは現在約20名のEngineerが開発している巨大なコードベースとなっていて、ビルド時間も日に日に長くなってました。

また、UnityFrameworkのbuild sizeが巨大な事からOptimizeの設定もRelease build相当に設定しないとビルドができないという問題も抱えており、さらにbuild時間が長くなる原因ともなっていました。

2. Unity側の問題に対するworkaroundを各自がローカルで対応する必要がある

1.に挙げたOptimizeの設定変更や、UnityバージョンによってはM1 macでbuildする際にエラーとなってしまう問題が存在したためworkaroundが必要なケースもありました。
ある程度はworkaroundの自動化も可能ではありますが、基本的には開発者各々がworkaroundを実施していたため、新しくiOSエンジニアが入社した際に環境構築に躓いてしまう、などの問題がよくありました。

3. Simulatorでの実行が大変

Unityの仕様上、Unity側でXcode Projectを生成する段階でDevice / Simulatorの指定を行う必要があります。
つまり、Device / SImulatorそれぞれのためのUnityFrameworkを作成する必要があり、環境を切り替えるたびにFrameworkのビルドが必要となるためSimulatorでの実行に手間がかかるという問題を抱えていました。

UnityFrameworkのxcframework化とSwift Package Managerによる導入

これらの問題を解決するため、UnityFrameworkをxcframework化してSwiftPM経由でアプリケーションに組み込む手法を取り入れ、以下のような構成に改善しました。

手順としては以下の通りで、これら全てのステップをMakefileに定義して環境構築時のmakeコマンド一つで実行可能となっています。

① Unity側でbuildを行いUnity-iPhone projectの生成
ここは改善前同様の手順を行います
(今回は解説を省きますが、Unity ProjectのbuildはUnityEditor拡張を書いてbatchmodeで実行しています)

② 生成されたUnity-iPhone projectからFrameworkをbuildし、UnityFramework.xcframeworkを生成

# device向けのframeworkをarchive
xcodebuild archive -project Unity-iPhone.xcodeproj -scheme UnityFramework \
  -destination 'generic/platform=iOS' -archivePath "UnityFramework-Device" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES

# simulator向けのframeworkをarchive
xcodebuild archive -project Unity-iPhone-for-Simulator.xcodeproj -scheme UnityFramework \
  -destination 'generic/platform=iOS Simulator' -archivePath "UnityFramework-Simulator" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES
# device向け / simulator向けのframeworkからxcframeworkを生成
xcodebuild -create-xcframework \
  -framework UnityFramework-Device.xcarchive/Products/Library/Frameworks/UnityFramework.framework \
  -framework UnityFramework-Simulator.xcarchive/Products/Library/Frameworks/UnityFramework.framework \
  -output UnityFramework.xcframework

この時にdevice用とsimulator用それぞれのUnityFrameworkをbuildし、xcframeworkを生成しています。

※現在clusterではUnityが依存するライブラリでSimulator動作しないものが別途あるため、simulator用のUnityFrameworkには必要最小限の構成を備えたMock用のProjectから生成しています

③ SwiftPMのbinaryTargetでUnityFrameworkを指定する

以下のようなPackage.swiftを追加します

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
 name: "UnityFrameworkSwiftPM",
 products: [
    .library(
        name: "UnityFrameworkSwiftPM",
        targets: ["UnityFramework"])
 ],
 targets: [
    .binaryTarget(
        name: "UnityFramework",
        path: "UnityFramework.xcframework")
 ]
)

④ 生成したSwiftPackageをアプリターゲットに導入し、Frameworkをlinkする

これで今まで同様にUnity as a Libraryを利用することができます!

この手法を取り入れて得られた効果

ビルド時間の短縮

iOS projectのclean buildの時間はM1 Max / RAM 64GBのマシンで以下のように変化しました。
※クラスター社のエンジニアにはM1 Max / RAM 64GBのMacBook Proが支給されています!

200秒以上のbuild時間を削減することができました🎉 

Device / Simulatorの切り替えが容易になった

xcframeworkにDevice / Simulatorそれぞれのframeworkが入ったことにより環境の切り替えも以前よりも簡単に行うことができるようになりました!
おかげで様々な解像度やOSバージョンでのデバッグを行いたい場合でもすぐに確認できるようになりました。

ライブラリ管理が全てSwiftPackageに統一されてプロジェクト構成がSimpleになった

今回の対応と並行して、CocoaPodsで入れていたライブラリのSwiftPackage移行も進めていたため、現在clusterのworkspace内に含まれるprojectファイルはアプリケーションのメインプロジェクト1つになりました!
workspaceの構成がシンプルになったことで、新規の開発者が環境構築で躓く事も少なくなりました。
また、今後multi-module化などもスムーズにできるような下準備が整いました。

今後の課題

今回UnityFrameworkをxcframework化してSwiftPM経由で導入することが可能となりました。

現状は各開発者がlocal環境でxcframeworkの生成をしていますが、今後はxcframework / SwiftPackageをprivate repositoryなどに配置してremoteから取得可能にすることや、UnityFrameworkが依存しているライブラリの整理などを検討しています。

おわりに

このようにクラスター社では新規開発と並行して開発環境の改善にも力を入れ、よりモダンで快適な環境で開発ができるよう取り組んでいます。
iOSエンジニアをはじめ、Unity、Android、Web、Serverやその他各職種も採用強化中ですので、興味ある方は是非以下よりエントリーをお待ちしております!