Unity/C#を使ったクライアントビルドの活用

Unity

はじめに

こんにちは、エンジニアの森です。

今回はエンジニア向けの記事として、現在運用中のタイトルでどのようなクライアントビルドを行っているかについてご紹介したいと思います。2022年にリリースした「夢職人と忘れじの黒い妖精」(以下「ゆめくろ」という。)はUnityを使って開発しております。Unity開発での利点を活かすため、iOS/Androidの各プラットフォーム向けビルドではなるべくC#を活用しています。C#を使ったクライアントビルドの概要と、実際に不具合修正行ったケースのUnity活用についてご紹介します。

環境

  • macOS 12.6.3
  • Xcode 13.1
  • Unity 2020.3.42.f1

クライアントビルドパイプラインの概要

ゆめくろのクライアントビルドスクリプトは以下の図のようになっています。

ビルドスクリプトの概要図

ScriptableObjectの活用

クライアントビルドを行う際には、ビルド用の設定(ビルド番号、識別子……)と環境設定(Outputファイル名、環境用の識別子)と多くのパラメータが必要です。またこういったパラメータは環境ごとに細かく設定しなければいけないため、取り回しのよい使い方が求められます。

ゆめくろのプロジェクトはこのビルド用の設定をScriptableObjectとして管理しています。ScriptableObjectとして管理することで、各環境のパラメータをGUI上で確認することができたり、またスクリプトの中で環境変数をチェックしたり、時にはビルド時にパラメータを上書きしたりと自由に使用することができます。

ゆめくろプロジェクトではjenkinsを使って一日に何度もクライアントビルドが走らせています。ScriptableObjectを使ってパラメータ管理することで、使いやすくかつ拡張性の高い環境を構築しています。

ScriptableObject - Unity マニュアル
ScriptableObject は、クラスインスタンスとは無関係に、大量のデータを保存するために使用できるデータコンテナです。ScriptableObject の主な使用例の 1 つは、値に対し複数のコピーを避けることによってプロジェクトのメモリ消費を削減することです。これは、プロジェクトに、プレハブ (同じデータを...

ビルドスクリプト継承の活用

またUnity向けにビルドする際に、iOS用とAndroid用のビルドスクリプトが別れており、それぞれBaseビルドスクリプトを継承する形を取っています。

Baseスクリプトでは、ScriptableObject化したビルド用の設定や環境設定の解析などを行っています。またEditor拡張やCLIからコマンドを叩くための仮想関数を定義しています。

各プラットフォーム用のビルドスクリプトでは、固有の対応を行っています。例えばプラットフォーム用アプリアイコンの指定を行ったり、iOS用のinfo.plistの書き換えやAndroid向けのPlayerSettingsの書き換えを行ったりしています。

各プラットフォーム向けに完全にビルドスクリプトを分離することで、プラットフォーム毎にビルドの影響が出ることがない簡潔なスクリプトを記述することができます。iOSとAndroidでUnityへ指定しなければいけないパラメータが違うという理由もあり、ビルドスクリプトを分離することでプラットフォームごとに影響を気にせずスクリプトが記述できています。

OnPostGenerateGradleAndroidProjectの活用事例

最近、UnityのOnPostGenerateGradleAndroidProject関数を活用した不具合対応を行う事例がありました。こちらのケースについてご紹介します。

背景

ゆめくろプロジェクトでは最近Unityアップデートを実施しました。Unityバージョンの更新を行った際、ビルドしたUnityバージョンによってAndroidManifest.xmlのscreenOrientation値が変わってしまう問題が発覚しました。

問題

調べてみたところ、こちらの現象はUnityForumでも報告されていました。

Bug - ScreenOrientation.AutoRotation not working on Unity2021.3.9f1 Android
Whether set auto rotation in project setting or set Screen.orientation = ScreenOrientation.AutoRotation,it always be landscape on phone. I must check...

ビルド後のバイナリを解析してみたところ、UnityのScreenOrientation.AutoRotation設定とUnityが出力するAndroidManifest.xmlの関係がアップデートによって変わってしまったようです。

2020.3.42.f1のUnityバージョンで検証したところ、Unity側のオプションであるScreenOrientation.AutoRotationの設定を更新しても、AndroidManifest.xmlに変更がないことが確認されました。

対応

本問題に対して、プロジェクトとしてはUnityアップデート前後でなるべく同じ挙動を維持する方針を取ることにしました。そこでUnityがAndroidビルド時に吐き出しているAndroidManifest.xmlのscreenOrientationの値をビルド中に更新する必要があります。

OnPostGenerateGradleAndroidProjectとはUnityがAndroidビルドを開始し、Gradleプロジェクトを吐き出した後にコールバック動作を定義する関数です。こちらの関数を使って、ScreenOrientation.AutoRotationの設定の値を強制的に上書きします。

Android.IPostGenerateGradleAndroidProject-OnPostGenerateGradleAndroidProject - Unity スクリプトリファレンス
Implement this function to receive a callback after the Android Gradle project is generated and before building begins. It is not called when doing an Internal ...

Android用ビルドスクリプトに以下のようなコードを追記しました。

 /// <summary>
/// unityから吐き出したmanifestファイルを上書きする
/// </summary>
/// <param name="gradleProjectPath"></param>
public void OnPostGenerateGradleAndroidProject(string gradleProjectPath)
{
    PatchAndroidManifest(gradleProjectPath);
}

/// <summary>
/// AndroidマニフェストファイルをGradleビルド前段階でパッチを当てる処理
/// </summary>
private void PatchAndroidManifest(string root)
{
    // Manifestファイルのロード
    var manifestFilePath = GetManifestFilePath(root);
    var manifest = new XmlDocument();
    manifest.Load(manifestFilePath);

    // screenOrientationのDom要素の取得
    var elm = manifest.GetElementsByTagName("activity");
    if (elm.Count <= 0)
    {
        return;
    }
    var attr = elm[0].Attributes["android:screenOrientation"];
    if (attr == null)
    {
        return;
    }

    // Dom要素の書き換え
    attr.Value = "sensorLandscape";

    // Manifestファイルの保存
    manifest.Save(manifestFilePath);
}

OnPostGenerateGradleAndroidProjectの関数を使って、Unityが吐き出したAndroidManifest.xmlに直接アクセスを行い、XMLデータ形式のDOM要素を直接上書きしています。今回は”sensorLandscape”の値で固定化しています。

備考

本来Unityにはプロジェクト固有のマニフェスト設定をうまくマージしてくれる機能があります。

Android マニフェスト - Unity マニュアル
AndroidManifest は XML ファイルで、Android アプリケーションに関する重要なメタデータが含まれています。これには、パッケージ名、アクティビティ名、主要アクティビティ (アプリケーションの開始個所)、設定、Android バージョンサポート、ハードウェア機能サポート、パーミッションなどが含まれま...

ただしあくまで設定をマージするものであり、今回の”android:screenOrientation”の値はテンプレートマニフェストを設定してもUnity側が吐き出す設定で上書きされてしまいました。そのためOnPostGenerateGradleAndroidProjectを使ったアプローチを取ることになりました。

最後に

今回はiOS/Androidの各プラットフォーム向けビルドについて、C#を使ったビルド方法について紹介しました。

C#やUnityが用意しているInterfeceを使うことで、ある程度自由にビルドパラメータの指定からビルド実行まで行うことができます。

ビルド時にShellやPythonスクリプトと併用するケースもありますが、Unityでは各プラットフォームの細かい対応までC#スクリプトで記載ができます。