ゴイサギ日記

東京でエンジニアとして頑張って何とか生きてます。。ゆる~く更新していきます

【Unity】Android13対応メモ

Unityで開発したAndroidアプリを Android11 → Android13 に上げた際の色々を備忘録的に書いておく

経緯

GooglePlay に公開した Androidアプリが「アプリの安定性」に関わるポリシー違反に該当するとのメールを受信、対象アプリの Firebase Crashlytics を確認したところ、Android12以上端末で起動するとクラッシュする

以下はクラッシュ時のログ

Fatal Exception: java.lang.IllegalArgumentException
com.sample.app: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles. 

対策

対象アプリを記事作成時点(2023年11月)で対応必須となっている Android 13 に上げます。

Project Settings 更新

Minimum API Level は 21 を設定
Target API Level は 33 を設定

Custom Main Manifest にチェック
Custom Main Gradle Template にチェック
Custom Gradle Properties Template にチェック

Assets/Plugins/Android/AndroidManifest.xml に以下を追加

<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>

Assets/Plugins/Android/mainTemplate.gradle の dependencies に以下を追加

implementation 'androidx.work:work-runtime-ktx:2.7.0'

Assets/Plugins/Android/gradleTemplate.properties に以下を追加

android.useAndroidX=true
android.enableJetifier=true

その他

以下のエラーが出る場合、Assets/Firebase/Editorをこちらに置き換える

DllNotFoundException: Unable to load DLL 'FirebaseCppApp-6_15_2': The specified module could not be found.

【Unity】Object Pool の注意点

Object Poolとは?

オブジェクトを事前生成して使い回す手法です。
インスタンス生成によるCPU負荷(スパイク)を抑えるメリットがあります。

何度も使い回すオブジェクトに適用します。
(例:シューティングゲームのミサイル・レーザーなど)

Unity2021.3以降であればUnity標準のObjectPoolが使えます。
Unity - Scripting API: ObjectPool<T0>

注意点

Object Poolはポピュラーな最適化手法として話題になりますが幾つか注意点もあります。

初期化・終了の仕方

都度生成・破棄の場合、Unityにおける初期化はAwake(), Start(), 終了処理は OnDestroy() に記述しますが、ObjectPoolの場合、オブジェクトのアクティブ切り替えによる使い回しになるため 初期化は OnEnable(), 終了処理は OnDisable() に記述するなどの工夫が必要です。

UnityのOjectPoolを使う場合はコンストラクタで各種コールバックを指定できます。
Unity - Scripting API: Pool.ObjectPool_1.ObjectPool<T0>

メモリ使用量

オブジェクトを事前生成するため、都度生成・破棄と比較してメモリ使用量が増えます。

例えば、画面にミサイルが5個しか出ないのに、100個事前生成するなど必要以上に確保してメモリを圧迫する設計にしない注意が必要です。

プール作成のタイミング

特定フレームにまとめてオブジェクトを生成するため、そのタイミングでスパイク発生のリスクがあります。画面遷移や暗転時に生成を行うなどの工夫が必要です。

まとめ

Object Pool の注意点をまとめてみました。ゲーム仕様によっては上記以外の注意点も出てくると思うので上手く活用して快適に遊べるゲームを作れると良いですね ^_^

【Unity】Scene管理

自分なりのUnityのScene管理をまとめてみた。


あと本記事のSceneは.unityファイルを指します。

基本方針

想定環境

個人開発などの小規模プロジェクトでカジュアルゲーム想定、複雑な遷移は無し

Scene単位は大きく取る

自分の経験上、細かくすると仕様変更に耐えられないためです。

Multi Sceneで構成

ゲーム部分とシステム部分を分けたいので Multi Sceneで構成します。

Scene単位の機能

各Sceneの役割です。
(タイトル画面 > インゲーム画面へ遷移するゲームを例とする)

名前 役割
System シーン切り替え・入力・サウンド等ゲーム全体で使う常駐システム
Debug パラメータ調整・デバッグ機能ON/OFF・任意シーンへ直遷移等デバッグ関連
Title タイトル画面
InGame インゲーム画面

Debug版とRelease版の構成

開発(Debug版)と製品(Release版)で初回起動時の遷移を変えてます。

Debug版

System起動後、Debug画面へ


Debug画面です。ここから任意のSceneへ遷移します。

Release版

System起動後、Title画面へ

Multi Scene管理の注意点

ActiveSceneの変更を忘れずに

LoadSceneMode.Additive を使う場合は
SceneManager.SetActiveScene
アクティブScene を適切に切り替えます。

そうしないとGameObject生成時に親オブジェクト未指定ではデフォルトの配置がアクティブScene直下になるため前回Sceneに配置される問題が発生します。


Hierarchyビューで太字表示のものがアクティブScene

Release版から Debug Scene を除外

Release版でデバッグ機能が誤作動すると困るので
関連するSceneや処理は全て消します。

私はビルド時に以下のように除外してます。

BuildPlayerOptions options = new BuildPlayerOptions();
options.scenes = new string[] { "System", "Title", "InGame" }; // Debugを除外.
BuildReport report = BuildPipeline.BuildPlayer( options );

不要なSceneの削除

LoadSceneMode.AdditiveはSceneを破棄しないので注意が必要です。
SceneManager.UnloadSceneAsync を使い不要Sceneを削除します。

まとめ

今回はカジュアルゲーム向けでしたが
複雑なゲームではSceneを細かくを分けたり
Sceneを作業者単位で分けるといった工夫が ゲーム仕様によって必要になります。

参考

Unity - Scripting API: SceneManager

【Unity】Google Mobile Ads 更新後にiOSビルドに失敗する

Google Mobile Ads を v6.1.2 にアプデしたら
CocoaPods関連でエラーがあったので備忘録的にまとめておきます。

不具合

iOSビルド時に以下のエラーが出力されビルドが失敗する

iOS framework addition failed due to a CocoaPods installation failure. This will will likely result in an non-functional Xcode project.

After the failure, "pod repo update" was executed and succeeded. "pod install" was then attempted again, and still failed. This may be due to a broken CocoaPods installation. See: https://guides.cocoapods.org/using/troubleshooting.html for potential solutions.

pod install output:

Analyzing dependencies
[!] `Google-Mobile-Ads-SDK` requires CocoaPods version `>= 1.9.0`, which is not satisfied by your current version, `1.8.4`.

pod repo update output:

Updating spec repo `cocoapods`

原因

MacにインストールされているCocoaPodsのバージョンが古い

[!] `Google-Mobile-Ads-SDK` requires CocoaPods version `>= 1.9.0`, which is not satisfied by your current version, `1.8.4`.

解決方法

以下コマンドで CocoaPods を指定バージョンに更新

sudo gem install -v1.9.0 cocoapods -n /usr/local/bin

しかし、以下のエラーが発生

Building native extensions. This could take a while...
ERROR:  Error installing cocoapods:
    ERROR: Failed to build gem native extension.

    current directory: /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.4/ext/ffi_c
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby -I /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0 -r ./siteconf20211229-38719-1beaswp.rb extconf.rb
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/$(RUBY_BASE_NAME)
    --with-ffi_c-dir
    --without-ffi_c-dir
    --with-ffi_c-include
    --without-ffi_c-include=${ffi_c-dir}/include
    --with-ffi_c-lib
    --without-ffi_c-lib=${ffi_c-dir}/lib
    --enable-system-libffi
    --disable-system-libffi
    --with-libffi-config
    --without-libffi-config
    --with-pkg-config
    --without-pkg-config
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:467:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
You have to install development tools first.
    from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:546:in `block in try_link0'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/tmpdir.rb:93:in `mktmpdir'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:543:in `try_link0'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:570:in `try_link'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:672:in `try_ldflags'
    from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:1832:in `pkg_config'
    from extconf.rb:9:in `system_libffi_usable?'
    from extconf.rb:42:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /Library/Ruby/Gems/2.6.0/extensions/universal-darwin-19/2.6.0/ffi-1.15.4/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.4 for inspection.
Results logged to /Library/Ruby/Gems/2.6.0/extensions/universal-darwin-19/2.6.0/ffi-1.15.4/gem_make.out

rubyバージョンが古いのが原因、
こちらを参考に最新版3.1.0(2021/12/29時点)をインストール
(rubyのバージョンが切り替わらない場合はこちらを参考)

先程のコマンドを再実行、無事、CocoaPods のアプデに成功

と思ったら、iOSビルドで別のエラー発生、、

iOS framework addition failed due to a CocoaPods installation failure. This will will likely result in an non-functional Xcode project.

After the failure, "pod repo update" was executed and succeeded. "pod install" was then attempted again, and still failed. This may be due to a broken CocoaPods installation. See: https://guides.cocoapods.org/using/troubleshooting.html for potential solutions.

pod install output:

Analyzing dependencies
[!] `GoogleAppMeasurement` requires CocoaPods version `>= 1.10.2`, which is not satisfied by your current version, `1.9.0`.

CocoaPods を 1.10.2 以上にする必要あり

[!] `GoogleAppMeasurement` requires CocoaPods version `>= 1.10.2`, which is not satisfied by your current version, `1.9.0`.

バージョンを書き換えて再実行

今度は無事にiOSビルドが通りました! いやはや、とても疲れました・・^_^ ;

参考

MacでRubyをインストールする方法をわかりやすく解説【初心者必見】 | 侍エンジニアブログ
Rubyのバージョンが切り替わらない時の対処法! #Ruby - Qiita

【Unity】UI開発 おまけ

前回の記事に入り切らなかった最適化の内容です。

Image

Raycast Target

タッチ制御なしの場合はチェックを外す、無駄な計算を省けます。

Cull Transparent Mesh

チェックすると、α値が0以下の時に頂点データを転送しません。

Animator

Animatorは常にCanvasを更新するため、一時的なUI演出などに使うと良いです。 こちらのUnity公式ページも言及しています。

Shader

前回の最適化でも少し触れましたが、左右や上下に揺れる周期性のある動きはシェーダで表現するのも手です。GPU処理のためCanvasの更新も走らないです。

こういう表現とか面白いですよね。 baba-s.hatenablog.com

【Unity】UI開発 Part 4 最適化編

UI開発シリーズ、最後は最適化です。CanvasのBatch処理、Overdrawを例に上げた後、Profilerによる確認方法をまとめてます。

Canvas

全てのUIはCanvas配下になります。何も考えず大量にUIを配置するとパフォーマンスを低下させます。

Batch処理

最も効果的なのはBatch処理です。これはザックリ言うとCPUがGPUに描画の命令を出す時に、必要な情報(Material, Texture)をまとめる事で命令数を減らしCPUの負荷を軽減するというものです。

という訳で、以下にBatch処理を有効にする条件・注意点をまとめました。

  1. 同じTextureを参照
    Batch処理を有効にするために、個別TextureをAtlas化して一つのTextureにまとめてにます。
    kan-kikuchi.hatenablog.com

  2. 同じMaterialを参照
    Textureと同様にまとめれるものは同じMaterialを参照します。 UIのレイヤー表示に注意
    MaterialA, MaterialB を参照するImageを下図のように交互に重ねて表示する場合、Batch処理が効かないです。

    この場合はMaterialをまとめるか、重ねない表示にするかです。

  3. 同じCanvasに配置
    uGUIのBatch処理はCanvas単位で実行されます。前述でまとめたTexture,Materialが他のCanvasを跨がないよう調整します。
    Canvas単位の表示制御に注意
    UIをCanvas単位で表示・非表示する時はCanvasを無効にしてください。Canvas を無効化すると頂点データを維持するため、次回有効時に頂点バッファの更新せずにすむためです。

    Canvasの親子関係に注意
    ただし、Canvasに親子関係を設定している場合、子Canvasの無効化時に親Canvasは更新されてしまいます。

  4. 動かさない
    各UIを更新すると都度Batch処理が実行されます。これは中々に重たい処理でUI生成時など限定的なタイミングならまだ良いですが毎フレーム走る場合は問題になります。

    Canvas単位でBatch処理を実行するため、動かしていない他UIも含めて影響します。

    Canvasを分割する
    とはいえ毎フレーム更新するUIは出てきます。対処法の1つは更新用Canvasに分割します。
    こうすれば更新用CanvasのみBatch処理が走り、影響範囲を最小限に抑えれます。

    ただし、Canvasを分割し過ぎると今度はBatch処理の意味がなくなるので、負荷を計測して分割数を決めていきます。
    Shaderで動かす
    もう1つの対処法は周期性(左右・上下振動)のある表現をシェーダで実装します。GPU側で処理するためCanvasのBatch処理は走らないです。
    baba-s.hatenablog.com

Overdraw

半透明のUIを大量に重ねると余計な描画処理が発生するため、なるべく半透明UIを重ねないよう気をつける必要があります。Overdrawは下図のようにSceneビューで確認できます。明るい所ほどポリゴンが重なっており負荷が高いです。

Profiler

最後にUnityのProfilerを使い、ここまでの最適化の効果を確認する方法です。
Window > Analysis > Profiler で開きます。

CPU Usage

Batch処理の発生回数・負荷を確認できます。下図の検索フォームに "Canvas.BuildBatch" と入力すると確認できます。

UI/UI Details

Batch処理の実行回数と対象オブジェクトを確認できます。

Batch処理失敗の原因をBatch Break Reasonで確認できます。

UISystemPreviewWindow

UI/UI Details の右下に表示されるウインドウです。Batch範囲とOverdrawを確認できます。

composite overdraw を選択すると対象UIの overdraw を確認できます。

重なりに応じた色分けをします。

まとめ

Canvasを中心に最適化方法をまとめました。Batch処理・OverdrawはUIに限らず3Dでも注意すべき内容です。UnityのProfilerはすぐに起動できて詳細情報も確認しやすく優秀です。

これらを活用して普段からパフォーマンスを意識して開発をしたいですね。終盤でバタバタしながら対応するのは大変ですし・・^_^;

参考

https://unity3d.com/jp/how-to/unity-ui-optimization-tips
Unityでパフォーマンスの良いUIを作る為のTips | PPT

【Unity】UI開発 Part 3 Tween編

今回もUIです。UIアニメーションでよく使うTween関連です。

Tween

シンプルな動きはTween制御系Assetが便利です。有名な DOTween, LeanTween, iTweenの特徴とサンプルコードを貼ってます。

また、パフォーマンス比較はDOTween公式サイトにまとまってます。

ちなみに私はパフォーマンス重視なのでLeanTweenを使ってます。また、ゲーム仕様に依存する部分はこちらこちらを参考に自前のTween制御を書いてます。

DOTween

無料版はパッケージにコードは含まれないです。(GitHubにアップはされている)有料版はTextMeshPro対応とEditor上でTweenを編集できます。

// !!初回起動時に1回だけ呼ぶ必要あり.
DOTween.Init();

// 実行. 2秒で targetPos へ Linear補間で移動後、OnCompleteに通知.
Tweener twr = this.transform.DOMove( targetPos, 2.0f );
twr.SetEase( Ease.Linear );
twr.onComplete = OnComplete;

// 中断.
twr.Kill( complete:true );

// 一時停止.
twr.Pause();

// 再開.
twr.Play();

LeanTween

パフォーマンス重視のTweenです。コードは全て含まれてるのでカスタムしやすいです。

// !!初回起動時に1回だけ呼ぶ必要あり.
LeanTween.init();

// 実行. 2秒で targetPos へ Linear補間で移動後、OnCompleteに通知.
LTDescr dsc = this.transform.LeanMove( targetPos, 2.0f );
dsc.setEase( LeanTweenType.linear );
dsc.setOnComplete( OnComplete );

// 中断.
LeanTween.cancel( dsc.uniqueId, callOnComplete:true );

// 一時停止.
dsc.pause();

// 再開.
dsc.resume();

iTween

iTween.cs だけのシンプルな作りです。

// 実行. 2秒で targetPos へ Linear補間で移動後、OnCompleteに通知.
var hash = iTween.Hash( "position", targetPos, 
                        "time", 2.0f, 
                        "easetype", iTween.EaseType.linear,
                        "oncomplete", "OnComplete" );
iTween.MoveTo( this.gameObject, hash );

// 中断.
iTween.Stop( this.gameObject );

// 一時停止.
iTween.Pause( this.gameObject, includechildren:true );

// 再開.
iTween.Resume( this.gameObject );

Time.timeScaleの影響

上記3つのTweenの時間制御は Time.deltaTime を参照しています。こちらの値は Time.timeScale の影響を受けます。そして、これは演出系のUIアニメーションで問題になる場合があります。

例えば以下のような格闘ゲームのKO演出です。キャラ・背景は一時停止していますが、"KO"のUIはアニメーションしてます。

ゲーム全体のアニメーションを停止であればTime.timeScaleの値を変更すれば良いです。

しかし、このケースではUIアニメーションは影響を受けない設定が必要です。

以下に各TweenのTime.timeScaleの影響を受けない設定をするサンプルコードです。

DOTween

twr.SetUpdate( UpdateType.Normal, isIndependentUpdate:true );

LeanTween

dsc.setIgnoreTimeScale( useUnScaledTime:true );

iTween

// "ignoretimescale" に true を設定する.
var hash = iTween.Hash( "position", targetPos, 
                        "time", 2.0f, 
                        "easetype", iTween.EaseType.linear,
                        "oncomplete", "OnComplete",
                        "ignoretimescale", true );  // ←ここ.

おまけ

Tweenとは別ですがParticleSystemで作られたUIエフェクトは Delta Time を Unscaled にすると TimeScale の影響を受けません。

まとめ

Tween制御系Assetをまとめてみました。簡単なアニメーションはTweenはとても便利です。 より複雑なアニメーションは Animator, AnimationClip を使う感じですかね。