Fast Shadow ReceiverのデモはWeb Playerで見れるようになっていて、色々なシーンを切り替えることができるようになっているのだけれど、ドキュメントからリンクを貼るときに特定のシーンを指定したくなった。それで、URLのパラメータでシーンを指定できないかと試行錯誤したときの話。
例えば、http://nyahoon.com/unity-demo/FastShadowReceiverDemo.html?scene=BulletMarksにリンクを貼れば、Web Playerを開いて”BulletMarks”というシーンをロードしたいということ。
最初はApplicationクラスのabsoluteURLで簡単にパラメータを取ってこれるだろうと思っていたのだが、このプロパティにはURLのクエリパラメータは含まれていなかった。次に、System.EnvironmentクラスのCommandLineプロパティを試してみたけど、この文字列は空。
しかし、ちゃんとマニュアルを読むと、Web Playerとブラウザの間でやりとりできると書いてある。ブラウザからWeb Playerへはお決まりのSendMessage()
で、Web PlayerからブラウザへはApplication.ExternalCall()
もしくはApplication.ExternalEval()
を使う。
Web PlayerをロードするHTMLファイル(UnityでWeb Playerをビルドすると自動で作成されるやつ)にJavascriptを追加すれば、SendMessageで何でも出来そうだが、問題はSendMessageをするタイミング。ブラウザでボタンを押したら何かするというのであれば問題ないのだけど、Web Playerのロードが完了したら何かするというのをどうすれば良いのかがわからない。試しにUnityObject2のobserveProgressコールバックで次のように指定してみたけど、うまく行かなかった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
u.observeProgress(function (progress) { switch(progress.pluginStatus) { case "broken": // 中略. break; case "missing": // 中略. break; case "installed": $missingScreen.remove(); // URLのパラメータを解析してシーンをロード! でもこれはうまく行かない... var p = window.location.search; var i = p.indexOf("scene="); if (0 <= i) { var j = p.indexOf("&",i); if (0 < j) { p = p.substring(i+6,j); } else { p = p.substring(i+6); } u.getUnity().SendMessage("SceneController", "LoadScene", decodeURIComponent(p)); } break; case "first": break; } }); |
そこで、Unity側からJavascriptを実行して、SendMessageを使うことにした。Unity側であれば、AwakeやStartでシーンをロードするべきタイミングを特定できる。しかもUnity側で完結するので自動生成されるHTMLをいじらなくて済む。
これはFast Shadow Receiverのデモで使っているScene Controllerのソースコード。このコンポーネントをScene Controllerという名前のオブジェクトに追加して使っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
using UnityEngine; namespace FastShadowReceiver.Demo { public class SceneController : MonoBehaviour { public string[] m_scenes; private int m_currentScene = -1; void Awake () { DontDestroyOnLoad(this); #if UNITY_WEBPLAYER if (Application.isWebPlayer) { Application.ExternalEval( "var p = window.location.search;" + "var i = p.indexOf(\"scene=\");" + "if (0 <= i) {" + " var j = p.indexOf(\"&\",i);" + " if (0 < j) {" + " p = p.substring(i+6,j);" + " }" + " else {" + " p = p.substring(i+6);" + " }" + " u.getUnity().SendMessage(\"SceneController\", \"LoadScene\", decodeURIComponent(p));" + "}" ); } #endif } void Start () { if (m_currentScene == -1) { m_currentScene = 0; Application.LoadLevel(m_scenes[0]); } } void LoadScene(string sceneName) { for (int i = 0; i < m_scenes.Length; ++i) { if (string.Compare(sceneName, m_scenes[i], true) == 0) { m_currentScene = i; Application.LoadLevel(m_scenes[i]); } } } void OnGUI () { const int sceneNameWidth = 300; const int buttonWidth = 100; const int totalWidth = 2*buttonWidth + sceneNameWidth; const int height = 50; const int sceneNameHeight = 24; if (GUI.Button(new Rect(0.5f*(Screen.width - totalWidth), Screen.height - height, buttonWidth, height), "Prev")) { m_currentScene = (m_currentScene + m_scenes.Length - 1) % m_scenes.Length; Application.LoadLevel(m_scenes[m_currentScene]); } GUI.Box(new Rect(0.5f*(Screen.width - sceneNameWidth), Screen.height - 0.5f*(sceneNameHeight + height), sceneNameWidth, sceneNameHeight), m_scenes[m_currentScene]); if (GUI.Button(new Rect(0.5f*(Screen.width - totalWidth) + buttonWidth + sceneNameWidth, Screen.height - height, buttonWidth, height), "Next")) { m_currentScene = (m_currentScene + 1) % m_scenes.Length; Application.LoadLevel(m_scenes[m_currentScene]); } } } } |
Awake関数の中でApplication.Eval()
を使ってJavascriptを実行し、そこでSendMessageをしてシーンをロードしている。Javascriptの中でu
というオブジェクトを使っているが、これはデフォルトのWeb PlayerのHTMLの中で生成されている。
JavascriptでSendMessageを実行しても即座に実行されるわけではなく次のフレームで実行されるようなので、StartではなくAwakeでやるのが良い。Startでやると最初にデフォルトのシーンがロードされてしまう。
これでURLのパラメータを使ってシーンをロードすることができたわけだが、実際にはまだ問題があった。このサイトはWordPressで作られていて、デモもWordPressの固定ページで、iframeでWeb PlayerのHTMLを埋め込んで作られている。そのため、iframeのsrcにURLのパラメータを渡さないといけないのである。
もうここまで来るとUnityは関係なく、WordPressの問題なんだけど、WordPressにはまだ慣れていないものだから、ここからが大変だった。まずURLのクエリパラメータをそのままiframeのsrcに渡すようにPHPをいじってみたけど、ブラウザのXSS対策に引っかかってしまってうまく行かなかった。まあ実際XSSに利用されてしまうのも困るので、あまり良い方法ではない。
そこで、URLのリライト機能を使って、http://nyahoon.com/demos-ja/fast-shadow-receiver/<シーン名>のようにURLを指定したら、http://nyahoon.com/demos-ja/fast-shadow-receiver/のページを開いて、iframeのsrcに?scene=<シーン名>
を追加するようにした。これでもURLの一部をHTMLに埋め込むことになるので、XSS対策としてちゃんと文字列はエスケープして、変なシーン名は通さないようにする。
WordPressのURLのリライト機能については、ここのサイトがわかりやすかった。
書いたPHPのコードはこんな感じ。これでhttp://nyahoon.com/demos-xx/xxxxxx/<シーン名>のようなURLからget_query_var('scene')
を使ってシーン名を取れるようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
add_action( 'init', 'nyahoon_add_unitydemo_rewrite_rules' ); function nyahoon_add_unitydemo_rewrite_rules() { add_rewrite_rule( '(demos[A-Za-z\-]*/[0-9A-Za-z\-\+%]+)/([0-9A-Za-z\-\+%]+)/?$', 'index.php?pagename=$matches[1]&scene=$matches[2]', 'top' ); //flush_rewrite_rules(false); // この行はデータベースにリライトルールを書き込むためのもので、一度実行すればコメントアウトできる. } add_filter( 'query_vars', 'nyahoon_add_sceneparam' ); function nyahoon_add_sceneparam( $vars ) { $vars[] = 'scene'; return $vars; } |
実際、こんな感じでリンクを貼って使っています。
http://nyahoon.com/demos-ja/fast-shadow-receiver/bulletmarks