Unityでの開発が本格的に始まる前にやっておきたいこと

この記事は [Akatsuki Advent Calendar](http://qiita.com/advent-calendar/2016/aktsk) 24日目の記事として書かれました。
前回の記事は @begaborn による、 [AWSの失敗談 - しらなくてミスした話](http://qiita.com/begaborn/items/94d926fb7f741c0fd931)でした。

## 始めに

私が所属する株式会社アカツキでは2013年ごろよりモバイルのネイティブゲームの開発を始めており、携わったプロジェクトの数はいつの間にか片手だと数えられなくなりました。

プロジェクトではコードを書く以外の役割として、チーム内のルール整備、CI環境のセットアップ、開発進行上の問題解決、会議のファシリテーションなど、チーム開発で発生する様々なことにも対応してきました。

クライアント側のゲームエンジンも、独自実装 (C++) -> Cocos2d-x (C++) -> Unity (C#) とプロジェクト開始時点の開発生産性や採用難易度等を考慮し変えてきましたが、最初にやることはそんなに変わっていません。

この記事では、[平鍋さんの記事 "アジャイルの「ライトウィング」と「レフトウィング」"](http://blogs.itmedia.co.jp/hiranabe/2012/09/rightwing-and-leftwing-of-agile.html) のうち、ライトウィング（開発環境）の話を中心に、Unityでのプロジェクト開発初期で何を整えるか、ということを書きたいと思います。


# 開発ルールを決める

最初にチームの開発者全員が納得できるコーディングルールを決めることが重要です。開発が進んでいくと他人のコードを読むことが多くなりますが、統一されていないコードを読むのは中々辛いものがあります。
また、最初にルールを決めておくと、エディタやCIである程度自動化することができるため、全体の開発効率が上がります。

## コーディングルール

### コーディングスタイル

コーディングスタイルは、人によって考え方の違いがでやすいものなので、何かを指標として、カスタマイズするかどうかを話し合うのが良いでしょう。
C#であれば、https://msdn.microsoft.com/ja-jp/library/ff926074.aspx を元に話し合うのが良いと思います。

### タブと空白

チームで話し合ってソフト or ハードタブ、インデント等を決めたら、[どんなエディタでもEditorConfigを使ってコードの統一性を高める](http://qiita.com/naru0504/items/82f09881abaf3f4dc171) を参考に .editorconfig をレポジトリ直下に置き、エディタに設定しておきます。

※ 既に混在しているプロジェクトでも、GitHubを使っている場合はWeb上の表示が `.editorconfig` に対応しているので、レビューしやすくなります。 （新しく追加するファイルなどでうまく適用されない場合もありますが、大抵はサポートしてくれます）

### チェックの自動化

StyleCop等を使い、厳密に管理するかどうか決めます。
CIでチェックを自動化しているというケースも聞いたことがありますが、自分のプロジェクトではそこまでやったことはないです。

## バージョン管理とレビュールール

### バージョン管理

バージョン管理システムに何を使うかを決めます。

[Unityのドキュメント](https://docs.unity3d.com/ja/current/Manual/VersionControl.html) には

> Unity は Perforce や PlasticSCM など、よく使用されているバージョン管理ツールをサポートしています。

という記載があり、[PlasticSCMの比較表](https://www.plasticscm.com/comparisons.html) を見るとPlasticSCM最高っぽい感じですが、プロプライエタリなソフトウェアはエコシステムが発展しづらいですし、マージに大きく違いがあるかというと疑問です。実質的には以下の様な比較になると考えています。

バージョン管理ツール | エコシステムの発展度 | Unityエディタのサポート | バイナリの扱い
--- | --- | --- | ---
git | ◎ | × | △
mercurial | △ | × | △
Perforce | × | ◯ | ◎
PlasticSCM | × | ◯ | ◯

チームでの開発プロセス全体を見たときにGitHubの機能は大変に魅力的ですので、アカツキではGitを選択しています。
また、Unityでの開発では大きなバイナリデータを扱うことがおおく、gitはバイナリデータの扱いに弱い（差分管理しない）という特徴があるので、GitLFSと併用しています。

少し前まで、GitLFSは1ファイルずつAPIコールしてしまうために、大量のファイルをダウンロードするのに向かないという特徴がありました。
最近になって、[GitLFSは遅いというイメージを払拭したい](http://qiita.com/shibukk/items/e9af45a6009646c7e14f) の通り実務に耐えられる程度の速度改善が行われているので、大きな問題では無くなってきました。

ただ、SceneやPrefabの管理について最善の方法はまだ見つかっていません。
Unity5から [SceneやPrefabの構造がマージしやすくなったり](https://blogs.unity3d.com/jp/2015/02/17/production-workflow-improvements/)、[UnityYAMLMerge](http://labs.gree.jp/blog/2015/04/13836/) というツールが生まれたりして改善しているらしいですが、衝突しないわけではないですし、衝突したら辛いので、「同じSceneを複数人で編集しないように気をつける」といった運用でのカバーはまだ必要です。

これは、どのVCSを使っても完全に対応できるツールは無いと思いますので、現在は Git と Unity の組み合わせを避ける大きな理由はなくなっているように思います。

### レビュールール

レビューステータス（レビュー可、レビュー済、対応待ち）といった状態をどう監理するか？誰がOKを出すのか？マージは誰が担当するか？を決めます。

アカツキのプロジェクトの例だと、「レビューステータスをGitHubのタグで管理し、レビューイーが指定した担当者がOKを出し、マージはレビューアーが行う」というルールになっています。

そのルールを推進するために、GitHubの使い方にいくつかの工夫をしています。

<img width="992" alt="ReviewStatus.png" src="https://qiita-image-store.s3.amazonaws.com/0/15918/c9c3959b-213b-7cb0-9aea-371e6e46c917.png">


## ディレクトリ構造

ディレクトリ構造を最初に決めるのは大変ですが、後で変更するのはもっと大変です。
Unityでは、`Assets/` 以下の構造を決めます。大まかにルール決めておき、状況にあわせて対応していくのが良いと思います。

- Scriptの配置ルール
  - ManagerやUtilityといった共通で使うクラスをどこに置くか？
  - UI用のクラスとデータ管理のクラスをどのように分けるか？

- SceneやPrefabの配置ルール

- AssetBundle化する（であろう）リソースの配置ルール
  - リソース種別で配置するか？利用元の機能別で配置するか？組み合わせる場合、どういう構造にするか？



# CI環境

1日1回程度の定期的なビルドで要求を満たす場合は、Unity Cloud Buildで十分です。

しかし、Pull Requestベースでビルドしたい、かつUnityでの開発に携わる人数が5人以上になる場合は、Unity Cloud Buildの待ち時間が辛くなるので、Jenkins + Mac Mini等で自前のCI環境をセットアップすることをおすすめします。

以下はJenkins + MacOSを利用した、CI環境のセットアップ手順です。Jenkinsの詳細なインストール手順などは省力し、Unityのビルド自動化に必要な内容を記載しています。

## CI 環境(Mac OS)のセットアップ

1. Jenkinsのインストール

    特に気をつけることはありません。

1. Unityのインストール

    複数のUnityバージョンで実行できるよう、インストール先ディレクトリにバージョンを含めます。
例えば、`/Applications/Unity5.5.0f3/Unity.app` といった形で配置します。

## Androidビルド

コメンドラインからビルドできるようにします。

### 環境設定

Android SDK, NDKをインストールしておきます。

```sh
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew update
brew install android-ndk
brew install android-sdk
android update sdk --no-ui --all --filter "tools,platform-tools,build-tools-24.0.1,android-24,extra-google-google_play_services,extra-google-m2repository"
```

### ビルド設定

#### Unityビルド処理

```c#:Assets/Editor/Build/Package.cs
public static void AndroidBuild()
{
    PlayerSettings.statusBarHidden = true;
    PlayerSettings.bundleIdentifier = Environment.GetEnvironmentVariable("BUNDLE_IDENTIFIER");
    PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, env);
	
    SetPrefsByEnv("AndroidSdkRoot", "ANDROID_SDK_ROOT");
    SetPrefsByEnv("AndroidNdkRoot", "ANDROID_NDK_ROOT");
    SetPrefsByEnv("JdkPath", "JDK_ROOT");
	
    BuildPipeline.BuildPlayer(GetAllScenes(), Environment.GetEnvironmentVariable("APK_PATH"), BuildTarget.Android, BuildOptions.None);
}
	
static void SetPrefsByEnv(string key, string environmentName)
{
    var value = Environment.GetEnvironmentVariable(environmentName);
    if (!string.IsNullOrEmpty(value)) {
        EditorPrefs.SetString(key, value);
    }
}
```

#### シェルスクリプト

Unityで定義したスクリプト処理を実行するシェルスクリプトを書き、実行します。

※ GUIでUnityが起動している場合はエラーになってしまうので、Unityが終了していることを確認してください。
	
```sh:jenkins/android_build.sh
JDK_ROOT=`/usr/libexec/java_home`
ANDROID_NDK_ROOT=/usr/local/Cellar/android-ndk-${NDK-VERSION}/${NDK-VERSION} # NDKバージョンを指定してください
ANDROID_SDK_ROOT=/usr/local/Cellar/android-sdk/${INSTALLED_SDK_VERSION} # SDKバージョンを指定してください

UNITY_PATH=/Applications/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity # Unityバージョンを指定してください
BUNDLE_IDENTIFIER="your.domain.application-name" # Bundle ID を指定してください
APK_PATH="path-to-apk-file.apk" # 出力先を指定してください

rm -f $APK_PATH
${UNITY_PATH} -batchmode -quit -projectPath ${PROJECT_DIR} -executeMethod Build.Package.AndroidBuild -logFile /dev/stdout
```
	
実行後、`APK_PATH`で指定したパスに、apkファイルが出力されていれば成功です。

## iOS

iOSもAndroid同様、コメンドラインからビルドできるようにします。
Androidビルドでは、Unityから直接APKファイルを生成することができましたが、iOSビルドでUnityができるのはプロジェクトディレクトリの生成までです。生成後はXCodeでビルドする処理が必要なので、うまく自動化する必要があります。

### CI環境設定

1. XCodeのインストール

	最新のXCodeをインストールしておきます。


2. Rubyのインストール

	Ruby gemとして提供されているFastlaneを使うため、http://qiita.com/ringo/items/4351c6aee70ed6f346c8 あたりを参考に、新しいRubyをインストールしておきます。
	
### ビルド設定

#### Fastlane

FastlaneによるiOSのビルドの自動化を紹介します。
FastlaneはiOSの複雑な認証設定やビルドを自動化するためのツールです。証明書管理からビルド、App Storeへの申請まで、iOSのリリースに関わる全ての作業をコマンドラインにできてとても便利です。また、ログ出力がアイコンや表で見やすくフォーマット化されているのも良いです。

Fastlaneには複数のツール群で構成されており、[現在は大きく10個のツール](https://fastlane.tools/) に分かれています。また、どのような機能があるか細かく確認したい時は、`fastlane actions` でリストアップすることが出来ます。

コードサインの管理に使うツールでも`cert`及び`sigh`、または`match`の2種類が存在するので、運用方法に合わせて使い方を考える必要があります。

最初は証明書、プロビジョニングプロファイルの管理とビルドができれば十分なので、`match`と`gym`の2つを使うこととします。

<img width="1165" alt="fastlane.png" src="https://qiita-image-store.s3.amazonaws.com/0/15918/6ad9385e-04be-9fe4-8602-bd2852e7f5b9.png">


※ 引用元: https://fastlane.tools/


#### Fastlaneの設定

##### Gemfile

リポジトリ直下にGemfileを置き、`bundle install`を実行しておきます。

```ruby:Gemfile
source 'https://rubygems.org'

gem 'fastlane'
```

##### Fastlane

FastlaneはFastfileというファイルによって、ビルド内容を定義できます。
例えば、以下のように定義します。

```ruby:jenkins/Fastfile
DEV = OpenStruct.new(
  {
    username: "my-apple-developer-account@aktsk.jp",
    git_url: "git@github.com:aktsk/my-project-ios-certificates.git",
    app_id: "jp.aktsk.my.project.application.id",
  }
).freeze
	
lane :dev_certificates_update do
  match(git_url: DEV.git_url, username: DEV.username,
        app_identifier: DEV.app_id, type: "appstore", readonly: false, force: true)
end
	
lane :dev_certificates_fetch do
  match(git_url: DEV.git_url, username: DEV.username,
        app_identifier: DEV.app_id, type: "appstore", readonly: true)
end
	
lane :adhoc_build do
  dev_certificates_fetch
  gym(
    project: "iOS/Unity-iPhone.xcodeproj",
    scheme:  "Unity-iPhone",
    output_directory: "iOS/build",
    use_legacy_build_api: true,
  )
end
```

1. dev\_certificates\_update

	`username`で指定したAppleアカウントに、新しく証明書とプロビジョニングプロファイルを生成し、`git_url`にアップロードします。

2. dev\_certificates\_fetch

	`git_url`にアップロードされているファイルをダウンロードし、証明書をKeychainに登録して、プロビジョニングプロファイルをXCode管理下のディレクトリに配置します。

3. adboc_build

    `dev_certificates_fetch`を実行した後、`gym`によるビルドを実行します。なぜか`project`で指定するディレクトリは`fastlane`実行ディレクトリの一つ上からの相対パスになるので、この例だと`iOS`ディレクトリにFastfileを配置する必要があります。（`iOS/../iOS/Unity-iPhone.xcodeproj`という指定です。）

    また、XCode8からAutomatic Code Signingという機能ができ、チームを指定することで、XCodeにプロビジョニングプロファイルの生成を任せることができるようになりました。これは、[UnityプロジェクトをJenkinsを使用してXCode8対応した](http://qiita.com/altemina/items/c4747f26615792495a97)の通り、Unity上でDEVELOPMENT_TEAMを指定すると、iOSプロジェクトにチームを設定することができます。また、gymのオプションに`export_team_id`を指定することでAutomatic Signingを有効にすることが出来ます。
    しかし、コマンドラインからプロビジョニングプロファイルの更新をすることができないという問題が残ります。そのため、デバイス追加の度にCI環境でのプロビジョニングプロファイル更新作業が必要です。
    上記理由により、gymのオプションに`use_legacy_build_api: true`を指定して、XCode7以前のAPIを使うことで`match`により管理されたプロビジョニングプロファイルを使うようにしています。

##### 証明書の更新

2で設定したレポジトリに新しい証明書をアップロードしておきます。
実行すると新しい証明書が生成されますので、既存のアプリケーションに影響がないか確認してから実行してください。

```sh
bundle exec fastlane dev_certificates_update
```

#### Unityビルド処理

以下のように定義しておきます。

```c#:Assets/Editor/Build/Package.cs
public static void IOSBuild()
{
	BuildOptions opt = BuildOptions.SymlinkLibraries | BuildOptions.AllowDebugging |
	                   BuildOptions.ConnectWithProfiler | BuildOptions.Development;
	PlayerSettings.statusBarHidden = true;
	PlayerSettings.defaultInterfaceOrientation = UIOrientation.LandscapeLeft;
	PlayerSettings.iOS.sdkVersion = iOSSdkVersion.DeviceSDK;
	PlayerSettings.bundleIdentifier = Environment.GetEnvironmentVariable("BUNDLE_IDENTIFIER");
	BuildPipeline.BuildPlayer(GetAllScenes(), "iOS", BuildTarget.iOS, opt);
}
```


#### ビルドスクリプト

ビルドを実行するためのスクリプトです。同ディレクトリにFastfileがあることを前提としています。


```sh:jenkins/ios_build.sh
BUNDLE_IDENTIFIER="your.domain.application-name" # Bundle ID を指定してください
UNITY_PATH=/Applications/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity # Unityバージョンを指定してください

# Make iOS Project
${UNITY_PATH} -batchmode -quit -projectPath ${PROJECT_DIR} -executeMethod Build.Package.AndroidBuild -logFile /dev/stdout

# Copy fastfile
cp ./Fastfile ../iOS/

# Install fastlane
cd ..
bundle install

# build package & export ipa file
cd iOS
rm -f build/*
bundle exec fastlane adhoc_build	
```

スクリプトを実行後、`iOS/build/application-name.ipa`が出力されていれば成功です。

## Jenkinsジョブ設定

フリースタイルジョブとして、`jenkins/android_build.sh`, `jenkins/ios_build.sh` をJenkinsから実行するジョブを作成します。

Unityはバックグラウンドで実行する場合、複数起動できますので、並列に実行するように設定しておくと良いでしょう。

http://blog.monochromegane.com/blog/2014/11/09/jenkins-github-commit-status/ 等を参考に、GitHub pull request builder plugin をトリガーとして、ビルドジョブの実行結果を通知すると、レビューが捗ります。

注意点を以下に記載します。

- ビルド後の処理で、「成果物を保存」する
    - ビルドに失敗している場合でも、Unityの戻り値が0の場合があるので、成果物ができているかどうかで検証をする
    - 古いビルドの破棄を設定する。保存日数と保存最大数は、CI環境のスペックに応じて調整しておく
- TestFlightやDeployGate等の開発アプリ配信環境があれば、アップロード処理を加える
- 終了後の処理で、チャットツールに失敗時の通知をすると良い

## 最後に

詳細に書かなかった例として

- Unity Assetやライブラリの選定と検証計画
- ゲームに組み込むSceneの単位と、開発Sceneとのマージ方法
- 何をPrefab化して、どう監理するか
- アセットバンドルの設計とバージョン管理、CIの設定
- 画像や音アセットの圧縮方法の検討
- ネットワークAPIとの通信設計

などなど、チーム開発を進めるために考えることはたくさんありますが、「開発ルールが決まっていて」、「CI環境が存在している状態であれば」開発を素早く進める準備はできました。あとは、「協同でゴールに向かうチーム環境」を作っていくフェーズになります。

これからUnityでの開発を始める方、ビルドの自動化を進めたいけど手を付けられていない方の参考になれば幸いです。