4
4

More than 1 year has passed since last update.

UnityでROS2用スマホコントローラを作る

Last updated at Posted at 2023-08-13

※今回の記事はほとんど下の記事の再現です。偉大な先人に感謝。
https://qiita.com/Kotakku/items/cdc3eca89dd8aec4ee86

 今回はAndroidとROS2を通信させてスマホコントローラーを作成する。なお,UnityはUbuntu22.04に対応していないため,大人しくWindowsにインストールすること。
 それぞれのバージョンは以下の通り。

  • Windows11
  • Unity 2021.3.25f1
  • Ubuntu22.04
  • ROS2 Humble

 それぞれの導入については簡単なので省略する。

大まかな解説

 最近はTelloなどの市販品に限らず,ロボコンなどでもスマホでロボットを操作する事例をよく見かける。遅延はどうなってるの?とか,コントローラーで良くね?とか,色々消極的な意見はあるだろうが,やはりスマホからロボットを操作しているとなんとなくカッコよく見えるのは確かだ。
 そんなわけでスマホをコントローラーとして用いようと思った時に,最も簡単と思われるのがUnityとROS2を用いるやり方だ。この方法ではros2-for-unityという,ネイティブでUnityとROS2を通信させるライブラリを利用する。こちらの記事によるとこのライブラリは他のものと比べて高速で通信できるらしい。

ros2-for-unityはRobotec.aiによって開発されたUnityとROS2間の通信を行うライブラリです。
ROS2 C#クライアントライブラリをネイティブライブラリとしてUnity Projectに取り込み、DDSのプロトコルで他のROS2アプリケーションと通信します。
OSはWindowsとUbuntuをサポートしています。
無駄な変換が一切挟まっていないため、これより早いプロセス間通信の方法は存在しないと言えます。

 OSはWindowsとUbuntuをサポートしていますという部分を読んで「ならAndroidでは使えないじゃん!」と思った人がいるかもしれないが,Androidに対応するためのパッケージを有志が公開しているので問題ない。
 また,Unityを利用する事でUI開発が容易になるなどのメリットもある。便利なツールはどんどん使って開発を効率化していこう。

準備

Unityで開発を始めるための準備を行う。

パッケージのダウンロード

こちらからros2-for-unityのビルド済みソースコードをダウンロードする。
image.png (60.9 kB)

今回の例ではROS2がインストールされていないWindows上での利用が想定されるので,Ros2ForUnity_humble_standalone_windows11.zipをダウンロードする。
次にこちらからros2-for-unity-android-packageをダウンロードする。これはros2-for-unityをAndroidでも使えるようにしたパッケージだ。
image.png (62.5 kB)

ダウンロードした2つのファイルを適当に解凍しておく。

プロジェクトの作成

Unity Hubを開く。
日本語の方が良いという人は左上の歯車マークをクリックしてAppearance>Language>日本語とする。
右上の新しいプロジェクトを選択してテンプレートとして2D Mobileを選択する(恐らく初回はテンプレートのダウンロードが求めらられる)。
image.png (113.3 kB)

プロジェクト名と保存場所を指定し,プロジェクト作成をクリックする。
なお,今回はプロジェクト名をAndroid Controllerとした。

パッケージの導入

Unityのウィンドウが開く。日本語にしたい人は左上からEdit>Preferences...>Languages>Editor language>日本語(Experimental)とすれば良い。

先ほどダウンロードしたパッケージを導入する。
プロジェクトウィンドウ上で右クリックをすると以下の画像のように表示が出てくるので,パッケージをインポート>カスタムパッケージ...をクリックする。
image.png (62.8 kB)

先ほど解凍したros2-for-unity-android-packageの中にあるRos2ForUnityAndroid.unitypackageを選択し,開く。
すると下の画像のようなポップアップが出てくるので,そのままインポートを選択する。
image.png (30.0 kB)

プロジェクトウィンドウにRos2ForUnityというフォルダが追加されているのが確認できると思う。
image.png (9.0 kB)

Ros2ForUnity>Pluginsと開くとAndroidというフォルダがあるので,その上で右クリックをしてエクスプローラーで表示をクリックする。
image.png (65.5 kB)

エクスプローラーをもう一つ開いて先ほど解凍したもう一つのフォルダ(Ros2ForUnity)を開く。Ros2ForUnity>Pluginsと開くと,中にWindowsというフォルダがあるので,それを先ほど開いた別のエクスプローラーのウィンドウにコピペする。
image.png (155.8 kB)

Unityの画面をクリックすると自動的にインポートが行われる。

諸々の設定

次のステップでInput System Packageというパッケージを利用したいのでその設定を行う。
ウィンドウ>パッケージマネージャーを開く。
image.png (42.2 kB)

出てきたウィンドウの左上のパッケージ:プロジェクト内となっている部分をクリックし,Unityレジストリに変更する。
image.png (62.9 kB)

右上の検索欄にInputと入力するとInput Systemというパッケージが表示されるので,それをインストールする。
image.png (41.3 kB)

画像のような表示が出てくるのでYesを選択してUnityを再起動する。
image.png (9.3 kB)

次にInput System Packageをアクティブにする。
左上のメニューから編集>プロジェクト設定...と開き,プレイヤー>Windows, Mac, Linuxの設定>その他の設定を開く。
image.png (71.6 kB)

その他の設定>設定>アクティブな入力処理*>入力システムパッケージ(新)とする。
image.png (25.9 kB)

ポップアップが出てくるのでApplyを選択してUnityを再起動する。
image.png (6.2 kB)

Unityでの作業

Buttonを作成する

コントローラーのボタンの代替となるようなものを作る。
ヒエラルキーウィンドウ上で右クリックし,UI>ボタン-TextMeshProと選択。
image.png (65.6 kB)

このような表示が出てきたらImport TMP Essentialsを選択しておく。
image.png (27.4 kB)

必要であればヒエラルキーウィンドウからButton>Text(TMP)の不可視マークを外しておく。
image.png (13.4 kB)

同じくヒエラルキーウィンドウのCanvasをダブルクリックすると,シーンビューがUIサイズに調整されるので,Buttonのオブジェクトをいい感じのサイズに調整しておく。
image.png (24.1 kB)

ヒエラルキーウィンドウでText(TMP)を選択すると,右側のインスペクターウィンドウにText(TMP)の情報が表示される。
TextMeshPro-Text(UI)>Text Inputにある文字列を変更しておく。
image.png (37.5 kB)

今回はSay Helloとした。その下のMain Settingでは文字の色やサイズが変更できるので気になる人は触ってみるといいだろう。

次にButtonにアタッチするC#スクリプトを作成する。
プロジェクトウィンドウ上で右クリックし,作成>C#スクリプトとする。
image.png (76.2 kB)

今回はファイル名をHelloButtonとした。作成したC#ファイルをダブルクリックすると,VisualStudioが開くのでそこに以下のコードを貼り付ける。

using UnityEngine;
using ROS2;
using UnityEngine.UI;

public class HelloButton : MonoBehaviour
{
    private ROS2UnityComponent ros2Unity;
    private ROS2Node ros2Node;
    private IPublisher<std_msgs.msg.String> chatter_pub;
    private int i = 0;
    // ボタンの参照
    [SerializeField] private Button button;
    // シーン読み込み時に呼び出される関数
    void Start()
    {
        TryGetComponent(out ros2Unity);
        // onClickに関数を登録
        button.onClick.AddListener(OnButton);
    }

    // フレーム更新時に呼び出される関数
    void Update()
    {
        if (ros2Unity.Ok())
        {
            if (ros2Node == null)
            {
                // Nodeの名前を指定する
                ros2Node = ros2Unity.CreateNode("ROS2UnityTalkerNode");
                //トピックの名前と型を指定する
                chatter_pub = ros2Node.CreatePublisher<std_msgs.msg.String>("hello_from_Unity");
            }
        }
    }

    // ボタンが押されたときに呼び出される関数
    void OnButton()
    {
        i++;
        std_msgs.msg.String msg = new std_msgs.msg.String();
        msg.Data = "Hello world from Unity" + i;
        chatter_pub.Publish(msg);
    }
}

ちなみにUnityではファイル名とclass名が同じでないとエラーが出るのでコピペする際には注意する事。

今作成したHelloButton.csと/Ros2ForUnity/Scripts/ROS2UnityComponent.csをヒエラルキーウィンドウのButtonにドラッグ&ドロップしてアタッチする。
インスペクターウィンドウで画像のように表示されていればアタッチできている。
image.png (88.8 kB)

インスペクターのHello Button(スクリプト)>ボタンの⦿をクリックしてButtonオブジェクトを割り当てる。
image.png (19.9 kB)
これをしないとボタンを押しても何も起こらないので注意。

JoyStickを作成する

コントローラーのJoyStickの代替となるようなものを作る。
先ほどと同様にヒエラルキーウィンドウ上で右クリックし,UI>画像と選択。Imageでは分かりにくいので名前をJoyStickに変更しておく。
インスペクターウィンドウからImage>ソース画像の右側の⦿をクリックし,出てきたポップアップから好きな画像を選ぶ。今回はKnobという白い円の画像を選んだ。
image.png (54.2 kB)

さらにインスペクターの下部にあるコンポーネントを追加からOn-Screen Stickを追加する。
image.png (20.8 kB)

追加されたOn-Screen Stickの設定を以下の画像のように変更する。
image.png (12.4 kB)
Movement Rangeはジョイスティックの動き回る範囲であり,小さすぎると操作しずらいので値を増やしておく。

シーンビューからJoyStickのサイズや位置を調整しておく。
image.png (12.2 kB)

次にJoyStickにアタッチするC#スクリプトを先ほどと同様の手順で作成する。今回はファイル名をJoyControllerとした。
ファイルを開いて以下のコードをコピペする。

using UnityEngine;
using UnityEngine.InputSystem;
using ROS2;

public class JoyController : MonoBehaviour
{
    private ROS2UnityComponent ros2Unity;
    private ROS2Node ros2Node;
    private IPublisher<geometry_msgs.msg.Twist> twist_pub;
    // シーン読み込み時に呼び出される関数
    void Start()
    {
        TryGetComponent(out ros2Unity);
    }

    // フレーム更新時に呼び出される関数
    void Update()
    {
        if (ros2Unity.Ok())
        {
            if (ros2Node == null)
            {
                // Nodeの名前を指定する
                ros2Node = ros2Unity.CreateNode("UnityJoyNode");
                // トピックの名前と型を指定する
                twist_pub = ros2Node.CreatePublisher<geometry_msgs.msg.Twist>("/turtle1/cmd_vel");
            }
            geometry_msgs.msg.Twist msg = new geometry_msgs.msg.Twist();
            var current = Gamepad.current;
            var input_vec = current.leftStick.ReadValue();
            msg.Linear.X = input_vec.x;
            msg.Linear.Y = input_vec.y;
            twist_pub.Publish(msg);
        }
    }
}

turtlesimを動かしたいのでこのようなコードになっている。

先ほどと同様に,今作成したJoyController.csと/Ros2ForUnity/Scripts/ROS2UnityComponent.csをヒエラルキーウィンドウのJoyStickにドラッグ&ドロップしてアタッチする。

その他必要な作業

  • ROS_DOMAIN_IDの設定
     これまでの実装では特にROS_DOMAIN_IDを設定してこなかったが,実際に使う場合は必ずIDを設定する必要がある。また,ROS_DOMAIN_IDはROS2UnityComponent.csの実行よりも先に設定される必要があるため,その設定を行う。
    ※ROS_DOMAIN_IDは例として123としているが,適宜自分の環境のものに置き換えること。

先ずは,プロジェクトウィンドウ上で右クリックをしてSetIDという名前のC#スクリプトを作成し,そこに以下のコードをコピペする。

using System;
using UnityEngine;

public class SetID : MonoBehaviour
{
    void Start()
    {
        // 現在設定されているIDを調べる
        string value = Environment.GetEnvironmentVariable("ROS_DOMAIN_ID");
        Debug.Log("current ROS_DOMAIN_ID:" + value + "\n");
        // ROS_DOMAIN_IDに123を設定する
        Environment.SetEnvironmentVariable("ROS_DOMAIN_ID", "123");
        Debug.Log("DOMAIN_ID set OK\n");
        // 上手く設定できているか確認する
        value = Environment.GetEnvironmentVariable("ROS_DOMAIN_ID");
        Debug.Log("current ROS_DOMAIN_ID:" + value + "\n");
    }
}

作成したスクリプトを適当な空のオブジェクトに貼り付けて実行されるようにする。
ヒエラルキーウィンドウ上で右クリックをして空のオブジェクトを作成を選択し,分かりやすくするためオブジェクトにSetID_Objectという名前を付けた。
image.png (41.3 kB)

先ほど作成したSetID.csをプロジェクトウィンドウからヒエラルキーウィンドウ上のSetID_Objectへドラッグ&ドロップしてスクリプトをアタッチする。
上手くいっていればSetID_Objectのインスペクターが画像のようになる。
image.png (22.3 kB)

次にメニューから編集> プロジェクト設定...>スクリプト実行順序と開き,右下の+からSetIDを追加する。
image.png (55.9 kB)

SetIDの右側の欄に-1と入力し,ApplyをクリックすればSetIDは通常のスクリプトよりも早く実行されるようになる。
image.png (41.1 kB)

  • Input System Packageの準備の続き
    ヒエラルキーウィンドウからEventSystemを選択し,インスペクターウィンドウでStandalone Input ModuleReplace with InputSystemUIInputModuleをクリックする。
image.png (64.0 kB)

以上でInput System Packageを使う準備ができた。

ビルド&インストール

ビルドの実行

今作ったゲーム?を実際にアプリとしてビルドしていく。
ファイル>ビルド設定を開く。
image.png (33.4 kB)

下の画像のようなウィンドウが出てくるので,プラットフォームにAndoridを選択してプレイヤー設定...を開く。
image.png (69.0 kB)
※Androidが選択できない人はUnity HubからAndroid Bulid Supportをインストールすること。

解像度と表示>自動回転を許可する向き横向き(右)横向き(左)だけにする。
image.png (48.5 kB)

その他の設定>設定>スクリプティングバックエンドをMonoからIL2CPPに変更し,その下のターゲットアーキテクチャのARM64にチェックを入れる。さらにインターネットアクセス必須に変更する。
image.png (40.1 kB)

Project Settingsを閉じてBuild Settingに戻り,ウィンドウ右下のターゲット切り替えをクリックする。
すると下の画像のような画面になるのでビルドを選択する。
image.png (68.4 kB)

ファイル名と保存場所を聞かれるので適当に決める。しばらく時間がかかるので気長に待つこと。

Androidへのインストール

先ほど作成したapkファイルをAndroidにインストールする。以下の操作はスマホによって微妙に異なると思われるのであくまでも一例。

Androidの設定アプリを開き,デバイス情報>ビルド番号を連打して開発者向けオプションを有効化する。
システム>開発者オプション>デバッグからUSBデバッグを有効にする。
image.png (157.4 kB)

スマホをPCにUSB接続し,先ほど作成したapkファイルをスマホの適当なフォルダにコピペする。
スマホ側で適当なファイルアプリでapkファイルを探してタップすると画像のような表示が出てくるので,そのままインストールする。いろいろ警告が出てくるかもしれないが全て無視すれば良い。
image.png (71.8 kB)

インストールできた。
image.png (514.2 kB)

テスト

スマホ側で先ほどインストールしたアプリを起動する。
Ubuntu22.04を起動し,スマホと同じWiFi(テザリングでも良い)に接続していることを確認してから,ターミナルに以下のコマンドを打ち込む。

$ ros2 topic list

このように表示されていればうまくいっている。

/hello_from_Unity
/parameter_events
/rosout
/turtle1/cmd_vel

次に以下のコマンドをそれぞれ別のターミナルで実行する。

$ ros2 run turtlesim turtlesim_node
$ ros2 topic echo /hello_from_Unity

スマホ側のボタンを押すとメッセージが,joystickを操作すると亀が動くはずだ。
ros2 Android controller
※動画に飛びます

終わり

ROS_DOMAIN_IDの設定にかなり手こずってしまったが,なにはともあれ憧れのスマホコントローラーができた。今回はボタンとjoystickというコントローラーの基礎的な要素のみの実装だったが,これを応用すれば素晴らしいオリジナルUIを作成できるのではないだろうか。

参考文献

入力システムパッケージ (Input System Package) を導入する
ros2-for-unity-android-package
AndroidをROS2ノードにしてロボットのコントローラを作る
ROS2 for Unityで始めるUnityとROS2間の高速データ通信

4
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4