Unreal Engine 5を使ってみる その9(Remote Control API、Node-RED)

はじめに

 以前HTTP Blueprintを使ってUnreal Engine 5(UE5)からNode-REDへの通信は試したのですが、今回はその逆です。

 HTTP Remote Control APIを使ってNode-REDからUE5のオブジェクトを操作してみました。Node-REDと双方向に通信できるようになれば、私はPythonでもマイコンでもいろいろ使えるようになります。

▼図示するとこんな感じです。

▼今回もOpen Manipulatorのモデルを使います。

Unreal Engine 5を使ってみる その7(Open Manipulator)

はじめに  前回、Unreal Engine 5(UE5)でロボットアームのようなものを作成しました。今回はROBOTIS社がオープンソースとして公開している、Open Manipulatorというロ…

▼Open Manipulatorについてはこちらにサンプルレベルを公開しています。

https://github.com/404background/ue5-open-manipulator

▼以前の記事はこちら

Unreal Engine 5を使ってみる その6(ロボットアーム、ブループリント)

はじめに  今回はUnreal Engine 5(UE5)でロボットアームのようなものを作ってみました。まだ試作段階で、これから厳密なモデルにしていきたいなと考えています。  ま…

WSL2を使ってみる その4(Ubuntu 16.04、ROS Kinetic、Open Manipulator)

はじめに  これまでWSL2にUbuntu 18.04、20.04、22.04をインストールして、ROS/ROS2の環境を構築しました。今回はUbuntu 16.04です。  オンラインドキュメントや書籍の…

プラグインについて

2種類のAPI

 少し紛らわしいのですが、Remote Control API とRemote Control Preset APIがあります。

▼Remote Control API HTTP Referenceについてはこちら。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/remote-control-api-http-reference-for-unreal-engine?application_version=5.1

▼Remote Control Preset API HTTP Referenceについてはこちら。

https://dev.epicgames.com/documentation/en-us/unreal-engine/remote-control-preset-api-http-reference-for-unreal-engine?application_version=5.1

 HTTP エンドポイントを提供しているのがRemote Control APIです。

 Remote Control Preset以前の記事で使いました。リモート コントロール プリセットで公開されているプロパティにアクセスすることができます。

▼Remote Control APIは追加しておいてください。

▼プロジェクト設定から、ポート番号などを変更できます。

プレイ中に操作できない問題

 注意点として、Remote Control APIの場合は編集画面でプロパティを変更することはできたのですが、プレイ中は変更することができませんでした

 原因は分かっていて、リファレンスに書かれていました。

▼こちらに書かれています。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/remote-control-api-http-reference-for-unreal-engine#putremote/object/property

-game モードまたはプレイ イン エディタ (PIE) モードでオブジェクトにアクセスする場合は、オブジェクトが BlueprintVisible に設定されている必要がある。値を変更するには、プロパティが BlueprintReadOnly に設定されていてはなりません。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/remote-control-api-http-reference-for-unreal-engine#putremote/object/property

 ブループリントのプロパティについては、ブループリントと等価なC++ヘッダをプレビューすることで確認できました。

▼今回の場合、Opne Manipulatorのブループリントを右クリックすると、「等価なC++ヘッダをプレビュー」という選択肢がありました。

▼UPROPERTYで BlueprintReadOnlyになっています。

 いろいろ調べていたのですが、BlueprintVisibleの変更方法が分かりませんでした。最初からC++で作成していれば変更できるのかもしれませんが、ブループリントで作成しているせいなのか、BlueprintVisibleに関する設定項目が見当たりませんでした。

  BlueprintReadOnlyの変更方法をご存じの方がいらっしゃったら、コメント等で教えて頂けると幸いです。

 結局、Remote Control Preset APIの場合はプレイ中もプロパティを変更できたので、そちらを使うことにしました。

他端末からアクセスする場合

 以前の記事にも書いていたのですが、他の端末からRemote Control APIを利用する場合は、ファイルに少し追記する必要があります。

▼このページの一番下に書かれています。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/remote-control-quick-start-for-unreal-engine?application_version=5.1

 「他のデバイスから Remote Control API を使って Unreal セッションにアクセスできるようにするには、プロジェクトの DefaultEngine.ini ファイルを端末の IP アドレスに変更します。」と書かれています。

 DefaultEngine.iniファイルを編集します。一度プロジェクトを閉じてから編集し、再度起動しました。

▼プロジェクトのConfigファイルにあります。

▼以下の2行を追加しました。0.0.0.0はワイルドカードです。

Node-REDのHTTP Requestノードについて

 以前の記事ではUE5からのHTTPリクエストをNode-REDで受け取るのに、http in/outノードを使いました。

 今回はNode-REDのhttp requestノードを使って、UE5にHTTPリクエストを送ります。

▼http requestノードについては、こちらに詳しく書かれています。

https://qiita.com/utaani/items/a4ea0cba414f63adf09f

▼メソッドやURLを設定できるようになっています。

Remote Control APIを利用してみる

 先述したように、ブループリントが BlueprintReadOnlyに設定されている場合はプレイ中にアクセスできません。その点はご注意ください。

▼URLはリファレンスに従って設定していきます。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/remote-control-api-http-reference-for-unreal-engine?application_version=5.1

 まずはGETメソッドで送信してみます。GET remote/infoを送信すると、利用可能なHTTPルートが表示されます。

▼URLはこちら。<IPアドレス>は適宜変更してください。Node-REDとUE5を同じPCで起動しているのであれば、localhostでも大丈夫です。

http://<IPアドレス>:30010/remote/info

▼フローはこちら

▼http requestノードのメソッドはGETにして、URLを設定します。

▼HTTPルートが表示されました。

 次にPUTメソッドでUE5上のオブジェクトを操作してみます。PUT remote/object/propertyを送信します。

▼URLはこちら

http://<IPアドレス>:30010/remote/object/property

▼フローはこちら

▼メソッドはPUTにして、URLを設定します。

 まずOpen Manipulatorのモデルでアクセスできるプロパティを表示してみます。

▼injectノードの中身を設定していきます。

▼左側の選択肢でJSONを選択しておきます。

▼右側の三つの点を選択すると、JSONエディタが開きます。

{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1",
    "access": "READ_ACCESS"
}

 上記のように、今回はオブジェクトのパスにOpen Manipulatorのモデルを指定しています。

▼UE5でアクセスしたいオブジェクトを右クリックすると、パスのコピーという選択肢があります。これを貼り付けます。

▼実行すると、値が表示されます。

▼debugノードでメッセージ数が100件を超えると表示されないことがあったので、システムコンソールにも表示されるようにしておきました。

▼Node-REDを起動しているNode.jsのシステムコンソールに、値が表示されました。

▼ブループリントでのコンポーネント名も表示されています。

 さらに取得する値を絞っていきます。

▼Open Manipulatorに含まれていた、Joint1の値を取得してみます。

{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.Joint1",
    "access": "READ_ACCESS"
}

▼抜粋したものですが、RelativeLocation、RelativeRotationなどがありました。

▼さらにJoint1のRelativeRotationの値を取得しました。

{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.Joint1",
    "access": "READ_ACCESS",
    "propertyName": "RelativeRotation"
}

▼RelativeRotationの値だけ表示されました。

 Open Manipulatorのモデルのプロパティの構造が分かったので、RelativeRotationについて値を設定し、HTTPリクエストを送信してみます。

▼プロパティの値を設定します。

{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.Joint1",
    "access": "WRITE_ACCESS",
    "propertyName": "RelativeRotation",
    "propertyValue": {
        "RelativeRotation": {
            "Pitch": 0,
            "Yaw": 180,
            "Roll": 0
        }
    }
}

▼Pitchの値を0と90に変更すると、操作することができました。

 後で使いやすいように、角度だけ渡すようにしてみました。

▼フローはこちら。injectノードのmsg.payloadで数値だけ送信しています。

▼injectノードで試した内容をもとに、functionノード内のJavaScriptでリクエストボディを作成しています。

let angle = msg.payload
let joint = 'Joint1'

let body = `{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.${joint}",
    "access": "WRITE_ACCESS",
    "propertyName": "RelativeRotation",
    "propertyValue": {
        "RelativeRotation": {
            "Pitch": 0,
            "Yaw": ${angle},
            "Roll": 0
        }
    }
}`

msg.payload = body
return msg;

▼HTTPリクエストが正常に処理された場合、msg.payloadには特に何も返ってきません。エラーが出た場合は表示されます。

Remote Control Preset APIを利用してみる

▼URLはリファレンスを参考に設定します。

https://dev.epicgames.com/documentation/en-us/unreal-engine/remote-control-preset-api-http-reference-for-unreal-engine?application_version=5.1

プロパティの公開

 リモートコントロールプリセットでプロパティを公開する必要があります。以前の記事にも書いていたことですが、少しだけ再掲しておきます。

 リモートコントロールプリセットを追加します。

▼コンテンツドロワーで右クリックすると、遠隔操作の欄にあります。

▼ダブルクリックすると、グループやプロパティを設定できる画面が表示されます。

 リモートコントロールプリセットを開いた状態だと、ワールドに配置したオブジェクトのパラメータの右側にアイコンが追加されます。

▼パラメータの右側に、四角と丸のアイコンが追加されているのが分かるでしょうか。

 このアイコンを選択すると、プロパティを公開できます。

▼回転の場合、Pitch、Yaw、Rollを選択できるようになっています。

 今回はOpen ManipulatorのJoint1のYaw、Joint2~4のPitchを公開しました。

GETメソッド

 公開されたプロパティを、Node-REDで取得します。GET remote/presetsを送信してみます。

▼URLはこちら

http://<IPアドレス>:30010/remote/presets

▼フローはこちら

▼http requestノードのメソッドはGETにしておきます。URLは設定しません。

▼それぞれのinjectノードではmsg.urlにURLを入れています。

▼作成したリモートコントロールプリセットのNameやPathが表示されました。

 取得したNameの"NewRemoteControlPreset"という値に対して、GET remote/preset/insert_preset_nameを送信します。

▼URLはこちら

http://<IPアドレス>:30010/remote/preset/NewRemoteControlPreset

▼Joint1~4の公開しているプロパティが表示されています。

 さらに取得した"Joint1"という名前に対して、GET remote/preset/insert_preset_name/property/insert_property_nameを送信します。

▼URLはこちら

http://<IPアドレス>:30010/remote/preset/NewRemoteControlPreset/property/Joint1

▼Joint1のみのプロパティが表示されました。Yawを公開していたので、その値が表示されています。

PUTメソッド

 ここまではGETメソッドで値を取得してきましたが、PUTメソッドで操作してみます。

 PUT remote/preset/insert_preset_name/property/insert_property_nameを送信します。

▼URLはこちら

http://<IPアドレス>:30010/remote/preset/NewRemoteControlPreset/property/Joint1

▼フローはこちら

▼http requestノードはPUTメソッドにしておきます。

▼injectノードでProperty Valueを変更します。

 これでinjectノードのボタンを押して、正常に送信できていればOKです。

▼値によって回転します。

▼送信できた場合は、msg.payloadには特に何も返ってきません。何かが間違っている場合はエラーが起きます。

操作してみる

オブジェクトの配置

 以前の配置よりも見やすくするために、ThirdPersonCharacterのカメラを近づけて、Open Manipulatorのモデルを台の上に載せておきました。

▼こんな感じです。

 ThirdPersonCharacterはゲームが開始したときにPossessするようにしました。

▼Auto Possess PlayerでPlayer 0に設定するとできました。

 これでプレイ開始時にThirdPersonCharacterの視点で始まります。

Remote Control APIでの操作

 Node-REDのdashboardノードを使って操作できるようにしてみました。

 sliderノードで作成したスライドバーを動かすと、操作できます。

▼全体のフローはこちら

▼こちらをNode-REDで読み込むと、同じものが表示されると思います。

[{"id":"ee6b1a2f32212f1a","type":"http request","z":"2e28e0391bbfbac3","name":"","method":"PUT","ret":"txt","paytoqs":"body","url":"http://192.168.10.33:30010/remote/object/property","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1110,"y":2640,"wires":[["e80c7c94a658aabf"]]},{"id":"e80c7c94a658aabf","type":"debug","z":"2e28e0391bbfbac3","name":"debug 13","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1110,"y":2720,"wires":[]},{"id":"c1e6d38d114a5bf1","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint1","tooltip":"","group":"925c5c0fb531daae","order":2,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":330,"y":2620,"wires":[["26e5824ca835f9a4"]]},{"id":"26e5824ca835f9a4","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"angle","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"joint","pt":"msg","to":"Joint1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2620,"wires":[["4550bc53e7726cbe"]]},{"id":"376904a0ec84c5eb","type":"json","z":"2e28e0391bbfbac3","name":"","property":"payload","action":"","pretty":false,"x":890,"y":2600,"wires":[["ee6b1a2f32212f1a"]]},{"id":"4550bc53e7726cbe","type":"function","z":"2e28e0391bbfbac3","name":"Yaw","func":"let angle = msg.angle\nlet joint = msg.joint\n\nlet body = `{\n    \"objectPath\": \"/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.${joint}\",\n    \"access\": \"WRITE_ACCESS\",\n    \"propertyName\": \"RelativeRotation\",\n    \"propertyValue\": {\n        \"RelativeRotation\": {\n            \"Pitch\": 0,\n            \"Yaw\": ${angle},\n            \"Roll\": 0\n        }\n    }\n}`\n\nmsg.payload = body\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":2620,"wires":[["ba0c7e63a3b930b5","376904a0ec84c5eb"]]},{"id":"ba0c7e63a3b930b5","type":"debug","z":"2e28e0391bbfbac3","name":"debug 14","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":900,"y":2640,"wires":[]},{"id":"a64adb2c8233d579","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint2","tooltip":"","group":"925c5c0fb531daae","order":3,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":330,"y":2700,"wires":[["68d341881780cfa6"]]},{"id":"68d341881780cfa6","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"msg.angle","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"joint","pt":"msg","to":"Joint2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2700,"wires":[["f24d83155acdeb3a"]]},{"id":"53493dac4f57d416","type":"json","z":"2e28e0391bbfbac3","name":"","property":"payload","action":"","pretty":false,"x":890,"y":2740,"wires":[["ee6b1a2f32212f1a"]]},{"id":"f24d83155acdeb3a","type":"function","z":"2e28e0391bbfbac3","name":"Pitch","func":"let angle = msg.angle\nlet joint = msg.joint\n\nlet body = `{\n    \"objectPath\": \"/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.${joint}\",\n    \"access\": \"WRITE_ACCESS\",\n    \"propertyName\": \"RelativeRotation\",\n    \"propertyValue\": {\n        \"RelativeRotation\": {\n            \"Pitch\": ${angle},\n            \"Yaw\": 0,\n            \"Roll\": 0\n        }\n    }\n}`\n\nmsg.payload = body\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":2760,"wires":[["d7fd5f69427766d6","53493dac4f57d416"]]},{"id":"d7fd5f69427766d6","type":"debug","z":"2e28e0391bbfbac3","name":"debug 15","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":900,"y":2780,"wires":[]},{"id":"3dc1571aa5d2a1b8","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint3","tooltip":"","group":"925c5c0fb531daae","order":4,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":330,"y":2740,"wires":[["478280dfc7866c25"]]},{"id":"478280dfc7866c25","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"msg.angle","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"joint","pt":"msg","to":"Joint3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2740,"wires":[["f24d83155acdeb3a"]]},{"id":"925c303276be5ce7","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint4","tooltip":"","group":"925c5c0fb531daae","order":5,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":330,"y":2780,"wires":[["acc9e755ecee2743"]]},{"id":"acc9e755ecee2743","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"msg.angle","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"joint","pt":"msg","to":"Joint4","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2780,"wires":[["f24d83155acdeb3a"]]},{"id":"4d3e8f4626b60a8b","type":"ui_button","z":"2e28e0391bbfbac3","name":"","group":"925c5c0fb531daae","order":1,"width":0,"height":0,"passthru":false,"label":"Reset","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"0","payloadType":"num","topic":"topic","topicType":"msg","x":270,"y":2980,"wires":[["b1d105a5add6942c","b2cd2513f7b1182f","e577ddd7c3d287bd","566950d99c3f2d19"]]},{"id":"d794c31e11ba6b32","type":"inject","z":"2e28e0391bbfbac3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":2860,"wires":[["b1d105a5add6942c","b2cd2513f7b1182f","e577ddd7c3d287bd","566950d99c3f2d19"]]},{"id":"b1d105a5add6942c","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"angle","pt":"msg","to":"180","tot":"num"},{"t":"set","p":"joint","pt":"msg","to":"Joint1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2860,"wires":[["4550bc53e7726cbe"]]},{"id":"b2cd2513f7b1182f","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"msg.angle","pt":"msg","to":"0","tot":"num"},{"t":"set","p":"joint","pt":"msg","to":"Joint2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2900,"wires":[["f24d83155acdeb3a"]]},{"id":"e577ddd7c3d287bd","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"msg.angle","pt":"msg","to":"90","tot":"num"},{"t":"set","p":"joint","pt":"msg","to":"Joint3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2940,"wires":[["f24d83155acdeb3a"]]},{"id":"566950d99c3f2d19","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"msg.angle","pt":"msg","to":"0","tot":"num"},{"t":"set","p":"joint","pt":"msg","to":"Joint4","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":2980,"wires":[["f24d83155acdeb3a"]]},{"id":"925c5c0fb531daae","type":"ui_group","name":"preset","tab":"bd328187fa445237","order":2,"disp":true,"width":"6","collapse":false,"className":""},{"id":"bd328187fa445237","type":"ui_tab","name":"Open Manipulator","icon":"dashboard","disabled":false,"hidden":false}]

▼sliderノードは-180~180の値を設定できるようにしています。

▼functionノードのYawのコードはこちら

let angle = msg.angle
let joint = msg.joint

let body = `{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.${joint}",
    "access": "WRITE_ACCESS",
    "propertyName": "RelativeRotation",
    "propertyValue": {
        "RelativeRotation": {
            "Pitch": 0,
            "Yaw": ${angle},
            "Roll": 0
        }
    }
}`

msg.payload = body
return msg;

▼functionノードのPitchのコードはこちら

let angle = msg.angle
let joint = msg.joint

let body = `{
    "objectPath": "/Game/Sample.Sample:PersistentLevel.OpenManipulator_C_1.${joint}",
    "access": "WRITE_ACCESS",
    "propertyName": "RelativeRotation",
    "propertyValue": {
        "RelativeRotation": {
            "Pitch": ${angle},
            "Yaw": 0,
            "Roll": 0
        }
    }
}`

msg.payload = body
return msg;

▼changeノードではmsg.angle、msg.jointにそれぞれ値を代入するようにしています。

 ダッシュボードの画面を開いてみます。

▼ブラウザで開いているのであれば、ダッシュボードの欄にある外部リンクへのボタンを押すと表示されます。

▼こんな画面が表示されます。RESETボタンを押すと、すべて0になります。

 先述したようにゲームが開始すると操作できないのですが、編集中であれば操作できます。

▼反応はかなり早いです。

Remote Control Preset APIでの操作

 こちらでもdashboradノードで操作できるようにしました。

▼全体のフローはこちら

▼こちらをNode-REDで読み込むと、同じものが表示されると思います。

[{"id":"b2928af6fb2fafaa","type":"http request","z":"2e28e0391bbfbac3","name":"","method":"PUT","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":750,"y":1960,"wires":[["9534e2c79abe5347"]]},{"id":"9534e2c79abe5347","type":"debug","z":"2e28e0391bbfbac3","name":"debug 11","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":1960,"wires":[]},{"id":"e4107f5c0e72575b","type":"function","z":"2e28e0391bbfbac3","name":"angle","func":"let angle = msg.payload\nlet joint = msg.joint\n\nlet body = `{\n    \"PropertyValue\":${angle}\n}`\n\nmsg.payload = body\nmsg.url = `http://192.168.10.33:30010/remote/preset/NewRemoteControlPreset/property/${joint}`\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":1960,"wires":[["dc6745b9e66e17e6"]]},{"id":"530255f9b6b430b8","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint1","tooltip":"","group":"925c5c0fb531daae","order":7,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":110,"y":1960,"wires":[["431a7a2519248b7f"]]},{"id":"431a7a2519248b7f","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"joint","pt":"msg","to":"Joint1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":1960,"wires":[["e4107f5c0e72575b"]]},{"id":"dc6745b9e66e17e6","type":"json","z":"2e28e0391bbfbac3","name":"","property":"payload","action":"","pretty":false,"x":590,"y":1960,"wires":[["b2928af6fb2fafaa"]]},{"id":"427916068d3fdf49","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint2","tooltip":"","group":"925c5c0fb531daae","order":8,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":110,"y":2020,"wires":[["e4c39878a09b12f9"]]},{"id":"e4c39878a09b12f9","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"joint","pt":"msg","to":"Joint2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":2020,"wires":[["e4107f5c0e72575b"]]},{"id":"807f934e714941cd","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint3","tooltip":"","group":"925c5c0fb531daae","order":9,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":110,"y":2080,"wires":[["d69bd19c62903a20"]]},{"id":"d69bd19c62903a20","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"joint","pt":"msg","to":"Joint3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":2080,"wires":[["e4107f5c0e72575b"]]},{"id":"58bc175c6f9f1be1","type":"ui_slider","z":"2e28e0391bbfbac3","name":"","label":"Joint4","tooltip":"","group":"925c5c0fb531daae","order":10,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-180","max":"180","step":1,"className":"","x":110,"y":2140,"wires":[["9c9d48be51e5ecbf"]]},{"id":"9c9d48be51e5ecbf","type":"change","z":"2e28e0391bbfbac3","name":"","rules":[{"t":"set","p":"joint","pt":"msg","to":"Joint4","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":2140,"wires":[["e4107f5c0e72575b"]]},{"id":"925c5c0fb531daae","type":"ui_group","name":"preset","tab":"bd328187fa445237","order":2,"disp":true,"width":"6","collapse":false,"className":""},{"id":"bd328187fa445237","type":"ui_tab","name":"Open Manipulator","icon":"dashboard","disabled":false,"hidden":false}]

▼functionノードのangleの中身はこちら

let angle = msg.payload
let joint = msg.joint

let body = `{
    "PropertyValue":${angle}
}`

msg.payload = body
msg.url = `http://192.168.10.33:30010/remote/preset/NewRemoteControlPreset/property/${joint}`
return msg;

▼changeノードでJoint名を指定しています。

▼こちらもダッシュボード画面にスライダーが表示されます。

 実際に操作してみました。

▼こちらだとプレイ中も操作できるのですが、反応が遅いです。

最後に

 書きながら思い出したのですが、Remote Control Preset APIだと反応が遅かったのは、プレイ中のPCの処理速度を制限していたからかもしれません。設定を見直してみたほうが良いかもしれません。

 UE5の画面にフォーカスを当てないと、変更が反映されないことがありました。1つのPCで2画面のうち片方でUE5、もう片方でNode-REDのダッシュボードを操作した場合、UE5の画面をクリックしないと動かなかったです。

 そのため、私は別のPCでNode-REDを操作していました。UE5のプレイ中の画面表示については調べる必要がありそうです。完全に遠隔操作をできるようにするには、ゲーム画面もどうにかして送信する必要がありそうですね。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です