KMP × Unity UaaL のモバイルアプリ開発 ── マルチリポジトリ構成と自動化の話
こんにちは!AnotherBall モバイルエンジニアチームです。
私たちが開発している「Avvy」は、ビジネスロジックをiOS/Androidで共通化する KMP(Kotlin Multiplatform) と、Unity製の2Dアバター描画機能をネイティブアプリへ組み込む Unity as a Library(UaaL) を組み合わせた、ちょっと複雑な構成のアプリです。
この記事では、5つのリポジトリをどう連携させているか、そしてGitHub Actionsでどこまで自動化しているかを紹介します。
リポジトリ構成
ビルドの複雑化やチーム間の依存関係といった課題を避けるため、リポジトリを機能ごとに分割しています。
| リポジトリ | 役割 | 成果物 |
|---|---|---|
shared-kmm |
ビジネスロジック | AAR / XCFramework(KMPライブラリ) |
unity-module |
2Dアバター描画 | Android / iOS向けUaaLライブラリ |
android-app |
Androidアプリ本体 | APK / AAB |
ios-app |
iOSアプリ本体 | IPA |
unity-spm |
Unity XCFrameworkのSPM配布用 | Swift Package |
各リポジトリは、ビルド済みのライブラリ(AndroidはAAR、iOSはXCFramework)を通じて連携します。こうすることで、各チームが独立して開発を進められます。
Unityモジュールの役割
Unityモジュールは、アバターの表示とリアルタイム制御を担当しています。
- アバター表示: 2Dアニメーションでアバターを描画
- フェイストラッキング: カメラで顔の動きを検出し、アバターに反映
- カスタマイズ: 衣装やアクセサリーの着せ替え
ネイティブアプリとの通信には工夫が必要です。フェイストラッキングは毎秒60回データを送るため、通常のブリッジでは遅延が発生します。iOSではポインタ経由の直接アクセス、Androidではメモリマップドファイルを使って高速にデータをやり取りしています。
自動化の仕組み
GitHub Actionsを使って、リポジトリ間の連携をほぼ自動化しています。月間約1,200件のPRが自動で処理されています(2025年12月実績)。
サーバーAPIの変更を反映
サーバーのAPI定義ファイル(OpenAPI)が更新されると、KMPリポジトリに更新PRが自動で作成されます。
KMPライブラリの更新
KMPライブラリのリリース → 各アプリリポジトリへの更新PRの作成まで、すべて自動です。
流れはシンプルで、「Publish → Trigger → Update」の3ステップです。
shared-kmmでリリースがトリガーされると、GitHub Packagesに公開- 公開が成功したら、
gh workflow runで各アプリリポジトリの更新ワークフローを起動 - 各アプリでバージョンを更新したPRが自動作成される
1 | # KMPリリース時(抜粋) |
1 | # Androidアプリ側の更新ワークフロー(抜粋) |
Unityライブラリの配布
UnityモジュールをiOSアプリで使うため、SPM(Swift Package Manager)形式で配布しています。
- Unity XCFramework をビルドし、GitHub Release にアップロード
- ファイルのハッシュ値を計算
- Package.swift を自動生成(ダウンロードURLとハッシュ値を埋め込み)
- 配布用リポジトリ(unity-spm)にPRを作成
unity-moduleリポジトリでの直接配布も可能ですが、Xcodeの仕様上、SPMパッケージ取得時にリポジトリ全体をダウンロードしてしまいます。Package.swiftのみを格納するunity-spmを用意することで、ダウンロードを高速化しています。
ハッシュ値を埋め込むことで、iOSアプリ側でファイルが壊れていないか確認できます。
リリースブランチの自動PR
release/* ブランチにpushされると、複数のマージPRが自動で作成されます。
release/2.10.0→mainへのPR(本番リリース用)release/2.10.0→release/2.11.0へのPR(バグ修正を次バージョンに伝搬)
バージョン番号を比較して、適切なマージ先を自動で判定しています。これにより、マージ漏れを防げます。
Package.resolved のコンフリクト解決(iOS)
複数のKMM/UaaL更新PRが同時に存在すると、Package.resolved ファイルで競合が発生します。iOSリポジトリでは、この問題を自動解決するワークフローを用意しています。
トリガー: release/* ブランチへのpush
処理の流れ:
- GitHub APIでリリースブランチ宛のオープンPRを取得
- タイトルが
chore: update KMMorchore: update UaaLで始まるPRを絞り込み - マージできるかどうかをチェックし、競合中のPRを特定
- 各PRの競合を解決
1 | # baseブランチをマージ試行 |
ポイントは make resolve-package-dependencies(内部で xcodebuild -resolvePackageDependencies を実行)で、マージ先ブランチの変更も含めて依存関係を再解決している点です。複数のPRがある場合は並列で処理します。
今後の課題
- CI/CD実行時間: Gradle/Xcodebuildで40分以上かかることがあり、キャッシュ改善を検討中
- ワークフローの重複: 似たような処理が複数ファイルにあるので、共通部品として切り出したい
- 更新PRの自動マージ: 現在はPR作成まで。テストが通れば自動マージまでやりたい
まとめ
KMPとUnity UaaLを組み合わせた複雑な構成でも、リポジトリを分けて成果物経由で連携すれば、各チームが独立して動けます。自動化は一度作って終わりではなく、運用しながら改善していくものだと実感しています。
We’re Hiring
AnotherBallでは、Kotlin/Swift/Unity/AIを活用したアプリ開発に興味のあるモバイルエンジニアを募集しています!
KMPなどの新しい技術を取り入れながら、プロダクトを一緒に成長させていく仲間を探しておりますので、ぜひ下記のリンクから応募いただけますと嬉しいです!