UnityEditorのバージョン管理をしたい
本記事は関東ゲーム制作部 Advent Calendar 2018 16日目の記事です。 adventar.org
経緯
昨日12/14にUnity 2018.3がリリースされましたね。prtimes.jp個人的には長らく切望していたNested Prefab機能が実装されたと聞いて早速使ってみたいと思いました。ですが、Unity使いの皆様ならよくあると思うのですが、「Unityのバージョンアップすると過去のが動かなくなるから上げたくないんだよなぁ...」ということがあるかと思います。デフォルトではUnityは1環境に1バージョンしか存在できないっぽい?ので、特に共同開発なんかしていると周りと足並みそろえて変更する必要があると思います。Pythonであれば、pyenvとかで複数バージョンを共存させられるけど、Unityでもそういったことできないのかな~と思っていたら2通りのやり方を見つけたので、誰かの役に立てばと思い、メモしておきます。
UnityHubを使う
私はこの記事を書くにあたって知りましたが、こちらを使っている人は割といるのではないでしょうか?UnityHubの特徴はこちらの記事にわかりやすくまとめられています。
qiita.com
要点だけ抜粋すると
- 複数のバージョンを管理できる
- Componentを追加インストールできる
- ver.2017以降であればUnityHub経由でインストールできる。それ以前もローカルにあれば、追加できる
- 同一バージョンのUnityEditorを同時に起動できる
あたりでしょうか。これを使えば、プロジェクトでは古いバージョンを使っているけど、自分の環境では新しいバージョンを試したいなんてときに便利だと思います。手っ取り早く使いたいならこっちを使うのがおすすめです。
Unity on Docker
ここからが本命かつマイナーなお話になります。最近、バージョン管理や環境を汚さないようにDocker上でいろいろやろうという話を聞きます。かくいう私もPythonの環境とかはDocker上で作ったりしています。そこで、最近流行りのDockerを使ってUnityを動かせないかと思いました。結論から言うとおすすめしません。それでも、供養の意味も込めて検討記録ということで長くはなりますが、それでもいいよという心優しい方はお付き合いください。
Docker自体のインストールも書いているといよいよ長くなりすぎるので、ここでは割愛します。私はWindows10なので、Docker for Windowsを使うためにこの辺りを参考にしました。 qiita.com ついでにGUIでコンテナ管理したいのでKitematicも入れています。
Dockerのインストールには成功しました。いよいよ、Unityのコンテナを用意します。といっても私はdocker-compose up -d
程度しか使えないので、誰かいい感じのDockerfile
なりdocker-compose.yml
など提供してくれてないかなぁと検索したところ、ありました。
github.com
探せばいるもんですね。先人は偉大です。しかもReadMeを読んだところなにも考えずにdocker-compose up -d
をと唱えれば良いと書かれているもんだからありがたい限りです。ということで、早速コマンドプロンプトを起動して、リポジトリをcloneしてきて、Dokerfile
があるディレクトリまで移動して、実行します。
※ここからは回線状況にもよりますが、作業に2~3時間程度要します。また、ディスク容量に4GB程度の空きを作っておく必要があります。
$docker-compose up -d
と叩くと、Dockerfile
に従ってインストールが始まります。時間がかかりそうだったので、放置していたら、タイムアウトの関係でいくつかのインストールに失敗していたのと、share directoryにのアクセスに失敗しました的なメッセージが出て止まっていました。同時にポップアップでshare directoryのアクセスを許可しますか?のようなものが出ていたので、OKを押して再インストール。うまくいかない場合は、一度インストールしたイメージ類をすべて消してから再度実行するとうまくいくと思います。
$docker image list #ダウンロードしたイメージ一覧を表示 $docker rmi *** #***で指定したイメージを削除
当該のイメージが見つかりませんでしたのようなメッセージが出た場合はタグをつけて削除するとうまくいくと思います。 www.memotansu.jp
これでコンテナのダウンロードまでは成功したようなのですが、コンテナの起動がうまくいかない。メッセージにリビルドしてねと出ていたので
$docker-compose up --build
を実行。概ね上手くいったぽいけど、すると今度は、
standard_init_linux.go:190: exec user process caused "no such file or directory"
Windows10を使っているのですが、どうやらリポジトリからcloneしてきたときに、改行コードがCRLF
に変換されて落としてきています。ですが、コンテナ自体はUbuntu
だからかLF
でないと動かないようです。
qiita.com
なので、テキストエディタで改行コードをLF
に変更して再度
$docker-compose up --build
を実行するとようやくUnityのコンテナが起動!!ゴールは近いです。提供ページに書かれている通り、VNC ViewerでVM内に入ってくださいとのとこなので、お好きなVNC Viewerを使ってコンテナ内に入ります。私は今回はUltra VNCを使いました。forest.watch.impress.co.jp www.atmarkit.co.jp辺りを参考にしながら、インストールの設定は基本デフォルトで、環境変数に登録とデスクトップにショートカットアイコンを作る的なものだけチェックを入れてインストール。さーて、IPアドレスを打っていざConnectと思ったのですが、またコケました。今度は何だと思ってKitematicのログを見ると、
Creating unity-service ... done Attaching to unity-service unity-service | stored passwd in file: /home/adminuser/.vnc/passwd unity-service | 16/12/2018 07:00:26 -usepw: found /home/adminuser/.vnc/passwd unity-service | 16/12/2018 07:00:26 x11vnc version: 0.9.13 lastmod: 2011-08-10 pid: 8 unity-service | 16/12/2018 07:00:26 unity-service | 16/12/2018 07:00:26 wait_for_client: WAIT:cmd=FINDCREATEDISPLAY-Xvfb unity-service | 16/12/2018 07:00:26 unity-service | 16/12/2018 07:00:26 initialize_screen: fb_depth/fb_bpp/fb_Bpl 24/32/2560 unity-service | 16/12/2018 07:00:26 unity-service | 16/12/2018 07:00:26 Autoprobing TCP port unity-service | 16/12/2018 07:00:26 Autoprobing selected TCP port 5900 unity-service | 16/12/2018 07:00:26 Autoprobing TCP6 port unity-service | 16/12/2018 07:00:26 Autoprobing selected TCP6 port 5900 unity-service | 16/12/2018 07:00:26 listen6: bind: Address already in use unity-service | 16/12/2018 07:00:26 Not listening on IPv6 interface. unity-service | 16/12/2018 07:00:26 unity-service | unity-service | The VNC desktop is: 767f1c3f2d95:0 unity-service | PORT=5900 unity-service | 16/12/2018 07:05:11 Got connection from client 172.16.250.1 unity-service | 16/12/2018 07:05:11 other clients: unity-service | 16/12/2018 07:05:11 webSocketsHandshake: unknown connection error unity-service | 16/12/2018 07:05:11 Client 172.16.250.1 gone unity-service | 16/12/2018 07:05:11 Statistics events Transmit/ RawEquiv ( saved) unity-service | 16/12/2018 07:05:11 TOTALS : 0 | 0/ 0 ( 0.0%) unity-service | 16/12/2018 07:05:11 Statistics events Received/ RawEquiv ( saved) unity-service | 16/12/2018 07:05:11 TOTALS : 0 | 0/ 0 ( 0.0%) unity-service exited with code 137
的なメッセージが出ていました。どうやらVNCアクセスに失敗していたのかよくわかりませんが、VNC Viewerを立ち上げる前にUnityのコンテナを立ち上げたのがいけなかったぽいので、一度コンテナをdown
させてからVNC Viewerを立ち上げた後に、再度
$docker-compose up --build
を実行すると、
Creating unity-service ... done Attaching to unity-service unity-service | stored passwd in file: /home/adminuser/.vnc/passwd unity-service | 16/12/2018 07:24:51 -usepw: found /home/adminuser/.vnc/passwd unity-service | 16/12/2018 07:24:51 x11vnc version: 0.9.13 lastmod: 2011-08-10 pid: 8 unity-service | 16/12/2018 07:24:51 unity-service | 16/12/2018 07:24:51 wait_for_client: WAIT:cmd=FINDCREATEDISPLAY-Xvfb unity-service | 16/12/2018 07:24:51 unity-service | 16/12/2018 07:24:51 initialize_screen: fb_depth/fb_bpp/fb_Bpl 24/32/2560 unity-service | 16/12/2018 07:24:51 unity-service | 16/12/2018 07:24:51 Autoprobing TCP port unity-service | 16/12/2018 07:24:51 Autoprobing selected TCP port 5900 unity-service | 16/12/2018 07:24:51 Autoprobing TCP6 port unity-service | 16/12/2018 07:24:51 Autoprobing selected TCP6 port 5900 unity-service | 16/12/2018 07:24:51 listen6: bind: Address already in use unity-service | 16/12/2018 07:24:51 Not listening on IPv6 interface. unity-service | 16/12/2018 07:24:51 unity-service | unity-service | The VNC desktop is: 6afa1c440a08:0 unity-service | PORT=5900
となり、Connection Errorは解消されたっぽい。ということで、ローカルホストにVNCServer: 127.0.0.1
でアクセスすると、パスワードを聞かれました。提供元のReadMeに書かれていた通り、パスワードpass123
を入力すると、無事、コンテナ内に入れました。こんな感じのコンソール画面だけが表示された画面が見えるはずです。で、肝心のUnity
はどこにあるのかというと、/opt/Unity/Editor/Unity
にあるとのことですが、デフォルトのディレクトリが/opt/Unity/Editor
なので、その場でUnity
を実行すれば起動できました。
adminuser@6afa1c440a08:~$cd Unity
あとは画面に従って通常のUnityのインストールと同様に進めていきます。
ログインもできて、さーて開発を始める準備が整ったことでしょう。新規に作るもよし、既存のプロジェクトを使うもよし。自分で作っていたプロジェクトを使いたい場合はCloudに上げておくか、ローカルであれば、cloneしてきたリポジトリ直下にあるcontextフォルダにおくように書かれていたのでお好みの箇所に置きましょう。コンテナ内からアクセスするときは、File System->context->任意のフォルダ
に入っています。
ところが、これで動くはず...あれ?一瞬エディター画面が表示されてAborted
とか言われるぞ...???なんかいろいろ調べるとバージョンによってはバグで動かないっぽいことを見かけたので、Unityのバージョンをアップすることにしました。
Installing Unity 2017.1.0f2 In Docker Container
使用したバージョンはDockerfile
に書かれていた当時のlatestであるver.2017.2.0f3にしました。バージョンを変更したい場合は、お好みのバージョンのURLをコピーしてDockerfile
の71行目あたりにある.deb
ファイルを指定しているURLを変更すれば行けました。それ以外のバージョンを使いたい場合は、たぶんこちらのスレッドから対応したUnityのイメージを落としてくればいいんだと思います。スレッドのページを下に繰っていくと、古いのだと5.1から最新で2017.2.0b11まであるみたいです。どれが行けてどれが行けないまでは検証していないのでわかりません(^-^;
バージョンアップして再度dockder-compose up -d
からやり直すと無事NewProject作成で画面が表示された...?
メモリが足りないのかこれ以上まともに描画されませんでした。こんな調子なので当然、プロジェクトが重すぎたのか、2dだからダメなのか自前のプロジェクトはうまく描画できませんでした。本当はこんな感じの画面を出したかったんですが...
画像は私の所属するサークルNOELで制作したゲームです。現在は第2作目のリリースに向けて鋭意制作中です。最新情報は随時Twitterで流していきますので、よろしければフォローしてやってください。@CircleNoelGame(←単なる宣伝です。)
これでめでたくDocker上でUnityが表示できるようになりました(まともに動くとは言っていない)。ものすっごく動作がもっさりしますが、とりあえず動いたということで、今回はここらへんで力尽きてしまいました。あとは割り当てメモリとか増やせばいいんですかね?でも、私の持つ低スペックPCでは3GB割り当てるのが精いっぱいなんですよね。年内に進展があれば、時間を見つけて続きを書きたいと思います。
ちなみにUnrealEngineのdockerイメージも存在はするようです。頑張ればこっちも行けるんでしょうかね(;^_^A github.com
少しでも誰かの参考になれば幸いです。よければサークルNOELもよろしくお願いします。 Twitterはこちら@CircleNoelGame
その他のリファレンス
2018年私的読みたいアドベントカレンダー(随時更新)
ゲーム開発
その3まである。やっぱり最近ホットなのでしょうか?その2でAgent-MLのことを書いてくださる方がいるようなので楽しみです。 qiita.com qiita.com qiita.com adventar.org ↑私も投稿しました t-n-clark.hatenadiary.jp
お勉強
最近、まじめにお仕事でも使う必要が出てきたのでこの機に勉強でもし直そうかと思ってます。GAN関連の論文紹介もちらほら見かけるのでそのあたりを中心に見ておこうと思ってます。 qiita.com qiita.com qiita.com
自作テストツールでもJenkinsで結果を可視化したい
経緯
C++で書いたコードのテストをしてテスト結果をJenkinsで可視化したい。だが、いかんせん低スペックな私にはその辺りの環境設定がわからん。周りからはとりあえず早くと急かされる。Jenkinsが読み込めるのはJUnitが出力する形式で書かれたXMLファイル。つまり、(わから)ないなら力業でその形式のXMLを書けばよかろうなのだ。 という頭の悪い発想に思い至ってしまったのが事の発端です。
JUnitの出力するXMLの構造を見てみる
今回は、こちらを参考にさせていただきました。 JUnitの実行結果のXMLフォーマット
私の稚拙な説明よりよっぽど明快に解説してくださっているので上のページを読んでいただければ十分かとは思いますが、概説すると
- testsuite
- name:テストスイート名(テストクラス名,テストのグループ名)
- tests:testsuiteに含まれるテストの総件数
- failures:testsuiteに含まれる失敗(AssertionError)となったテストの総件数
- errors:testsuiteに含まれるエラー(AssertionError以外の例外)となったテストの総件数
- time:testsuiteに含まれるテストの総実行時間(秒)
- testcase:testsuite要素の子要素
- classname:テストケースの属するクラス名
- name:テストケースの名前(半角数字で指定する?)
- time:テストケースの実行時間(秒)
- failure:テストが失敗した場合に追加するtestcase要素の子要素
- type:失敗の種類(例外クラス名など)
- message:例外に含まれるメッセージ
- error:テストが失敗でなくてエラーだった場合に利用。基本failure要素と同じ
- skipped:テストがスキップされた時に追加されるtestcase要素の子要素
- system-out:テスト実行時に標準出力に出力された情報をCDATAとして記述
- system-err:標準エラー出力について記述する以外はsystem-out要素と同じ
といった要素(スキーマ)を記述すればよさそうです。 恐らく日本語が混じっていてはダメです。
実装
ものすごく雑に組んでみます。実際は、テスト条件や期待値を外部ファイルから設定してテスト関数を動かすなど、もう少し工夫しています。
MakeXMLResult()
が最終的に出力を行っているところです。
#include <string> #include <vector> #include <fstream> #include <iostream> using namespace std; #define SUCCESS 0 #define FAILURE 1 #define EXCEPTION 2 struct SingleResult { int no; double time; int result; string name; string type; string message; }; struct TotalResult{ int test_tot_num; int ok_num; int error_num; int failure_num; double test_tot_time; }; class Test{ private: public: string OutputStdMessage(); string ErrorMessage(); void MakeXMLResult(string outputpath,vector<SingleResult> results,TotalResult tot_result); SingleResult DoTest(); TotalResult CalcTotalTest(vector<SingleResult> results); }; string Test::OutputStdMessage(){ return "std message"; } string Test::ErrorMessage(){ if(){//1件でもエラーか失敗があれば return "Some Failure or Error Tests Caused"; } else{ return "All Test Success"; } } void Test::MakeXMLResult(string outputpath,vector<SingleResult> results,TotalResult tot_result){ char c_filepath[_MAX_PATH]; sprintf_s(c_filepath,"%s",outputpath.c_str()); ofstream output(c_filepath); output << "<?xml version=\"1.0\" ?>" << endl; output << "<testsuite name=\"" << tot_result.testname << "\" " << "tests=\"" << tot_result.test_tot_num << "\" " << "errors=\"" << tot_result.error_num << "\" " << "failures=\"" << tot_result.failure_num << "\" " << "time=\"" << tot_result.test_tot_time << "\">" << endl; for(SingleResult result : results){ if(result.result == SUCCESS){ output << "<testcase classname=\"" << tot_result.testname << "\" " << "name=\"" << result.name << "\" " << "time=\"" << result.time << "\" />" << endl; } else if(result.result == FAILURE){ output << "<testcase classname=\"" << tot_result.testname << "\" " << "name=\"" << result.name << "\" " << "time=\"" << result.time << "\" >" << endl; output << "<failure type=\"" << result.type << "\" " << "message=\"" << result.message << "\" >" << endl; output << "</failure>" << endl; output << "</testcase>" << endl; } else if(result.result == EXCEPTION){ output << "<testcase classname=\"" << tot_result.testname << "\" " << "name=\"" << result.name << "\" " << "time=\"" << result.time << "\" >" << endl; output << "<error type=\"" << result.type << "\" " << "message=\"" << result.message << "\" >" << endl; output << "</error>" << endl; output << "</testcase>" << endl; } } output << "<system-out>" << "<![CDATA[" << OutputStdMessage() << "]]>" << "</system-out>" << endl; output << "<system-err>" << "<![CDATA[" << ErrorMessage() << "]]>" << "</system-err>" << endl; output << "</testsuite>" << endl; } SingleResult Test::DoTest(){ ///テストコードを書く ///例えば結果を構造体に書き込んでいく } TotalResult Test::CalcTotalTest(vector<SingleResult> results){ ///全体の集計結果を出しておく } int main(int argc, char* argv[]){ Test* test = new Test; string outputpath(argc<=1? ".": argv[1]); SingleResult result; vector<SingleResult> results; TotalResult tot_result; result = test->DoTest(); results.push_back(result); ///きっとテストは二つ以上あるだろうから tot_result = test->CalcTotalTest(results) test->MakeXMLResult(outputpath,results,tot_result); delete test; return 0; }
テストコードも書いてやって、実際に出力されたXMLはこんな感じです。各々の数字に深い意味はありません。
<?xml version="1.0" ?> <testsuite name="Sample" tests="10" errors="2" failures="7" time="10"> <testcase classname="Sample" name="101" time="1" > <error type="1" message="Error" > </error> </testcase> <testcase classname="Sample" name="201" time="1" > <failure type="2" message="Failure" > </failure> </testcase> <testcase classname="Sample" name="301" time="1" > <error type="3" message="Error" > </error> </testcase> <testcase classname="Sample" name="401" time="1" > <failure type="4" message="failure" > </failure> </testcase> <testcase classname="Sample" name="501" time="1" > <failure type="5" message="failure" > </failure> </testcase> <testcase classname="Sample" name="601" time="1" > <failure type="6" message="Execute" > </failure> </testcase> <testcase classname="Sample" name="701" time="1" > <failure type="7" message="Execute" > </failure> </testcase> <testcase classname="Sample" name="801" time="1" > <failure type="8" message="Execute" > </failure> </testcase> <testcase classname="Sample" name="802" time="1" /> <testcase classname="Sample" name="910" time="1" > <failure type="9" message="Failure" > </failure> </testcase> <system-out><![CDATA[std message]]></system-out> <system-err><![CDATA[Some Failure or Error Tests Caused]]></system-err> </testsuite>
これをJenkinsのビルド後の処理で「JUnitテスト結果の集計/テスト結果XML」で指定したディレクトリに出力されるようにしておけばJenkinsで見れるようになります。 注意点としては
PythonからJenkinsのジョブを実行したい
経緯
最近ぼちぼち出張も増えてきて、出先で作業することも増えてきました。 出先ではノートPCを使って作業するのですが、手配したばかりで中は環境構築がなされていないすっからかんのPCです。 今後のためにがっつり環境を作ってもいいのですが、せっかく新品なのであまり環境を汚したくないですし、頑張ったところでまあスペックはたかが知れているようなものなので、なんとか普段使っているPCのリソースを使って重たい作業を回したいなあと考えていました。 そんな折、何をとち狂ったのかのか、「Jenkinsのジョブで実行ファイルを叩けば行けるんじゃない?ついでにFlaskでフロントを作ってWebアプリとして動かせるようにしてしまえ。」と思い立ってしまったのが事の始まりでした。 ということでなんか本来の使い方から外れているような気もしますが、タイトルの通りpython(Flask)からJenkinsのジョブを実行する環境を作ったので備忘録的に残しておきます。
Jenkins環境
サーバー環境はすでに用意してあるものとします。 まず、ユーザー名とAPIトークンをメモっておきます。 Jenkinsにログインしてユーザー名をクリックすると設定の中から確認できます。 次にリモートから実行を受け付けられるようにjobを用意します。 jobの種類は恐らくはなんでもいいのですが、今回は「フリースタイル・プロジェクトのビルド」で作ります。基本的には普通にジョブを作る時と一緒なので、リモートから実行するときに便利そうな設定だけ書いておきます。
ビルドのパラメーター化
ビルドのスクリプトを書くときにパラメーターを使いたい場合に設定します。POSTリクエストを投げた際にここで設定した名前をキーとしてパラメータを取得できます。
ビルド・トリガ
「リモートからビルド」にチェックを入れて認証トークンを設定します。 恐らくなんでもいいので任意のトークン名を入力します。 ここでJenkinsの画面に、「リモートからビルドするには次のURLを使用します~」のようなメッセージが出ていると思うので、ここにリクエストを投げればリモートからジョブを実行できます。 ビルドのパラメーター化でパラメーターを設定した場合は、「buildWithParameters?token」の方を使用します。「TOKEN_NAME」の部分には認証トークンが入ります。 ビルド Windowsバッチコマンドであれば%
で囲ってあげるとビルドのパラメータを取得できます。
SET PARAM = %param%
APP.exe %PARAM%
シェルスクリプトの場合は$
でしたっけ?ここはJenkinsではなくバッチやシェルスクリプトの書き方に沿って書いてください。
これでJenkins側の設定は終わりです。
pythonのスクリプト
次にpython側のスクリプトを作ります。今回はFlask経由で実行するので若干冗長です。実行だけでよければjob
の関数の部分さえあれば十分です。
render_template
とredirect
はWebアプリとして使いたいがためのお飾りです。
使う環境は3系を使います。
from flask import Flask ,request,redirect,render_template
import urllib3
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/job')
def job():
param = request.args.get('param')
url = 'JENKINS_URL/job/jobname/buildWithParameters?token=TOKEN_NAME¶m='+param #¶mの部分はJenkinsのビルドのパラメーター化で設定したキーにしてください
username = "username" #Jenskinsのユーザー名
password = "password" #JenkinsのAPIトークン
http = urllib3.PoolManager()
headers = urllib3.util.make_headers(basic_auth=('%s:%s' % (username,password)))
response = http.request('POST', url, headers=headers)
return redirect('http://localhost:8080/')
if __name__ == '__main__':
app.debug = True
app.run(host='0.0.0.0',port=8080)
適当にhtmlも書きます。
See the Pen KxVvgQ by Sashimimochi (@sashimimochi) on CodePen.
これでlocalhost:8080
にアクセスして実行ボタンを押せばJenkinsが稼働しているサーバーのリソースを使って実行ファイルを動かせるようになりました。これで出先でもある程度作業ができるようになったかな。
セキュリティー等は考慮していないガバガバ構築なのでそのあたりは悪しからず。
認証proxy環境下でpythonからはてなブログに下書き投稿する
最近、プログラムはちょこちょこいじっているのですが、はてブのサイトを開いて記事を書くのをさぼっていたので投稿間隔が空いてしまいました。 ブラウザ開かなくても作業しながら手元のコードを手軽に投稿できれば少しはさぼりが減るかもと思い調べてました。 ということで、さっそく本題へ。 今回使用したコードはこちらを利用させていただきました。
とある事情があってproxy配下から送りたかったのですが、このままでは送れないので微修正をば。 上記のコードにproxyの設定を足すだけなので変更含めて加えるのは5行です。 79行目の`requests`の部分を
proxies = {
'http':'http://ユーザー名:パスワード@プロキシサーバーのアドレス:ポート番号/',
'https':'http://ユーザー名:パスワード@プロキシサーバーのアドレス:ポート番号/'
}
r = requests.post(url, data=data, headers=headers,proxies=proxies)
に変更するだけです。 これで無事送れるようになりました。今後はもう少し投稿頻度を上げられるといいな。
ちょっと気を付けないと行けないのが、送るmarkdownに&とか<とか>とかのHTML特殊文字が混じっていると400エラーを出して送信に失敗するので&などに直してから上げる必要がありそうですね。