【Unity】Unity Performance Casual Talk 「Unityカジュアルゲーム・ケーススタディ」 レポート


# レポート一覧

* [Live2Dの描画の裏側の話](http://qiita.com/baba_s/items/0de1702e6055a4503167)
* [Unityカジュアルゲーム・ケーススタディ](http://qiita.com/baba_s/items/9289f1eab711f04faec8)
* [ドローコール伝説の終わり](http://qiita.com/baba_s/items/5260807ced7fc3c02ca6)

# GREE Tech Talk #07 : Unity Performance Casual Talk

http://techtalk.labs.gree.jp/07/

## niconico LIVE

http://live.nicovideo.jp/watch/lv214504347

# スライド

http://www.slideshare.net/greetech/gree-tech-talk7

# グリーにおけるUnity

## Amy the Starry Archer(エイミー・ザ・スターリーアーチャー)における開発事例

* 開発期間は11月から3月
* Director Programer1名
* Programer(Junior)1名
* Artist2名
* (3D Designer1名)
* (Effect1名)
* (Sound1名)

## 対応デバイス

* iOS7以降、デバイスはiPhone5以降
* Android4.0以降/デバイスは2年以内に発売された機種
	* ただしQA結果で対応コストの高いデバイスは切る
* タブレット端末も対応

# Target Frame Rate

* iPhone5s~、Nexus5~等は60fpsをターゲットに
* 最低ラインは30fps
* 見た目だけじゃなくて、操作感、難易度にも影響する為やや注力
* Application.targetFrameRateを動的に変える

```cs
void Update(){
	if ( changeFpsDynamically == false ){
		return;
	}
	currentSec += Time.deltaTime;
	if ( currentSec > INSPECTION_END_SEC ){
		changeFpsDynamically = false;
	} else if ( currentSec > INSPECTION_START_SEC ){
		// 予想されるdeltaTimeの2倍以上ならフレームレートを落とす
		float deltaTimeThreshold = 2f * ( 1f / currentFrameRate );
		if ( Time.smoothDeltaTime > deltaTimeThreshold ){
			currentFrameRate--;
			if ( LOW_FAR_CLIP_THRETHOLD == currentFrameRate ){
				// ここまで下がったならfarClipを落とす
				geometryCamera.farClipPlane = 3000f;
			}
			if ( currentFrameRate < FPS_MIN ){
				// ここまで下がったら諦める
				changeFpsDynamically = false;
				return;
			}
			Application.targetFrameRate = currentFrameRate;
		}
	} else {
		// do nothing
	}
}
```

## 基本のおさらい

* Graphics
	* static batchingを有効に
	* dynamic batchingを有効にするための諸条件を満たす
	* ピクセルライティングを使わない
		* そもそもリアルタイムライティングをなるべく減らす
	* テクスチャ圧縮
	* アルファテスト、アルファブレンディング最小化
* Scripts
	* Object PoolingでInstanciate()を避ける
	* newを減らしてGCを減らす
* Physics
	* Colliderはなるべくプリミティブコライダを組み合わせる
	* fixedDeltaTimeは可能な範囲で小さく

# Clouds

## Overdraw多すぎ

* 雲の空間埋める近景オブジェクトとして使うと厄介
* 基本半透明だし、ある程度細かくないと雲間を通り抜けるような表現をした時不自然

## Overdrawを減らす

* 全体的に雲をカメラから遠くに置き、スプライトを大きく
* 中景用、遠景用の雲に分け、遠景の雲はより大きくして画面を埋める

## Scripts/Default

* Sprite用のシェーダの不要な処理を削ってみたりもしたものの、  
フラグメントシェーダは削れなかったのでほぼ効果無し
	* 検証環境：SC-04E/Android4.4.2
* フラグメントシェーダのColor処理も削りたかったが、  
雲出現時にフェードインさせる必要があった為削れず

## まとめ

* 雲のように、アルファブレンディングが発生するSprite(Particle含む)で画面を埋める場合、  
Overdrawをいかに減らすか工夫する
* 絵がチープにならない範囲で1枚あたりのSpriteを大きくし、枚数を減らす

# Lighting


* Asset Storeの3D背景素材をフル活用
* しかし、そのまま使うと統一感のない見た目になる
* メインのビジュアルとして「夜空」を押してたので
	* スカイボックスを夜空に変えて
	* フォグでごまかす

## ライトマップを直接編集

* Asset Storeの3D背景素材は、ライトマップに使われたLightが削除されている事がある
* 1からLightを配置し直して再現するのはしんどいので、
ライトマップテクスチャ自体を調整すると楽
* exrファイル(OpenEXR)は一部の画像編集ソフトで編集可能

## ライトマップテクスチャ解像度

* 1024px(700KB)
* 512px(170KB)
* 256px(43KB)

もともとAsset Storeの3D背景素材に付随していたライトマップが
1024px(700KB)だったので、解像度を256px(43KB)に落とした

## 動的にライトマップ解除

```cs
Renderer[] renderers = FindObjectsOfType<Renderer>();
foreach ( var renderer in renderers ){
	renderer.lightmapIndex = -1;
}
```

* ライトマップは動的にon/offを切り替えられる
* lightmapIndexはlightmapSettingslightmapsのインデックスだが、
-1はライトマップが割り当てられていないことを表す初期値
*どこかのバージョンで1バイトから2バイトに変更された模様

## その他Lightingについて

* 敵キャラクターは頂点ライティングのみ
	* 常に動き回ってて、しかも遠めなのでそこまで気にならない
	* でもまっすぐDirectional Lighを当てるだけだと安っぽいので、
Point Lightを複数使う
* Light Probesは使わず
* ライトマップは256px(43KB)×10枚前後×シーン数
	* あんまり背景の一箇所を見つける機会のないゲームなので

# Scripting

## Raycast()

* 弓はUnityの衝突判定を使わず、
毎フレームRaycast()で処理
* RaycastAll系は、衝突する順に返してくれるわけではない点に注意
	* 当ゲームでは仕様上、都度RaycastHit[]をソートで十分にワーク

## 検証

* 重たいとされるFind()系の中でも、FindObjectOfType()が段違いに重かった(7~10ms/call)
* Raycast()はColliderの数、Find()系はヒエラルキーに影響されるはずなので、実際に使うScene上で計測することが大事

# Enemy Move


* 敵の動きが静的な場合ってどんな実装がメジャーなんでしょうね？
* とりあえず手付けで制御点置いていくような作業はしたくなかった
* でも難易度設計上、少し複雑な動きもさせたい

* 線分と中心の円の二つの動きを足し合わせるなどして敵の動きを再現した
* Inspector上でパラメータを設定できるようにした

* フォーメーションを組んでくるような動きは、同じ機動の敵で出現座標をずらせばOK
* 同じ軌道と同じ出現位置で、出現時間をずらせば、1つの軌道上を連なって動く表現
* 補助機能を用意して生産性は上がったが、直感的に設定しづらいので、慣れるのには時間がかかる