Unity RectTransformでWebView制御

# uGUIとWebView
uGUIといえばUnityでUIを構築するAPIだ。
スクロールビューや画像など最低限のパーツはあるが、タブビューは標準で用意されていないし、グリッドビューやリストビューはあるが、情報のアダプターとなる機構は自前で用意する必要がある。
もちろんウェブビューはない。

# WebViewの必要性
あると言えばある。ないと言えばない。
今回の趣旨とは関係のない部分なので深くは触れないが、無いなら無いでuGUIで構築したUIパーツの画像やテキスト部分にJSONなどで取ってきた情報をはめれば良い訳だ。
だが、WebViewはそれ以上に便利なのも確か。
配信する情報をHTMLというuGUIとは違った生態系で後からレイアウトできるので、情報の形式をあらかじめ決めておく必要もないし自由度も高い。
ぶっちゃけウェブビューは使っていきたい。

# ネイティブのWebView
標準APIでサポートされていないとはいえ、オープンソースやアセットストア内で販売されているアセットなど、サードパーティのものは存在する。
今回使用させていただくのはこちら。
https://github.com/gree/unity-webview

プラットフォームごとのネイティブのウェブビューを呼び出すネイティブプラグインを含んでいる。Unityで構築される世界は、全画面表示されるGLViewのようなものに写り込んでいるに過ぎず、これらウェブビューアセットはネイティブに働きかけることによって、そのGLViewのようなものの上に、各プラットフォームごとのWebViewを重ねていることになる。Unity以外の環境でスマホアプリ開発をしたことがある方ならイメージ出来ているだろう。　

このアセットに限らず、現在存在するUnityのウェブビューの実装方法はすべてこの方式である。
つまり、この状態でどうあがいてもウェブビューの上にUnityの世界を描画することは叶わない。WebViewはUnity上のレイヤーの一つとして考えることはできない。ネイティブ側でどう工夫をしたところで、「Unity」の上か、下にしか描画できない。（もちろん下にした場合見えないので意味がないが。）

# それを承知の上で少しでもuGUIとの親和性を高めたい
ウェブビューが最前面にしか描画できないことは分かってもらえたと思うので、その範疇でuGUIとの親和性を高めたい。
uGUIといえば、シーン内、Canvas以下に配備されるRectTransformコンポーネントを持つオブジェクトということになると思う。
このRectTransformが画期的で様々な画面サイズや解像度の差異を解決する機構を持つUIの位置決め要素である。
今回は、RectTransformによってウェブビューの表示サイズ、表示位置をあたかもuGUIの一部であるかのように解決するコードを紹介する。

ちなみにUniRxも使わせていただいているので要インポート。

```WebViewRectAllocator.cs
using System;
using System.Linq;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(RectTransform))]
public class WebViewRectAllocator : MonoBehaviour {

	public StringReactiveProperty URL = new StringReactiveProperty();

	[SerializeField] private Button goBackButton;
	[SerializeField] private Button goForwardButton;

	private void Awake() {
		var webViewObject = this.GetOrAddComponent<WebViewObject>();
		webViewObject.Init(msg => Debug.Log(msg));

		URL.Where(url => !string.IsNullOrEmpty(url))
		   .Subscribe(webViewObject.LoadURL);

		GetComponentsInParent<CanvasGroup>()
			.Select(cg => cg.ObserveEveryValueChanged(c => c.alpha >= 1f))
			.CombineLatestValuesAreAllTrue()
			.CombineLatest(this.ObserveEveryValueChanged(self => self.enabled), (l, r) => l && r)
			.Subscribe(webViewObject.SetVisibility)
			.AddTo(this);

		var rect = GetComponent<RectTransform>();
		rect.OnRectTransformDimensionsChangeAsObservable()
			.Merge(Observable.Return(Unit.Default))
			.Select(_ => new Vector3[4])
			.Do(rect.GetWorldCorners)
			.Select(corners => new Vector4(corners.Min(c => c.x), corners.Max(c => c.x), corners.Min(c => c.y), corners.Max(c => c.y)))
			.SelectMany(lrbt => Observable.Return(new Vector3[4])
						.Do(rect.GetComponentInParent<Canvas>().GetComponent<RectTransform>().GetWorldCorners)
						.Select(corners => new Vector4(corners.Min(c => c.x), corners.Max(c => c.x), corners.Min(c => c.y), corners.Max(c => c.y)))
						.Select(canvasLRBT => canvasLRBT - lrbt))
			.Select(lrbt => new { left = (int)-lrbt.x, right = (int)lrbt.y, bottom = (int)-lrbt.z, top = (int)lrbt.w })
			.CatchIgnore()
			.Do(t => Debug.LogFormat("WebView margins: top={0}, left={1}, right={2}, bottom={3}", t.top, t.left, t.right, t.bottom))
			.Subscribe(t => webViewObject.SetMargins(t.left, t.top, t.right, t.bottom))
			.AddTo(this);

		var checkInterval = 2f;

		if (GoBackButton != null) Observable
			.Interval(TimeSpan.FromSeconds(checkInterval))
			.Where(_ => webViewObject.GetVisibility())
			.Select(_ => webViewObject.CanGoBack())
			.BindToButtonOnClick(GoBackButton, _ => webViewObject.GoBack());

		if (GoForwardButton != null) Observable
			.Interval(TimeSpan.FromSeconds(checkInterval))
			.Where(_ => webViewObject.GetVisibility())
			.Select(_ => webViewObject.CanGoForward())
			.BindToButtonOnClick(GoForwardButton, _ => webViewObject.GoForward());
	}

}
```

コンポーネントの拡張メソッド

```ComponentExtensions.cs
using System;
using System.Collections.Generic;
using UnityEngine;

public static class ComponentExtensions {
	public static T GetOrAddComponent<T>(this Component component) where T : Component => component.GetComponent<T>() ?? component.gameObject.AddComponent<T>();
}
```

このWebViewRectAllocatorコンポーネントをRectTransformが付いているオブジェクトにつけておくと、あたかもuGUI x WebViewという感じで動く。
やっていることは、ウィンドウサイズなどが変わった際にその要素のサイズや位置などが変わると思うので追従する。
また、その要素が所属するCanvasGroupのアルファ値やその要素か親たちのenabledを監視し、WebViewのVisibilityに反映させている。

# 終わりに
当たり前といえば当たり前なので仕方のないことだが、それでもuGUIでごくごく自然にウェブビューが使えればどんなに便利か。（もちろん透明度込みのウェブビューの上にも下にもUnityワールドを重ねていけるという意味である。）
例えば、ネイティブプラグインを作り、ユーザーの見えないところで生成したウェブビューでウェブを表示し、スクリーンショットを作成しテクスチャー化するか、C#でブラウザエンジンが実装されたライブラリなんかがあったりすると、と思ったりするがあくまでゲームアプリの一部なのでそこまでする必要があるのかというと疑問である。

大分、本題からずれたのでこの辺で。