エディターで WebView を使う話【エディター拡張】

この投稿は [Unity Advent Calendar 2015](http://qiita.com/advent-calendar/2015/unity) の 22日目の記事です。

※ この話は、ゲーム内の WebView の話ではなく、EditorWindow 内で WebView を使用する話です。
※ おもいっきり非推奨です。

## 現在までの WebView に関する情報

今まで、WebView を使った情報はチラホラと出してきました。

[UnityEditor上で艦これはできる<br/><img width="400px" src="https://qiita-image-store.s3.amazonaws.com/0/5549/7cebf107-61c8-7e37-bcf9-fe349891bda2.png">](http://qiita.com/kyusyukeigo/items/a7d3940b95c4ec224d12)

---

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">ゆにてぃでさいきょうのSNSくらいあんとができた！ <a href="http://t.co/pfYCNRJD5n">pic.twitter.com/pfYCNRJD5n</a></p>&mdash; けーご (@kyusyukeigo) <a href="https://twitter.com/kyusyukeigo/status/583905721877536768">2015, 4月 3</a></blockquote>
---
<img width="400px" src="https://dl.dropboxusercontent.com/u/153254465/screenshot2/ss%202015-12-20%2017.01.50.png">


---

最近はこちらの記事が話題になりました。

[【Unity】僕の考えた最強のエディタ拡張【AdventCalendar】](http://chroske.hatenablog.com/entry/2015/12/08/110332)


そして更に試行錯誤して双方向通信が実現しました。

---

Unity上の C# コード -> WebView の HTML

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">わーい、UnityEditor -&gt; WebView の js 実行できた <a href="https://t.co/zbHJz074Ue">pic.twitter.com/zbHJz074Ue</a></p>&mdash; けーご (@kyusyukeigo) <a href="https://twitter.com/kyusyukeigo/status/678117239560650752">2015, 12月 19</a></blockquote>

---

WebView の HTML -> Unity上の C# コード

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">わーい、WebView -&gt; UnityEditor の通信できたー。これでHTMLでGUIが作れるぞ <a href="https://t.co/wJ248lSuUa">pic.twitter.com/wJ248lSuUa</a></p>&mdash; けーご (@kyusyukeigo) <a href="https://twitter.com/kyusyukeigo/status/678178551980519424">2015, 12月 19</a></blockquote>

今回は WebView を表示して双方向通信を行うまでを書いていきたいと思います。

## まずは DLL をデコンパイル。話はそれから

まずは、WebViewを使っていそうな AssetStore と Unity Services 系、そして「WebView」と言うキーワードを元に UnityEditor.dll をデコンパイルしたソースコードの中から該当しそうなクラスを探します。

関係ありそうなクラスが見つかりました。

* UnityEditor
  * WebView
* UnityEditor.Web
  * WebViewEditorWindow
  * WebScriptObject
  * JSProxyMgr 


クラス|説明
:---|:---
WebView|これが WebView 本体。
WebViewEditorWindow|WebView を載せる EditorWindow。基本的な操作はこのウィンドウを介して行う。
WebScriptObject| Unity と WebView が通信を行うときに使用するクラス。このクラスをいじることはないので知らなくていい。
JSProxyMgr|Unity と WebView の双方向通信を担うクラス。ちょっと難解だった。


## WebViewEditorWindow を作成する

まずは単純に WebViewEditorWindow を作成して任意のサイトを表示してみましょう。

`WebViewEditorWindow.Creat<T>(string title, string url, int mixWidth, int mixHeight, int maxWidth, int maxHeight)` を呼び出すだけで作成することができます。

```csharp
using UnityEngine;
using UnityEditor;
using System.Reflection;

public class Kancolle
{
    static BindingFlags Flags = BindingFlags.Public | BindingFlags.Static;

    [MenuItem("Window/Kancolle")]
    static void Open ()
    {
        var type = Types.GetType ("UnityEditor.Web.WebViewEditorWindow", "UnityEditor.dll");
        var methodInfo = type.GetMethod ("Create", Flags);
        methodInfo = methodInfo.MakeGenericMethod (typeof(Kancolle));
        methodInfo.Invoke (null, new object[]{
            "Kancolle",
            "http://www.dmm.com/netgame/social/-/gadgets/=/app_id=854854/",
            200, 530, 800, 600
        });
    }
}
```

## Unity (C#) から WebView の Javascript を呼び出す

これを実現させるには `WebView.ExecuteJavascript(string scriptCode)` を実行します。これのラッパーとして `WebViewEditorWindow.InvokeJSMethod(string objectName, string name, params object[] args)` もあります。 InvokeJSMethod を採用したものが最初のほうで見せた Tweet の内容になります。

これは、`EditorApplication.update` を使用して毎回 `Time.realtimeSinceStartup` の値を WebView 側に渡しているものになります。

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">わーい、UnityEditor -&gt; WebView の js 実行できた <a href="https://t.co/zbHJz074Ue">pic.twitter.com/zbHJz074Ue</a></p>&mdash; けーご (@kyusyukeigo) <a href="https://twitter.com/kyusyukeigo/status/678117239560650752">2015, 12月 19</a></blockquote>


## WebView から Unity (C#) を呼び出す

これが少し手こずりましたが、なんとかなったので紹介します。

WebView から Unity (C#) を呼び出す、細かく言うと任意のオブジェクトのメソッドを呼び出すことができます。モバイルや WebPlayer の SendMessage と同じイメージです。

WebView で表示されるページには `unityAsync` と言うオブジェクトが自動で登録されます。これが Unity (C#) と通信を行うためのものです。

```js
unityAsync({
    className: "window.webScriptObject",
    funcName: "ProcessMessage",
    funcArgs: [
        JSON.stringify({
            type: "INVOKE",
            messageID: 1,
            version: "0",
            reference: "ExampleWindow",
            destination: "ExampleWindow",
            method: 'Play',
            params: []
        })],
        onSuccess: function(res) {
            console.log("returnValue: " + res)
        }
})
```

プロパティー|説明
:---|:---
className|値は固定（たぶん）
funcName|値は固定（たぶん）
funcArgs| ここに呼び出すメソッドの情報を書いていく
type| "INVOKE" がメソッド実行機能。他にも type があるが使わないかも？
messageID| 値が固定でも問題はないが呼び出すごとにインクリメントしておくといいかも？
version| 0 固定
reference| GlobalObject に登録されている名前を設定する（Unity側で作成したクラスのこと）
destination| GlobalObject に登録されている名前を設定する（Unity側で作成したクラスのこと）
method| 呼び出すメソッド名
params|メソッドの引数に渡す値
onSuccess| （エラーであっても）何らかのレスポンスが帰ってくると呼び出される。Unity側のメソッドの戻り値も取得可能。

実際に使ってみましょう。

まずは、HTML側の実装です。

```html
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body style="background-color:gray">
    <button onclick="sendMessage('Play')">再生ボタン</button>
    <button onclick="sendMessage('Pause')">一時停止ボタン</button>
    <button onclick="sendMessage('Step')">ステップボタン</button>
    <script type="text/javascript">

      function sendMessage(method) {
          
          unityAsync({
            className: "window.webScriptObject",
            funcName: "ProcessMessage",
            funcArgs: [JSON.stringify(
                {
                    type:"INVOKE",
                    messageID:1,
                    version:"0",
                    reference:"ExampleWindow",
                    destination:"ExampleWindow",
                    method: method,
                    params:[]
                })],
            onSuccess: function(a) {
                console.log("returnValue: " + a)
          }})
      }
    </script>
  </body>
</html>
```

次に Unity 側。ラッパークラスを作成しているので混乱してしまうかもしれませんが、ここで見て欲しいのは ExampleWindow クラスに作成した `Play` `Pause` `Step` のメソッドです。

```csharp
using UnityEngine;
using UnityEditor;

// ラッパー書いてます
// https://gist.github.com/anchan828/2405b9b792366327e502
public class ExampleWindow : CustomWebViewEditorWindow
{

	[MenuItem ("Window/Example")]
	static void Open ()
	{
		CreateWebViewEditorWindow<ExampleWindow> (
			"Example",
			Application.dataPath + "/index.html", 200, 530, 800, 600);
	}

	public void Play ()
	{
		EditorApplication.isPlaying = !EditorApplication.isPlaying;
	}

	public void Pause ()
	{
		EditorApplication.isPaused = !EditorApplication.isPaused;
	}

	public void Step ()
	{
		EditorApplication.Step ();
	}
}
```

実行するとこうなります。

![](https://dl.dropboxusercontent.com/u/153254465/screenshot2/webview-test.gif)


## まとめ

いかがだったでしょうか。私はなんでもできそうな気がしてきました。

今回は技術面の解説をあまりいれていないので、「[エディター拡張入門](http://anchan828.github.io/editor-manual/)」にて書こうかなと思っております。（宣伝）

[![](https://gyazo.com/6bc5e5a62f4b4c577d9443b3f45946c5.png)](http://anchan828.github.io/editor-manual/)