パパコーダー

子どもと一緒にプログラミングを始めよう!2019年3月からCoderDojo溝口という子ども向けのプログラミングサークルを始めました。神奈川県川崎市の近くにお住まいの方はどうぞ遊びに来てください。

【Unity】micro:bitとシリアル通信してみた

micro:bitを買ったので、Unityで遊んでみました。

参考ページ

分かりやすくまとまっていました。
ありがとうございます。 qiita.com

環境準備

私が試した環境はこちらです。

項目 内容
OS Windows10
Unity 2018.4.14f1 (LTS)

mbed Windows serial port driver のインストール

micro:bitをWindows10にUSBで接続して、下記のサイトからドライバをインストールします。
Windows10でmicro:bitとシリアル通信できるようになります。

Windows serial configuration - Handbook | Mbed

micro:bit のプログラミング

MakeCode で micro:bit のプログラム作成

micro:bitのサイトに行き、プログラミングしましょう。 makecode.microbit.org

今回は、micro:bitの傾きをシリアル通信で送るだけのシンプルなものになります。

f:id:oco777:20200120192446p:plain
micro:bit のプログラミング

micro:bit にプログラムのインストール

micro:bit のサイトから、作成したプログラミングの hexファイルをダウンロードします。
ダウンロードした hexファイルをエクスプローラのMICROBITにコピーしましょう。
これで作成したプログラムのインストールは完了です。

Tera Term で確認

Tera Term というソフトウェアを使って、micro:bit とシリアル通信ができているか試してみます。
インストールがまだの方はこちらから。

ja.osdn.net

Tera Term のシリアルポートの設定

Tera Term でシリアルポート接続します。
下記のように設定しましょう。
後ほど使いますので、ポートのCOM番号は覚えておきましょう。
ポートのCOM番号は、環境によって変わります。

f:id:oco777:20200120193154j:plain
Tera Term のシリアルポート設定①

f:id:oco777:20200120193313j:plain
Tera Term のシリアルポート設定②

Tera Term で接続確認

シリアルポート接続すると、下記のように micro:bit から送られてきたメッセージが表示されます。

f:id:oco777:20200120193626j:plain
Tera Term で接続確認

Unity のプログラミング

それでは、Unity でプログラミングしていきましょう。

Unity のプロジェクト作成

今回は、3Dのプロジェクトを作成します。

Build Settings で「Universal Windows Platform」に切り替える

Build Settings で Platform を「Universal Windows Platform」に切り替えます。

f:id:oco777:20200120194238j:plain
Universal Windows Platform に切り替える

「Universal Windows Platform」が選択できない場合は、Unity Hub で「UWP Build Support (.NET)」モジュールを加えます。

MySerial.cs の作成

Assetsフォルダに、MySerial.cs スクリプトを新規作成します。
下記のようにプログラミングします。

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

public class MySerial : MonoBehaviour
{
    public enum Baudrate
    {
        B_9600 = 9600,
        B_19200 = 19200,
        B_38400 = 38400,
        B_57600 = 57600,
        B_115200 = 115200,
        B_230400 = 230400,
    }

    public event Action OnDataReceived;
    SerialPort _serialPort;
    Thread _thread;
    Queue _messages;
    string _data = "";
    bool _isRunning;

    // Update is called once per frame
    void Update()
    {
        if (! _isRunning)
        {
            return;
        }
        lock(_messages)
        {
            while (_messages.Count > 0)
            {
                string msg = _messages.Dequeue();
                if (OnDataReceived != null)
                {
                    OnDataReceived(msg);
                }
            }
        }
    }

    public static string[] GetPortNames()
    {
        if (Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor)
        {
            return Directory.GetFiles(@"/dev/", "tty.usb*", SearchOption.AllDirectories);
        }
        else if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor)
        {
            return SerialPort.GetPortNames();
        }
        Debug.LogError("Unsupported platform");
        return new string[0];
    }

    public bool Open(string portName = null, Baudrate baudRate = Baudrate.B_9600)
    {
        if (string.IsNullOrEmpty(portName))
        {
            var ports = GetPortNames();
            if (Debug.isDebugBuild)
            {
                foreach (var port in ports)
                {
                    Debug.LogFormat("port : {0}", port);
                }
                if (ports.Length == 0)
                {
                    Debug.LogWarning("Serial port not found");
                }
            }
            if (ports.Length == 0)
            {
                return false;
            }
            portName = ports[0];
        }
        _messages = new Queue();
        _serialPort = new SerialPort(portName, (int)baudRate, Parity.None, 8, StopBits.One);
        try
        {
            _serialPort.Open();
        }
        catch (IOException e)
        {
            Debug.LogError(e);
            _serialPort.Dispose();
            return false;
        }
        _isRunning = true;
        _thread = new Thread(Read);
        _thread.Start();
        return true;
    }

    public void Close()
    {
        _isRunning = false;
        if (_thread != null && _thread.IsAlive)
        {
            _thread.Join();
        }
        if (_serialPort != null && _serialPort.IsOpen)
        {
            _serialPort.Close();
            _serialPort.Dispose();
        }
    }

    void Read()
    {
        while (_isRunning && _serialPort != null && _serialPort.IsOpen)
        {
            try
            {
                byte b = (byte)_serialPort.ReadByte();
                while (b != 255 && _isRunning)
                {
                    char c = (char)b;
                    if (c == '\n')
                    {
                        lock (_messages)
                        {
                            _messages.Enqueue(_data);
                            _data = "";
                        }
                    }
                    else
                    {
                        _data += c;
                    }
                    b = (byte)_serialPort.ReadByte();
                }
            }
            catch (Exception e)
            {
                Debug.LogWarning(e.Message);
            }
            Thread.Sleep(1);
        }
    }

    static MySerial _instance;

    public static MySerial Instance
    {
        get
        {
            if (_instance == null)
            {
                var go = new GameObject(typeof(MySerial).ToString());
                DontDestroyOnLoad(go);
                _instance = go.AddComponent();
            }
            return _instance;
        }
    }
}

ObjRotation.cs の作成

Assetsフォルダに、ObjRotation.cs スクリプトを新規作成します。
下記のようにプログラミングします。

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

public class ObjRotation : MonoBehaviour
{
    MySerial serial;
    float _pitch;
    float _roll;
    public string portNum;

    // Start is called before the first frame update
    void Start()
    {
        serial = MySerial.Instance;
        bool success = serial.Open(portNum, MySerial.Baudrate.B_115200);
        if (! success)
        {
            return;
        }
        serial.OnDataReceived += SerialCallBack;
    }

    private void OnDisable()
    {
        serial.Close();
        serial.OnDataReceived -= SerialCallBack;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void SerialCallBack(string m)
    {
        objRotation(m);
    }

    void objRotation(string message)
    {
        string[] a;
        a = message.Split("="[0]);
        if (a.Length != 2)
        {
            return;
        }
        int v = int.Parse(a[1]);
        switch (a[0])
        {
            case "pitch":
                _pitch = v;
                break;
            case "roll":
                _roll = v;
                break;
        }
        Quaternion AddRot = Quaternion.identity;
        AddRot.eulerAngles = new Vector3(-_pitch, 0, -_roll);
        transform.rotation = AddRot;
    }
}

テスト

シーンにキューブを配置し、ObjRotation.cs を AddComponent します。
Inspector ビューで、ObjRotationコンポーネントの Port Num にポートのCOM番号を設定します。

f:id:oco777:20200120195145j:plain
ObjRotation の設定

Playボタンを押して、動作確認しましょう。

f:id:oco777:20200120195425j:plain
Unity でテスト