UE4でBluePrint HTTP REST通信
Unreal EngineのBluePrintを使ってHTTP通信を行ってみます。
使用環境:UE4.25.1
1. 準備
BluePrintでHTTP通信を行うために、VaRestというプラグインを使用します。 マーケットプレイスからダウンロードすることができます。無料。
ダウンロードが終わったらゲームエンジンにインストールします。 次に使用するプロジェクトを開いてメニューの [Edit] > [Plugins]をクリックします。
インストールしたVarestプラグインをEnabledにします。これでBluePrintからVaRestが使えるようになります。
2. GET
レベルブループリントを開いて以下のようにノードを配置します。接続先には httpbin.org を使っています(便利!)。 Xキーを押すと画面上にString型に変換されたJSONが表示されます。
Get String Fieldノードを使用するとJSONオブジェクトから指定したフィールド値を取得することができます。
3. POST
Request BodyにJSONを組み込んでPOSTする方法です。エラーハンドリングを入れています。
成功すると以下のように返ってきます。
{ "args": {}, "data": "", "files": {}, "form": { "id": "test_id", "password": "mypassword" }, "headers": { "Accept": "*/*", "Accept-Encoding": "deflate, gzip", "Content-Length": "30", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "HttpRequest01/++UE4+Release-4.25-CL-13594126 Windows/10.0.18363.1.256.64bit", }, "json": null, "origin": "xxx.xxx.xxx.xxx", "url": "http://httpbin.org/post" }
4. まとめ
VaRestを使うことでBluePrintからHTTP REST通信を行うことができました。VaRestの公式ドキュメントには具体的な方法が記されていなかったので参考になれば幸いです。
本記事の内容もマーケットプレイスの Questions の販売者の回答を参考にしています。より詳しい使い方が知りたい場合は、Questionsの内容を探してみるか販売者に直接質問してみるのが良いかもしれません。
本来であればUE4の標準機能としてBluePrintからHTTP REST通信ができると最高なのですが、あまり需要がないってことなのでしょうかね?
【Android】LiveDataを使った双方向Data Binding
LiveDataを使ってデータをViewに即座に反映させる方法はお馴染みですが、ViewからViewModelへリアクティブに制御する事例はまだ少ないため、ここではMediatorLiveDataを使ってその機能を実装してみたいと思います。
コードは以下の記事を参考にさせていただきました。ありがとうございます。
コード
まずはレイアウトです。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <data> <variable name="viewModel" type="com.example.twowaydatabinding01.MainActivityViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.google.android.material.textfield.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="ユーザ名" android:layout_margin="8dp"> <com.google.android.material.textfield.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:text="@={viewModel.username}"/> </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="パスワード" android:layout_margin="8dp"> <com.google.android.material.textfield.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:text="@={viewModel.password}"/> </com.google.android.material.textfield.TextInputLayout> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:enabled="@{viewModel.canSubmit}" android:onClick="@{() -> viewModel.onClick()}" android:text="LOGIN" /> </LinearLayout> </layout>
次に要となるViewModelです。
MainActivityViewModel.kt
package com.example.twowaydatabinding01 import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import timber.log.Timber class MainActivityViewModel : ViewModel() { val username = MutableLiveData<String>() val password = MutableLiveData<String>() private fun isValid(): Boolean { return !username.value.isNullOrBlank() && !password.value.isNullOrBlank() } val canSubmit = MediatorLiveData<Boolean>().also { result -> result.addSource(username) { result.value = isValid() } result.addSource(password) { result.value = isValid() } } fun onClick() { Timber.d(username.value + " - " + password.value) } }
最後にActivityです。
MainActivity.kt
package com.example.twowaydatabinding01 import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import com.example.twowaydatabinding01.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) val viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java) binding.viewModel = viewModel binding.lifecycleOwner = this } }
実行
よくあるログイン画面の構成です。ユーザ名とパスワードに何かしら入力されるまではLOGINボタンは押せません。ViewModelでユーザ名とパスワードの入力を監視し、両方入力された瞬間にボタンを有効化しています。再びどちらか一方でも空になるとボタンは無効化されます。
まとめ
LiveDataを使って双方向のDataBindingを行うことができました。UIをリアクティブにすることで、ユーザ操作を円滑にするための手助けをすることができます。積極的に取り入れていきたいですね。
参考
- https://qiita.com/YusukeIwaki/items/3fb4e10ac87fa1c7f6ba
- https://it.senatus.jp/android-livedata%E3%81%AE2-way-%E3%83%90%E3%82%A4%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%8C%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%8F%E3%81%A6%EF%BC%92%E6%97%A5%E6%BD%B0%E3%81%97%E3%81%9F/
- https://developer.android.com/topic/libraries/data-binding/two-way
Vector Drawableのgradientの挙動にハマった件
グラデーションを表現したSVGファイルをVector DrawableとしてインポートしてViewのBackgroundに配置したときに、Androidのバージョンで差が出たのでそのときの対応メモです。
やりたい表現
drawable
これをImageViewのbackgroundへ配置
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="200dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="100"> <path android:pathData="M0,0 L200,0 L200,200 L0,200 Z" > <aapt:attr name="android:fillColor"> <gradient android:gradientRadius="50" android:centerX="50" android:centerY="50" android:type="radial"> <item android:offset="0" android:color="#FF0066BA"/> <item android:offset="0.47648278" android:color="#FF024D8B"/> <item android:offset="1" android:color="#00000000"/> </gradient> </aapt:attr> </path> </vector>
うまくいかなかった例
Android 5.1.1 - Android 7.1.x (API 22 - 25)
何かおかしい...透過が効いていない?
どのバージョンでもうまくいった例
3つ目の android:offset
を2つ目のカラーコードと同じにしてアルファの値のみ0に変更
drawable
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="200dp" android:height="200dp" android:viewportWidth="100" android:viewportHeight="100"> <path android:pathData="M0,0 L200,0 L200,200 L0,200 Z" > <aapt:attr name="android:fillColor"> <gradient android:gradientRadius="50" android:centerX="50" android:centerY="50" android:type="radial"> <item android:offset="0" android:color="#FF0066BA"/> <item android:offset="0.47648278" android:color="#FF024D8B"/> <item android:offset="1" android:color="#00024D8B"/> </gradient> </aapt:attr> </path> </vector>
結果
まとめ
Vector Drawableの一部の属性がAPI 25以下だと対応していなかったり、アルファ値の扱いが異なるのかもしれません。
Drawable Resource でもだいぶ色々な表現が可能になっていますが、仕様的に表現しきれない部分もあります。一方、Vector Drawableであればデザイナーがイラレ等で用意してくれたSVGファイルをインポートするだけで豊かな表現が可能です。しかし、こちらもインポートが完全ではなく表現が抜け落ちるところは同様にあります。
Vector Drableのインポートで抜け落ちたところは手動設定でカバーすることも可能ですが、設定は大変難しく、将来的にインポート精度が向上するか、もっと簡単に表現できるようになると嬉しいですね。
AndroidのCardViewを使って画像を丸くする
アバター画像を丸く表示する場合、Picassoなどのtransformが良く使われますがコードを書く必要があり少し手間がかかります。代わりにCardViewを使うとXML上でプロパティを指定することで簡単に画像を丸くすることができます。
準備
まずCardViewが使えるようにするために build.gradle
に以下の行を追加します。
※今回はAndroidXを使っています。
dependencies { implementation 'androidx.cardview:cardview:1.0.0' }
AndroidXを使わない場合は、以下のリンクを参照してください。
レイアウト
以下のレイアウトを参考にしてCardViewを追加します。画像を丸くするには cardCornerRadius
プロパティを使います。値はCardViewサイズの半分を指定するとちょうど円にすることができます(正方形の場合)。
子要素には画像を表示するためのImageViewを入れてください。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.cardview.widget.CardView android:layout_width="200dp" android:layout_height="200dp" app:cardCornerRadius="100dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/test_image" /> </androidx.cardview.widget.CardView> </androidx.constraintlayout.widget.ConstraintLayout>
サンプルで使用した画像はこちらです。私が撮った写真です。
Android Designer上(Android Studio 3.6.2)では残念ながらプレビューはされませんが気にせず進めてください。Android Studioがバージョンアップされればいずれプレビューできるかもしれません。
結果
ビルドして実行すると以下のようになります。
cardCornerRadius
の値をCardViewの半分のサイズよりも小さい値を指定すると角丸に指定することができます。下の例では16dpを指定しました。
まとめ
CardViewを使うと cardCornerRadius
を指定するだけで簡単に画像を丸くすることができました。今回はImageViewを丸くしましたが、他のViewも子要素に入れることで丸くすることができます。
Android Studioのツールバーにショートカットボタンを置く
Android Studioではよく使う機能をツールバーにショートカットボタンとして配置することができます。 今回はAndroid開発で何かと便利なPluginであるADB Ideaの機能をショートカットボタンに割り当ててみます。
■使用環境
- Windows 10
- Android Studio 3.5
1. ADB Ideaとは
ADB IdeaはAndroid StudioからアプリのアンインストールやKill、RestartなどができるようになるAndroid StudioのPluginです。機能的にはADBコマンドと同じですが、Android Studio上から実行できるため開発効率を上げることができます。
2. ADB Ideaのインストール
Android Studioのメニューから[File] -> [Settings]を選択します。設定画面が開いたら、[Plugins]のメニューをクリックします。
検索欄に「adb idea」と入力して検索するとADB Ideaが出てくるのでクリックします。
Installボタンを押します。(画面は既にインストール済みのためInstalledになっています)
インストールが完了すると、Android Studioメニューの[Tools] -> [ADB Idea]からADBコマンドを実行することができます。
3. ツールバーへのショートカットボタン配置
次にもう一度、Android Studioのメニューから[File] -> [Settings]を選択します。設定画面が開いたら、[Appearance & Behavioer] -> [Menus and Toolbars]をクリックします。
次に[Main Toolbar] -> [Toolbar Run Action] -> [Run/Debug]を選択した状態で、[+]ボタンから[Add Action]をクリックします。 Actionを選択するウィンドウが表示されるので、[Plugins] -> [ADB Idea] -> [ADB Uninstall App]を選択します。
また、ボタンにはアイコンを設定することができます。以下のアイコン画像をダウンロードして、C:\Program Files\Android\Android Studio
に保存してください。
アイコンは以下のサイトのフリー素材をAndroid Studioに馴染むようにアレンジしました。ありがとうございます!
アイコンの保存先については任意の場所で構いませんが、ファイル名を変更したり、保存先を変更するとAndroid Studioから見えなくなるので、普段あまり触らないAndroid Studioと同じ場所に保存しておくのが良いでしょう。
保存したら[Set icon]ボタンで保存先のアイコンを設定します。 (アイコンは後からでも設定できます)
[OK]ボタンを押すと[Menus and Toolbars]上で「ADB Uninstall App」ボタンが追加されていることが確認できます。さらに△と▽ボタンを押すと順番(位置)を変更することができます。
[Apply]または[OK]ボタンを押すとツールバー上にアンインストールボタンが追加されます。
4. まとめ
Android Studioのツールバーをカスタマイズする方法を解説しました。Androidのパーミッション関連はとても複雑なため、初回起動の処理をデバッグするために頻繁にアプリのインストールとアンインストールを繰り返す必要があります。そのため、このアプリのアンインストールボタンがあると効率良くデバッグすることができます。
今回はADB Ideaの機能を割り当てましたが、他にもデフォルト以外の様々な機能を割り当てることができるので、是非、自分好みにカスタマイズしてみてください。
UnityWebRequestを使ってBasic認証する
UnityWebRequestを使ってBasic認証する方法です。 サンプルコードのBasic認証のテストとして以下のサイトを使わせていただきました。
1. コード
以下のスクリプトを適当なGameObjectにアタッチします。
using System.Collections; using UnityEngine; using UnityEngine.Networking; public class MainScript : MonoBehaviour { private string url = "http://leggiero.sakura.ne.jp/xxxxbasic_auth_testxxxx/secret/kaiin_page_top.htm"; void Start() { StartCoroutine(WebRequest("kaiin", "naisho")); } IEnumerator WebRequest(string id, string pass) { UnityWebRequest request = UnityWebRequest.Get(url); // Basic認証用のAUTHORIZATIONヘッダー付加 string authorization = authenticate(id, pass); request.SetRequestHeader("AUTHORIZATION", authorization); yield return request.SendWebRequest(); if (request.isNetworkError) { Debug.Log(request.error); } else { Debug.Log(request.responseCode); } } string authenticate(string username, string password) { string auth = username + ":" + password; auth = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(auth)); auth = "Basic " + auth; return auth; } }
2. 実行結果
認証に失敗すると 401
(Unauthorized)
成功すると 200
(OK)が返ります。
返り値のbodyは request.downloadHandler.text
で取得することができます。
UniWebView 3を使う
Unity上でWebブラウザの機能を使えるようにするためのWebViewアセットはいくつかありますが、ここではその一つであるUniWebView 3の解説を行います。
1. UniWebView 3とは
Unity上でWebブラウザを表示することができるアセットで、インターネットWebブラウジングだけでなく、ローカルHTMLの読み込みやCSSスタイリング、Javascriptの実行が可能です。
$25の有料アセット(2019.09時点)なだけあって ドキュメント がしっかり整備されています。使用する際はドキュメントを参照すると良いでしょう。
対応プラットフォームはiOS 9.0以上、Android 5.0以上に対応しており、macOSのUnity Editor上でも動作します。ただmacOSのUnity Editorで動かすときは、Scene ViewやGame View内ではなく独立のウィンドウで表示されるためデバッグ用途としてはやや使いづらい部分があります。
また、Windowsには対応していないことからもモバイルプラットフォーム向けのアセットといえます。
2. 導入
Asset StoreからUniWebView 3を購入してインポートします。
3. ひとまず使ってみる
インポートできたらProjectの UniWebView
> Demo
> UniWebViewDemo
シーンを開きます。実行すると以下のようにウィンドウが開いてUniWebViewのドキュメントサイトが表示されます。
接続先を変更したい場合はヒエラルキーにあるUniWebView PrefabにアタッチされているUniWebView Scriptの Url On Start
の欄に任意のURLを入力します。
4. 色々使ってみる
ドキュメントには チュートリアル がありますので、これに沿って進めていくと大体いい感じに使えるようになります。ここでは要点だけに絞って解説します。
まずは任意のシーンに空のGameObjectを作成して WebView
と名前を付けます。次にProjectに WebView
というスクリプトを作成して、先ほど作成したWebViewオブジェクトにアタッチします。
スクリプトに以下のコードを貼り付けます。
using UnityEngine; public class WebView : MonoBehaviour { UniWebView webView; void Start () { var webViewGameObject = new GameObject("UniWebView"); webView = webViewGameObject.AddComponent<UniWebView>(); webView.Frame = new Rect(0, 0, Screen.width, Screen.height); webView.Load("https://www.google.co.jp/"); webView.Show(); } }
実行すると以下のようになります。
5. ローカルHTMLファイルの読み込み
次にネット上のサイトではなく、ローカルにあるHTMLファイルを読み込んで表示させてみます。
まず StreamingAssets
フォルダを作成します。
以下のHTMLファイルを作成し、sample.html
と名前を付けてStreamingAssets
フォルダに保存します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> Hello World </body> </html>
先ほど作成したスクリプト WebView.cs
を以下のように変更します。
void Start () { var webViewGameObject = new GameObject("UniWebView"); webView = webViewGameObject.AddComponent<UniWebView>(); webView.Frame = new Rect(0, 0, Screen.width, Screen.height); var url = UniWebViewHelper.StreamingAssetURLForPath("sample.html"); webView.Load(url); webView.Show(); }
実行すると以下のように表示されます。StreamingAssets
フォルダ内にjavascriptファイルやcssファイルを配置すればHTMLから参照することが可能です。
6. Unity(C#) → WebView(Javascript)の通信
UniWebViewではUnity側からJavascriptの関数を実行することが可能です。
sample.html
を以下のように変更します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> Hello World </body> <script language="javascript" type="text/javascript"> function SumTest(a, b) { return a + b; } </script> </html>
WebView.cs
を以下のように変更します。
using UnityEngine; public class WebView : MonoBehaviour { UniWebView webView; void Start () { var webViewGameObject = new GameObject("UniWebView"); webView = webViewGameObject.AddComponent<UniWebView>(); webView.Frame = new Rect(0, 0, Screen.width, Screen.height); var url = UniWebViewHelper.StreamingAssetURLForPath("sample.html"); webView.Load(url); webView.Show(); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { webView.EvaluateJavaScript("SumTest(1, 2)", (payload) => { if (payload.resultCode.Equals("0")) { Debug.Log("1 + 2 = " + payload.data); } }); } } }
実行してスペースキーを押すと以下のようになります。
Unity→Javascript関数の実行は以下の部分で行っています。EvaluateJavaScript
の第一引数でHTMLファイル側に記述したJavascriptの関数名を指定します。今回の例では引数も一緒に渡しています。
さらにJavascript関数の戻り値も第二引数で受け取ることが可能になっています。例ではラムダ式を用いてpayload変数で値を受け取っています。戻り値には resultCode
が付与されており、0
で正常終了。0
以外はエラーと判断することができるようになっています。
webView.EvaluateJavaScript("SumTest(1, 2)", (payload) => { if (payload.resultCode.Equals("0")) { Debug.Log("1 + 2 = " + payload.data); } });
うまくいかない場合は resultCode
を調べてみると良いでしょう。Javascriptの関数名が間違っていたりするとエラーになります。戻り値のデータは data
で取得することができます。
7. WebView(Javascript) → Unity(C#)の通信
Javascript側からUnity側の関数を呼び出すことも可能です。
sample.html
を以下のように変更します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <input type="text" id="mytext"> <input type="button" id="btn" value="OK" onclick="OnButtonClick();"> </body> <script language="javascript" type="text/javascript"> function OnButtonClick() { var message = document.getElementById('mytext').value; window.location.href = "uniwebview://action?value1=" + message + "&value2=" + "hoge"; } function SumTest(a, b) { return a + b; } </script> </html>
WebView.cs
を以下のように変更します。
using UnityEngine; public class WebView : MonoBehaviour { UniWebView webView; void Start () { var webViewGameObject = new GameObject("UniWebView"); webView = webViewGameObject.AddComponent<UniWebView>(); webView.Frame = new Rect(0, 0, Screen.width, Screen.height); var url = UniWebViewHelper.StreamingAssetURLForPath("sample.html"); webView.Load(url); webView.Show(); webView.OnMessageReceived += (view, message) => { if (message.Path.Equals("action")) { Debug.Log("value1: " + message.Args["value1"]); Debug.Log("value2: " + message.Args["value2"]); } }; } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { webView.EvaluateJavaScript("SumTest(1, 2)", (payload) => { if (payload.resultCode.Equals("0")) { Debug.Log("1 + 2 = " + payload.data); } }); } } }
実行するとWebView上でText入力とボタンが表示されるので、Textに適当な値を入れて[OK]ボタンを押します。
うまく動作すると以下ようにDebug.Logに表示されます。
JavascriptからUnityへは
window.location.href = "uniwebview://action?value1=" + message + "&value2=" + "hoge";
で値を送ることができます。&
でパラメータをつなげることで複数の値を送ることができます。
Unity側は
webView.OnMessageReceived += (view, message) => { if (message.Path.Equals("action")) { Debug.Log("value1: " + message.Args["value1"]); Debug.Log("value2: " + message.Args["value2"]); } };
で値を受け取っています。メッセージのPathはactionとしていますが送り手と受け手で一致していれば任意の値で構いません。値は message.Args[""]
でキーを指定することで取得することができます。
8. まとめ
UniWebView 3を使うとUnity上でWebブラウザの機能が使えることがわかりました。Unity上からJavascriptを使ってWebの豊富な資源を活用したり、洗練されたUIなどを流用することができるようになるため、手段の1つとして覚えておくと便利でしょう。