UnityでHoloLens開発する際のGit管理について


概要

対象読者

HoloLens開発初心者の自分自身と、私の周りの開発者を想定しています。周りの人はGitや開発自体も初めてなので、その辺りも少しだけ関連ページを紹介するつもりです。同じ境遇の人にも参考になれば嬉しいです。
私はHoloLens開発もQiitaも初心者で、右も左も分からずやっているところがあります。もしアドバイスなどあればいただけると嬉しいです。

今回の構成

背景

HoloLensでチーム開発

HoloLens開発クラスタは今まさに伸び盛りな印象があるのですが、どうも色々話を聞いている感じだと個人プロジェクトになりがちのようです。要はチームで開発するノウハウ記事があまり無いイメージがあります。
私もHoloLens開発の立ち上げを行うこととなったのですが、チーム開発っぽくHoloLens開発を進めていきたいなぁと、まずは環境整備をしてみました。

チーム開発といえば、まずGit

ですよね。Gitについては検索すれば数多の解説サイトがありますが、取り急ぎ下記2つの記事をご紹介。

Gitの基本的な使い方は下記サイトが詳しいです。
サルでもわかるGit入門 〜バージョン管理を使いこなそう〜

実際のプロジェクトでGitを運用するのであれば、Git Flowなどのリポジトリ管理・運用方法に則っておくと分かりやすくて良いです。
Git-flowって何?

今回の記事ではGitについては既に知っているものとして進めます。

3Dで開発するUnityプロジェクトってどうやってgit mergeするの・・・?

Gitは言わずもがなバージョン管理システムのひとつです。ぶっちゃけその時々のバージョンを管理するだけなら何も考えずにUnityプロジェクトをGitに入れてしまえばある程度は使えます。
しかしチーム開発をするとなると、複数の開発中のバージョンをbranchとして運用していく必要があります。そこでは、他人が書いたソースコード(branch)と自分が書いたソースコード(branch)を上手く結合(merge)出来なければ、結局同時並行に開発することが出来なくなってしまいます。

私、普段はWEB開発・PHPがメインだったので、全てソースコードやHTMLなどテキストベースで管理されている世界でやってきました。テキスト同士ならmergeもイメージ湧くのですが、Unity Editorで管理している3Dの世界をmergeとか全然イメージ湧きませんでした。例えば2つのbranchで別々の場所にオブジェクトを配置したらどうやってmergeするのでしょう?むしろ3DのオブジェクトってUnity内部的にはどうやって表現されてるの?というのもよく分からず・・・。
というわけで、merge辺りを中心に色々調べてみました。

HoloLensのUnityプロジェクトをGitでバージョン管理する

今回の記事で使わなかったUnity Teamsなど

この辺りは先人が結構いて記事もそこそこあります。チーム開発環境としては既にUnity Teamsが公式で出ているのと、Github for Unityというツールも出ており、良い感じのようなのですが、ちょっと今回はBitBucketやGitLabを使いたかったのと、SourceTreeを使い慣れていたというのもあり、Unity TeamsやGithub for Unityは使わずにいきたいと思います2

参考:
Unity Teams
Github for Unity を導入してみる

UnityはYAMLで各情報を管理しているので、これをmergeしたい

シーンやオブジェクトはYAMLで管理されている

C#で色々コードを書いているのであれば、ソースコードがどこにあるかは自身で管理しているので分かります。ただ、私自身「オブジェクトやその設定などを、どこにどの形式で持っているのか」については、普段Unity Editorで編集しているので、先述した通り良く分かっていませんでした。

調べてみるとUnityのドキュメントには下記のようにあります。

Unity のシーン形式は YAML データシリアライズ言語により実装されています。ここでは YAML について深く掘り下げませんが、オープンな形式であり仕様については無料で http://yaml.org/spec/1.2/spec.html にて確認できます。シーンの各オブジェクトはファイルに対して別 YAML ドキュメントとして書かれ、ファイルの中では — シーケンスにより区切られます。文脈の中で “オブジェクト” はゲームオブジェクト、コンポーネント、および他のシーンのデータを一括して指し示します。これらの各々のアイテムは個々に YAML ドキュメントがシーンのファイルに必要です。
引用:フォーマットに関する説明

試しに.unityファイルをテキストエディタで開いてみる

Unityではシーンごとに.unityファイルを保存すると思うのですが、試しにこれをそのままテキストエディタで開いてみるとテキストのYAML形式で記載されています。中身を見てみると、何やら見慣れた用語が出てくるのに気が付くはずです。
とりあえず、Unityで新規プロジェクトを作成して、Cubeを配置してからそのままシーンを保存すると、Cube部分が下記のようなファイルになっています。

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:--- !u!1 &1621268630

~~中略~~

GameObject:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  serializedVersion: 5
  m_Component:
  - component: {fileID: 1621268634}
  - component: {fileID: 1621268633}
  - component: {fileID: 1621268632}
  - component: {fileID: 1621268631}
  m_Layer: 0
  m_Name: Cube
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
--- !u!23 &1621268631
MeshRenderer:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 1621268630}
  m_Enabled: 1
  m_CastShadows: 1
  m_ReceiveShadows: 1
  m_DynamicOccludee: 1
  m_MotionVectors: 1
  m_LightProbeUsage: 1
  m_ReflectionProbeUsage: 1
  m_Materials:
  - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
  m_StaticBatchInfo:
    firstSubMesh: 0
    subMeshCount: 0
  m_StaticBatchRoot: {fileID: 0}
  m_ProbeAnchor: {fileID: 0}
  m_LightProbeVolumeOverride: {fileID: 0}
  m_ScaleInLightmap: 1
  m_PreserveUVs: 1
  m_IgnoreNormalsForChartDetection: 0
  m_ImportantGI: 0
  m_StitchLightmapSeams: 0
  m_SelectedEditorRenderState: 3
  m_MinimumChartSize: 4
  m_AutoUVMaxDistance: 0.5
  m_AutoUVMaxAngle: 89
  m_LightmapParameters: {fileID: 0}
  m_SortingLayerID: 0
  m_SortingLayer: 0
  m_SortingOrder: 0
--- !u!65 &1621268632
BoxCollider:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 1621268630}
  m_Material: {fileID: 0}
  m_IsTrigger: 0
  m_Enabled: 1
  serializedVersion: 2
  m_Size: {x: 1, y: 1, z: 1}
  m_Center: {x: 0, y: 0, z: 0}
--- !u!33 &1621268633
MeshFilter:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 1621268630}
  m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1621268634
Transform:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 1621268630}
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
  m_LocalPosition: {x: 0, y: 0, z: 0}
  m_LocalScale: {x: 1, y: 1, z: 1}
  m_Children: []
  m_Father: {fileID: 0}
  m_RootOrder: 2
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

全部見てしまうとなんだか訳が分からないように見えますが、要は「YAMLというテキスト形式で設定値を管理している」というのがポイントです。MeshFilterとかTransformとか、Unityで開発していると良く見る単語があるので、そこを手掛かりに眺めていると、「あ、Unityエディタで見ているあの設定値がここに書いてある!」のような発見があると思います。しかも同様に.assetファイルなどもテキストエディタで開いてみるとYAML形式で書かれています。

テキスト形式ならDiffやMergeの十八番じゃん」ってことで、いつも通りにDiffやMergeすれば大丈夫そうです。

UnityではmergeするのにSmart MergeやUnityYAMLMergeを使う

競合が起きたら中身を解釈しながらMergeしなければならない

とはいえ、YAML形式で書かれたUnity用のフォーマットを、中身を解釈しながらMergeするのは骨が折れます。何せ元々YAMLをテキストファイルとして編集している訳でもなく、Unity EditorでGUIを通して編集しているので慣れていません。
そんな中、特に「オブジェクトの順番が入れ違った上で一部変更があって・・・」などが起きると、Diffした時に変更している部分とそうでない部分がパッと見分からず発狂しかねません3。更に、チーム開発しているとどんどん競合(confrict)が起きます。これを解消するにはどうしたら良いでしょうか?

SourceTreeでUnityYAMLMergeを使う

UnityではSmart MergeというUnity内部でのマージツールと、UnityYAMLMerge.exeという外部ツール用のマージツールが標準で入っています。これらを使うことで、YAMLの中身をある程度解釈しながらmergeすることが可能です。今回はGitクライアントにSourceTreeを使用し、これでマージをしていきたいので、SourceTreeにUnityYAMLMerge.exeを設定します。

設定の仕方は割と簡単で、SourceTree側のオプションを設定し、リポジトリ上にUnityYAMLMergeの設定ファイルを作成すれば大丈夫です。

SourceTreeの設定

まずはSourceTreeの設定を行っていきます。SourceTreeの[ツール]⇒[オプション]を開き、[Diff]タブを選んで下記のように設定します。

[外部Diffツール]にはP4Mergeを使います。これはインストールしておき、SourceTreeのプルダウンで「P4Merge」を選択しておけばすぐに使えるようになります。
[マージツール]にUnityYAMLMergeは無いので、「カスタム」を選びます。Unityをインストールしたフォルダを「C:\Program Files\」もしくは「C:\Program Files (x86)」から探してきて、そこにあるUnityYAMLMerge.exeを[Diffコマンド]に指定します。

<UnityフォルダまでのPath>\Editor\Data\Tools\UnityYAMLMerge.exe
例:C:\Program Files\Unity-2017.4.1f1\Editor\Data\Tools\UnityYAMLMerge.exe

[引数]にはmergeコマンドを指定します。

merge -p $BASE $REMOTE $LOCAL $MERGED

ここまでで、SourceTree側の設定は完了です。

UnityYAMLMergeの設定

また、UnityYAMLMergeにはマージ対象のソースフォルダの直下に「auto」という設定ファイルが必要です。これは拡張子の無いテキストファイルで、下記のようなP4Mergeを指定する内容を作成して保存しておきます。サンプルがUnityのUnityYAMLMerge.exeが入っているToolsフォルダにmergespecfile.txtとして記載されています。他のマージツールを使用する場合はこのファイルを参考にしてみてください。

# Perforce merge
* use "%programs%\Perforce\p4merge.exe" "%b" "%r" "%l" "%d"
* use "%programs%/p4merge.app/Contents/Resources/launchp4merge" "%b" "%r" "%l" "%d"

参考

Smart Merge - Unity Documentation
【Unity】数名で一つのSceneやPrefabを編集しスマートにマージする
概要UnityYAMLMerge

.gitignoreで不要なファイルを除く

さて、ここまででなんとなくGit上にあるコードをDiffしたりMergeしたりすることが分かってきました。ある意味、これで最低限のリポジトリ管理は出来るのですが、余計なファイルが入っていたり、リポジトリ自体が肥大化してしまうと管理が大変です。

まずは余計なファイルをリポジトリに入れないようにする.gitignoreを整備していきましょう。.gitignoreはGitHubが提供しているサンプル(下記)そのままでOKそうです。Unityプロジェクトフォルダの一番上の階層に.gitignoreというファイルを作ってメモ帳などで保存しておきます。

gitignore/Unity.gitignore

Unityでビルドする時は「Build」「Builds」などのフォルダを作ってそこにビルドするようすると、Git側が無視してくれます。
また、クローンした後、購入したアセットなどが正常に動くか心配になりますが、最初にUnityで開いた際に、Unityが改めて展開してくれるので大丈夫です。たまに上手くいかない時もありますが、Unityを閉じて開き直すと上手くいったりすることもあります。

Unityプロジェクトはファイルサイズが肥大しがちなのでGit LFSを使う

Git LFSは大きなファイルを扱う

不要なファイルはこれでリポジトリにアップロードされなくなりました。これだけでもいらないファイルがGit・SourceTreeに上がらなくなるのでMergeも楽になります。ところが、今度は3Dのオブジェクトを扱うUnityあるあるなプロジェクトの肥大化との闘いになります。特に素材となる3Dモデルや画像ファイルなどのバイナリファイルをそのままGitに上げてしまうとファイルがすぐに何GBにもなり、ローカルへcloneするのも一苦労になるだけでなく、BitBucketやGitLabなどもリポジトリが肥大化しすぎて使えなくなってしまいます4。Webシステムなら軽いファイルばかりであんまり気にする必要もなかったのに今回はそうもいきません。

Gitにはこのような大きなファイルなどを上手く扱うための仕組みとして、GitLFS(Git Large File Storage)というものが用意されています。各種Gitサービスもこれを使うことで大きなファイルを上手くハンドリングできるようになっています。

Git LFSはGitサーバー側とGitクライアント側で設定が必要です。

Gitサーバー側の設定

Gitサーバー側は今回BitBucketを使っているので、必要ならリポジトリの設定にある「Allow LFS」にチェックを入れます。ただ、bitbucket.orgだとデフォルトでONになっているようなので、そのままGitクライアントで設定をすれば大丈夫そうです。

Gitクライアント側の設定

Gitクライアント側はSourceTreeを使っていればSourceTree側がGit LFSツールをインストールしてくれて、後はよろしくやってくれます。ただ、問題はリポジトリの設定です。どのファイルをGit LFSに上げてどのファイルを今まで通りGitに上げるかを設定しなければなりません。設定は.gitignoreと同様、Unityプロジェクトフォルダの一番上の階層に.gitattributesを作成し、そこに行います。

今回は下記の設定をお借りしようと思います。いくつか事例を探してみたのですが、これがシンプルで分かりやすかったです。
guitarrapc/.gitattributes

下記の設定はさらに細かく作りこんでいるので、今度もう少し試したり中身を見てみたいと思っています。
nemotoo/.gitattributes

参考

GitLFSは遅いというイメージを払拭したい
Unityで始めるバージョン管理 Git LFS 入門編
BitBucket Serverでgit lfsを利用する

(宿題)HoloLensってUWP側の開発はVisual Studioでやるけど大丈夫?

これが今回まだ悩んでいるところです。Gitとはちょっと範疇が異なるのですが、バージョン管理する上で何か起きそうかなと警戒しています。
Unity側(.Net Framework)とHoloLens側(.NET Core)で裏で動いているライブラリが違うのが原因で、Unity内部でUWP(.NET Core)の機能を使えない問題が、そのままソースコードの管理に影響ありそうな気がしています。とは言え、まだUWP側開発に手を出していないのですが、HoloLensの強みはUWPでこそ色々出てくる雰囲気を感じています。

下記のサイトがとても分かりやすいのですが、要はUnity側でリビルドした時にVisual Studio側で設定していたUWP周りの構成が上書きされてしまうのが問題のようです。

参考:
UnityによるHoloLens用UWPアプリケーション開発の勘所
unity側からUWP側の機能をつかう(改訂版)とデバッグのちょっと便利な方法(備忘録)

注釈


  1. 2018年8月12日現在、HoloLensで使用するMixed Reality ToolkitはUnity 2017.4.0が最新だったのですが、3日前にv2018.7.0.0版のPre-releaseが出てるので胸熱感ある。 

  2. また、もう少しUnityのコード管理のことを実感持って知りたかったのもあり、結果的には良い勉強になりました。 

  3. 順番が入れ違った部分全てが差分として表示されてしまうため、その中の部分的な変更は全て目で見比べる必要が出てしまいます。 

  4. 2018年8月12日現在、Bitbucketでは上限1GBで警告され、2GBまでいくとpushできなくなります。