テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】このメソッドを呼び出した犯人を見つけ出す方法。スタックトレースについて

ゲームを作成するにあたり、このメソッドはどのメソッドから呼ばれているのか知りたくなるケースがあります。

そういった場合のアプローチについて紹介します。

目次

メソッドの参照元をたどる

メソッドを呼び出すには、何らかの方法でメソッドを呼び出すコードが必ず必要になります。

このメソッドを呼び出すコードを探す方法ですが、Unityプロジェクト内部で完結するのであればFind Referencesを使うのが手っ取り早いといえば手っ取り早いです。

Monodevelopでクラスやメソッド、もしくはフィールドを選択中に[右クリック] -> [Find References...]を選択することで、該当の要素を参照しているコードを一覧として表示してくれます。

f:id:tsubaki_t1:20151130000621j:plain

f:id:tsubaki_t1:20151130000631j:plain

勿論この方法ではメソッドを呼ぶ可能性があるクラス・メソッドを見つける事までとなりますが、割とこれでもなんとかなります。

VS Code

この機能において優秀なのがVS Codeです。

VS Codeは該当のメソッドやクラスへの参照数を常に上に表示する機能を持っており、しかも参照先のコードのプレビューも持っています。

f:id:tsubaki_t1:20151130001245j:plain

f:id:tsubaki_t1:20151130001150g:plain

文字列による参照

面倒なのが、文字列等のメタデータによる参照です。

これらはコードのみではテキストベースで検索するか、もしくはスタックトレースを利用し逆算する必要があります。

例えばOnRecieveというメソッドをSendMessageで呼び出していた場合、Monodevelop[Search] -> [Find in Files...]でプロジェクト内の検索が行えます。

f:id:tsubaki_t1:20151130002601j:plain

f:id:tsubaki_t1:20151130003020j:plain

当然、実行時に結合した文字列や、メタデータ(シナリオデータ等)から取得したデータを利用したメソッド呼び出しに対しては無力です。

スタックトレース

スタックトレースは、メソッドが度の順番で呼び出されたかを確認する事が出来る機能です。これは、メソッドがどのように呼ばれたのかを知る強いヒントになります。

上のFind ReferencesやFind in Filesで検索した際に2~3か所からメソッドが呼ばれていた場合、どちらのメソッドから呼ばれていたかを知る手っ取り早い方法でもあります。

また、「文字列による参照」が行われた場合でも参照元を特定する超パワーを持っているので実際超強いです。

Consoleの下のアレ

Unityのエラーログを確認できる事でお馴染みのConsoleですが、下の方にはスタックトレースが表示されています。

下のログを読み解くならば、

「SenderクラスのStartメソッドがSendMessageを使用してRecieveのOnRecieveメソッドを呼び出して、その中でrecieveというログをDebug.Logを使用して表示した」

といった内容になります。

f:id:tsubaki_t1:20151130013210j:plain

また、呼び出したソースコードや行もスタックトレースに表示されています。

例えばSender.Start()はSender.csの11行目、受信したOnRecieveメソッドはReciever.csメソッドの11行目という事が分かります。

もっと詳細なスタックトレース

Unity 5.2で、Stack Trace LoggingのFullが追加されました。
こいつを設定すると、Windowsで動かしたときはWinMainから始まるような非常に細かいログを表示してくれるようになります。

f:id:tsubaki_t1:20151130013644j:plain

設定方法は、Console右上の右上にある「三」をクリックし、[Stack Trace Logging] > [Full]を選択します。

次に再生したログが物凄く細かい感じで表示されるようになります。戻したければStack Trace Logging > ScriptOnlyへ。

f:id:tsubaki_t1:20151130013634j:plain

ちなみにメインスレッド以外が出力したDebug.LogのスタックトレースもFullなら少し見れます。他だと見れません。

同一ログをまとめる機能とStack Trace

 ログをまとめるCollapseという機能があります。これは同一のログ表示をまとめ、呼ばれた回数を右に表示してくれる機能なのですが、例えば下のように同一のログであってもまとめて表示してくれない事があります。

これは単純にスタックトレースの内容が異なるからです。

f:id:tsubaki_t1:20151130015316j:plain

例えば、上の二つはStartから呼び出しているのですが、実は下のrecieveはButtonのイベントから呼び出しています。
同一のAPIを読んでいる場合でも、スタックトレースが異なると異なる種類のログとしてまとめられる事になります。

f:id:tsubaki_t1:20151130015739j:plain

f:id:tsubaki_t1:20151130015745j:plain

ログを「同一の内容なら同じ物としたい」場合、Stack Trace LogginをNoneに設定します。スタックトレースが見れなくなりますが、同じ物として扱われるようになります。

f:id:tsubaki_t1:20151130020127j:plain

ちなみに、残念な事に「呼び出し元のコードは何か」と「ログを出力したオブジェクトは誰か」はスタックトレースとDebug.Logの第二引数で分かるのですが、「呼び出し元のオブジェクトは誰か」はConsoleから見つけることは出来ません。

Unityエディタでスタックトレースを追える奴

こんなのがあります。

http://u3d.as/3dG

Unityエディタ単体でスタックトレースの記述してるソースコードへジャンプできるアセットです。

紹介はまた今度。

Monodevelopスクリプトデバッガ

自分が良くやるのが、デバッガでスクリプトを停止させてスタックトレースを見るアプローチです。大抵の場合、この方法が最も良いアプローチのように思います。

f:id:tsubaki_t1:20151130010226j:plain

問題点は、一々挙動を停止させるので何度も呼ばれる部分には向いていないという事くらいでしょうか。
その場合はブレークポイントに条件を設定する等を行うのが良さそうです。

以前はデバッガ繋ぐとUnityエディタが落ちる事がありましたが、最近そういえば起きた記憶が無いです。

レッツ Stack Trace

やり方はMonodevelopの[Run] > [Attatch to Process...]で該当のプラットフォーム(UnityエディタやAndroid等)へ接続すればOKなのですが、最近はDebugの横のプラットフォームを指定して「再生ボタンを押す」でプロセス接続してます。

f:id:tsubaki_t1:20151130010708j:plain

スタックトレースは、Call Stackウィンドウに表示され、このウィンドウ内のメソッドをダブルクリックしていくと、呼び出したコードを追うことが出来ます。

f:id:tsubaki_t1:20151130011241g:plain

ブレークポイントの設定や条件設定についてはこちら。

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

VS Codeスタックトレース

ちなみにVSCodeも出来ます。やり方はスクリプトデバッグ方法も含めてまた明日。

f:id:tsubaki_t1:20151130055152j:plain

スクリプトからスタックトレースをエクスポートする

この記事が参考になります。

takashicompany.hatenablog.com

 メソッドを呼び出した奴は何処のどいつなのか

ではメソッドについては分かったので「誰が」オブジェクトを呼び出したか…という処に焦点を当てたい処ですが、残念ながらスタックトレースから「どのオブジェクトが持ってるコンポーネントから呼び出されたのか」を知る事は出来ません。
片手落ちも良い所ですが、スタックトレースから知れないだけで、回避策はあります。

 オブジェクト名がユニークな場合

まず、単純にオブジェクトの名前が一意であるならば、下の手順で知る事が出来ます。

  1. スクリプトデバッガでスタックトレースを取得し、Call Stackで呼び出し元をダブルクリックし、呼び出し元を選択
    f:id:tsubaki_t1:20151130044258j:plain
  2. WatchgameObject.nameと入力。コンポーネントを持つオブジェクト名が表示されます
    f:id:tsubaki_t1:20151130044334j:plain
  3. Valueに表示された名前でHierarchyを検索
    f:id:tsubaki_t1:20151130045954j:plain

 同一名オブジェクトが複数ある場合

次に同一名のオブジェクトが複数ある場合です。この場合、非常に面倒な手順が必要になります。
また、以下のコードが必要です。

gist.github.com

  1. 上のコードをプロジェクトのAssets/Editorフォルダ以下へ配置する
    f:id:tsubaki_t1:20151130050252j:plain
  2. メニューバーのWindow/Pingでウィンドウを開く(スクリプトデバッガ復帰時にゲームを一時停止したい場合)

    f:id:tsubaki_t1:20151130051414j:plain

  3. スクリプトデバッガでスタックトレースを取得し、Call Stackで呼び出し元をダブルクリックし、呼び出し元を選択(ここは上の手順と同一)

    f:id:tsubaki_t1:20151130044258j:plain

  4.   ImmediateウィンドウでgameObject.GetInstanceID();と入力し、インスタンスIDを入手する。

    f:id:tsubaki_t1:20151130051641j:plain

  5. ImmediateウィンドウにPingTargetWindow.Stop();と入力し、エディタを一時停止する(スクリプトデバッガ復帰時にゲームを一時停止したい場合)

    f:id:tsubaki_t1:20151130051837j:plain

  6.  スクリプトデバッグを終了し、エディタに戻る

  7. 先ほど開いたPINGウィンドウにインスタンスID(今回は-1764)を入力する

    f:id:tsubaki_t1:20151130051935j:plain

これでメソッドを呼び出したオブジェクトが誰なのかが分かります。一応SendMessageのような文字列呼び出しからでも呼び出し元が分かります。でもAnimatorのAnimationEventは勘弁な。

uGUIの場合

ちなみに、uGUIのEventSystemのような「コマンドだけ発行して処理は特定のオブジェクトに移譲するタイプ」はこういった形で探すのは若干異なります。
uGUIの場合はコマンド発行者を残してくれているので、それを使用します。

  1. スタックトレースでイベントを受け取るインターフェース(今回はOnPointerClick)を選択
    f:id:tsubaki_t1:20151130053437j:plain
  2. LocalsウィンドウのeventData/base/selectedObject/base/nameでオブジェクトが分かります。面倒…ではありますが、スクリプトを全部読むのと比べてどの程度かと言われると、意外とどっこいだったりします。
    f:id:tsubaki_t1:20151130053702j:plain

関連

takashicompany.hatenablog.com

tsubakit1.hateblo.jp