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 クラスのコルーチンとして動作している handleTimeOfDaym メソッドで起こったことを示しています。それがスクリプトコードであれば、通常、正確な行番号を通知します (例えば、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 のメモリが少なくなって、アプリケーションにいくつかのメモリを解放するを求めていることを示しています。一般的に、メールのようなバックグラウンドプロセスは、いくつかのメモリを解放し、アプリケーションを実行し続けることができます。ただし、アプリケーションがメモリを引き続き使用、またはそれ以上を求めた場合、iOS は最終的にアプリケーション達とそれらのいずれかを終了させる可能性があります。Apple はメモリ使用量がいくらあれば安全であるかを明文化していませんが、経験からデバイスの全 RAM50 %未満(第 2 世代 iPad ではおよそ 200 〜 256MB )をアプリケーションが使用している場合、メモリ使用量について大きな問題ありません。 注意するべき指標は、アプリケーションがどのくらいの RAM 使用するかです。あなたのアプリケーションのメモリ使用サイズは 3 つの主要なコンポーネントで構成されています。
注意: ビルトインプロファイラーでのパフォーマンス測定は、.NET スクリプトによって割り当てられたヒープのみを表示します。総メモリ使用量は、上記のように Xcode Instruments を介して決定できます。この図では、アプリケーションバイナリの一部、いくつかの標準フレームワークバッファ、Unity エンジン内部ステートバッファ、.NET ランタイムヒープ(内部プロファイラーによって表示される番号)、GLES ドライバヒープ、いくつかの他のさまざまなものを含みます。
他のツールはアプリケーションによって行われた割り当てをすべて表示し、ネイティブヒープとマネージドヒープの両方の統計を含みます。(アプリケーションの現在の状態を取得するために、 Created and still living ボックスを忘れずにオンにしてください)。重要な統計は、 Net bytes 値です。
メモリ使用を抑えるために:
空きメモリの量についての OS に照会することは、アプリケーションのパフォーマンスを評価するためのよいアイデアのように思えるかもしれません。OS はダイナミックバッファとキャッシュを多く使用してるので、空きメモリの統計は信頼できない可能性が高いです。唯一信頼できるアプローチは、アプリケーションのメモリ消費量を追跡し、これを主な指標として使うことです。特に新しいレベルを読み込んだとき、上記のツールでどのようにグラフが時間の経過とともに変化するかに注意を払います。
これにはいくつかの理由が考えられます。より多く詳細を確認するために、デバイスのログをチェックする必要があります。Mac にデバイスを接続し、Xcode を起動してメニューから Window > Organizer を選択します。Organizer の左ツールバーで使っているデバイスを選択し、“Console” タブをクリックして慎重に最新のメッセージを確認します。さらにクラッシュレポートを調べる必要があるかもしれません。クラッシュレポートを取得する方法は以下にあります。 http://developer.apple.com/iphone/library/technotes/tn2008/tn2151.html
貧弱なドキュメントには iOS アプリケーションの最初のフレームのレンダリングと入力処理に対しての時間制限があります。アプリケーションがこの制限を超えると SpringBoard に強制終了されます。これは、例えばアプリケーションの最初のシーンが大きすぎると起こるかもしれません。この問題を避けるには、スプラッシュスクリーンを表示するだけの小さな初期のシーンを作成し、 yield で 1 、 2 フレーム待ってから、実際のシーンを読み込むことをお勧めします。これは以下のような簡単なコードで実行できます。
function Start() {
yield;
Application.LoadLevel("Test");
}
今のところ、 Type.GetProperty() と Type.GetValue() は、 .NET 2.0 Subset プロファイルでのみサポートされています。プレイヤー設定 での .NET API の互換性レベルを選択することができます。
注意: Type.GetProperty() と Type.GetValue() は、マネージコードストリッピングと互換性がない可能性があり、除外する必要があるかもしれません(これを達成するためにストリッピング処理中にカスタムの非ストリッピングタイプのリストを提供して除外することができます)。より詳しいことは ビルドした iOS プレイヤーのサイズ最適化を参照してください。
iOS 用の Mono.NET 実装は、AOT (ネイティブコードへ ahead of time compilation:事前コンパイル)に基づいており、制約があります。それは明示的に他のコードで使用されているジェネリック型のメソッド(バリュータイプがジェネリックパラメーターとして使用される場合で)のみをコンパイルします。このような方法が reflection または、ネイティブコード(すなわち、シリアライゼーションシステム)だけを経由して使われているとき AOT コンパイル中にスキップされます。AOT コンパイラは、スクリプトコードのどこかにダミーメソッドを追加することで、コードを include すると指示できます。これで足りないメソッドを参照できるので、それらを事前にコンパイルできます。
void _unusedMethod() {
var tmp = new SomeType<SomeValueType>();
}
注意: バリュータイプは基本的な enums と structs です。
.NET の暗号化サービスは、reflection に大きく依存し、これは静的コード分析を伴うため、マネージコードは、ストリッピングと互換性がありません。クラッシュが、ストリッピングプロセスから >System.Security.Crypography 名前空間の全体を取り除くことで、簡単に解決されることもあります。
ストリッピングプロセスは、Unity プロジェクトの Assets フォルダーにカスタム link.xml ファイルを追加することでカスタマイズすることができます。これでストリッピングから取り除く型と名前空間を指定します。詳細は ビルドした iOS プレイヤーのサイズ最適化を参照してください。
<linker>
<assembly fullname="mscorlib">
<namespace fullname="System.Security.Cryptography" preserve="all"/>
</assembly>
</linker>
上記のアドバイスを検討するか、スクリプトコード、特定のクラスに、参照を追加することでこの問題を回避できるかもしれません。
object obj = new MD5CryptoServiceProvider();
通常、再帰的なジェネリックを多く使用する場合に、このエラーは発生します。より多くの type0、type1、type2 トランポリンを割り当てるよう AOT コンパイラへ指示できます。追加の AOT コンパイラのコマンドラインオプションは プレイヤー設定の “Other Settings” セクションで指定できます。type 1 トランポリンの場合、 nrgctx-trampolines=ABCD を指定、つまり ABCD が必要な新しいトランポリンの数(すなわち 4096 )を指定します。type2 トランポリンは nimt-trampolines=ABCD を指定し、type0 トランポリンは ntrampolines=ABCD を指定します。
最新の Xcode リリースには PNG 圧縮と最適化ツールに変更を加えたものがあります。これらの変更は、スプラッシュスクリーン変更とき Unity の iOS 実行時チェックで誤った判定をする場合があります。このような問題が発生した場合は、Unity を最新の公開された利用可能なバージョンにアップグレードしてみてください。それで解決しない場合は、以下の回避策を試してください。
Unity からビルドするとき(それを追加するのではなく) Xcode プロジェクトをスクラッチから作ってビルドします。
すでにデバイスにインストールされているプロジェクトを削除します。
Xcode でプロジェクトをクリーンします。( Product -> Clean )
Xcode の Derived Data folder をクリアします。( Xcode -> Preferences -> Locations ) これでもまだ解決しない場合は Xcode で PNG の再圧縮を無効にしてみてください。
Xcode プロジェクトを開きます。
そこで“Unity-iPhone” プロジェクトを選択します。
そこで“Build Settings”タブを選択します。
“Compress PNG files”を見つけて、NO に設定します。
以前 armv6 サポートを受けて提出された既存のアプリケーションを更新するときには、そのようなメッセージが表示されることがあます。Unity4.x と Xcode の 4.5 は armv6 のプラットフォームをもうサポートしていません。提出問題を解決するために、Unity Player Settings の Target OS Version を 4.3 以上に設定します。
よくある間違いは、WWW のダウンロードが常に別のスレッドで起きていることを前提にしていることです。一部のプラットフォームでは正しいかもしれませんが、当たり前のこととするべきではありません。WWW の状態を追跡するための最良の方法は、yield ステートメントを使用するか、または Update メソッド でステータスをチェックするかのどちらかです。while ループはビジーになるため。使うのは no です。
UI のいくつかの操作は iOS がすぐにウィンドウを再描画させます(もっとも一般的な例は、メインの UIWindow へ UIViewController と UIView を追加する場合です)。スクリプトからネイティブ関数を呼び出した場合、それは Unity の PlayerLoop 内部で発生し、結果的に PlayerLoop で再帰的に呼び出されることになります。その場合、waitUntilDone を false に設定し、performSelectorOnMainThreadメソッドの使用を検討する必要があります。Unity の PlayerLoop の呼び出しの間に実行する処理のスケジュールを iOS に通知します。
アプリケーションはエディター上では実行が [OK] なのですが、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 アセンブリがビルドに含まれたことによって発生します。さらに、スクリプトのデバッグを有効にすると、事態を悪化させる場合があります。なぜなら、各関数にかなりの数の命令が追加されるため、容易に許容限度に達してしまうからです。
Payer Sttings で managed code stripping を有効にすると問題を解決する場合があります。大きな外部 .NET アセンブリが関与している場合は特にそうです。しかし、問題が続く場合の最善の解決策は、ユーザーのスクリプトコードを複数のアセンブリに分割することです。このためのもっとも簡単な方法は、 Plugins フォルダーにいくつかのコードを移動することです。この場所にあるコードは、別のアセンブリに配置されます。また、スクリプトのコンパイルにどのように影響するかは、特殊フォルダーとスクリプトコンパイル順について情報を確認してください。