株式会社ディー・エヌ・エー(以下DeNA)が公開しているオープンソースのUnity向けオートパイロットフレームワーク Anjin(あんじん)に関する記事・リソースのまとめです。随時更新予定。
統合テストの合否判定を Gemini API の画像解析で行なう Visual Verification Agent 実装サンプル
Googleの提供するマルチモーダルLLM Gemini を利用して、ゲームプレイの自動テストの合否判定を画像(スクリーンショット)ベースで行なう方法を紹介します。
これまでも、Anjin などのオートパイロットフレームワークを利用することで規定のシナリオに沿った自動テストは実現できていました。 しかし、テストの合否判定は「シナリオの最後まで画面遷移できたこと」をもって合格とすることが多く、操作完了時点のSceneの状態まで検証することは高コスト*1になるため避けてきました。
本記事では、操作完了など合否判定を行ないたいタイミングでスクリーンショットを撮影し、Gemini APIを使用して「画像になにが描かれていれば合格か」をプロンプトとして与えるだけで検証ができるAnjinのカスタムAgentを実装します*2。

利用イメージ
まず、どのように動作するかを紹介します。 動作するプロジェクトは、Unity社の提供する2Dシューターサンプル GalacticKittens をフォークしたリポジトリの judge-by-gemini-agent ブランチにあります。
事例1: マルチプレイが成立していることを検証する
GalacticKittensは4人までのマルチプレイが可能なゲームです。マルチプレイのテストシナリオでは、別プロセスで立ち上げたプレイヤーがJoinできていればインゲームで2機の戦闘機が表示されます。

これを、次のAgentで検証します。

Prompt には「スクリーンショットに何が写っていれば成功と判断できるか」を自然言語で書きます。ここでは「右向きの赤い戦闘機と青い戦闘機が表示されていること」としています。
Success Threshold には、Geminiが返すスコア(0.0〜1.0)がいくつ以上であればテスト成功とみなすかのしきい値を設定します。LLMの性質上、プロンプトの工夫だけでは偽陰性・偽陽性はゼロにはできません。そのテストシナリオの目的によって偽陰性・偽陽性どちらを許容するかは異なるため、このプロパティで調整します。
Anjinを起動し、インゲームが開始されて数秒経つと、Geminiから次のようなレスポンスが返されます。 スコアが0.8以上ですので、テストは成功と判断されます。
Response: { "comment": "画像には右向きの赤い戦闘機と青い戦闘機が表示されています。", "score": 1.0 }
もし別プレイヤーのJoinに失敗した場合、次のようなレスポンスが返ります。 スコアが0.8未満なので、テストは失敗と判断されます。
Response: { "comment": "画像には右向きの赤い戦闘機が表示されていますが、青い戦闘機は表示されていません。", "score": 0.5 }
事例2: リザルト画面の文字を判読する
GalacticKittensでは、ボスを倒したとき、もしくは全滅したときにリザルト画面が表示されます*3。

これを、次のAgentで検証します。

Prompt は「VICTORY!と表示されていること」としています。 これはボスを倒したときに画面に表示される文字で、テキストでなくスプライトです。
Geminiからは次のようなレスポンスが返ります。ちゃんと文字が読めています。
Response: { "comment": "画像には「DEFEAT」と表示されており、「VICTORY!」とは表示されていません。", "score": 0.0 }
実装方法
続いて実装方法を紹介します。 環境は Unity 6000.0.23f1、Anjin v1.9.0 です。
GemiNetのインストール
Gemini には公式 C# SDK は存在しないため、REST APIもしくはサードパーティのSDKを利用します。
今回は、GemiNet v1.0.3を使用しました。 APIデザインが公式SDKに準じているため、公式ドキュメントのサンプルコードを見ながら実装するのがとても楽でした。
NuGetパッケージですので、NuGetForUnityを使用してインストールします。 NuGetForUnityについては次の記事を参照してください。
インストールしたら、Agentを置くasmdefのAssembly Referencesに次のDLLを追加します。
- GemiNet.dll
- System.Text.Json.dll
続いて、Agentのコードを実装していきます。
Geminiクライアントの初期化
Geminiクライアントの初期化にはGeminiのAPIキーが必要です。APIキーはハードコードするのではなく、コマンドライン引数か環境変数から受け取るようにします。
Anjinに含まれるユーティリティクラス Argument<T> が便利なので使っています。
var apikey = new Argument<string>("GEMINI_API_KEY"); if (!apikey.IsCaptured()) { return; } using var ai = new GoogleGenAI(); ai.ApiKey = apikey.Value();
Unityエディターで実行するときは、Unity Hubでプロジェクト右端のドロップダウン > Add command line arguments クリックで表示されるダイアログでコマンドライン引数を設定できます。


スクリーンショットの撮影とアップロード
スクリーンショットを撮影し、GoogleGenAI.Files.UploadAsync メソッドでGeminiにアップロードします。
画像は、後で紹介する GoogleGenAI.Models.GenerateContentAsync メソッドに直接渡すこともできるのですが、20MB制限があるため、事前にアップロードします。
var texture = ScreenCapture.CaptureScreenshotAsTexture(); var bytes = texture.EncodeToPNG(); var base64 = Convert.ToBase64String(bytes); var file = await ai.Files.UploadAsync( new UploadFileRequest() { File = new UploadFileContent(new Blob() { Data = base64, MimeType = "image/png" }), MimeType = "image/png", }, cancellationToken: cancellationToken);
画像解析
次に、GoogleGenAI.Models.GenerateContentAsync メソッドに画像やプロンプトを渡してレスポンスを受け取ります。
var response = await ai.Models.GenerateContentAsync(new GenerateContentRequest { Model = Models.Gemini2_0Flash, Contents = Content.CreateUserContent( Part.FromUri(file.Uri, file.MimeType), Part.FromText(prompt)), SystemInstruction = "Analyze the image and determine whether it meets the user prompt's requirements. The response consists of a score (maximum = 1.0) and a corresponding comment in Japanese.", GenerationConfig = new GenerationConfig { ResponseMimeType = "application/json", ResponseSchema = new Schema { Properties = new Dictionary<string, Schema> { { "score", new Schema { Type = DataType.Number } }, { "comment", new Schema { Type = DataType.String } } }, Required = new[] { "score", "comment" }, Type = DataType.Object }, } }, cancellationToken: cancellationToken);
引数の GenerateContentRequest に設定している内容は次のとおりです。
Model: 推論に使用するモデルを指定します。ここではGemini 2.0 FlashContents: アップロードした画像の情報と、SerializeFieldに設定されたユーザープロンプトをそのまま渡しますSystemInstruction: システム命令は固定にしています。内容は「画像を解析し、ユーザープロンプトの要件を満たしているかどうかを判断します。応答は、スコア(最大1.0)と、それについての所見を日本語で返すこと」としていますGenerationConfig: レスポンス本文のフォーマットを指示します。JSONで "score" と "comment" を含むフォーマットと定義しています
レスポンスの処理
レスポンスは指定したJSON形式なので、System.Text.Jsonパッケージを使ってスコアとコメントを取り出し、合否を判定します。
var json = JsonDocument.Parse(response.GetText()).RootElement; var score = json.GetProperty("score").GetDouble(); var comment = json.GetProperty("comment").GetString(); if (score < successThreshold) { var message = $"Visual verification is a failure! score:{score} comment:{comment}"; AutopilotInstance.TerminateAsync(ExitCode.AutopilotFailed, message).Forget(); }
テストシナリオ(AutopilotSettings)への組み込み
作成した VisualVerificationAgent は、他のAgentと同じようにコンテキストメニューからインスタンスを生成し、プロンプトを設定します。
そして、テストシナリオの終端(および途中の要点)に配置すれば完了です。

※ 実際は2つのテストシナリオにそれぞれ使用するべきところ、1つのAutopilotSettingsにまとめて設定した例
注意事項
画像解像度
画像解析の精度は、画像の解像度に影響を受けるようです*4。当初、320x180サイズで試していたところ、インゲームの画面で次のレスポンスが返ってきました。
Response: { "comment": "画像には赤い戦闘機が表示されていません。宇宙空間のような背景の中に、赤い惑星が見られます。", "score": 0.0 }
背景は認識しているようなので、プロンプトを「宇宙にいる」に変えてみたところ、次のレスポンスが返りました。
Response.text: { "comment": "画像は宇宙空間を示しているようです。", "score": 0.95 }
ゲームタイトルによって異なるはずですが、今回の GalacticKittens では、800x480以上で安定したレスポンスが返るようになりました。
消費トークン
画像解像度によって消費トークンが変わってきます。 320x180の場合、300くらい。 800x480の場合、1900くらいでした。
なお、800x480より大きくしても結果の精度も消費トークンも上がりませんでした*5。
まとめ
マルチモーダルLLMの登場で、これまでルールベースでの自動化が困難だった分野も低コストで自動化できる可能性が出てきました。 ゲーム領域において統合/E2Eレベルのテストは難しく、しかしコストはかけられず、ただ動かして進行不能にならないかを見るだけだったり、自動でスクリーンショットだけ撮影して後で人間がチェックする「半自動化」が最適解だったわけですが、状況は変わってきそうです。
なお、LLMとルールベースとは異なる特性を持っています。 LLMは人間に近く、柔軟ですがミスもします。 従って、たとえば自動で操作する部分は従来通りルールベースやキャプチャ/プレイバックのほうが向いており、LLMの推論ベースに置き換える必要はないと考えています。 適材適所です。
関連
本記事のように小さな部品を簡単に組み合わせて使えるAnjinは良いフレームワークなので(自画自賛)、ぜひ使ってみてください。使いかたから拡張方法まで、こちらの同人誌に詳しく載っています。
CEDEC 2025で登壇します。Anjinの話もしますが、ユニットテストを含めた開発者テスト全般についてのセッションです。
*1:ゲームは画面に表示される内容が決定的でないことが多く、またそうでなくてもヒエラルキーをたどる実装コスト、仕様変更に対応するメンテナンスコストがかかります
*2:将来的にAnjinパッケージのSamplesに追加するかもしれません
*3:実はボスを倒したときと全滅したときで異なるSceneに遷移するのでこのような仕組みは不要なのですが、文字を読む事例として…
*4:サイバーエージェントのAI Labの方々に伺ったところ、前処理とかコントラストとかを考えるよりも、まず解像度だそうです
*5:サイバーエージェントのAI Labの方曰く、Gemini内でサイズに上限があり圧縮されているのではないかとのこと
Anjinでシナリオ終了前N秒の動画を保存するレポーターの実装サンプル(Instant Replay for Unityパッケージを利用)
株式会社ディー・エヌ・エー(以下DeNA)が公開しているオープンソースのUnity向けオートパイロットフレームワーク Anjin(あんじん)は、シナリオ終了時に動作するReporterを設定できます。
ビルトインではJUnit形式のテストレポートを出力する JUnitXmlReporter、スクリーンショット付きでSlack投稿を行なう SlackReporter が提供されていますが、ゲームタイトル個々にニーズに合うものを実装して使用できます。
本記事では、株式会社サイバーエージェントが公開しているオープンソースのプレイ動画保存ライブラリ Instant Replay for Unity パッケージを使用して、Anjinのシナリオ終了前N秒の動画を保存するレポーターを実装する方法を紹介します。

環境
- Unity 6000.0.23f1
- Anjin v1.9.0
- Instant Replay for Unity v0.2.0
動作するプロジェクトは、Unity社の提供する2Dシューターサンプル GalacticKittens をフォークしたリポジトリの instant-replay ブランチにあります。
Instant Replay for Unity のインストール
まず、UnityNuGetから次のNuGetパッケージをインストールします。
- System.IO.Pipelines
- System.Threading.Channels
2025年3月以降、UnityNuGetレジストリはOpenUPMにホスティングされ、package.openupm.com レジストリからアップリンクされているため、透過的に使用できます。 詳しくは次の記事を参照してください。
続いて Instant Replay パッケージをインストールします。 こちらはGitHubリポジトリのURLを指定します。
https://github.com/CyberAgentGameEntertainment/InstantReplay.git?path=Packages/jp.co.cyberagent.instant-replay
VideoRecordingReporter の実装
AnjinのReporterは、AbstractReporter を継承して実装します。
2つのメソッドを実装します。
Instant Replay セッションの開始
AnjinのReporterは、Anjinの実行終了時にのみ明示的に呼び出されます。専用の初期化メソッドはありません。
そこで、Instant Replayセッションを開始する契機には InitializeOnLaunchAutopilot 属性を使用します。この属性を付与したstaticメソッドは、Anjinのシナリオ開始時にコールバックを受けられます。
ここでは、実際に実行されるAutopilot設定に紐づいた VideoRecordingReporter を探してInstant Replayセッションを開始します。
なお、numFramesなどの引数はReporterのシリアライズフィールドに指定された値を渡しています。
[InitializeOnLaunchAutopilot] private static void InitializeReporter() { foreach (var reporter in AutopilotState.Instance.settings.reporters.OfType<VideoRecordingReporter>()) { reporter._session = new InstantReplaySession( numFrames: reporter.NumFrames, fixedFrameRate: reporter.FixedFrameRate, maxWidth: reporter.MaxWidth, maxHeight: reporter.MaxHeight); } }
動画の書き出し
Anjinのシナリオ実行が終了するとき、Reporterの PostReportAsync メソッドが呼ばれます。
ここでInstant Replayセッションを停止し、動画をファイルに書き出します。
public override async UniTask PostReportAsync(string message, string stackTrace, ExitCode exitCode, CancellationToken cancellationToken = new CancellationToken()) { var outputPath = await _session.StopAndTranscodeAsync(ct: cancellationToken); if (outputPath != null) { var exportPath = Path.Combine(AutopilotState.Instance.settings.ScreenshotsPath, $"{this.name}.mp4"); File.Move(outputPath, exportPath); } else { Debug.LogWarning("Video Exporting failed."); } _session.Dispose(); }
Reporterアセットの設定
実装した VideoRecordingReporter は ScriptableObject です。コンテキストメニューから.assetファイルを生成し、動画に関する設定を行ないます。
実装例では最大フレーム数、フレームレート、画面サイズを指定できるようにしてあります。 シナリオの実行に失敗したときの調査用途を想定しているので、解像度は低めにしてみました。フレームレートももっと下げていいはず。

設定したアセットをオートパイロット設定ファイルの Reporters に追加すれば動作します。
補足
VideoRecordingReporterの運用についての補足
本記事では動画を保存する単独のReporterを実装する方法を紹介しました。 動画は、実行したマシンのローカルディスクに保存されます。 これは、GitHub ActionsやJenkinsによって定期実行され、問題が生じたときにアーティファクトを参照する運用を想定しています。
もしシナリオ実行に問題があったときのSlack投稿に動画を添付したいニーズがあるなら、ビルトインの SlackReporter をコピーして独自Reporterを作り、そこに本記事の内容をマージすることで実現できます。
AgentにInstant Replayを組み込む場合の補足
Instant ReplayをReporterでなくAgentに組み込むアプローチも考えられます。 用途によっては機能しますが、制限が生じるためお勧めしません。 Anjinは停止時、Agentの実行タスクをキャンセルし、その終了を待たずにプロセスを終了します。 そのためテストシナリオの終了契機で動画を書き出しをはじめても、完了できずに終了してしまうでしょう。
ただ、Agentとして利用したいニーズはありそうなので、Anjin本体の変更も視野に入れて検討はしてみます。
それも踏まえて、本記事の VideoRecordingReporter は当面ビルトインしません。
参考
Anjinは、本記事で紹介したReporterのほかにも Agent、Loggerをさまざまに拡張できます。 詳しくは『Anjin非公式ファンブック』を参照してください。
『Anjin非公式ファンブック』は、5/31から開催される技術書典18でも頒布予定です。
Unityプロジェクト向け .editorconfigサンプル
EditorConfig は、異なるIDE間でもコーディングスタイルや静的解析の設定を共有できる仕組みです。 JetBrains Riderをはじめ、多くのIDE/ エディタでサポートされています。
本稿では、筆者の使用している設定を紹介します。
Roslynの.editorconfig
元にしているのはRoslynの.editorconfigファイルです。RoslynはC# 6.0から導入された.NETコンパイラプラットフォームの通称で、MITライセンスで公開されています。
roslyn/.editorconfig at main · dotnet/roslyn · GitHub
このファイルそのままで、Unity社の公開しているコードスタイルガイド『Use a C# style guide for clean and scalable game code』に準拠したコードフォーマットが得られます。 同ガイドのサンプルコードをこの.editorconfigでフォーマットしてみたところ、行末やコメントのスペースと一部の空行を除いて差分は出ませんでした。
なお、file_header_template にはライセンス表記が入っています。ここだけはプロジェクトに応じて修正が必要です。
スタイルの変更
個人的な好みで、いくつか追加しています。詳細はキーで検索すればJetBrainsのページが見つかりますのでそちらを参照してください。
なお、.editorconfigはファイルパスごとにセクションが分かれています。[*.{cs,vb}] の下に追加してください。
フィールドなどと属性を別の行にする
csharp_place_type_attribute_on_same_line = false csharp_place_method_attribute_on_same_line = false csharp_place_accessorholder_attribute_on_same_line = false csharp_place_field_attribute_on_same_line = false
SerializeField 属性などを別の行に書きたいので次の定義を追加しています。
すべての属性ではなく、たとえば Values 属性のように引数につけるものは除外しています。
連続した行のコメントの開始位置をそろえる
resharper_csharp_int_align_comments = true
C#8.0以降の構文をサジェストしない
resharper_convert_to_using_declaration_highlighting = none resharper_convert_to_null_coalescing_compound_assignment_highlighting = none resharper_merge_into_logical_pattern_highlighting = none resharper_use_negated_pattern_in_is_expression_highlighting = none
筆者がメンテナンスしているUPMパッケージがUnity 2019 LTSをサポートしているため、Unity 2020.2以降で使用できる構文はサジェストされないように設定しています。
コードインスペクションの設定
switch文にenumを使用するとき、すべての値をcaseで列挙することを強制する
resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting = warning
類似の設定に resharper_switch_statement_missing_some_enum_cases_no_default_highlighting がありますが、こちらは default がないときに検出されるものです。
一方この resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting は、default があってもすべての値を case に書かないと検出されます。
ゲームの開発中や運用中、enum が追加されたときの対応漏れを検知できます*1。
リグレッションの原因として割と怖いものなので、error でもいいかもしれません。
BannedApiAnalyzersによる禁止をエラーにする
dotnet_diagnostic.RS0030.severity = error
BannedApiAnalyzers ではデフォルトの重大度が warning なのですが、チームで禁止にするなら error でいいはずです。
テストコード向け設定
テストコードは /Tests/ を含むパスに置くようにしているので、以下は [**/Tests/**/*.cs] と指定することでテストコードにのみ適用させています。
親クラスに定義されたstaticメソッドを許容する
resharper_access_to_static_member_via_derived_type_highlighting = none
一般的な例では、GameObject.FindAnyObjectByType<T>() と書くと検出されます(Objectクラスのメソッドなので)。
Unity Test Framework を使っていると、制約モデルの Is クラスを拡張することがあるのですが、それを使うときに煩わしいので none にしています。
複数のAssertを許容する
dotnet_diagnostic.NUnit2045.severity = none
テストメソッドに複数のアサーションを書くことは原則しないのですが、たとえば生成されたオブジェクトのプロパティを検証するときなど例外はあります。
NUnit.Analyzers ではこのとき Assert.Multiple を使うようサジェストするのですが、これはUnity Test Frameworkでは使用できないため、抑止しています*2。
サンプル.editorconfig
以上を取り込んだ.editorconfigファイルが『Unity Test Framework完全攻略ガイド』*3のサンプルプロジェクト(MITライセンス)に置いてあります。
なお、file_header_template にはライセンス表記が入っています。ここだけはプロジェクトに応じて修正が必要です。
UnityTestExamples/.editorconfig at master · nowsprinting/UnityTestExamples · GitHub
また、上記のほかに次の差分があります。ご注意ください。
spelling_exclusion_pathを削除:Riderの辞書を使っているためcharsetをutf-8-bomからutf-8に変更:Windowsを使っている場合はBOMつけたままでいいと思います
関連
Monkey Test Helper v0.15.0 マイグレーション ガイド
Unity上でオブジェクトベースのモンキーテストを実行できる Monkey Test Helper パッケージ*1の v0.15.0 では、いくつか破壊的変更が入りました*2。 そのマイグレーションについて解説します。
Monkey Test Helper は、オートパイロットフレームワーク Anjin の UGUIMonkeyAgent の内部実装にも使われています。Monkey Test Helper v0.15.0 は、Anjin v1.9 で反映予定です。
なお Monkey クラスおよび Anjin をカスタマイズなしで使用しているプロジェクトには、本変更の影響はありません。
IOperator.OperateAsync() のシグネチャ変更
まず、第一引数の型、つまり操作対象の指定が Component から GameObject に変更されました。
この変更は実際のマウスやタッチ操作と合わせるためで、1つのGameObjectにイベントを受けられるコンポーネントが複数あるとき、そのすべてにイベントが飛びます。
また、第二引数として RaycastResult を受け取るようになりました。
これは Camera からのレイキャストで返される先頭(最前面)の RaycastResult で、クリック座標などを含みます。
RaycastResult は、後述の IReachableStrategy および GameObjectFinder の戻り値として取得できます。
オペレータの実装によっては使用しませんし(たとえば UGUITextInputOperator)、ゲームタイトル側で EventData の中身を参照しないのであれば default で構いません。
この変更により、たとえば『Anjin非公式ファンブック』7.4.2で紹介している RandomClickOperator のようなカスタムオペレーターにおいて、レイキャストを通した座標を引き継いでクリックできるようになります。
IOperator.OperateAsync() にログおよびスクリーンショット出力の責務が移動
v0.14 までは呼び出し元(たとえば Monkey クラス)がログおよびスクリーンショットの出力を担っていましたが、
v0.15.0 以降はオペレーターの責務となります。
この変更は、将来の機能追加への布石です。 ドラッグ操作などのオペレーターや、操作座標をGameViewにオーバーレイしてスクショに収めることを想定しています。
なお、この変更に伴ない OperateAsync() の引数に ILogger と ScreenshotOptions が追加されています。
IsIgnored() および IsReachable() がインスタンスメソッド化
モンキーテストで無視するオブジェクトを判断する IsIgnored() とオブジェクトがユーザー操作可能かを判断する IsReachable() が、static 関数からインスタンスメソッドに変更されました。
それぞれ、IIgnoreStrategy.IsIgnored() と IReachableStrategy.IsReachable() になります。
デフォルト実装も提供されています。
この変更により、各ストラテジは状態を持てるようになります。
IReachableStrategy および GameObjectFinder のシグネチャ変更
IReachableStrategy.IsReachable() が、out パラメータで RaycastResult を返すようになりました。
また GameObjectFinder の FindByNameAsync() および FindByPathAsync() メソッドの戻り値の型が GameObject から GameObjectFinderResult に変更されました。
GameObjectFinderResult は、GameObject と RaycastResult を保持する struct です。
この変更によって得られるようになった RaycastResult は、前述 IOperator.OperateAsync() の第二引数に渡されることを想定しています。
コード例
v0.14以前
var finder = new GameObjectFinder(); var buttonObject = await finder.FindByNameAsync("StartButton", reachable: true, interactable: true); var buttonComponent = buttonObject.GetComponent<Button>(); var clickOperator = buttonComponent.SelectOperators<IClickOperator>(_operators).First(); // ここでスクリーンショット撮影・ログ出力(呼び出し側の責務) await clickOperator.OperateAsync(buttonComponent);
v0.15.0以降
var finder = new GameObjectFinder(); var result = await finder.FindByNameAsync("StartButton", reachable: true, interactable: true); var buttonObject = result.GameObject; var clickOperator = buttonObject.SelectOperators<IClickOperator>(_operators).First(); await clickOperator.OperateAsync(buttonObject, result.RaycastResult);
関連
Monkey Test Helper パッケージのトラブルシュート
Unity上でオブジェクトベースのモンキーテストを実行できる Monkey Test Helper パッケージ*1のトラブルシュート資料を和訳しました。 対応バージョンは 0.14.0 です。
Monkey Test Helper は、オートパイロットフレームワーク Anjin の UGUIMonkeyAgent の内部実装にも使われています。
Monkey
TimeoutExceptionがスローされた
次のメッセージを伴なう TimeoutException がスローされたとき、操作可能な GameObject がSceneに1つもない状態が5秒間続いたことを示します。
Interactive component not found in 5 seconds
デフォルト実装では*2、次の要件をすべて満たす GameObject が1つもないことを示します。
Selectableを継承かつinteractableプロパティが true であるか、EventTriggerか、IEventSystemHandlerインタフェースを実装したコンポーネントIgnoreAnnotationコンポーネントがアタッチされていないことCamera.mainからピボット座標へのレイキャストが通ること(ほかのオブジェクトで隠されていないこと)
どの条件で対象外とされたかを知るには、バーボーズログ(後述)を有効にします。
なお、タイムアウトまでの秒数は、
MonkeyConfig.SecondsToErrorForNoInteractiveComponent
で指定できます。
この機能を無効にするには 0 を指定します。
InfiniteLoopExceptionがスローされた
次のメッセージを伴なう InfiniteLoopException がスローされたとき、指定されたバッファ長内で繰り返し操作が検出されたことを示します。
Found loop in the operation sequence: [44030, 43938, 44010, 44030, 43938, 44010, 44030, 43938, 44010, 44030]
上記メッセージでは、パターン [44030, 43938, 44010] がループしています。
数字は、操作された GameObject のインスタンス ID です。
検出可能な繰り返しパターンの最大長は、バッファ長の半分です。
バッファ長は MonkeyConfig.BufferLengthForDetectLooping で指定できます。
この機能を無効にするには 0 を指定します。
この例外は、Monkey Test Helper v0.15.0で追加されました。Anjinには v1.9で反映予定です。[3/8追記]
操作ログ
UGUIClickOperator operates to StartButton (UGUIMonkeyAgent01_0001.png)
このログは、オペレーター UGUIClickOperator が、StartButton という名前の GameObject を操作する直前に出力されます。
"UGUIMonkeyAgent01_0001.png" は、操作直前のスクリーンショットのファイル名です。
スクリーンショットは、MonkeyConfig のScreenshots を設定すると撮影されます。
バーボーズログ
詳細なログは、MonkeyConfig のVerbose に true を設定すると出力されます。
抽選対象
Lottery entries: [ StartButton(30502):Button:UGUIClickOperator, StartButton(30502):Button:UGUIClickAndHoldOperator, MenuButton(30668):Button:UGUIClickOperator, MenuButton(30668):Button:UGUIClickAndHoldOperator ]
各エントリのフォーマットは「GameObject 名(インスタンスID):コンポーネントの型:オペレーターの型」です。
このメッセージは、Monkeyが操作するオブジェクトおよびオペレーターの抽選対象をすべて出力しています。
uGUI 互換コンポーネントかつ interactable プロパティが true であるものです。
この段階では IsIgnore および IsReachable による判定は行われていません。
なお、この時点で抽選対象となるオブジェクトがひとつもないときは、次のメッセージが出力されます。
No lottery entries.
無視されたGameObject
抽選した GameObject が無視するように指定されたもの(デフォルトでは IgnoreAnnotation コンポーネントがアタッチされたもの)であったとき、次のメッセージを出力して再抽選されます。
Ignored QuitButton(30388).
ユーザーが到達不可能なGameObject
抽選した GameObject がユーザー到達不可能(デフォルトでは Camera.main からピボット座標へのレイキャストが通らないもの)であったとき、次のメッセージを出力して再抽選されます。
Not reachable to CloseButton(-2278), position=(515,-32). Raycast is not hit.
もしくは
Not reachable to BehindButton(-2324), position=(320,240). Raycast hit other objects: [BlockScreen, FrontButton]
前者は画面外など、後者はほかのオブジェクトによってピボット座標が隠されている状態です。
レイキャストを送る座標は ScreenOffsetAnnotation などのアノテーションコンポーネントでアレンジできます。
操作可能なGameObjectがひとつもない
操作可能なGameObjectがひとつもなかったとき、次のメッセージが出力されます。
この状態が一定時間続くと TimeoutException がスローされます。
Lottery entries are empty or all of not reachable.
GameObjectFinder
TimeoutExceptionがスローされた
名前が一致するものが見つからない
指定された名前を持つ GameObject が見つからなかったとき、次のメッセージを伴なう TimeoutException がスローされます。
GameObject `Target` is not found.
パス不一致
指定された名前を持つ GameObject のパス(Sceneのヒエラルキー)が一致しないとき、次のメッセージを伴なう TimeoutException がスローされます。
この判定は FindByPathAsync メソッドでのみ行われます。
GameObject `Target` is found, but it does not match path `Path/To/Target`.
ユーザー到達不可能
指定された名前を持つ GameObject がユーザー到達不可能(デフォルトでは Camera.main からピボット座標へのレイキャストが通らないもの)であったとき、次のメッセージを伴なう TimeoutException がスローされます。
この判定を行なうか否かは FindByNameAsync および FindByPathAsync の引数 reachable で指定できます。デフォルトは true(判定する)です。
GameObject `Target` is found, but not reachable.
詳細なログが必要な場合は、ILogger インスタンスを GameObjectFinder のコンストラクターに渡してください。
操作不可能
指定された名前を持つ GameObject が操作不可能(uGUI 互換コンポーネントでない、もしくは interactable プロパティが false)であったとき、次のメッセージを伴なう TimeoutException がスローされます。
この判定を行なうか否かは FindByNameAsync および FindByPathAsync の引数 interactable で指定できます。デフォルトは false(判定しない)です。
GameObject `Target` is found, but not interactable.
関連
#C105 ありがとうございました

コミックマーケット105、無事終わりました。 ご購入いただいた方々、また、スペースにお立ち寄りいただいた方々、準備会やインフラの方々、本当にありがとうございました。
新刊は電子・物理ともBOOTHで販売しております。 新刊は折を見て(遅くとも次のイベント出展までに)値上げを検討しています。お早めにお買い求めください。
頒布した書籍の詳細・フォローアップは次の記事を参照してください。
今回も西2ホールで人が少なかったのと新刊がニッチさを極めているのとで、ずいぶん平和な一日でした。 既刊の『Unity Test Framework完全攻略ガイド 統合テスト編』が完売すると、ぱっと見Unity成分がスペースからなくなるという事態に。 テスト自動化ピラミッドの図も小さかったし、ディスプレイもっと頑張るべきだったと反省。
ちなみに今回、ブースクロスをグラフィックさんで新調。疾走するめの一夜干しバージョンがいい感じに。 ちゃんとアイロンかけよう…
