他人のシーン編集を見える化するぜ！

# プロローグ

「Advent Calendar2016Unity 2 Advent Calendar 2016」の記事だよ。
だれかが書こうとしてたけど、12月12日はとっくに過ぎてたんで上書きますｗ
もとの方はUniRxで書こうとしてたっぽいから、僕もUniRxも使うことにしますね。

# 前書き

チームでUnity使っていて、シーン編集をするときにいちいちチャット部屋で共有とか、他の人に確認するの手間かと思います。
それを見える化する方法を考えました。

## 今回使うもの

- Unity 5.5.0x1-CollabOreview
- UniRx 5.5.0　（Unityとほぼ同じだｗ）
- Python 3.5
- gunicorn 19.6.0
- Falcon 1.1.0

※今回DBは面倒なのでプレーンテキストにしますｗ

## サーバ準備

API作るのにFalconが楽そうだったのでそうします。
PHP使いたい人はPHP使って下さい。

### バーチャル環境
```
virtualenv-3.4 falcon_apps
source bin/activate
```

Falconとgunicorn入れるよ

```
pip install falcon gunicorn
```

### Pythonのコード

```python3:sample.py
#!/usr/bin/env python
# -*- coding:utf-8 *-

import falcon
import json

FNAME = 'text.json' 

class QuoteResource:
    def on_get(self, req, resp):
        str = self.read()
        # resp.body = json.dumps(str)
        resp.body = str

    def on_post(self, req, resp):
        resp.status = falcon.HTTP_200
        resp.content_type = 'text/plain'
        resp.body = 'post!!'
        # リクエストボディを読み込んで JSON を解析
        body = req.stream.read().decode('utf-8')
        print(body)
        self.write(body)

    # 本当はDBから書く
    def write(self, data):
        # 追記なのでa
        with open(FNAME, 'a') as outfile:
            json.dump(data, outfile, indent=4, sort_keys=True, separators=(',', ':'))
            # これがないと改行がない
            outfile.write('\n')

    # 本当はDBから読む
    def read(self):
        content = ''
        with open(FNAME) as fp:
            for line in fp:
                data = json.loads(line)
                dic = json.loads(data)
                msg = '{} が {} の {} を編集しました ({})'.format(dic['user'], dic['screenPath'] , dic['sceneName'], dic['time'])
                print(msg)
                content += msg + '\n'
    
        return content

api = falcon.API()
api.add_route('/quote', QuoteResource())
```

#### Falconはここを参考にしました

https://freedom-man.com/blog/python-falcon/

#### jsonのファイル記載と、読み込み系はここから参考に(Martin Thomaさんのコメント)

http://stackoverflow.com/questions/12309269/how-do-i-write-json-data-to-a-file-in-python

### 起動

```
gunicorn sample:api
```

sampleはファイル名`sample.py`から、
apiは中にある変数名。

## Unity側

UniRXはAsseetStoreからとってきて下さい。
別にUniRXを使わずに`WWW`を生で書いてもOKです。
こちらを書きます。`Editor`フォルダに入れなくてもいいですが、入れた方がいいかも？

```csharp:MyAssetModificationProcessor.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UniRx;

public class MyAssetModificationProcessor : AssetModificationProcessor
{
	[Serializable]
	public class CustomModel
	{
		public string user;
		public string screenPath;
		public string sceneName;
		public string time;
	}

	public static string[] OnWillSaveAssets(string[] paths)
	{
		// Get the name of the scene to save.
		string user = "shinriyo";
		string scenePath = string.Empty;
		string sceneName = string.Empty;

		foreach (string path in paths)
		{
			if (path.Contains (".unity")) {
				scenePath = Path.GetDirectoryName (path);
				sceneName = Path.GetFileNameWithoutExtension (path);
				string msg= string.Format ("{0}が{1}パスの{2}ファイルを編集中です.",
					user, scenePath, sceneName
				);
				Debug.Log (msg);

				// Postリクエスト
				// form要素設定
				var obj = JsonUtility.ToJson(new CustomModel {
					user = user,
					screenPath = scenePath,
					sceneName = sceneName,
					time = DateTime.Now.ToString ()
				});

				WWWForm form = new WWWForm();
				form.AddField("data", obj);

//				ObservableWWW.Post ("http://127.0.0.1:8000/quote", form)
				byte[] data = System.Text.Encoding.UTF8.GetBytes (obj);
				ObservableWWW.Post ("http://127.0.0.1:8000/quote", data)
					// タイムアウト時間設定.
					.Timeout(TimeSpan.FromSeconds(30))
					.Subscribe(result =>
						{
							try
							{
								// JSONをパース.
								Debug.Log("ok");
							}
							catch (System.Exception e)
							{
								Debug.Log("JSONのパースに失敗しました.");
							}
						},
						err =>
						{
							if (err.GetType() == typeof(System.TimeoutException))
							{
								Debug.Log("タイムアウトしました.");
							}
							else
							{
								Debug.Log("通信に失敗しました.");
							}
						});
			}

			if (sceneName.Length == 0)
			{
				return paths;
			}
		}

		return paths;
	}
}
```

これを記載して配置しておくだけで、スクリプトが勝手にPOSTしてくれます。。

#### Webへの投稿コードはこちらを参考にしました。

http://belhb.hateblo.jp/entry/2016/05/02/025713

#### ソースについて

`CustomModel`とかわざわざ作るの面倒なので、無名クラス(`new {hoge="val1", bar="val2"}`)とかでできればよいのに！

`Form`でやると`data=`パラメータがくっついてしまう。

```
WWWForm form = new WWWForm();
form.AddField("data", obj);
ObservableWWW.Post ("http://127.0.0.1:8000/quote", form)
```

substringとかで一生懸命`data=`を消したりとかやろうとしてしてしまいましたが、

今回のソースコードのように`byte[]`にしました。

#### この時ここを参考

http://naichilab.blogspot.jp/2014/08/unityunirxrest-apipost.html


## 実行

シーンを編集して保存してみて下さい。

http://127.0.0.1:8000/quote
にアクセス

<img width="701" alt="Screen Shot 2016-12-23 at 9.02.22 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/11951/7faa44de-ace8-73f9-d6b9-fcf343037608.png">

こんな感じのログが見えます。

# 最後に

- 今回のはほんの小さなサンプルです。
- 実際はデータベースに入れましょう。
- quoteのURLもきちんとしましょうね。
- ファイル名も今回sample.pyとかだったので名前きちんとしましょう。
- 今回はローカルで行いましが実際はサーバを立てて置きましょう。
- だれかキチンと作ったらシェアしてね。それか僕が作ったらAssetStoreに置くかも？
- Prefabも対応するのもいいかも？