Unityのエディタを複数同時に開いて同時編集したり色々とやるアイディアを伊藤周 (@warapuri) | Twitter氏に頂いたので色々とやってみました。
この方法は明らかにUnityエディタの想定した動きではないので、やる場合は自己責任でお願いします。
Unityエディタが複数同時に起動できない理由
細かい所は置いておいて、Unityがエディタを並列に立ち上げることが出来ないのは、大体LibaryとTempフォルダのせいです。
- Temp:エディタ終了時とエディタ開始時に消す短期的フォルダ
- Library:プラットフォームの変換結果等を格納する長期的なフォルダ
このため、単一のプロジェクトフォルダでUnityエディタを複数立ち上げると、TempフォルダやLibraryフォルダへの不当な書き込みが発生しおかしな挙動となります。
例えば以前Unityエディタのクラッシュ時に失われたシーン情報を復旧させる裏ワザ_で紹介しましたが、__EditModeSceneファイルにはシーン再生時に直前まで編集していたシーン情報が格納されます。ここで2つ目のエディタがこの情報を上書きすると、一つ目のエディタのゲーム再生終了時におかしな挙動となります。
また、1つ目のUnityエディタでPC向けに操作中に、2つ目のUnityエディタでiOS向けプラットフォームに切替え、1つ目のUnityエディタでゲームを出力しようとすると「フォーマットが対象プラットフォームに変換出来てない」的なエラーとなります。これの応用で、Unityエディタをバックグラウンドで切り替えておくと、ベースのプロジェクトのスイッチプラットフォームが一瞬で終わります。*1
複数のUnityエディタの開き方はこちら
Mac版のUnityで複数のプロジェクトを同時に開く方法 - 強火で進め
プロジェクトを複数のUnityエディタで開く
Unityエディタ並列起動時の問題について書きましたが、
逆を言えば、このTempフォルダとLibraryフォルダを共有さえしなければ、Unityエディタは並列でビルドを行うことが可能になります。
つまりAssetsとProjectSettingsを共有すれば、ある意味同一プロジェクトを並行作業する事が出来る訳です。場合によってはLibraryを共有しても良いかもしれません。ビルドしかしない場合とか。
フォルダのコピー作戦
例えばプロジェクトフォルダをもう複数用意すれば同時にエディタを起動する事ができます。各プラットフォーム毎にプロジェクトを用意すればスイッチプラットフォーム時の変換も走りませんし、Tempが競合する事もありません。
各プラットフォーム毎のファイルはGitのブランチか何かで管理しておき、ベースのプロジェクトから常にフォルダをプルする感じで管理します。他とくらべて同期が非常に安全に出来るので、安心して使えます。エラーも出ませんし。
この方法は、プラットフォーム毎に色々と設定したい場合は案外良いです。
問題点は親フォルダの変更が即時反映されない点、それとHDD容量がマッハな点です。HDDは正直コストと見るには安い所ですが*2、即時同期では無いのは安心して使える代わりに少しワークフローが必要になるかもしれません。
ファイルをコピーするので初回待ち時間は次に紹介するシンボリックリンクよりちょっとだけ長いです。*3
シンボリックリンクでAssetsフォルダ共有作戦
AssetsやProjectSettingsといった「並列に作業していても余り競合しない」物に関しては、シンボリックリンクを使用する手があります。
シンボリックリンクとは、要するに「凄いショートカット」です。通常のショートカットやエイリアスの場合は単なるファイルとして扱われていますが、シンボリックリンクの場合はリンク先のフォルダが有るかのように振る舞います。
要するに、一つのフォルダやファイルをシンボリックリンクを使って複数のフォルダでシェアします。これならばHDDに優しく、コピーの負荷のありません。またワークフローとして「ベースで調整→子で動作確認→ベースで調整…」のような面倒な所を省き、同一プロジェクトの複数のシーンを同時に編集する事ができます。
シンボリックリンクはmacでは以下の様なターミナルのコマンドで設定できます。*4
ln -s ソース 対象
例えば下のような構造の場合、以下のように行います。*5
- ターミナルを開きます。
- ターミナルのカレントディレクトリを、シンボリックリンクを作成したいフォルダへ移動します。
面倒なら cd と入力後に 対象のフォルダをドラッグ&ドロップし、エンター - 以下のコマンドを入力します。
ln -s ../ABSample/ProjectSettings/ ProjectSettings
ProjectSettingsのフォルダが追加され、下に矢印のような物が表示されていれば成功です。もし白いファイルのような物が表示された場合は、大抵の場合は「パスが間違ってる」状態です。
ただ、自分はシンボリックリンクを作るフォルダ以外でシンボリックリンクを作ると大抵白いファイルが表示されてしまうので、実際の条件は微妙にわかってません。
Assetsの中身を追加するのは少しトリックが必要です。というのも、どうやらUnityは「Assetsフォルダの親フォルダ」をRootと認識しているらしく、Assetsフォルダ自体をシンボリックリンクにしてしまうと上手くいきません。
なので、Assetsフォルダ以下のファイル/フォルダ全部をシンボリックリンクに設定してしまいます。
ln -s ../../ABSample/Assets/* .
これで下の画像のように、Assets以下に大量にファイルが配置されます。
この操作を行うと、UnityをAndroid BuildとABSampleの2つのシーンを開くことが出来るようになります。また、Android Buildが変更した内容は即座にABSampleに反映され、逆にABSampleで行った変更はAndroid Buildに反映されます。シーンの場合は再読込が発生します。
シンボリックリンクの問題点は、ファイルの有無を保証しない事です。例えばAssets直下にあるtest1.unityの名称をtest2.unityに変更した場合、リンクが切れ参照が出来なくなります。
このため、シンボリックリンクで作業を行う場合、大本のシーンがファイルやフォルダに関して何らかの変更を行った場合、シンボリックリンクを再構築する必要があります。もしくはAssets/ゲーム名 みたいな形で設計し、Assets直下のフォルダ構成を可能な限り変更しないといったアイディアがあります。
またもう一つ、先ほど紹介したメリットである同時編集がそのままデメリットになります。つまりビルド時にAssetsフォルダ以下に変更を加える系の処理が同じプロジェクトを操作している全Unityエディタに影響してしまい、ビルドが上手くいかないケースがあるかもしれません。
それと起動時にエラーメッセージが出ます。「これシンボリックリンクで動いてるけど本当に良いの?俺はしらねーぞ」的な。
ちなみにシンボリックリンクみたいな機能で色々とやるのは幾つかの所で普通に使ってるみたいです。
unionfs作戦
もう一つのアイディあとしてunionfsという物があるそうです。
検証に時間かかりそうなので後回しにします。どうも、複数フォルダを単一フォルダとしてマウントし、親フォルダの変更を即座に子フォルダに同期する、ローカルバージョン管理みたいな物らしいです。こちらの場合は子の変更が即座に親に影響を与える事は無いみたいです。
AssetBundleを作る
Unityは知っての通り異なるプラットフォームへ切り替えるのは非常に時間がかかります。特にAssetBundleのを 生成する場合、AssetBundleにまとめるファイルだけではなく、プロジェクトの全ファイルを再インポートします。
このため、例えば Assetbundleを作る際に何も考えずに作るファイルのプラットフォームを何度も切り替えるととても時間がかかります。この辺りの情報は AssetBundleを高速に作る が参考になります。
例えば以下のコードのようにファイル毎にプラットフォームを切り替えてると馬鹿みたいに時間がかかります。
foreach( var obj in objects )
{
// Android向けにプラットフォーム切替が発生
BuildPipeline.BuildAssetBundle (obj, new Object{obj},
string.Format("AB/{0}android.pack", obj.name),
BuildAssetBundleOptions.CollectDependencies,
BuildTarget.Android);
// iOS向けにプラットフォーム切替が発生
BuildPipeline.BuildAssetBundle (obj, new Object{obj},
string.Format("AB/{0}iOS.pack", obj.name),
BuildAssetBundleOptions.CollectDependencies,
BuildTarget.iPhone);
}
この場合、プラットフォーム毎にまとめてビルドするのが良いです。但し、どちらにしてもスイッチプラットフォームが走ります。
foreach( var obj in objects )
{
BuildPipeline.BuildAssetBundle (obj, new Object{obj},
string.Format("AB/{0}android.pack", obj.name),
BuildAssetBundleOptions.CollectDependencies,
BuildTarget.Android);
}
foreach( var obj in objects )
{
BuildPipeline.BuildAssetBundle (obj, new Object{obj},
string.Format("AB/{0}iOS.pack", obj.name),
BuildAssetBundleOptions.CollectDependencies,
BuildTarget.Android);
}
この点、Libraryフォルダをプラットフォーム毎に保持している今回紹介している方法は良い感じに動作します。要するにプラットフォーム切替が発生しないため、基本的なコストはassetbundleを纏めて圧縮するコストになります。*6*7
AssetBundleのビルドはエディタを立ち上げるまでも無いので、コマンドでやってしまいます。例えば以下の様なコマンドです。
/Applications/Unity/Unity.app/Contents/MacOS/Unity -buildTarget プラットフォーム -quit -batchmode -executeMethod メソッド -projectPath パス
これはこんな感じで使います。
AssetbundleCreaterクラスを用意し下のメソッドを定義します。コードの中身はGetObjectsが返すオブジェクト一覧を一つづつ取り出してAssetBundleにパックする的な内容です。実際使う場合は一つづつと言わず幾つかのパックに纏めるのが良いです。*8
static void BuildBothAssetbundles ()
{
Object objects = GetObjects ();
foreach( var obj in objects )
{
BuildPipeline.BuildAssetBundle (
obj,
new Object{obj},
string.Format("AB/{0}{1}.pack",
obj.name,
EditorUserBuildSettings.activeBuildTarget),
BuildAssetBundleOptions.CollectDependencies,
EditorUserBuildSettings.activeBuildTarget );
}
}
後はコマンド叩いてAssetBundleをビルドします。
/Applications/Unity/Unity.app/Contents/MacOS/Unity -buildTarget Android -quit -batchmode -executeMethod AssetbundleCreater.BuildBothAssetbundles -projectPath 各プラットフォーム向けプロジェクトのフルパス
またLibraryをシンボリックリンクでシェアしておけば、複数のエディタで並列してAssetBundleを作成する事ができます。コアが多いPCであれば、短時間でビルドが終わります。
その場合、例えばiOSコピーを作ったとして以下のような形になります。但し、Libraryを並行して作らせる訳にはいかないので、一旦指定プラットフォームで起動するだけのコマンドを一度実行し*9、しかる後に並行でビルドするコマンドを叩くのが良さそうです。
BaseProject
|- Assets
|- Library
|- ProjectSettings
iOS Build
|-Assets(BaseProject/Assetsのシンボリックリンク)
|-Library
|-ProjectSettings(BaseProject/ProjectSettingsのシンボリックリンク)
iOS Build2
|-Assets(BaseProject/Assetsのシンボリックリンク)
|-Library(iOS Build/Libraryのシンボリックリンク)
|-ProjectSettings(BaseProject/ProjectSettingsのシンボリックリンク)
並列化に関しては色々と考えたのですが、すべての状況に対応できてフレキシブルに並列処理数を変動出来る並列化の回答が思いつかなかったのでとりあえずここまで書きます。
要望があればテストに使用した単純なプロジェクトとかを公開するかもしれません。
*1:ただし、スイッチプラットフォームの負荷が半端ないので実用的かと言われると微妙です
*2:今は2テラバイト8千円前後で買えます
*3:どちらにしろ初回インポートが長いので気にしてもしょうがない所ですが…
*5:※今回はプロジェクト名「ABSample」を使うので、ソースのパスは大体コレが入ります。実際は自分の使ってるフォルダパスを指定して下さい。
*6:当然、Libraryが保持してるプラットフォーム以外のプラットフォームのAssetBundleを出力しようとすると変換が走ります
*7:Androidはなにかそれ以外のコストが発生しているように見えます
*8:なお、当たり前の事ですが、コマンドで実行する前にエディタで動作確認した方が良いです。
*9:プラットフォーム切替が完了しLibraryが作成される