Unityで作ったゲームが、外部の実行ファイルを起動しなきゃいけないとき、たまにあるよね。
うん、僕は最近あったんだ。先月のゲームジャムのときにね。
ただ何も考えずに外部の実行ファイルを起動する処理を作成してしまうと、エディタで遊ぶ時と、実行ファイルで遊ぶ時で処理が違ってしまい、痛い目を見ることもあるからね。
うん、僕は最近あったんだ。先月のゲームジャムのときにね。
というわけで今回は外部の実行ファイルをUnityプロジェクトに含めてビルド出力時にビルドに含めるようにすることをやっていきます。
僕はMacを持っていないから、今回の記事の起動テストはWindowsでしか行っていないので悪しからず。
時間があるときに、自分の知っている情報をきちんと纏めておかないと、その情報が必要な時にテンパって、うまく生かせないことがあるからね。
うん、僕は最近あったんだ。先月のゲームジャムのときにね。
この記事にはUnity2017.3.1f1を使用しています。
Unity上で外部の実行ファイルを起動するぞ
では早速、Unity上で外部の実行ファイルを起動してみよう。
外部の実行ファイルには、先月のゲームジャムで使ったjuliusの実行ファイルを使って検証していこう。
外部の実行ファイルの起動にはSystem.Diagnostics.Process
を使っていくぞ。
MSDN Process クラス
https://msdn.microsoft.com/ja-jp/library/system.diagnostics.process.aspx
TestController.cs
using UnityEngine; public class TestController : MonoBehaviour { [SerializeField] private string m_fileName = null; [SerializeField] private string m_directory = null; [SerializeField] private string m_arguments = null; [SerializeField] private bool m_hidden = false; private System.Diagnostics.Process m_process = null; private void Start() { System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(); // 実行ファイル名ー。 info.FileName = m_fileName; // 実行フォルダーー。 info.WorkingDirectory = m_directory; // 実行ファイルに渡す引数ー。 info.Arguments = m_arguments; // 実行ファイルを隠すか普通に表示するかー。 info.WindowStyle = m_hidden ? System.Diagnostics.ProcessWindowStyle.Hidden : System.Diagnostics.ProcessWindowStyle.Normal; try { m_process = System.Diagnostics.Process.Start( info ); } catch( System.ComponentModel.Win32Exception i_exception ) { Debug.Assert( false, i_exception ); m_process = null; } } private void OnDestroy() { if( m_process == null ) { return; } // まだプロセスが閉じていないなら、強制的にさようならだ。 if( !m_process.HasExited ) { m_process.Kill(); } // Dispose メソッドは Close を呼び出します。 配置すること、 Process 内のオブジェクト、 using ブロックを呼び出すことがなくリソースを破棄 Closeします。 // MSDNには上記のようにDispose()内でClose()を呼ぶらしいから、Dispose()だけでいいと思っているんだけど、 // Close()とDispose()を連続で呼んでいる、コードを見たことあるんだよなぁ。 // どうするのが一番正しいのかなぁ。 // m_process.Close(); m_process.Dispose(); m_process = null; } } // class TestController
はい、こんだけ。
簡単だね。
Unityプロジェクトに外部の実行ファイルを組み込んじゃうぞ
実行ファイルを絶対パスで指定して起動するなら簡単だ。
でも、これでいいのかなぁ。
自分一人だけのぼっちプロジェクトならいいけど、チームなど複数人でやる場合は面倒なことにならないだろうか。
チーム全員のパソコンの同じパスの位置に指定する実行ファイルがある必要があるんだ。
各自のパソコンのフォルダ構成は違うだろうし、Unityのプロジェクトフォルダの位置だって違うかもしれない。
その辺を考えると、絶対パスで外部の実行ファイルを起動するのは危険そうだなぁ。
だったらいっそのこと、この実行ファイルをUnityのプロジェクトの中に入れてしまおう。
プロジェクトのフォルダ構成はチームで同じものを使っているから、パスに気を使う必要はなさそうだ。
さあ、そうと決まったら早速今回検証用のjuliusのフォルダごと、Unityの中にぶっこんでしまおう。
OK。
これでチームの全員がこのjuliusを起動することが出来るようになったね。
念のため、このプロジェクトをビルドしたものでも動作を確認しておこうか。
めっさエラー出たけど。
どうやらjuliusの実行ファイルが無いらしいね。
まあ、そりゃそうだよね。
System.Diagnostics.Process
で指定しているのはAssetsフォルダだ。
どう見ても、ビルドして作成された実行ファイルの周辺にAssetsフォルダなんて無いもんなぁ。
データをそのままの形でビルドと一緒に出力されるようにする
では、どうすればビルドに外部の実行ファイルを含めることができるのだろうか。
先ほどはAssetsフォルダ直下に置いて駄目だったので、今度はResourcesフォルダに入れればいいのだろうか。
でもそれでも駄目そうだなぁ。
今回検証しているjuliusの場合は、juliusの実行ファイルが同フォルダ内のファイルを参照しているため、フォルダ構成がそのままの形でビルド時に出力される必要があるんだ。
うーん、どうすればいいんだ。
そう、そんなときはココ。
StreamingAssetsフォルダ。
これがあるんだよ。
StreamingAssetsフォルダに入れれば、ビルド時にそのままの形で出力されるんだ。
そしてこのフォルダにアクセスする際のパスは、Application.streamingAssetsPath
を使って簡単に指定することが出来る。
とっても簡単だ。
では、StreamingAssetsフォルダを使う形に処理とプロジェクトの構成を変えてみよう。
TestController.cs
using UnityEngine; public class TestController : MonoBehaviour { [SerializeField] private string m_fileName = null; [SerializeField] private string m_directory = null; [SerializeField] private string m_arguments = null; [SerializeField] private bool m_hidden = false; private System.Diagnostics.Process m_process = null; private void Start() { System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(); // 実行ファイル名ー。 info.FileName = m_fileName; // 実行フォルダーー。StreamingAssetsフォルダ内だよー。 info.WorkingDirectory = string.Format( @"{0}\{1}", Application.streamingAssetsPath, m_directory ); // 実行ファイルに渡す引数ー。 info.Arguments = m_arguments; // 実行ファイルを隠すか普通に表示するかー。 info.WindowStyle = m_hidden ? System.Diagnostics.ProcessWindowStyle.Hidden : System.Diagnostics.ProcessWindowStyle.Normal; try { m_process = System.Diagnostics.Process.Start( info ); } catch( System.ComponentModel.Win32Exception i_exception ) { Debug.Assert( false, i_exception ); m_process = null; } } private void OnDestroy() { if( m_process == null ) { return; } // まだプロセスが閉じていないなら、強制的にさようならだ。 if( !m_process.HasExited ) { m_process.Kill(); } // Dispose メソッドは Close を呼び出します。 配置すること、 Process 内のオブジェクト、 using ブロックを呼び出すことがなくリソースを破棄 Closeします。 // MSDNには上記のようにDispose()内でClose()を呼ぶらしいから、Dispose()だけでいいと思っているんだけど、 // Close()とDispose()を連続で呼んでいる、コードを見たことあるんだよなぁ。 // どうするのが一番正しいのかなぁ。 // m_process.Close(); m_process.Dispose(); m_process = null; } } // class TestController
この状態でビルドしてみるぞ。
うん、問題なく動いたね。
このことをゲームジャムの際に思い出していれば、余計な時間はかからなかったんだけどなぁ……。