标签归档:python

在 Unity Editor 中同步执行外部脚本

在 Unity Editor 中同步执行外部脚本

在 Unity 开发的过程中,我们都难免会碰到需要使用某些外部脚本来完成某些特定的任务,例如调用外部的 Python 脚本导入 Excel 配置表为 Json 文件等等。

C# 提供的 System.Diagnositics.Process 类可以很好的帮助我们达成这个目的,下面我们就来看看如果使用 Process 类来调用外部的脚本。

由于 C# 调用外部脚本执行的程序会新建一个进程来执行该指定的外部脚本,跟 Unity Editor 并不会在同一个进程,所以如果我们直接调用 Process.Start() 方法启动外部程序的话,该外部程序的执行与当前 Unity Editor 进程空间中执行的 C# 代码之间是无法交互的,从表现上来看,外部程序完全是独立于 Unity Editor 进程在执行。如此一来,在某些应用场景下就可能出现无法满足我们的需求,例如在每次调用 BuildPipeline.BuildPlayer 方法打包 APK 之前,需要导入最新的配置表文件,那么我们怎么确保 BuildPipeline.BuildPlayer 是在执行外部 Python 脚本导入配置表之后再执行的呢?我们来看代码:

// 同步调用外部脚本,并将其输出使用 Unity Editor 的 Log 进行输出
Process importProcess = new Process();
importProcess.StartInfo.FileName = batName;

// 如果需要在 Unity Editor 中使用 Log 输出外部程序执行时的输出进行查看的话,
// 就必须将 UseShellExecute 设置为 false
importProcess.StartInfo.UseShellExecute = false;

// 将标准输出重定向,所有输出会通过事件回调的方式在回调参数中返回
importProcess.StartInfo.RedirectStandardOutput = true;
importProcess.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(ImportAllOutputDataReceived);

// 将错误输出重定向,所有输出会通过事件回调的方式在回调参数中返回
importProcess.StartInfo.RedirectStandardError = true;
importProcess.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(ImportAllErrorDataReceived);
importProcess.Start();
sImportAllOutput = new StringBuilder();
sImportAllError = new StringBuilder();
importProcess.BeginOutputReadLine();
importProcess.BeginErrorReadLine();

// 这一句很关键哦,就是因为调用了这句话,我们才能让外部程序与 Unity Editor 中的脚本同步执行
importProcess.WaitForExit();

if (!string.IsNullOrEmpty(sImportAllOutput.ToString()))
{
    UnityEngine.Debug.Log(string.Format("Import all json log:\n {0}", sImportAllOutput.ToString()));
}
if (!string.IsNullOrEmpty(sImportAllError.ToString()))
{
    UnityEngine.Debug.LogError(string.Format("Import all json error:\n {0}", sImportAllError.ToString()));
}
UnityEngine.Debug.Log(string.Format("Import all json process exited at: {0} with code: {1}, start to imprted json files to protypes",
                                 importProcess.ExitTime, importProcess.ExitCode));

// 完成导入之后,调用一下刷新方法,确保 Unity Editor 加载到的配置文件都是最新的
AssetDatabase.Refresh();   

// 执行打包操作
BuildPipeline.BuildPlayer(SceneInfoManager.Levels, androidOutputPath, BuildTarget.Android, buildOptions);

👆 上面这段代码的正确执行除了需要补全一些变量之外,还需要实现两个事件回调方法:

private static void ImportAllOutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs args)
{
   if (!string.IsNullOrEmpty(args.Data))
   {
       sImportAllOutput.AppendLine(args.Data);
   }
}

private static void ImportAllErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs args)
{
   if (!string.IsNullOrEmpty(args.Data))
   {
       sImportAllError.AppendLine(args.Data);
   }
}

如此一来,调用外部脚本就成为了整个工具脚本执行流程中的一个小环节了,而且执行成功或者出错,也能通过 Unity Editor 的 Log 进行查看,结合 UnityEngine.Debug.LogError 也能很好地作出相应地错误提示,是不是感觉棒棒哒。

👆 上面的这段代码中是直接调用了操作系统平台的可执行脚本文件,例如在 macOS 上就是 shell 脚本,在 Windows 上就是 bat 脚本了。这样其实还是有点蛋疼对吧,我得每个平台都写一个脚本,那么为了不这么蛋疼,我们可以这么做。

// 我们最终是调用 python 来执行这个 python 脚本
string convertJsonToExcelPythonScriptFielPath = Path.Combine(Path.Combine(Directory.GetParent(Application.dataPath).FullName, "Tools"), "convert_json_to_excel.py");

// 构建完成的参数列表
string arguments = string.Format("{0} -j {1} -e {2} -s {3}", convertJsonToExcelPythonScriptFielPath, jsonFilePath, excelFilePath, sheetName);

System.Diagnostics.Process convertJsonToExcelProcess = new System.Diagnostics.Process ();
// 针对不同的平台,找到不同的 python 执行程序
if (Application.platform == RuntimePlatform.OSXEditor) 
{
    System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo ("/usr/bin/python", arguments);
    convertJsonToExcelProcess.StartInfo = startInfo;
} 
else if (Application.platform == RuntimePlatform.WindowsEditor) 
{
    System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo (@"C:\Python27\python.exe", arguments);
    convertJsonToExcelProcess.StartInfo = startInfo;
}
convertJsonToExcelProcess.StartInfo.UseShellExecute = false;
convertJsonToExcelProcess.StartInfo.RedirectStandardOutput = true;
convertJsonToExcelProcess.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(ConvertJsonToExcelOutputDataReceived);
convertJsonToExcelProcess.StartInfo.RedirectStandardError = true;
convertJsonToExcelProcess.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(ConvertJsonToExcelErrorDataReceived);
sConvertJsonToExcelOutput = new StringBuilder();
sConvertJsonToExcelError = new StringBuilder();
convertJsonToExcelProcess.Start ();
convertJsonToExcelProcess.BeginOutputReadLine();
convertJsonToExcelProcess.BeginErrorReadLine();
convertJsonToExcelProcess.WaitForExit();

好了,基于以上的两段示范代码,实际上我们已经能很好地完成在 Unity Editor 中同步执行外部程序,配合 Unity Editor 工具脚本完成各种任务了。