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ステップです。

  1. shared-kmmでリリースがトリガーされると、GitHub Packagesに公開
  2. 公開が成功したら、gh workflow runで各アプリリポジトリの更新ワークフローを起動
  3. 各アプリでバージョンを更新したPRが自動作成される
1
2
3
4
5
6
7
8
9
# KMPリリース時(抜粋)
- name: Publish to GitHub Packages
run: ./gradlew publish

- name: Trigger Android update
run: |
gh workflow run update-kmm-version.yml \
--repo AnotherBall/android-app \
--field version=${{ env.VERSION }}
1
2
3
4
5
6
7
8
9
10
11
# Androidアプリ側の更新ワークフロー(抜粋)
- name: Update version in libs.versions.toml
run: |
sed -i "s/kmm = \".*\"/kmm = \"${{ inputs.version }}\"/" \
gradle/libs.versions.toml

- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: "Update KMP to ${{ inputs.version }}"
branch: "auto/kmm-${{ inputs.version }}"

Unityライブラリの配布

UnityモジュールをiOSアプリで使うため、SPM(Swift Package Manager)形式で配布しています。

  1. Unity XCFramework をビルドし、GitHub Release にアップロード
  2. ファイルのハッシュ値を計算
  3. Package.swift を自動生成(ダウンロードURLとハッシュ値を埋め込み)
  4. 配布用リポジトリ(unity-spm)にPRを作成

unity-moduleリポジトリでの直接配布も可能ですが、Xcodeの仕様上、SPMパッケージ取得時にリポジトリ全体をダウンロードしてしまいます。Package.swiftのみを格納するunity-spmを用意することで、ダウンロードを高速化しています。

ハッシュ値を埋め込むことで、iOSアプリ側でファイルが壊れていないか確認できます。

リリースブランチの自動PR

release/* ブランチにpushされると、複数のマージPRが自動で作成されます。

  • release/2.10.0main へのPR(本番リリース用)
  • release/2.10.0release/2.11.0 へのPR(バグ修正を次バージョンに伝搬)

バージョン番号を比較して、適切なマージ先を自動で判定しています。これにより、マージ漏れを防げます。

Package.resolved のコンフリクト解決(iOS)

複数のKMM/UaaL更新PRが同時に存在すると、Package.resolved ファイルで競合が発生します。iOSリポジトリでは、この問題を自動解決するワークフローを用意しています。

トリガー: release/* ブランチへのpush

処理の流れ:

  1. GitHub APIでリリースブランチ宛のオープンPRを取得
  2. タイトルが chore: update KMM or chore: update UaaL で始まるPRを絞り込み
  3. マージできるかどうかをチェックし、競合中のPRを特定
  4. 各PRの競合を解決
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# baseブランチをマージ試行
if git merge "origin/$BASE_REF" --no-edit; then
echo "Merge succeeded"
else
# 競合時: PR側のPackage.resolvedを一旦採用
git checkout --ours Package.resolved
git add Package.resolved
git merge --continue
fi

# SPMの依存関係を再解決(baseの変更も反映される)
make resolve-package-dependencies

git commit -m "chore: resolve Package.resolved conflict"
git push

ポイントは 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などの新しい技術を取り入れながら、プロダクトを一緒に成長させていく仲間を探しておりますので、ぜひ下記のリンクから応募いただけますと嬉しいです!