iOS では、ゲームが Unity エディター上では完璧に動作するのに、その後、実際のデバイス上で動作しない、起動しないなどの状況が発生することがあります。問題は、多くの場合、コードやコンテンツの品質に関連してます。このセクションでは、もっとも一般的なシナリオについて説明します。
これが起こる理由はいくつかあり、よくある原因は次のとおりです。
このメッセージは通常、アプリケーションが NullReferenceException を受信したときに iOS デバイスに表示されます。障害が発生した場所を特定するには 2 つの方法があります。
Unity には NullReferenceException のソフトウェアベースの処理が備わっています。AOT コンパイラーはメソッドや変数がオブジェクト上でアクセスされるたびに、null 参照のクイックチェックを行います。この機能はスクリプトのパフォーマンスに影響します。そのため、開発ビルドでのみ使用可能です (Build Settings ダイアログの script debugging オプションを有効にします)。すべてが正しく行われ、実際に障害が .NET コードで発生している場合は、EXC_BAD_ACCESS は表示されなくなります。代わりに、.NET 例外テキストが Xcode コンソールに表示されます (そうでない場合は、コードは単に catch 文でそれを処理します)。典型的な出力は以下のとおりです。
Unhandled Exception: System.NullReferenceException: A null value was found where an object instance was required.
at DayController+$handleTimeOfDay$121+$.MoveNext () [0x0035a] in DayController.js:122
これは、障害が DayController クラスのコルーチンとして動作している handleTimeOfDay メソッドで起こったことを示しています。それがスクリプトコードであれば、通常、正確な行番号を通知します (例えば、“DayController.js:122”)。問題のある行は、以下のようなものが考えられます。
Instantiate(_imgwww.assetBundle.mainAsset);
これは例えば、スクリプトが最初にそれが正しくダウンロードされたことを確認せずにアセットバンドルにアクセスした場合に発生する可能性があります。
ネイティブスタックトレースは、障害調査のためのより強力なツールですが、使用する際、いくつかの専門知識を必要とします。さらに一般的にネイティブエラー(ハードウェアメモリアクセス)が起きた後には続行できません。ネイティブスタックトレースを取得するには、Xcode のデバッガーコンソールに bt all と入力します。出力されたスタックトレースを慎重に調べると、エラーが発生した場所についてのヒントを含んでいることがあります。例えば、次のように表示されます。
...
Thread 1 (thread 11523):
1. 0 0x006267d0 in m_OptionsMenu_Start ()
1. 1 0x002e4160 in wrapper_runtime_invoke_object_runtime_invoke_void__this___object_intptr_intptr_intptr ()
1. 2 0x00a1dd64 in mono_jit_runtime_invoke (method=0x18b63bc, obj=0x5d10cb0, params=0x0, exc=0x2fffdd34) at /Users/mantasp/work/unity/unity-mono/External/Mono/mono/mono/mini/mini.c:4487
1. 3 0x0088481c in MonoBehaviour::InvokeMethodOrCoroutineChecked ()
...
最初に、メインスレッドである Thread 1 のスタックトレースを見つける必要があります。スタックトレースの最初の行は、エラーが発生した場所を指しています。この例では、トレースは NullReferenceException が OptionsMenu スクリプトの Start メソッド内で起こったことを示しています。このメソッドの実装を注意深く見ると、問題の原因が明らかになります。一般的に、NullReferenceExceptions は、初期化の順序について間違った前提が行われる際に Start メソッドの内部で起きます。 部分的なスタックトレースだけがデバッガーコンソールに表示されるケースもあります。
Thread 1 (thread 11523):
1. 0 0x0062564c in start ()
これはネイティブのシンボルがアプリケーションのリリースビルドの際に取り除かれたことを示しています。フルスタックトレースは、以下の手順で得ることができます。
通常、外部ライブラリは、ARM Thumb 命令セットでコンパイルされたときに生成されます。現在、このようなライブラリは Unity と互換性がありません。Thumb 命令なしでライブラリを再コンパイルすることで、簡単に問題を解決することができます。Xcode プロジェクトのライブラリからは以下の手順でこれを行います。
ライブラリのソースが利用できない場合は、Thumb コードではないライブラリのバージョンをサプライヤーに依頼する必要があります。
場合によっては、Program received signal: “0” のようなメッセージが表示されることもあります。 この警告メッセージは多くの場合は致命的ではなく、iOS のメモリが不足していることを示しており、メモリを一部解放するようアプリケーションに要求していることを示しています。通常、メールのようなバックグラウンドプロセスはメモリの一部を解放し、アプリケーションは引き続き実行できます。ただし、アプリケーションが引き続きメモリを使用したり、それ以上のメモリを要求する場合、OS は最終的にアプリケーションを強制終了し、その中に作業中のものが含まれてしまう場合もあります。Apple はどのメモリの使用が安全であるかを明文化していませんが、経験から、デバイスの全 RAM の 50% 未満をアプリケーションが使用している場合、メモリ使用量の問題はほとんどありません。 主に参考にする基準は、アプリケーションが使用する RAM の量です。アプリケーションのメモリ使用量は、以下の 3 つの主要コンポーネントから成ります。
注意: ビルトインプロファイラーでのパフォーマンス測定は、.NET スクリプトによって割り当てられたヒープのみを表示します。総メモリ使用量は、上記のように Xcode Instruments を介して決定できます。この図では、アプリケーションバイナリの一部、いくつかの標準フレームワークバッファ、Unity エンジン内部ステートバッファ、.NET ランタイムヒープ(内部プロファイラーによって表示される番号)、GLES ドライバヒープ、いくつかの他のさまざまなものを含みます。
もう 1 つのツールは、アプリケーションによって行われたすべての割り当てを表示し、ネイティブヒープとマネージヒープ統計の両方を含んでいます。重要な統計情報は Net bytes 値です。
メモリ使用を抑える方法は以下の通りです。
空きメモリの量について OS に照会することは、アプリケーションのパフォーマンスを評価するためのよいアイデアのように思えるかもしれません。ただし、OS はダイナミックバッファとキャッシュを多く使用してるので、空きメモリの統計は信頼できない傾向があります。唯一、信頼できるアプローチは、アプリケーションのメモリ消費量を追跡し、これを主な指標として使うことです。特に新しいレベルを読み込んだとき、上記のツールでどのようにグラフが時間の経過とともに変化するかに注意を払います。
これにはいくつかの理由が考えられます。より多く詳細を確認するために、デバイスのログをチェックする必要があります。Mac にデバイスを接続し、Xcode を起動してメニューから Window > Devices and Simulators を選択します。ウィンドウの左ツールバーで使っているデバイスを選択し、Show the device console ボタンをクリックして慎重に最新のメッセージを確認します。さらにクラッシュレポートを検証する必要があるかもしれません。クラッシュレポートを取得するには、以下を参照してください。http://developer.apple.com/iphone/library/technotes/tn2008/tn2151.html
iOS アプリケーションが最初のフレームのレンダリングと入力処理を行うために、報告が不十分な時間制限があります。アプリケーションがこの制限を超えると SpringBoard に強制終了されます。例えば、アプリケーションの最初のシーンが大きすぎると発生する場合があります。この問題を避けるには、スプラッシュスクリーンを表示するだけの小さな初期のシーンを作成し、 yield で 1 、 2 フレーム待ってから、実際のシーンをロードすることをお勧めします。これは以下のような簡単なコードで実行できます。
IEnumerator Start() {
yield return new WaitForEndOfFrame();
// UnityEngine.SceneManagement ディレクティブの使用をわすれないでください
SceneManager.LoadScene("Test");
}
現在、Type.GetProperty() と Type.GetValue() は .NET 2.0 Subset プロファイルでのみサポートされています。.NET API の互換性レベルは、Player 設定で選択できます。
注意: Type.GetProperty() と Type.GetValue() は、マネージコードストリッピングと互換性がない可能性があり、除外する必要があるかもしれません(これを達成するためにストリッピング処理中にカスタムの非ストリッピングタイプのリストを提供して除外することができます)。より詳しいことは ビルドした iOS プレイヤーのサイズ最適化を参照してください。
iOS 用の Mono .NET の実装は、AOT (ネイティブコードへの事前コンパイル) 技術に基づいていますが、その制限があります。それは、他のコードによって明示的に使用されるジェネリック型のメソッド (ジェネリックパラメーターとして値型が使用される) のみをコンパイルします。このようなメソッドがリフレクションを通してのみ、または、ネイティブコード(すなわちシリアライゼーションシステム) からのみ使用される場合、それらは AOT コンパイル中にスキップされます。AOT コンパイラーは、スクリプトコードのどこかにダミーのメソッドを追加することで、コードを加えることが暗示されます。これは、欠けているメソッドを参照するので、それらを事前にコンパイルします。
void _unusedMethod() {
var tmp = new SomeType<SomeValueType>();
}
注意 値型は基本型、enums、structs です。
.NET の暗号化サービスはリフレクション に大きく依存しているため、マネージコードストリッピングと互換性がありません。なぜなら、これが静的コード分析を含むからです。クラッシュのもっとも簡単な解決法は、ストリッピングプロセスから System.Security.Cryptography 名前空間全体を取り除くことです。
ストリッピングプロセスは、カスタムの link.xml ファイルを Unity プロジェクトの Assets フォルダーに加えてカスタマイズできます。これは、どの型と名前空間をストリッピングから除外するかを指定します。詳細は iOS プレイヤーサイズの最適化ガイド を参照してください。
<linker>
<assembly fullname="mscorlib">
<namespace fullname="System.Security.Cryptography" preserve="all"/>
</assembly>
</linker>
上記のアドバイスを検討するか、特定のクラスへの追加の参照をスクリプトコードに加えることによって、この問題を回避してください。
object obj = new MD5CryptoServiceProvider();
通常、再帰的なジェネリックを多く使用する場合に、このエラーは発生します。より多くの type0、type1、type2 トランポリンを割り当てるよう AOT コンパイラへ指示できます。追加の AOT コンパイラーのコマンドラインオプションは Player 設定 の Other Settings セクションで指定できます。type 0 トランポリンの場合、 ntrampolines=ABCD
を指定、つまり ABCD は必須の新しいトランポリンの数 (すなわち 4096) を指定します。type 1 トランポリンは nrgctx-trampolines=ABCD
を指定し、type 2 トランポリンは nimt-trampolines=ABCD
を指定します。
最新の Xcode リリースでは、PNG 圧縮と最適化ツールに変更が加えられました。これらの変更は、スプラッシュスクリーンの変更に関する Unity iOS ランタイムチェックで誤検出される原因になる場合があります。このような問題が発生した場合は、Unity を一般公開されている最新のバージョンにアップグレードしてみてください。これで問題が解決しない場合は、以下の回避策を検討してください。
これでもまだ解決しない場合は Xcode の PNG の再圧縮を無効にしてみてください。
よくある間違いは、WWW のダウンロードが常に別のスレッドで起きていることを前提にしていることです。一部のプラットフォームでは正しいかもしれませんが、当然のこととするべきではありません。WWW の状態を追跡するための最良の方法は、yield ステートメントを使用するか、または Update メソッド でステータスを確認するかのどちらかです。ビジーになる while ループは使うべきではありません。
UI のいくつかの操作は iOS に即座にウィンドウを再描画させます (もっとも一般的な例は、メインの UIWindow へ UIViewController を伴う UIView を追加する場合)。スクリプトからネイティブ関数を呼び出す場合、それは Unity の PlayerLoop 内部で発生し、結果的に PlayerLoop が再帰的に呼び出されることになります。その場合、performSelectorOnMainThread で waitUntilDone を false に設定して使用する必要があります。これにより、Unity の PlayerLoop の呼び出しの間に実行する処理のスケジュールを iOS に通知します。
アプリケーションがエディターで正常に実行するにもかかわらず、iOS プロジェクトでエラーが発生する場合は、DLL (例えば、I18N.dll、I19N.West.dll) が見つからないことが原因かもしれません。このような場合、それらの DLL を Unity.app からプロジェクトの Assets\Plugins フォルダーにコピーしてみてください。Unity アプリケーション内の DLL の場所は以下の通りです。 Unity.app\Contents\Frameworks\Mono\lib\mono\unity 次に、プロジェクトのストリッピングレベルをチェックして、ビルドの最適化時に DLL のクラスが削除されていないことを確認する必要があります。iOS ストリッピングレベルの詳細は、iOS ストリッピングレベル を参照してください。
通常、このようなメッセージはマネージ関数デリゲートがネイティブ関数に渡されたときに受信されますが、アプリケーションのビルド時に必要なラッパーコードは生成されません。どのメソッドがデリゲートとしてネイティブコードに渡されるかを暗示して AOT コンパイラーを助けることができます。これを行うには MonoPInvokeCallbackAttribute カスタム属性を加えます。現在、静的メソッドのみがデリゲートとしてネイティブコードに渡されます。
コードサンプル
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;
using AOT;
public class NewBehaviourScript : MonoBehaviour {
[DllImport ("__Internal")]
private static extern void DoSomething (NoParamDelegate del1, StringParamDelegate del2);
delegate void NoParamDelegate ();
delegate void StringParamDelegate (string str);
[MonoPInvokeCallback(typeof(NoParamDelegate))]
public static void NoParamCallback() {
Debug.Log ("Hello from NoParamCallback");
}
[MonoPInvokeCallback(typeof(StringParamDelegate))]
public static void StringParamCallback(string str) {
Debug.Log(string.Format("Hello from StringParamCallback {0}", str));
}
// 初期化にこれを使用
void Start() {
DoSomething(NoParamCallback, StringParamCallback);
}
}
このエラーは通常、1 つのモジュールにあまりにも多くのコードがあることを意味します。一般的に、多くのスクリプトコードがあるか、大きな外部 .NET アセンブリをビルドに含めることによって発生します。また、スクリプトのデバッグを有効にすると、各関数にかなり多くの命令を追加し、制限に達しやすくなるため、状況を悪化させる可能性があります。
Player 設定でマネージコードのストリッピングを有効にすると、特に大きな外部 .NET アセンブリが含まれている場合に、この問題の解決に役立ちます。しかし、問題が解決しない場合は、ユーザースクリプトのコードを複数のアセンブリに分割することをお勧めします。これを行う最も簡単な方法は、コードの一部を Plugins フォルダーに移動することです。この場所のコードは別のアセンブリに置かれます。また、特別なフォルダー名 がスクリプトのコンパイルに与える影響に関する情報も確認してください。