[Unity]Editorでスプレッドシートの情報をJsonで受け取り反映する

## ○経緯
- 前回の「[[Unity]Runtimeでスプレッドシートの情報をJsonで受け取り反映する](http://qiita.com/cyber1496/items/2a08c152097d96eba3d4)」を使う場面を考えてみました。(すみません、記事の題名を変更させてもらいました。)
- しかしビルドに含める時点で更新すれば良い場合や形式だった場合は…？
- その際、思い出したのは[「宴」](http://madnesslabo.net/utage/)というUnity用のビジュアルノベルツールでした。
- (たしか)宴では設定やシナリオスクリプトを一度ScriptableObjectに格納する扱いだったかなと…。(最新版の宴は触れていないので分からないですけど)
- スクリプタもUnityを使って作業するのが理想ではありますが、もっと手軽にシナリオスクリプトを編集し、動作確認できても良いんじゃないか…と思いました。
- 「アセットバンドル(ダウンロードファイル)化すれば？」という話にもなりますが、結局固める作業が煩わしくなるなと…。(もちろんリリース時はダウンロードファイルなどに固めると思いますが)
- じゃあ直接ScriptableObjectの設定値を反映できれば実現できるのでは？
- と、いう訳でまずはエディタプログラムから任意のタイミングでスプレッドシートにアクセスし、更新する機能を作ってみようと思います。

## ○環境
- Windows64bit、Unity 5.5.0f3、無料の外部アセット(Json.NET Converters - Simple compatible solution)
- Androidの実機テストは後ほどやる予定です。iOSに関しては家にMacが無いので…(´・ω・｀)
- もしかするとビルド設定や動的コンパイルなどで引っかかるかもしれません…。

## ○先に結論
- エディタプログラムでの通信待ちが厄介すぎました。
- UnityWebRequestが何故か動かない(解せぬ)。ひとまずWWWに切り替えてやっています。

## ○発生した問題
- エディタプログラムでの通信待ち
    - Coroutineは使えないという事は知っていました。
    - 通信して認証する外部のアセットなどがあるのでなにか逃し方があるやろ程度の理解度でした。
    - 結局見つかったのは[Unity Answers「Yielding with WWW in Editor」](http://answers.unity3d.com/questions/221651/yielding-with-www-in-editor.html)という記事。
    - 上記の ContinuationManager を参考にさせて頂き、それを UnityWebRequestで回してみました。
    - isDone がいつまでたってもtrueにならんがな…(´・ω・｀)ちゃんとSend()も呼んでるのに…。
    - 結局既存のユーティリティで UnityWebRequestの生成メソッドの WWW版を作るやり方で逃げました。(解せぬ)

## ○成果物
- 前回同様、Githubにあります。https://github.com/cyber1496/GAS
- サンプルの実行方法などは付属のREADMEを見てください。

## ○細かな実装など
### ◆取り込みたいデータのScriptableObjectに付与するインターフェース
前回と違い ScriptableObjectのメンバにスプレッドシートの型のデータを持たせるので Interfaceで  Deserializeメソッドをサポートするという形にしています。

```
public interface ISyncScriptableObject
{
    void Deserialize(string json);
}
```

### ◆取り込みたいデータのScriptableObject
Json.NET Converters が今回も良い仕事しています。

```
public class TestScriptableObject : ScriptableObject, ISyncScriptableObject
{
    [SerializeField]
    public SerializeClass[] Array;

    public void Deserialize(string json)
    {
        Array = JsonConvert.DeserializeObject<SerializeClass[]>(json);
    }
}
```

### ◆エディタウインドウ本体

```
public class SyncScriptableObjectEditor : EditorWindow
{
    // メニューバーからウインドウを開く
    [MenuItem("Cyber/GAS/Examples/SyncScriptableObject")]
    static void Open()
    {
        GetWindow<SyncScriptableObjectEditor>();
    }
    // エディタでの通信待ちを管理する
    static ContinuationManager continuationManager;

    void OnGUI()
    {
        EditorGUILayout.LabelField("SyncScriptableObject");
        if (GUILayout.Button("Sync"))
        {
            continuationManager = new ContinuationManager();
            SyncProcess<TestScriptableObject>(
                "Assets/Cyber/GAS/Examples/TestScriptableObject.asset",
                "ScriptableObject",
                syncedData =>
                {
                    foreach (var data in syncedData.Array)
                    {
                        Debug.LogFormat("id:{0} name:{1} number:{2} value:{3}", data.id, data.name, data.number, data.value);
                    }
                }
            );
        }
        Progress();
    }

    /// 進捗ゲージ
    void Progress()
    {
        if (continuationManager != null)
        {
            if (!continuationManager.IsDone)
            {
                EditorUtility.DisplayProgressBar("Progress", "Synchronization progress", continuationManager.Progress);
            }
            else
            {
                EditorUtility.ClearProgressBar();
                continuationManager = null;
            }
        }
    }

    /// 同期プロセス
    void SyncProcess<T>(string assetPath, string sheetName, System.Action<T> action)
        where T : ScriptableObject, ISyncScriptableObject
    {
    // For UnityWebRequest isDone does not return true...
    #if USE_UNITY_WEBREQUEST
        var request = Utility.CreateRequestGetSheetJson(sheetName);
        request.Send();
        continuationManager.Add(
            () => request.isDone,
            () =>
        {
            if (!string.IsNullOrEmpty(request.error))
            {
                Debug.LogError(request.error);
            }
            else
            {
                T data = AssetDatabase.LoadAssetAtPath<T>(assetPath);
                data.Deserialize(request.downloadHandler.text);
                EditorUtility.SetDirty(data);
                action(data);
            }
        });
    #else   //USE_WWW
        var request = Utility.CreateWWWGetSheetJson(sheetName);
        continuationManager.Add(
            () => request.isDone,
            () =>
        {
            if (!string.IsNullOrEmpty(request.error))
            {
                Debug.LogError(request.error);
            }
            else
            {
                T data = AssetDatabase.LoadAssetAtPath<T>(assetPath);
                data.Deserialize(request.text);
                EditorUtility.SetDirty(data);
                action(data);
            }
        });
    #endif
    }
}
```