memorandums

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

Unityの複数シーンでシリアル通信を共有したかった話(改)

昨晩(といっても今日でしたが)、以下を書きました。書いたあと「あれ?ReadLine()ってブロックするんじゃね?」と思ったんですね。それをUpdate()にいれたら後続処理が止まってしまいます。

memorandums.hatenablog.com

今日は会社の方が人買い(←シゴですね💦)にやってくるのでいつもより2時間くらい早く出勤でした。

行きの電車であれこれやりました。一応、結論が出ました。確かにReadLine()はブロックするので、ReadLine()を実行する前にバッファに受信データがあるかどうかをチェックします(もっとエレガントな方法があるようですが今後学生さんが使うことを想定して簡単な構造にしておきたかったので)。バッファにデータがあればReadLine()を実行するようにしました。

また、SerialHandlerをstatic classにしたのですがAwakeとかでわざわざ呼んでもらうのは実装忘れとかありそうでいやでした。ということで、 SerialHandlerクラスをMonoBehaviourの子クラスにしました。要はgameObject化という感じでしょうか。

以下が変更後のソースになります。あ、ブロックしないように「&& serialPort_.BytesToRead > 0」を条件に追加しています。

SerialHandler.cs

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO.Ports;
using System.Threading;

public class SerialHandler : MonoBehaviour 
{
    public string portName = "/dev/cu.wchusbserial10"; //★
    public int baudRate    = 9600; //★
    private SerialPort serialPort_;

    void Awake() {
        Open();
    }

    void OnDestroy() {
        Close();
    }

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

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

    void Update() {
        Read();
    }
    
    private void Read()
    {
    if (serialPort_ != null && serialPort_.IsOpen && serialPort_.BytesToRead > 0) {
            try {
                string message = serialPort_.ReadLine();
                //============================ここから★
                switch(message[0]) {
                    case '1':
                        GameData.on_b1();
                        break;
                    case '2':
                        GameData.on_b2();
                        break;
                }
                //============================ここまで★
            } catch (System.Exception e) {
                Debug.LogWarning(e.Message);
            }
        }
    }

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

あとGameDataクラスもついでにつけておきます。単純なクラスですが。これは変更はありません。

public static class GameData {
    private static bool b1 = false;
    private static bool b2 = false;

    public static void on_b1() {
        b1 = true;
    }
    public static void on_b2() {
        b2 = true;
    }
    public static bool get_b1() {
        bool tmp = b1;
        b1 = false;
        return tmp;
    }
    public static bool get_b2() {
        bool tmp = b2;
        b2 = false;
        return tmp;
    }
}

で、使い方ですが、SerialHandlerをシーン中のオブジェクトにアタッチします。シーンそれぞれに1つアタッチする感じです。MainCameraがあるならそこにアタッチするのがいいと思いますね。

あとは、ゲーム側の事情でボタンが押されたかを知りたいときに以下のように呼び出せば直前のボタンの状態が取れます。

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

public class S1 : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKey(KeyCode.Space)) {
            SceneManager.LoadScene("S2");
        }
        if (GameData.get_b1()) {
            Debug.Log("b1 was pushed");
        }
        if (GameData.get_b2()) {
            Debug.Log("b2 was pushed");
        }
    }
}

こっちの方がシンプルですよね?めでたしめでたし。