■はじめに
M5Stack というデバイスを使う機会があったので、使ってみました。
また、今回はUIFlowというビジュアルスクリプティングツールを使用してます。
僕は、シリアル通信を今回初めて利用しましたが、コードはそこまで複雑ではなく、簡単に実装できました。
(セットアップなどは少し面倒でした)
■実行結果
こんな感じで、デバイスの加速度をUnity側に反映させることができます!
■準備
●M5Stack
今回は、USB接続でやってます。
M5Stackには色々な種類がありますが、デバイス操作以外はほとんど同じです。
必要ツールのインストール
Burnerの使い方
- ファームウェアってのを書き込むために必要っぽい。
- 使用するデバイスを探して、[Download]からの[Burn]でOK
- エラーが出る場合、UIFlowなどの接続を切断すれば解決するかもです。
UIFlowのセットアップ
-
-
COM:シリアルポートというもの。
- とりあえず、選択できるものを選択すればOK
- COMが複数ある場合は、TeraTermというソフトを利用して接続確認できます。
-
Device:使用デバイスを選択。
-
- 次に、デバイスとの接続を行います。
-
USBに接続して、電源をつける
-
UIFlow側で、左下の[リツイートみたいなアイコンのボタン]をクリック
接続できない場合
-
左上の[DriverInstallation]でドライバーをインストールしてみる
- たぶん、一番初めに利用する場合にインストールする必要がある。
-
もう一度、Burnerで[Burn]してみる
- 僕はこれで解決することが多いです。
- 何度か[Refreshボタン(RTみたいなやつ)]をクリックする
- デバイスの設定を見直す
-
Web版の方を利用する
- 最終手段です。Wifiの環境があればWebでも大丈夫です。
- 多分WebのほうがBurnerとか使わなくてもいけるから楽かもです。
Unity
PlayerSettingsの「Other Settings
」で、API Compatibility Level
を「.NFT Framework
」に変更する必要がある
■実装
●UIFlowでデータ送信機能を作る
シリアル通信に必要な機能などを紹介します。
今回は、デバイスの傾きをUnity側に送信するようにしました。
最低限必要な要素は、以下の2つです。
初期化
- 初期化ブロックのパラメータは、こんな感じです。
- 送信ピン、受信ピンに適切な値を入力。
- USB接続の場合、送信ピン(tx)=1,受信ピン(rx)=3
- ボーレートに適当な値を入力。
- ボーレートの値が大きいほど、データ送れるらしい。
- でも大きすぎるとエラーになるっぽい
- 送信ピン、受信ピンに適切な値を入力。
書き込み
書き込みには3種類あります。今回は一番上の「1行書き出し」を利用しています。
今回は、xとyの値が欲しいので、CSVのようにカンマ区切りをしたものを書き込みます。
今回は、送信するデータ量が少ないのでこのように1行だけで書き込みをしていますが、より多くのデータを送信したい場合は、一番下の[write raw data]を利用するといいかもです。
これらの処理を追加したら、右下の[Download]でデバイスにこのプロジェクトをダウンロードします。
ダウンロードが完了したら、デバイス上で傾きが表示されます。
(※裏画面切り替え処理をしていないので、テキストがチカチカすると思います。ご了承ください。)
無事に表示されたら、UIFlow側の操作は完了です!
●Unity側でデータ受信する
クラス図
僕のコードでは、一応きっちり継承とか使って設計していますが、
簡易的なプロジェクトだったら、SerialHandler
クラスとクライアントのクラスだけで大丈夫です。
Scene
- シリアル通信のあれこれを行う、空のオブジェクトです。
-
SerialHandler
クラスでは、シリアルポート名とボーレートを指定します。
- デバイスの傾きをもとに、回転するプレイヤーです。
- 可動範囲や、向きの反転などのパラメータを用意しています。
コード
リポジトリ↓
https://github.com/kiku09020/Unity_SerialCommunication_Test
- 以下のページのコードを参考にさせていただいています。
SerialHandler
シリアル通信のあれこれを担うクラスです。
📄クリックしてコードを表示
using UnityEngine;
using System.IO.Ports;
using System.Threading;
using System;
// データを送受信する、シリアル通信クラス
public class SerialHandler : MonoBehaviour
{
// シリアル通信で、データを受け取った時のイベント
public event Action OnDataReceived;
[Header("Serial Options")]
[SerializeField,Tooltip("開かれるポート名")] string openedPortName = "COM1";
[SerializeField, Tooltip("開かれるポートのボーレート")] int baudRate = 9600;
SerialPort serialPort; // シリアルポート
// thread
Thread readingThread; // 読み取り用のスレッド
bool isThreadRunning; // スレッド実行中フラグ
// message
string message;
bool isNewMessageReceived; // 新しくメッセージを受け取ったかどうか
//--------------------------------------------------
// 開始時にポートを開く
void Awake()
{
Open();
}
void Update()
{
// 受け取ったら、受け取り時の処理を実行
if(isNewMessageReceived) {
OnDataReceived();
}
isNewMessageReceived = false;
}
// 終了時にポートを閉じる
private void OnDestroy()
{
Close();
}
//--------------------------------------------------
// シリアルポートを開く
void Open()
{
serialPort = new SerialPort(openedPortName, baudRate, Parity.None, 8, StopBits.One); // ポートインスタンス作成
serialPort.Open(); // 作成したポートを開く
isThreadRunning = true; // 実行中フラグ立てる
readingThread = new Thread(Read); // スレッド作成
readingThread.Start(); // スレッド開始(読み込み)
print("port was setuped.");
}
// シリアルポートを閉じる
void Close()
{
// フラグ降ろす
isNewMessageReceived = false;
isThreadRunning = false;
if (readingThread != null && readingThread.IsAlive) {
// スレッドが終了するまで待機
print("waiting");
readingThread.Join(TimeSpan.FromSeconds(.5f));
}
// ポートが開いていたら、閉じる
if (serialPort != null && serialPort.IsOpen) {
serialPort.Close(); // 閉じる
serialPort.Dispose(); // リソース開放
print("port was closed.");
}
}
//--------------------------------------------------
// シリアルポートに読み込む
void Read()
{
while (isThreadRunning && serialPort != null && serialPort.IsOpen) {
try {
// ポートからのバイト数が0より多かったら、読み込み
if (serialPort.BytesToRead > 0) {
message = serialPort.ReadLine(); // データ読み込み
isNewMessageReceived = true; // メッセージ受け取りフラグ立てる
}
}
// 例外
catch(Exception exception) {
Debug.LogWarning(exception.Message);
}
}
}
// シリアルポートに書き込む
public void Write(string message)
{
// 書き込み
try {
serialPort.Write(message);
}
// 警告
catch (Exception exception){
Debug.LogWarning(exception.Message);
}
}
//--------------------------------------------------
// コンマで区切られたメッセージを返す
public string[] GetSplitedData()
{
// 受け取ったメッセージを区切る
return message.Split(",");
}
}
Point💡
-
Open()
- M5Stackのシリアルポートをインスタンス化して開きます。
- また、データの読み取りを別のスレッドで行うために、読み取り用のスレッドを作成して開始します。
-
Close()
- スレッドを終了して、シリアルポートを閉じます。
-
Read()
- 受信されたデータを読み取ります。
- 別スレッドで処理が実行されます。
-
Write()
- データをシリアルポートに送信します。(今回は使用しません。)
-
GetSplitedData()
- コンマ区切りされたデータのメッセージを、コンマで区切って文字列の配列にして返します。
-
OnDataReceived()
- データ受け取り時のイベントです。
Receiver
クラスで処理が追加されています。(データを読み取り、文字列の配列から値に変換して、フィールドに値を代入しています)
DataReceiver_Base
データ受信用の基底クラスです。
必須ではないです。
📄クリックしてコードを表示
using UnityEngine;
// データ受信基底クラス
public abstract class DataReceiver_Base:MonoBehaviour
{
[SerializeField] protected SerialHandler handler;
//--------------------------------------------------
/// <summary>
/// データ受信時の処理
/// </summary>
protected abstract void OnReceivedData();
//--------------------------------------------------
private void Awake()
{
handler.OnDataReceived += OnReceivedData;
}
}
Point💡
-
Awake()
で、派生クラスで定義されたOnReceivedData()
をhandler
のイベントに追加します。
AccelarationDataReceiver
デバイスの加速度(傾き)を受けとるクラスです。
傾きなんですが、UIFLOWでは加速度と表示されてるので合わせてます
📄クリックしてコードを表示
using UnityEngine;
// デバイスの加速度を取得する
public class AccelarationDataReceiver : DataReceiver_Base
{
// デバイスの加速度
public Vector2 Accelaration { get; private set; }
//--------------------------------------------------
// 受信
protected override void OnReceivedData()
{
var data = handler.GetSplitedData(); // データ取得
try {
if (data.Length >= 2) {
// データ文字列からfloat型に変換して、ベクトルに適用
float x = float.Parse(data[0]);
float y = float.Parse(data[1]);
Accelaration = new Vector2(x, y);
}
}
// 例外
catch (System.Exception exc) {
Debug.LogWarning(exc.Message);
}
}
}
Point💡
- 文字列の配列から、それぞれの要素を
float
型に変換して、それをUnityのVector2
型の要素に適用させています。
PlayerController
プレイヤーの回転をさせているクラスです。
📄クリックしてコードを表示
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Parameters")]
[SerializeField, Tooltip("可動範囲")] float rotatableRigion = 90;
[SerializeField,Tooltip("反転するか")] bool isReverced;
[Header("Components")]
[SerializeField] AccelarationDataReceiver receiver;
//--------------------------------------------------
private void Awake()
{
// 反転させる場合、-1をかける
if (isReverced) {
rotatableRigion *= -1;
}
}
// 移動
void LateUpdate()
{
var euler = receiver.Accelaration * rotatableRigion;
transform.rotation = Quaternion.Euler(euler.y, euler.x, 0);
}
}
Point💡
-
AccelarationDataReceiver
の加速度の値を適用させて、回転させています。
■さいごに
デバイスを利用したゲームを制作するのは、新鮮で楽しいなーって思いました。
●参考サイト
- セットアップ方法
- M5Stackのrx,txなどの指定方法、ボーレートについて
- コード