【Unity】Unite 2015「白猫プロジェクトの裏側！～パフォーマンスチューニングとリアルタイム通信の全て～」レポート


# レポート一覧

- [Unity パフォーマンス・チューニング](http://qiita.com/baba_s/items/c536b923b6cdd919e73e)
- [Unityプロジェクトをひも解き把握するには](http://qiita.com/baba_s/items/446fa7a040f09bcd0ac6)
- [エディター拡張マニアクス 2015](http://qiita.com/baba_s/items/8abfebb18f43fcc5d319)
- [MOBIUS FINAL FANTASYにおけるUnity開発事例](http://qiita.com/baba_s/items/ce6b11a5a8952ecf2485)
- [白猫プロジェクトの裏側！～パフォーマンスチューニングとリアルタイム通信の全て～](http://qiita.com/baba_s/items/7c5680b4b808defddcb7)
- [Unity × Sprite Animation](http://qiita.com/baba_s/items/a410ca070a2d3d037ff8)
- [Unityを利用したスマートフォン向けゲームアプリ開発へのアプローチ方法](http://qiita.com/baba_s/items/34d97d970b7f8b17a568)

# 開発フロー

|時期|人数|開発期間|
|:--|:--|:--|
|プロトタイプ開発|2~3名|1か月|
|アルファ|10~13名|4か月|
|ベータ|15~18名|4か月|

リリースまでにかかった期間：1年弱
開発人数：最大18名

# 開発の特徴

- 全社配布
	- 全社員にベータ版の配布を行います
- 子供レビュー
	- 小学校高学年の子達にレビューをしてもらいます

# メモリ・パフォーマンス

- 基本はキャッシュ
- フラグメントシェーダ見直す
- 起動時間の高速化
- ロード時間の短縮化で出会った問題

# オブジェクトのキャッシュ

- ボトルネックはInstantiate
- ほとんどのオブジェクトがキャッシュされていなかった
- もっとも負荷が高かった箇所

```cs
if (action.hit)
{
	Instantiate(DamgeNumber);
}
```

- 改善方法

```cs
void Start(){
	CachedObjectManager.Instance.PreChache(obj, num);
}
if (action.hit){
	CachedObjectManager.Instance.Borrow(obj);
}
```

# AudioClipのキャッシュ

- ボトルネックはLoad
- 毎回ロードしてから再生していた
- 必要になった時にキャッシュするように

```cs
if (cachedClips.Contains(audio))
{
	clip = cachedClips[audio];
}
else
{
	cachedClips.Add(audio, clip);
}
```

# エフェクトのキャッシュ

- 今回は断念
- Activeにしたときに別の座標に表示されることがある
- ParticleSystems以外の制御もあった
- カメラ外にいるやつの座標の指定がうまくいかなかった
- スプライトアニメーションとかモーションでアニメを制御していたりとか
- キャッシュする前提で作られていなかった

# GPU側のボトルネック

- マップ画面で処理落ち
- 端末によってはバトルよりも重い
- シェーダーの問題
	- 原因は海のシェーダ
	- 描画面積が多い
	- フラグメントシェーダが原因

# フラグメントをとにかく軽く

- フラグメントシェーダーでアニメーション計算をしていた
- フラグメントで計算する必要がないものをバーテックスシェーダに移した
	- バーテックスシェーダへ波の計算を移動
	- 使っていない変数に計算後の値をセット

# シェーダの一括変換

- 必要のないカラー計算をしているシェーダーを一括変換
- PC用のシェーダーを使ってしまっているものも変換

# 白猫で実際に変換した際の条件

- Colorプロパティをもつ
- Colorをフラグメントシェーダーで乗算
- Colorの値が初期値のままである
- アニメーションをしない

# 起動時間の高速化

- ある程度プレイすると起動時間が遅くなることが判明
	- タイトルが出るまで
		- Android: 25秒
		- iOS: 1分
	- 白猫プロジェクトのアセットバンドルは現在約3万種類
	- `Caching.ready`が遅かった

# 独自のキャッシュシステムの作成

- 目標
	- `Caching.ready`を待たずに起動速度を早く
	- `WWW.LoadFromCacheOrDownload`を使わない
	- AssetBundleのバージョン管理ができる必要がある
- リリース後にシステムを書き換えた
	- 一度全部落とし直しになった
	- スリングショットブレイブ、ランブルシティなど弊社でのゲームでほとんど  
`WWW.LoadFromCacheOrDownload`は使っていない

1. 起動時にAssetVersionの入ったリストをダウンロード
2. PlayerPrefsに保存されているバージョンと比較
3. 更新されていたらDLする
4. ストレージへ書き込み
5. PlayerPrefsに新しいバージョンを入れる

- iOSの起動時間がが1分から8秒に!

# AssetVersionListの作成

- Path,VersionNumberの形式で各AssetBundleのバージョンを管理
- 複数の.csvで管理
- アセットバンドルがビルドされると同時にアセットバンドルバージョンリストのバージョンもアップする
- アセットバンドルとリストをサーバーにアップロードする

# ファイル読み込み数の問題

- iOSでは同時に開けるファイルの制限が256個
	- カットシーンで大量のAssetBundleをDLしていた
- 問題点
	- 上限数を超えたときに発生するため原因が特定しにくい
- 解決方法
	- カットシーンでのロードは1ファイルずつCloseしOnCompleteで何も行わないようにした

# メモリ、プログラムサイズの最適化

- 不必要なDLL、コードの削除
	- AssetStoreで購入したアセットのサンプル
	- Editorフォルダに入れる
- 同様の機能を行うものがすでに入っているのにdllをリンクしてしまっているケースがあった
	- 1つでもjsがあるとboo.lang.dllがリンクされるので必要ないのであれば削除しておく

# StrippingLevelの変更

- UnityEngine以下の必要なコンポーネントまでStripされてしまうことがあった
- AssetBundleに該当するコンポーネントを含んでしまっていた場合に正常に動作しなかった

# Linkerシーンの作成

- 必要なコンポーネントを持つオブジェクトをシーンに配置
- シーンの作成には自動でコンポーネントを抽出してGameObject生成
- AwakeでルートをDestroy

# AssetBundleのメモリリークの修正

- 白猫では明示的にReleaseする必要がある管理形式
- Releaseの前にDestroyされてしまいAssetBundleが解放されなかった
- 参照カウンタが理想

# Xcodeを使って調査

- FrameDebuggerでVRAMの中身をチェックして破棄されてなければならないテクスチャを調査

# 今後の課題

- CharacterControllerは重いからやめる
- UIの構築用ではない軽量な2Dアセットを探す
- キャッシュできて大量に表示できるエフェクトシステムの実装
- ダメージパケットなど大量に送信する際のバッチング

# リアルタイム・サーバー

- 白猫プロジェクトシステム構成
	- AWS
	- CacheServer: redis
	- WebServer(API): php zend
	- Load Balaner(ELB)
	- DB Server: MySQL
	- Web Soket Server: nodejs
	- Batch Server: php zend 
	- Adin Server: php zend 
- Web Socket Server
	- NGiNX
	- nodejs
	- RabbitMQ
	- php
	- MySQL
- MQの使用理由
	- デプロイするとコネクションが切れてしまうため簡単にデプロイできない
	- nodejsは汎用的に作ることで常に動き続けるようにした
	- ゲーム的な変更はCL側とバッチのPHPのみ

# マルチプレイの同期データ

- ホスト
	- ボスデータ
	- 自キャラ
		- 位置
	- アクション
		- アクション結果
	- ステージギミック
	- 共闘で必要になるデータのマスタデータを持つ

- ゲスト
	- 自キャラ
		- 位置
	- アクション
		- アクション結果
	- ホストからデータをもらう
		- 雑魚的の動きは同期しておらず各ユーザで自由に動き、ダメージなどは同期

# 通信保障データ

- データが受け取れないと進行不能になるような重要度の高いデータは  
通信保障データとして受け取り済みであることを送信者に送信する
- 接続されているかは定期的に各クライアントがpingを送信し  
サーバ側でpingがないユーザは切断したと判断する

# リアルタイム通信

- クライアント・サーバー注意点
	- データ不一致
		- クライアントがデータを保持するようなゲームアプリの場合データの不一致が発生する
		- リクエストが成功してレスポンスを受け取れないケース
		- モバイルでは移動などにより接続が切断されやすい
		- データが不一致だとサーバー側でエラーが発生
		- 消費したリクエストが成功したかどうかわからないためCL側のアイテム個数はもとのまま
		- tokenを付与して実行
	- クライアントの数値データの上限値
		- Bigintなど
		- DBの最大値を入れてテストする
		- クライアントの修正を伴う不具合になるかもしれないため