Amazonで商品を見るセール会場へ

UnityのバッチモードでEditorスクリプトを実行|引数・ログ・終了コード

UnityのEditorスクリプトをコマンドから呼び、テキストの保存先・ログ・終了コードを確認するWindows向けの最小例です。最初に1ファイルを出力し、次に同じ出力を検証する2段階の例を示します。ビルドやPrefab加工を追加する前に、実行条件と失敗の扱いを確かめます。

仕様の参照はUnity 6.3 LTS(6000.3)です。掲載した純粋C#の引数・ファイル処理と、BATの引数・終了コード・失敗時停止はWindowsで独立検証しました。Unity本体の起動、ライセンス、Editorコンパイル、実プロジェクトでの動作は未確認です。元記事の画像は旧手順の資料で、今回の修正版の実行画像ではありません。

下の画像は旧版の実行画面です。今回の例はデスクトップを自動選択せず、指定した新しい実行フォルダーへ保存します。

Unityのバッチモードを利用してエディタスクリプトを実行し、デスクトップへテキストファイルを出力する最小手順のイメージ。
目次

batchmodeとnographicsの違い

項目・症状条件・確認先
-batchmodeEditorの対話ダイアログを抑え、コマンドラインの処理を実行する。Editor処理であり、Playerの起動ではない。
-nographicsグラフィックスデバイスを初期化しない。このテキスト出力例では使用する。カメラ・RenderTexture・PNG撮影用の条件とは分ける。
-executeMethodEditor専用スクリプトのstaticメソッドをクラス名.メソッド名で呼ぶ。名前空間がある場合は名前空間も指定する。
-quitここで扱う同期メソッドが戻った後に終了させるための指定。非同期処理の完了待ちやTest FrameworkのrunTestsへそのまま流用しない。
-logFileログの保存先を指定する。Windowsの標準出力と画面のコンソール表示を同一視せず、最初は絶対パスのログファイルを残す。
-helloOutputこの掲載コード独自の引数。Unityの標準オプションではなく、出力するhello.txtの絶対パスを渡す。

Unity公式のコマンドライン引数一覧が技術仕様の基準です。バッチ処理でもライセンスとプロジェクトのImport・コンパイルは必要です。2D PrefabのPNG出力やuGUIのPNG出力の記事は対話ダイアログを使う撮影ツールなので、この-nographics付きの実行例へ直接接続しません。

実行前に確認する条件

  1. プロジェクトを複製して使います。ProjectSettings/ProjectVersion.txtのEditor版を確認し、その版に合う実際のUnity.exeを指定します。Hubの一般的な保存先はC:\Program Files\Unity\Hub\Editor\版番号\Editor\Unity.exeですが、インストール先を固定して決めつけません。
  2. そのアカウントでEditorの利用に必要なライセンスを用意します。定期実行で別アカウントを使う場合は、そのアカウントの条件も確認します。初回セットアップの対話操作をbatchmodeで代行する例ではありません。
  3. 同じプロジェクトを開いているEditorを閉じます。同一プロジェクトを複数のEditorやジョブで同時に開かないよう、定期実行にも重複起動を防ぐ設定を用意します。
  4. 出力先はローカルドライブの書込可能な親フォルダーにします。BATへ渡す実行フォルダー自体はまだ存在しない新しい絶対パスにします。UNC・相対パス・ドライブ相対パスはこの例の対象外です。
  5. 下のC#をAssets/Editor/BatchHelloWriter.csとして保存します。旧Batch_HelloWriterとクラス名が異なります。Editor専用asmdefを使う場合はそのAssemblyへ配置し、Consoleのエラーを解消しておきます。

1. 同期処理のC#を用意する

Runは-helloOutputを1つだけ受け取り、UTF-8・BOMなし・改行なしのhello.txtをCreateNewで保存します。既存ファイルを上書きせず、親フォルダーがない場合も失敗にします。VerifyOutputは保存されたバイト列を照合するメソッドです。

using System;
using System.IO;
using System.Text;
using UnityEngine;

// Save as Assets/Editor/BatchHelloWriter.cs. Synchronous Windows example.
public static class BatchHelloWriter
{
    public static void Run()
    {
        Execute(false);
    }

    public static void VerifyOutput()
    {
        Execute(true);
    }

    private static void Execute(bool verify)
    {
        try
        {
            if (!Application.isBatchMode)
                throw new InvalidOperationException("This entry point requires -batchmode.");
            string path = BatchHelloFiles.ParseOutput(Environment.GetCommandLineArgs());
            if (verify)
                BatchHelloFiles.Verify(path);
            else
                BatchHelloFiles.WriteNew(path);
            Debug.Log((verify ? "HELLO_VERIFY_OK: " : "HELLO_WRITE_OK: ") + path);
        }
        catch (Exception error)
        {
            Debug.LogError("HELLO_FAILED: " + error);
            // The executeMethod caller receives the exception. Do not swallow it.
            throw;
        }
    }
}

// No Unity APIs: these exact helpers can be tested separately on Windows .NET.
public static class BatchHelloFiles
{
    public const string Text = "Hello from Unity batch mode!";

    public static string ParseOutput(string[] args)
    {
        if (args == null) throw new ArgumentNullException("args");
        string value = null;
        for (int i = 0; i < args.Length; i++)
        {
            if (args[i] != "-helloOutput") continue;
            if (value != null) throw new ArgumentException("Duplicate -helloOutput.");
            if (i + 1 >= args.Length || String.IsNullOrWhiteSpace(args[i + 1]) || args[i + 1].StartsWith("-"))
                throw new ArgumentException("-helloOutput needs an absolute file path.");
            value = args[++i];
        }
        if (value == null) throw new ArgumentException("Missing -helloOutput.");
        return ValidatePath(value);
    }

    public static string ValidatePath(string path)
    {
        // Limit this example to local drive paths; no relative, drive-relative or UNC paths.
        if (String.IsNullOrWhiteSpace(path) || path.Length < 4 ||
            !((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) ||
            path[1] != ':' || (path[2] != '\\' && path[2] != '/'))
            throw new ArgumentException("Use a local drive absolute file path, such as C:\\UnityBatch\\run01\\hello.txt.");
        if (path.IndexOf(':', 2) >= 0 || path.EndsWith("\\") || path.EndsWith("/"))
            throw new ArgumentException("A regular file path is required.");
        string full = Path.GetFullPath(path);
        string parent = Path.GetDirectoryName(full);
        if (String.IsNullOrEmpty(Path.GetFileName(full)) || !Directory.Exists(parent))
            throw new DirectoryNotFoundException("Create the output directory before starting Unity.");
        return full;
    }

    public static void WriteNew(string path)
    {
        path = ValidatePath(path);
        byte[] bytes = new UTF8Encoding(false, true).GetBytes(Text);
        // CreateNew refuses to replace an existing file. A failed write can leave a partial file.
        using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None))
            stream.Write(bytes, 0, bytes.Length);
    }

    public static void Verify(string path)
    {
        path = ValidatePath(path);
        byte[] expected = new UTF8Encoding(false, true).GetBytes(Text);
        byte[] actual = File.ReadAllBytes(path);
        if (actual.Length != expected.Length)
            throw new InvalidDataException("Unexpected output length.");
        for (int i = 0; i < expected.Length; i++)
            if (actual[i] != expected[i]) throw new InvalidDataException("Unexpected output bytes.");
    }
}

例外をログへ出した後にthrowで呼出し元へ返します。公式資料ではexecuteMethodから例外を送出すると終了コード1を返すとされています。ライセンス・Import・起動時の失敗もあるため、BATでは1に限定せず0以外を失敗として停止します。ログの成功メッセージだけでプロセス成功と判断しません。

FileMode.CreateNewは既存ファイルを拒否しますが、書込み途中の失敗では不完全なファイルが残る場合があります。その実行フォルダーとログを残し、原因を確認してから別の新しいフォルダーで再実行してください。自動削除や自動再試行をするコードではありません。

2. 1回実行するBATを保存する

次をrun_hello.batとして保存します。コードはASCII文字なので保存時の文字化けを避けられます。WindowsのBATとして改行はCRLFを使います。3つの引数はUnity.exe・プロジェクトルート・新しい実行フォルダーの順です。Assetsフォルダー自体を-projectPathに渡しません。

@echo off
setlocal DisableDelayedExpansion
if "%~1"=="" goto usage
if "%~2"=="" goto usage
if "%~3"=="" goto usage
if not "%~4"=="" goto usage
set "UNITY=%~1"
set "PROJECT=%~2"
set "RUN=%~3"
if not exist "%UNITY%" goto bad_input
if not exist "%PROJECT%\ProjectSettings\ProjectVersion.txt" goto bad_input
if not "%RUN:~1,2%"==":\" goto bad_input
if exist "%RUN%" goto existing_run
mkdir "%RUN%"
if errorlevel 1 exit /b 2
"%UNITY%" -batchmode -quit -nographics ^
 -projectPath "%PROJECT%" ^
 -executeMethod BatchHelloWriter.Run ^
 -helloOutput "%RUN%\hello.txt" ^
 -logFile "%RUN%\write.log"
set "RESULT=%ERRORLEVEL%"
if not "%RESULT%"=="0" goto failed
echo Unity process returned 0. Check log and output: "%RUN%"
exit /b 0
:failed
echo Unity process returned %RESULT%. Inspect logs in "%RUN%".
exit /b %RESULT%
:existing_run
echo Run path already exists. Choose a new absolute run directory.
exit /b 2
:bad_input
echo Check Unity.exe, ProjectVersion.txt and the absolute run directory.
exit /b 2
:usage
echo Usage: %~nx0 "Unity.exe" "Project root" "New absolute run directory"
exit /b 2

3. コマンドと期待結果を照合する

コマンドプロンプトで実行する例です。6000.3.xf1は実際にインストールした版へ、プロジェクトと出力先も自分の環境へ置き換えます。実行フォルダーには存在しない新しい名前を付けます。

run_hello.bat "C:\Program Files\Unity\Hub\Editor\6000.3.xf1\Editor\Unity.exe" "C:\UnityProjects\MyProject" "C:\UnityBatch\run01"
echo ExitCode=%ERRORLEVEL%
項目・症状条件・確認先
BATの終了コード0を期待。入力不備・既存の実行フォルダーはこのBATが2を返す。Unity起動後の失敗はUnityプロセスの非0コードをそのまま返す。
write.logUnityのログにHELLO_WRITE_OKと指定したhello.txtのパスがあることを確認。失敗時はHELLO_FAILEDやそれより前の起動・コンパイルエラーを読む。
hello.txt内容はHello from Unity batch mode!。UTF-8、BOM・末尾改行なし。ログとファイルの両方を確認する。
保存場所C:\UnityBatch\run01\hello.txtとwrite.log。BATは%~3の指定先を作る。デスクトップ・BATの置き場所・ジョブのカレントディレクトリを保存先にしない。

次の旧画面は、以前のデスクトップ出力を使った手順です。保存先とスクリプト名は上の修正版へ読み替えます。

バッチファイルを実行した後に、デスクトップ上に指定したテキストファイルが生成されたことを確認する様子。
バッチ実行時のログが出力され、エラーや処理失敗時の原因調査に利用できるログファイルの内容。

4. 2段階を順番に実行し、失敗したら止める

次をrun_hello_verify.batとして保存します。Runが返したプロセスコードを直後に保存し、失敗時はVerifyOutputを起動しません。2段階目も失敗したらその終了コードを返します。未定義のPrefabCheckerやTextureOptimizerを呼ぶ例にはしていません。

@echo off
setlocal DisableDelayedExpansion
if "%~1"=="" goto usage
if "%~2"=="" goto usage
if "%~3"=="" goto usage
if not "%~4"=="" goto usage
set "UNITY=%~1"
set "PROJECT=%~2"
set "RUN=%~3"
if not exist "%UNITY%" goto bad_input
if not exist "%PROJECT%\ProjectSettings\ProjectVersion.txt" goto bad_input
if not "%RUN:~1,2%"==":\" goto bad_input
if exist "%RUN%" goto existing_run
mkdir "%RUN%"
if errorlevel 1 exit /b 2
"%UNITY%" -batchmode -quit -nographics ^
 -projectPath "%PROJECT%" ^
 -executeMethod BatchHelloWriter.Run ^
 -helloOutput "%RUN%\hello.txt" ^
 -logFile "%RUN%\write.log"
set "RESULT=%ERRORLEVEL%"
if not "%RESULT%"=="0" goto failed
"%UNITY%" -batchmode -quit -nographics ^
 -projectPath "%PROJECT%" ^
 -executeMethod BatchHelloWriter.VerifyOutput ^
 -helloOutput "%RUN%\hello.txt" ^
 -logFile "%RUN%\verify.log"
set "RESULT=%ERRORLEVEL%"
if not "%RESULT%"=="0" goto failed
echo Unity process returned 0. Check log and output: "%RUN%"
exit /b 0
:failed
echo Unity process returned %RESULT%. Inspect logs in "%RUN%".
exit /b %RESULT%
:existing_run
echo Run path already exists. Choose a new absolute run directory.
exit /b 2
:bad_input
echo Check Unity.exe, ProjectVersion.txt and the absolute run directory.
exit /b 2
:usage
echo Usage: %~nx0 "Unity.exe" "Project root" "New absolute run directory"
exit /b 2
run_hello_verify.bat "C:\Program Files\Unity\Hub\Editor\6000.3.xf1\Editor\Unity.exe" "C:\UnityProjects\MyProject" "C:\UnityBatch\run02"
echo ExitCode=%ERRORLEVEL%

成功時はhello.txt・write.log・verify.logがあり、2つ目のログにHELLO_VERIFY_OKが出ます。Unityを2回起動するので、起動やImportの費用も2回発生します。多数の処理を1回のEditor起動で済ませたい場合は、1つのstatic入口から同期処理を順番に呼ぶ構成を別途作ります。非同期処理は完了通知を待って終了する設計が必要です。

5. 定期実行で変わる条件

まず同じアカウントのコマンドプロンプトで手動実行を確認します。その後、定期実行の操作へ同じ3引数を渡します。毎回存在しない実行フォルダーを用意する運用が必要で、run01を繰り返し渡すと2回目は入力エラーで止まります。ログの保存・保持期間と出力フォルダー名をジョブ側で管理してください。

項目・症状条件・確認先
実行アカウントUnityライセンス、プロジェクト読取、出力書込をそのアカウントで確認。ログオン中の自分とサービス用アカウントのデスクトップが同じとは限らない。
パスと開始フォルダーUnity.exe・プロジェクト・実行フォルダーは絶対パスで引用する。開始フォルダーに依存したlogs\への書込みを避ける。
重複起動同じプロジェクトの実行が終わる前に次を始めない。対話Editorも閉じる。
権限と対話最上位権限を一律の必須条件にしない。必要なアクセス権を確認し、pause・ダイアログ・FolderPanelを無人処理へ持ち込まない。

次の2画像は旧タスク設定の資料です。OS全般のタスク作成操作と、ここで扱うUnityのライセンス・引数・ログ・同時起動の条件は分けて確認します。

タスクスケジューラーによる定期実行設定の全般タブで、最上位の特権で実行する項目を指定する画面。
タスクスケジューラーの操作タブで、実行するプログラムやスクリプト、引数などの設定項目を入力する画面。

失敗時に確認する順番

項目・症状条件・確認先
BATが2を返しUnityログがない3引数・実在するUnity.exe・ProjectVersion.txt・絶対出力先・既存フォルダーを確認。mkdir失敗なら親の書込権限と容量を確認。
Unityが起動しない・入口ログがないライセンス、使用版、プロジェクトの二重起動、Import、Package、コンパイルのログを先に読む。
メソッドが見つからないAssets/Editorの配置、static、BatchHelloWriter.RunまたはVerifyOutputの綴り、Assemblyと名前空間を確認。
HELLO_FAILEDがある例外の内容と出力パスを読む。引数の重複・欠落、親フォルダー、既存ファイル、ロックやアクセス拒否を切り分ける。
VerifyOutputだけ失敗hello.txtの存在と内容を確認。編集・削除・別パス・不完全な書込みを切り分け、2ログを並べて見る。
画像出力や非同期処理が終わらないこの同期テキスト例と条件が違う。描画が必要ならnographics、非同期ならquitと完了待ちの設計を見直す。

今回実際に確認した範囲

掲載コードから同一のBatchHelloFilesクラスを抽出し、Windowsの.NETでコンパイルしました。正常な引数1ケース、欠落・重複・相対・UNC等の拒否12ケース、UTF-8書込み・バイト照合・既存ファイル保護・破損内容・欠落ファイルの5確認を実行しました。

BATは自作の試験用実行ファイルで9ケースを実行しました。空白・日本語・&を含むパスの引数保持、子プロセスの0・7・9の終了コード、1段階目失敗時に2段階目を起動しないこと、入力不備では起動しないことを確認しました。この実行ファイルはUnityではありません。Unityの起動・Import・ライセンス・Editor APIの検証には数えません。

実Unityではまず複製プロジェクトで正常1回、既存ファイルや書込拒否による失敗、2段階実行、開いたEditorとの競合を試します。使用版・日時・実行アカウント・引数・ログ・終了コード・出力を保存してから、アセット加工やビルドへ広げてください。

関連するEditor拡張

元コードの配布先はGitHubの旧配布です。旧02.sh・03.shにはWindows BATの内容が入っているため、POSIXシェル用と判断しないでください。今回の修正版は本文のC#と.bat全文を使います。外部リポジトリは今回変更していません。

エディタ拡張の記事は、ほかにもあります。

次に学ぶ・作業環境を選ぶ

学習を続けたい方や、作業環境を整えたい方は、目的に合うガイドをご覧ください。

Unityのおすすめ書籍

開発PCの選び方

周辺機器の優先度

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次