ros2-for-unityを使ってunity上で自作メッセージをpub/sub

# ros2-for-unityとは

https://github.com/RobotecAI/ros2-for-unity

- unityでros2のtopicを送受信するためのパッケージです
- DDSネイティブな通信ができるので早いらしいです
- 自作メッセージを使用するには自分でビルドする必要があるため，手順を残しておきます
    <br>

    ![r24u.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/32f2ee0e-9882-46d2-a7d0-1891b9241dff.gif)

# 使用方法
- ROS2 Humble (Ubuntu 22)環境での手順を示します
:::note warn
**【重要】ROS2 Foxy(Ubuntu 20)を使用する場合**
- Humbleの場合と手順が異なります
- `Foxyの場合のみ実行`に書かれている手順を必ず実行してください
:::

## 1. ビルド環境をセットアップ
初回のみ実行します．
[ros2cs](https://github.com/RobotecAI/ros2cs/blob/master/README-UBUNTU.md#prerequisitesr)の`Prerequisites`に示されている手順を実行します．
- 依存パッケージをインストール
    ```bash
    sudo apt install ros-${ROS_DISTRO}-test-msgs
    sudo apt install ros-${ROS_DISTRO}-fastrtps ros-${ROS_DISTRO}-rmw-fastrtps-cpp
    sudo apt install ros-${ROS_DISTRO}-cyclonedds ros-${ROS_DISTRO}-rmw-cyclonedds-cpp
    ```

<br>

- vcstoolをインストール
    ```bash
    curl -s https://packagecloud.io/install/repositories/dirk-thomas/vcstool/script.deb.sh | sudo bash
    sudo apt-get update
    sudo apt-get install python3-vcstool
    ```

<br>

- patchelfをインストール
    ```bash
    sudo apt install patchelf
    ```

<br>

- .NET 6.0をインストール

    :::note warn
    **Foxyの場合**のみ実行
    ```bash
    wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
    sudo dpkg -i packages-microsoft-prod.deb
    rm packages-microsoft-prod.deb
    ```
    
        :::
        
    ```bash
    sudo apt-get update
    sudo apt-get install apt-transport-https
    sudo apt-get update
    sudo apt-get install dotnet-sdk-6.0
    ```


## 2. ros2-for-unityをビルド
[ros2-for-unityのドキュメント](https://github.com/RobotecAI/ros2-for-unity/blob/master/README-UBUNTU.md)に従います．

- リポジトリをクローンします
    ```bash
    git clone -b humble https://github.com/RobotecAI/ros2-for-unity.git
    cd ros2-for-unity
    ```

<br>

- 依存リポジトリを自動でクローンします
    ```bash
    ./pull_repositories.sh
    ```
    :::note warn
    **Foxyの場合**のみ実行
    - example-interfaceを手動インストールします
        ```bash
        sudo apt install ros-foxy-example-interfaces*
        git clone -b foxy https://github.com/ros2/example_interfaces.git src/ros2cs/src/ros2/
        ```

    - **共有ライブラリがなぜかテキストファイルになっている**ので置き換えます
        ```bash
        wget -O ./src/ros2cs/resources/foxy/Linux/x86_64/librcpputils.so https://github.com/RobotecAI/ros2cs/raw/refs/heads/master/resources/foxy/Linux/x86_64/librcpputils.so
        ```
        :::

 <br>

- カスタムメッセージを配置します
    - **使用したいカスタムメッセージを含むROS2パッケージをすべて`./ros2-for-unity/src/ros2cs/custom_messages/`の中にコピーします**
    ```bash
    mkdir -p .src/ros2cs/custom_messages
    cp -r ROS2パッケージのパス ./ros2-for-unity/src/ros2cs/custom_messages/

    # 例
    cp -r ~/my_workspace/src/my_msg ./ros2-for-unity/src/ros2cs/custom_messages/
    ```

 <br>

- ビルドします
    - `--standalone`オプションをつけると，環境依存性を排除したパッケージが生成されます
    ```bash
    ./build.sh --standalone
    ```

 <br>

- 完了後，`Ros2ForUnity`ディレクトリが`./install/asset`内に生成されていることを確認してください


## 3. Unityにインストール
- `./install/asset/Ros2ForUnity`をUnityプロジェクト内`Assets/`に配置します
- CLIの場合
    ```bash
    cp -r ./install/asset/Ros2ForUnity Unityプロジェクト/Assets/

    # 例
    cp -r ./install/asset/Ros2ForUnity ~/MyUnityProject/Assets/
    ```
- ファイルマネージャで上記フォルダを見つけてドラッグアンドドロップしても良いです

<br>

- このような構造が正しいです．**正しくない場所に配置するとエラーが出ます．**
    ```
    .
    └── MyUnityProject/
        ├── Assets/
        │   ├── Ros2ForUnity <--- ここに配置
        │   └── ...
        ├── Packages
        └── ProjectSettings
    ```


## 4. 動作確認
- 以降，Unityでの作業です

1. Unityでテスト用のシーンを作成します
1. オブジェクトを追加します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/420d0bb4-4c25-4ccf-a416-fb451bb2ad84.png)
    
    <br>

1. オブジェクトに新しいスクリプトをアタッチします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/fee31934-3ef0-4a14-a175-e5e6c3cffc52.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/20e89a67-26b8-4d57-ad01-6b17c9108658.png)
    
    <br>


1. 作成したスクリプトに以下を記述します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/7dd1e84d-7b4c-4e81-8624-7827831d671c.png)

    <details><summary>動作確認用スクリプト</summary>

    
    ```csharp
    using UnityEngine;
    using ROS2;
    using Unity.VisualScripting;
    
    public class TestRos2ForUnity : MonoBehaviour
    {
        /* *****************************************************************************
        * メンバインスタンス
        ***************************************************************************** */
        private ROS2UnityComponent _r24u;                               // ros2-for-unityコンポーネント
        private ROS2Node _node;                                         // ros2ノード
        private Publisher<geometry_msgs.msg.PoseStamped> _publisher;    // パブリッシャ
    
    
        /* *****************************************************************************
        * 初期化処理
        ***************************************************************************** */
        void Start()
        {
            // ros2-for-unityコンポーネントを追加
            _r24u = this.AddComponent<ROS2UnityComponent>();
    
            // ノードを作成
            _node = _r24u.CreateNode("my_unity_node"); 
    
            // パブリッシャを作成
            _publisher = _node.CreatePublisher<geometry_msgs.msg.PoseStamped>("my_unity_pose");
        }
    
    
        /* *****************************************************************************
        * 自身の位置をros2にパブリッシュ
        ***************************************************************************** */
        void Update()
        {
            // メッセージを生成
            geometry_msgs.msg.PoseStamped msg = new geometry_msgs.msg.PoseStamped();
            msg.Header.Frame_id = "map";
            
            // 自身の位置姿勢を取得
            Vector3 pos = this.transform.position;
            Quaternion rot = this.transform.rotation;
    
            // メッセージに位置姿勢を設定
            msg.Pose.Position.X = pos.x;
            msg.Pose.Position.Y = pos.z;
            msg.Pose.Position.Z = pos.y;
    
            msg.Pose.Orientation.X = rot.x;
            msg.Pose.Orientation.Y = rot.z;
            msg.Pose.Orientation.Z = rot.y;
            msg.Pose.Orientation.W = rot.w;
    
            // メッセージをパブリッシュ
            _publisher.Publish(msg);        
        }
    }
    ```

    
    </details>

    
    <br>

1. シーンを再生します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/f274aa5f-34d8-4993-9807-83850dee5edf.png)
    
    <br>



1. Unityからtopicがpublishされているか確認します
    - コマンドから確認
        - `ros2 topic echo /my_unity_pose` 
    - rviz2でも確認できます
    ![r24u.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018129/32f2ee0e-9882-46d2-a7d0-1891b9241dff.gif)


## 5. スクリプトの書き方
- [subscriberのサンプル](https://github.com/RobotecAI/ros2-for-unity/blob/master/src/Ros2ForUnity/Scripts/ROS2ListenerExample.cs)や[publisherのサンプル](https://github.com/RobotecAI/ros2-for-unity/blob/master/src/Ros2ForUnity/Scripts/ROS2TalkerExample.cs)を参考にしましょう

### メッセージ内のフィールドへのアクセス
- 基本的にはC++におけるそれを，C#の記法に直すだけです
    ```cpp
    // C++におけるそれ
    geometry_msgs::msg::PoseStamped msg;
    msg = geometry_msgs::msg::PoseStamped();
    msg.pose.position.x = 1.0;
    ```
    ↓
    ```cs
    // ros2-for-unity
    geometry_msgs.msg.PoseStamped msg;
    msg = new geometry_msgs.msg.PoseStamped();
    msg.Pose.Position.X = 1.0;
    ```

    - 命名規則がややこしいですが，私はIntelliSense様に教えてもらってどうにかしています
    
    <br>

- 自作メッセージを使用している場合も，正しくビルドできていればビルトインメッセージ型と同じように扱うことができます
    ```cs
    my_msgs.msg.MyCustomMessage msg;
    msg = new my_msgs.msg.MyCustomMessage();
    msg.MyScholarField = 1.0;
    ```