Unity + Arduinoできっちりオンオフのできる双方向通信をしよう

この記事は [Unity Advent Calendar 2022](https://qiita.com/advent-calendar/2022/unity "Unity Advent Calendar 2022") （カレンダー2） 21日目の記事です！
昨日の記事は@Kudo004さんの[【Unity】インスペクターに値が表示されない！](https://qiita.com/Kudo004/items/7708c5d4df09f918f5bd "【Unity】インスペクターに値が表示されない！")でした！

# 概要
* UnityとArduino で双方向通信をしたい
* Arduino→Unityは多く記事があるけど、Unity→Arduinoの記事は少ない
* UnityからArduinoを制御するとき、きっちりオンオフの動作を行いたい

と、いうことで自分が実装した動作をまとめてみました

特に連続的にUnityからデータを送り続けると、Arduinoとの同期の関係できっちり回路のオンオフができずモータが安定して回らなかったので、オンオフの動作を安定して行いたい方には参考になるかと思います。
今回はモータを使わずにLEDでテストしていますが、モータを使われる際も基本的には同じ実装方法で動くはずです（自分は動きました）。

# やること
### Arduino→Unity
センサの値をUnityで取得
※今回はダミーのデータを送信します
### Unity→Arduino
Unity側の処理に合わせてLEDをオンオフする
※今回はArduinoに初めからついているLED（13番）をオンにします

# Unityの準備
これをやらないとArduinoを制御することができないので注意してください。

1. Unityでプロジェクトを立ち上げます。
2. 左上Editの中のProject Settingをクリックします。
![s1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1303271/7b85b57f-147f-ae3e-e0ce-b9afaa8c5f73.png)
3. Player Setting内のOther Settings、スクロールして真ん中少し下くらいのConfigurationのApi Compatibillity Levelを **.NET Framework** に変更します。
![s3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1303271/89ec20a9-8361-1433-e4c6-ef777f4be955.png)

# Arduino側の実装
1. Arduino IDEを立ち上げて、以下のコードをコピぺして転送します。
Unityにセンサーのデータを送信して、Unityから送られてくる値を読み取ってLEDをオンオフするコードです。

:::note warn
今回はセンサーの読み取り方には触れないため、変数data に入れた1という値を仮のデータとしてUnityに送っています。
:::

詳しい内容はコメントを参照してください。
~~~diff_c
const int LED = 13; //LEDは13番ピンのものを使用
int num;            //受信したデータを入れる変数
float data = 1;     //送信するデータを入れる変数（例として1を入れてある）

void setup() {
  Serial.begin(9600);   //シリアルポートを9600bpsで開く
  pinMode(LED, OUTPUT); //ピンを出力に設定
 }

void loop() {
  
  //-----受信部-----
  
  if (Serial.available()>0){  //データが到着していたら
    num = Serial.parseInt();  //読み取って変数numへ
  }
  
  if (num == 1){              //Unityから届いた値が1（オン）であれば
    //オンの時の処理
    digitalWrite(LED, HIGH);  //LEDをオン
  }else{
    //オフの時の処理
    digitalWrite(LED, LOW);   //LEDをオフ
  }
  
  //---受信部終わり---


    
  //-----送信部-----
    Serial.println(data);      //変数dataの値を送信
  //---送信部終わり---

  delay(10);  //バッファが詰まらないように0.01秒待機

}
~~~

# Unity側の実装
Unity側では2つのスクリプトを用います。
1. Projectタブで右クリック→Create→C# Scriptを押して、
**SerialHandler**と**SerialManager**というスクリプトを作ってください。

2. 作り終わったら、それぞれ以下のコードをコピペします。
**その際、SerialHandler.cs内のportName変数をArduinoが接続されているポート番号に変更してください。**
:::note warn
ポート番号の書きかえを忘れると動作しないので注意してください。
:::

なお、SerialHandler.csについては凹みさんのサイトのコードをカスタムして使用しています。

https://tips.hecomi.com/?page=1407171487


~~~SerialHandler.cs
using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System.Threading;

public class SerialHandler : MonoBehaviour
{
    public delegate void SerialDataReceivedEventHandler(string message);
    public event SerialDataReceivedEventHandler OnDataReceived;

    string portName = "COM4";//ここにはArduinoのポート番号を記入
    int baudRate = 9600;

    private SerialPort serialPort_;
    private Thread thread_;
    private bool isRunning_ = false;

    private string message_;
    private bool isNewMessageReceived_ = false;

    public string readline;

    void Awake()
    {
        Open();
    }

    void Update()
    {
        if (isNewMessageReceived_)
        {
            OnDataReceived(message_);
        }
        isNewMessageReceived_ = false;
    }

    void OnDestroy()
    {
        Close();
    }

    private void Open()
    {
        serialPort_ = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
        serialPort_.Open();

        serialPort_.ReadTimeout = 20;

        isRunning_ = true;

        thread_ = new Thread(Read);
        thread_.Start();
    }

    private void Close()
    {
        Write("0"); //！！！今回のコードではこの行がないと実行終了時にLEDが消えないので注意！！！
        isNewMessageReceived_ = false;
        isRunning_ = false;

        if (thread_ != null && thread_.IsAlive)
        {
            thread_.Join();
        }

        if (serialPort_ != null && serialPort_.IsOpen)
        {
            serialPort_.Close();
            serialPort_.Dispose();
        }
    }

    private void Read()
    {
        while (isRunning_ && serialPort_ != null && serialPort_.IsOpen)
        {
            try
            {
                message_ = serialPort_.ReadLine();
                isNewMessageReceived_ = true;
            }
            catch (System.Exception e)
            {
                readline = e.Message;
                Debug.LogWarning("1:" + readline);
            }
        }
    }

    public void Write(string message)
    {
        try
        {
            serialPort_.Write("2:" + message);
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("3:" + e.Message);

        }
    }
}
~~~
~~~SerialManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SerialManager : MonoBehaviour
{
    public SerialHandler serialHandler;


    //受信用変数
    public float data;              //受信データのfloat型版変数
    string receive_data;            //受信した生データを入れる変数

    //送信用変数
    bool onoff = true;              //オンオフどちらにするかを決定する変数（今回はオンで固定）
    bool cansend = true;            //送信するかどうかを判断する変数
    

    void Start()
    {
        serialHandler.OnDataReceived += OnDataReceived;
    }

    //データを受信したら
    void OnDataReceived(string message)
    {
        receive_data = (message);           //受信データをreceive_dataに入れる
        data = float.Parse(receive_data);   //float型に変換してdataに入れる
        Debug.Log("受信データ: " + data);
    }

    private void Update()
    {
        if (onoff)
        {
            usendmsg();     //オン用メソッド呼び出し
        }
        else
        {
            dsendmsg();     //オフ用メソッド呼び出し
        }
    }

    //オン用メソッド
    void usendmsg()
    {
        if (cansend == true)            //送信可能かチェック
        {
            serialHandler.Write("1");   //Arduinoに1を送信
            Debug.Log("1を送信");
            cansend = false;            //オフになるまで送信不可に設定
        }
    }

    //オフ用メソッド
    void dsendmsg()
    {
        if (cansend == false)           //送信可能かチェック
        {
            serialHandler.Write("0");   //Arduinoに0を送信
            Debug.Log("0を送信");
            cansend = true;             //オフになるまで送信不可に設定
        }
    }
}
~~~

3. コーディングが完了したら、CreateEmptyで空のゲームオブジェクトを作成し、Serialと名前を付けます。
4. 作成したゲームオブジェクトに2つのスクリプトをアタッチします。
5. Serialをクリックし、Inspectorタブを見てください。SerialManage(Script)内のSerialHandlerに、ゲームオブジェクトのSerialをドラッグアンドドロップでアタッチしてください。
![s6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1303271/4894cc44-1dc0-170b-626f-da0737207115.png)

# 完成！
再生すると、ArduinoのLEDが光り始めることが分かるかと思います。
またUnityのConsoleウインドウには「受信データ: 1」と大量に表示され、Arduinoからのデータが届いていることが分かります。
再生をやめると、ArduinoのLEDが消えることも確認してみてください。

# まとめ
これで、Unity + Arduinoの双方向通信が実現しました。
ぜひ色々なセンサを接続して、遊んでみてください！

# 参考サイト

https://tips.hecomi.com/?page=1407171487