ROS講座124 Unityアプリを整える・androidで使う

# 環境
この記事は以下の環境で動いています。

| 項目 | 値 |
|:-:|:-:|
| CPU | Core i5-8250U |
| Ubuntu | 18.04 |
| ROS | Melodic |
| Gazebo | 9.0.0 |
| python | 2.7.17 |

Unityの動作環境は以下です。

| 項目 | 値 |
|:-:|:-:|
| ホストマシン | Windows10 |
| Unity | 2019.4 |

インストールについては[ROS講座02 インストール](https://qiita.com/srs/items/e0e0a9dc3f94c2d3348e)を参照してください。
またこの記事のプログラムはgithubにアップロードされています。[ROS講座11 gitリポジトリ](https://qiita.com/srs/items/25a56a6abda41dcf396e)を参照してください。

# 概要
今回はUnityを使ってROSのImageが見えて、画面タップでTwistが送れるアプリを作って、それをAndroidで動かしてみましょう。

# Unityアプリを作る

## 新規プロジェクトの作成
* 前回と同様に新規プロジェクトを作成します。前回と同じく3Dを選んでおきます。

## アセットの追加
今回はROSとの通信を行うROS#とタッチ画面でjoystickのような動作をするjoystick packの2つを使います。

### ROS#のインポート
前回と同様です

* [github上のリリースページ](https://github.com/siemens/ros-sharp/releases/tag/v1.7)から「RosSharp.unitypackage」をダウンロードしてローカルに保存します。
* メニューバーの「Assets」->「Import Package」->「Custom Package...」を選んで出てくるウィンドウで「RosSharp.unitypackage」を選択します。
* 出てくるウィンドウですべての項目にチェックが入っていることを確認して「import」 を押す。

### joystick packのインポート
joystick packはAssetストアからダウンロードできます。

* メニューバーの「Wondow」->「Asset Store」を選択
* 検索窓で「joystick pack」と入力、出てくるものから「joystick pack」を選択
* 次の画面で「Import」を押す。
* 出てくるウィンドウですべての項目にチェックが入っていることを確認して「import」 を押す。

![unity_mobile_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254442/afa6516d-dea1-edc4-e1d3-20b0bc814952.png)


## RosConnectorの追加
前回と同様です

* 空のGameObjectを追加、RosConnectorをリネーム
* `Assets/RosSharp/Scripts/RosBridgeClient/RosCommuncation/RosConnector.cs`をRosConnectorにアタッチ
* urlなどの情報を入れる。

## Imageの表示
前回は3D空間上のPlaneに表示しましたが、これだと画面いっぱいに表示したり、解像度・アスペクト比の変更に対応しずらいです。そこで今回はuGUIのImageを使って表示します。

### uGUI上でのオブジェクトの配置

* 「Hierarchy」ウィンドウで「+」ボタンを押して「UI」->「Canvas」を追加します。CanvasはuGUIを配置するためのオブジェクトです。
    * スマホなどの画面の大きさはまちまちです、これに対応する設定をします。
    * オプションの「UI Scale Mode」を「Scale With Screen Size」に設定します。その下の「Referen Resolution」を大体の画面のサイズ（今回は「1280x720」）にします。こうするとCanvas上のオブジェクトを1280x720を前提で配置しても、実際のサイズに合わせてよい感じにサイズ変更してくれます。
    * 「Screen Match Mode」は「Expand」を指定します。こうするとCanvasは実際の画面サイズに合わせて縦横が伸縮します。
![unity_mobile_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254442/acd2c7d9-7547-6842-bef1-6ac413768455.png)


カメラ画像はアスペクト比を固定して表示します。このための設定をします。

* Canvas直下にImageを配置します。以下のように「Rect Transform」を設定するとImageの縦横比が一定になるようCanvasとの間に余白ができます。
    * アンカーをCenter-Middleと設定
    * Width、Heightをはカメラ画像のアスペクト比と同じになるものを指定します（ここでは960x720）
        
### スクリプトの作成
デフォルトであるスクリプトでは3DのPlaneにしか画像を書き出せません。ROS#のスクリプトはシンプルな構成になっているので、オリジナルにあるものを改造して、Imageに書き出すものを作ります。

```cs:script/ImageSucscriberToImage 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace RosSharp.RosBridgeClient
{
    public class ImageSucscriberToImage : UnitySubscriber<MessageTypes.Sensor.CompressedImage>
    {
        public GameObject image_object;

        private Texture2D texture2D;
        private byte[] imageData;
        private bool isMessageReceived;

        protected override void Start()
        {
            base.Start();
            texture2D = new Texture2D(1, 1);
        }
        private void Update()
        {
            if (isMessageReceived)
                ProcessMessage();
        }

        protected override void ReceiveMessage(MessageTypes.Sensor.CompressedImage compressedImage)
        {
            imageData = compressedImage.data;
            isMessageReceived = true;
        }

        private void ProcessMessage()
        {
            texture2D.LoadImage(imageData);
            texture2D.Apply();
            isMessageReceived = false;

            Image image_component = image_object.GetComponent<Image>();
            image_component.sprite = Sprite.Create(texture2D,
                                                   new Rect(0, 0, texture2D.width, texture2D.height),
                                                   Vector2.zero);
        }
    }
}
```

ROS#の中のImageSucscriber.csを参考にしています。内容はシンプルで受信したImageメッセージをCanvasのimageに変換して書き込んでいます。

これをRosConnectorにアタッチします。

* Topicに対象の物（今回は/head_camera/image_raw/compressed）を指定します。
* image_objectに先ほど追加したImageを指定します。

## Twistの送信
Joyの入力をTwistにしてpublishします。

### uGUI上でのオブジェクトの配置
* `Assets/Joystick Pack/Prefabs/Floating Joystick.prefab`を選択して、「Hierarchy」にドラッグ＆ドロップします。名前をleft_joystickと変えます。
    * ancherをleft-bottomに変更
    * width,heightを500程度にします
* もう1つ作成して名前をright_joystickとします。右側のスティックは左右にのみ動くようにします。
    * ancherをright-bottomに変更
    * width,heightを500程度にします
    * axis optionを「Horizontal」にします。これによって軸が左右にのみ動きます。
    * 子のObjectのBackGroundとHandleも水平軸用の画像に変更します。


### スクリプトの作成

```cs:scripts/TwistPlublisher.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RosSharp.RosBridgeClient
{
    public class TwistPlublisher : UnityPublisher<MessageTypes.Geometry.Twist>
    {
        public Joystick left_joystick;
        public Joystick right_joystick;
        public float linear_speed = 0.5f;
        public float angular_speed = 1.5f;

        private MessageTypes.Geometry.Twist message;

        protected override void Start()
        {
            InitializeMessage();

            RosConnector ros_connector = GetComponent<RosConnector>();
            ros_connector.IsConnected.WaitOne(ros_connector.SecondsTimeout * 1000);
            base.Start();
        }

        private void Update()
        {
            UpdateMessage();
        }

        private void InitializeMessage()
        {
            message = new MessageTypes.Geometry.Twist();
        }

        private void UpdateMessage()
        {
            message.linear.x = linear_speed * left_joystick.Vertical;
            message.linear.y = -linear_speed * right_joystick.Horizontal;
            message.angular.z = -angular_speed * left_joystick.Horizontal;
            Publish(message);
        }
    }
}
```

* JoyPublisher.csを参考にしました。
* 左右のJoyStickの値をTwistに詰めて送信します。
* 注意点としてRosConnectorが接続ができる前に` base.Start();`が呼ばれると通信ができません。

このスクリプトをRosConnectorにアタッチします。
* LeftJoyStick、RightJoyStickにそれぞれCanvasに張ったオブジェクトをアタッチします。
* LinearSpeedに0.5、AnguarSpeedに1.0辺りを入れます。 

## 接続の表示
アプリがROSとつながっているのか切れているのか、それだけでも表示できると良いでしょう。

### Textの配置
* Canvas以下に「UI」->「Text」を配置します。

### スクリプトの追加

```cs:script/ConnectionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using RosSharp.RosBridgeClient;

public class ConnectManager : MonoBehaviour
{
    public Text status_text;
    public GameObject ros_connector_object;

    void Update()
    {
        RosConnector ros_connector = ros_connector_object.GetComponent<RosConnector>();

        Text text = status_text.GetComponent<Text>();
        if (ros_connector.IsConnected.WaitOne(0))
        {
            text.text = "connected";
        }
        else
        {
            text.text = "disconnect";
        }
    }
}

```

* 毎updateごとにRosConenectorの状態を監視して、結果をテキストに書き出します。

これをRosConnectorにアタッチします。status_textとros_connector_objectにそれぞれオブジェクトを割り当てます。

# 実行

## シミュレーターの立ち上げ（on Ubuntu）
シミュレーションを起動します。

```sh:ターミナル1(gazeboの起動)
roslaunch sim3_lecture base_world.launch 
```

```sh:ターミナル1(rosbridgeの起動)
roslaunch rosbridge_server rosbridge_websocket.launch
```

## Unityのプレビュー(on Windows)
画面上の三角形ボタンを押します。

![unity_mobile_vis.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254442/f5a4fbd1-5f45-b1ff-11d8-3ef7ee6b9fa1.gif)

# androidで動かす
Android上でこのアプリを動かしてみます。

## Android Build Supportのインストール
Unity起動時にやっているとして省略

## androidスマホを開発者モードに設定
アプリをAndroidスマホに書き込むには開発者モードを使う必要があります。

* androidの「設定」→「端末情報」→「ソフトウェア情報」→「ビルド番号」を7回タップします。これで開発者向けオプションが有効になります。
* 「設定」->「開発者オプション」を選択します。出てくるウィンドウでUSBデバッグを有効にします。

## Android用のアプリをビルド・書き出し
* 「File」->「build_setting」を選びます。
* 出てくるWindowでAndroidを選び「Switch Platform」を選びます。（ここの処理が時間がかかります）
* 強制横画面の設定をします。「Player Setting」を押して出てくるWindowでAmndroidのマークを選び、「Allowed Orientation for Auto Rotation」「Landscape Right」、「Landscape Left」のみを選択にします
* PCとAndroidスマホをUSBで接続します。この時にAndroid側に接続を信頼するかのダイヤログが出るので承認します。

この状態で「Build And Run」を押すとビルド、Androidへの転送が開始され、終了後にアプリが起動します。


# 目次ページへのリンク
[ROS講座の目次へのリンク](https://qiita.com/srs/items/5f44440afea0eb616b4a)