徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】プロジェクト内のデータをビルド出力時にそのままの形で

 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

f:id:urahimono:20180218212123p:plain

 はい、こんだけ。
 簡単だね。

Unityプロジェクトに外部の実行ファイルを組み込んじゃうぞ

 実行ファイルを絶対パスで指定して起動するなら簡単だ。
 でも、これでいいのかなぁ。

 自分一人だけのぼっちプロジェクトならいいけど、チームなど複数人でやる場合は面倒なことにならないだろうか。
 チーム全員のパソコンの同じパスの位置に指定する実行ファイルがある必要があるんだ。
 各自のパソコンのフォルダ構成は違うだろうし、Unityのプロジェクトフォルダの位置だって違うかもしれない。
 その辺を考えると、絶対パスで外部の実行ファイルを起動するのは危険そうだなぁ。

 だったらいっそのこと、この実行ファイルをUnityのプロジェクトの中に入れてしまおう。
 プロジェクトのフォルダ構成はチームで同じものを使っているから、パスに気を使う必要はなさそうだ。

 さあ、そうと決まったら早速今回検証用のjuliusのフォルダごと、Unityの中にぶっこんでしまおう。

f:id:urahimono:20180218212144p:plain
f:id:urahimono:20180218212155p:plain

 OK。
 これでチームの全員がこのjuliusを起動することが出来るようになったね。

 念のため、このプロジェクトをビルドしたものでも動作を確認しておこうか。

f:id:urahimono:20180218212235p:plain

 めっさエラー出たけど。
 どうやらjuliusの実行ファイルが無いらしいね。

 まあ、そりゃそうだよね。
 System.Diagnostics.Processで指定しているのはAssetsフォルダだ。
 どう見ても、ビルドして作成された実行ファイルの周辺にAssetsフォルダなんて無いもんなぁ。

f:id:urahimono:20180218212318p:plain

データをそのままの形でビルドと一緒に出力されるようにする

 では、どうすればビルドに外部の実行ファイルを含めることができるのだろうか。
 先ほどはAssetsフォルダ直下に置いて駄目だったので、今度はResourcesフォルダに入れればいいのだろうか。
 でもそれでも駄目そうだなぁ。
 今回検証しているjuliusの場合は、juliusの実行ファイルが同フォルダ内のファイルを参照しているため、フォルダ構成がそのままの形でビルド時に出力される必要があるんだ

 うーん、どうすればいいんだ。

 そう、そんなときはココ。
 StreamingAssetsフォルダ

docs.unity3d.com

 これがあるんだよ。
 StreamingAssetsフォルダに入れれば、ビルド時にそのままの形で出力されるんだ。
 そしてこのフォルダにアクセスする際のパスは、Application.streamingAssetsPathを使って簡単に指定することが出来る。
 とっても簡単だ。

docs.unity3d.com

 では、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

f:id:urahimono:20180218212332p:plain

 この状態でビルドしてみるぞ。

f:id:urahimono:20180218212348p:plain
f:id:urahimono:20180218212356p:plain

 うん、問題なく動いたね。
 このことをゲームジャムの際に思い出していれば、余計な時間はかからなかったんだけどなぁ……。