memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

Arduinoからシリアル通信でイベントを受信してUnity側で処理する方法(初心者向け)

まえおき

昨日に引き続き,授業関係の投稿です.

昨年から3年生のプレゼミでオリジナルコントローラ+ゲーム開発のプロジェクトを進めています.

2人でチームを作ってゲーム制作とコントローラ制作にわかれて1年かけて制作を進めてきました.

今年もあともう少しで完成するところまできました.

ちなみに,昨年の代表作は以下です.何となくやっていることをお伝えできるのではないかと思います.

ゲームはUnityで作成し,コントローラは各種センサー+Arduinoです.ケースなど必要であれば3Dプリンタ+レーザーカッターを使用します.

ArduinoはPCとUSBケーブルで有線接続します.無線化も簡単にできますがデバッグが面倒になるのでまずは有線で進めてもらっています.それでもまあまあの勉強になります.

ArduinoでSerial.print()を使用して文字列をUnityに向けて出力します.Unity側ではその文字列をシリアル通信で受信して処理します.Unityでのシリアル通信は意外と簡単ですが,慣れは必要なので以下のページにまとめた情報をもとに作成してもらっています.

Unityで受信した文字列をもとに,ゲームに関わる様々なGameObjectを操作する必要があります.これもなかなか面倒な処理になります.この辺も卒業研究くらいなら色々頑張って探索したりデバッグして欲しいところですが,プレゼミの授業では30回✕90分と時間が限られているので,ある程度のコードは与えるようにしています.

本題

手元にキーボードやマウスで動作するゲームがあったとして,そこに上記の「Arduino+Unity(シリアル通信)するときの〜」を参考にシリアル通信する設定を完了します.それができた状態から以下の説明を始めたいと思います.

ゲームを作るのは面倒なので,とりあえずCubeを追加して,そこにゲームに関わるスクリプトをアタッチしている状態だとします.ここではmoveというスクリプトだとします.

Unityプロジェクト
Cubeにmoveというスクリプトがアタッチしている様子

以下がアタッチしたmoveスクリプトです.ただ,Cubeをカーソルキーで移動するだけの簡単なスクリプトですが,そこに,シリアル通信のデータを関節的に受け取るための仕掛けをいれています.コメントしているところがそれです.キー以外に,シリアル通信でE1というイベントを受信していればh.getE1()がtrueになりますので,キーを押さなくてもCubeが動くはずです.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move : MonoBehaviour
{
    private hogehoge h; //シリアル受信するスクリプトのクラスの入れ物

    void Start()
    {
        //シリアル通信のスクリプトはMain Cameraにアタッチしているので,Main Cameraからhogehogeのコンポーネントを取得してhにセットしておく
        h = GameObject.Find("Main Camera").GetComponent<hogehoge>();    
    }

    void Update()
    {
        if (Input.GetKey("left") || h.getE1()) { //h.getE1() がシリアル受信した内容を取得する
            h.resetE1(); //取得したら受信した次の受信に備えてリセットしておく
            transform.position += new Vector3(-0.1F, 0, 0);
        }

        if (Input.GetKey("right")) {
            transform.position += new Vector3(0.1F, 0, 0);
        }
    }
}

あとは,シリアル通信のスクリプトに他のスクリプトに受信したデータを受け渡すためのコードを書き加えたのが以下です.ちょっと面倒ですが,e1, e2などは用途に応じて増やしたり減らしたりすればいいと思います.

ポイントはArduinoからシリアル受信したときにOnDataReceivedメソッドが呼び出されるわけですが,そのときにインスタンス変数e1にメモっておくためにonE1というメソッドを作ってtrueにしています.で,非同期に実行されるゲーム処理の中でe1の値を読み取ったらフラグを下げてもらうためresetE1を実行してもらいます(上記のコードに書いてあるでしょう?).

あともう1つ結構なはまりポイントとして,Arduino側でシリアルモニターで確認するときには改行がついていた方が見やすいのでSerial.printlnとlnをつけることがよくあると思います.そうすると,OnDataReceivedで処理するときにmessageの末尾に改行コードなどが入ってしまうことがあるわけで,それでmessage == "1"などと比較しても合致しないことがあります.Trimなどしてもいいのですが,うまく動かないケースもあって「あーもう!!」ってなります.そこで,単純に文字列の先頭の文字で比較すれば末尾に何が入っていようとだいたいうまく比較できるはずだろうということなんですね.ちなみにmessage[0]はstringではなくcharになりますので,比較する文字は文字列ではなく文字なのでシングルクォーテーションでくくる必要があります.つまり"1"ではなく'1'ということです.初心者向けの話です.はい.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class hogehoge : MonoBehaviour
{
    //先ほど作成したクラス
    public SerialHandler serialHandler;

    void Start()
    {
        //信号を受信したときに、そのメッセージの処理を行う
        serialHandler.OnDataReceived += OnDataReceived;
    }

    // ---------------------シリアル通信のための仕組み(ここから)
    private bool e1 = false; //ボタンなどセンサーの数が3つだった場合.各チームの用途に応じて増やしたり減らしたりすればいい
    private bool e2 = false;

    //Arduino側でイベントが発生した
    void onE1() {
        e1 = true; //イベントが発生したことを記憶しておく
    }

    public bool getE1() {
        return e1;
    }

    public void resetE1() {
        e1 = false;
    }

    public bool getE2() {
        return e2;
    }

    public void resetE2() {
        e2 = false;
    }

    // ---------------------シリアル通信のための仕組み(ここまで)
    //受信した信号(message)に対する処理
    void OnDataReceived(string message)
    {
    // ---------------------シリアル通信のための仕組み(ここから)
        //イベントコードに応じて処理する
        switch(message[0]) {
        case '1': //ArduinoでSerial.println("1")をしている想定
            onE1(); 
            //Debug.Log(message);
            break;
        case '2':
            //onE2();
            //Debug.Log(message);
            break;
        }        

    // ---------------------シリアル通信のための仕組み(ここまで)
    }
}

ついでなので,Arduino側のコードもはっておきますね.プッシュボタンの片方をGNDに接続し,もう片方をD2に刺します.プルアップしていますので,ボタンを押すとD2の値はLOWになります.LOWになったら"1\n"をシリアル通信で出力する感じです.

void setup() {
  pinMode(2, INPUT_PULLUP);
  Serial.begin(115200);
}

void loop() {
  if (digitalRead(2) == LOW) {
    Serial.println("1");
  }
  delay(100);
}

はい,おしまい!

あとは頑張ってください.