こんなクリエイターに見て欲しい
- Unityでシリアル通信を自動化したい
- Arduinoとの連携をスムーズに行いたい
- デバイスの自動検索機能を実装したい
完成したもの
まずは完成サンプルをご覧ください。
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
public class SerialHandler : MonoBehaviour
{
// Deviceのリストを保持するフィールドを追加
[SerializeField] private List<string> DeviceList = new List<string>();
private static SerialHandler instance;
public static SerialHandler Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<SerialHandler>();
if (instance == null)
{
Debug.LogError("MySerialHandler instance not found in the scene. Please ensure MySerialHandler is attached to a GameObject in the scene.");
}
}
return instance;
}
}
[SerializeField] string[] activePorts = null;
public string portName = null;
public int baudRate = 9600;
private SerialPort serialPort;
private Thread thread;
private string message;
public string Message;//ほかのスクリプトからアクセスするためのプロパティ
private bool isPortOpen;//シリアルポートが既に開かれているかどうかを示すフラグ
async void Awake()
{
Debug.Log("Start");
// instanceがすでにあったら自分を消去する。
if (instance && this != instance)
{
Debug.Log("Destroyed");
Destroy(this.gameObject);
return; // この行を追加して、以下のコードが実行されないようにする
}
instance = this;
// Scene遷移で破棄されなようにする。
DontDestroyOnLoad(this);
Thread.Sleep(2000);
if (!isPortOpen)//ポート名がnullの場合、接続を試みる
{
portName = await SearchPort();
}
Debug.Log(portName + " is setting to Device");
if (portName != null)
{
OpenToRead();// Deviceのポートを開く
Thread.Sleep(100);// 100ms待つ
Write("SetDevice");// 使用するDeviceに設定する
await ReadAsync();// 非同期にデータを読み取る
}
else
{
Debug.Log("Device is not found but try to open COM3");
manualOpen();// ポート名がnullの場合、COM3を開く
Thread.Sleep(100); // 100ms待つ
Write("setDevice");// 使用するDeviceに設定する
await ReadAsync();// 非同期にデータを読み取る
}
}
private async Task<string> SearchPort()
{
Debug.Log("SearchPort Start");
activePorts = SerialPort.GetPortNames();
foreach (string port in activePorts)
{
Debug.Log(port + " is active");
}
for (int i = 0; i < activePorts.Length; i++)
{
//Debug.Log(activePorts[i] + " is active");
portName = activePorts[i]; // activePortの名前をportNameに代入する
message = null; // メッセージをクリアする
try
{
OpenToSearch();
Thread.Sleep(2000);
Write("areyouDevice");//デバイスにシリアル通信で文字列を送信する
await ReadAreYouDevice();// 非同期にデータを読み取る
Close();
if (message != null && message.Contains("iamDevice"))
{
Debug.Log(portName + " is Device!");
DeviceList.Add(portName);// Deviceのリストに追加
Debug.Log(portName);
return portName;// Deviceのポート名を返す
}
else
{
Debug.Log(portName + " is not Device");
Close();
Thread.Sleep(1000);
}
}
catch (System.Exception e)
{
Debug.Log(e);
Close();
}
}
Debug.Log("Device is not found in active ports");
return null;
}
private void OpenToSearch()
{
Debug.Log(portName + " will be opened to search");
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
{
ReadTimeout = 500//タイムアウトの設定
};
serialPort.Open();//ポートを開く
isPortOpen = true; // ポートが開かれたことを示す
}
private void OpenToRead()
{
Debug.Log(portName + " will be opened to read");
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
{
};
serialPort.Open();//ポートを開く
isPortOpen = true; // ポートが開かれたことを示す
}
public void Close()
{
if (thread != null && thread.IsAlive)//もしスレッドがあったら
{
thread.Join();//リードの処理が終わるまで待つ
thread = null; // スレッドをクリアする
}
if (serialPort != null && serialPort.IsOpen)//もしシリアルポートが開いていたら
{
serialPort.Close();//ポートを閉じる
Debug.Log(portName + " is closed");
serialPort.Dispose();//リソースの解放
Debug.Log("Resource is cleared");
serialPort = null; // シリアルポートをクリアする
isPortOpen = false; // ポートが閉じられたことを示す
}
}
private async Task<string> ReadAreYouDevice()//areyoudeviceのメッセージを読み取る
{
message = null;
await Task.Run(() =>
{
if (serialPort != null && serialPort.IsOpen)
{
try
{
message = serialPort.ReadLine();
}
catch (System.Exception e)
{
Debug.LogWarning("1:" + e.Message);
Close();
}
}
});
return message; // 読み取ったメッセージを返す
}
public async Task<string> ReadAsync()//非同期でデータを読み取る
{
message = null;
await Task.Run(() =>
{
while (true)
{
if (serialPort != null && serialPort.IsOpen)
{
try
{
message = serialPort.ReadLine();
//Debug.Log(message + " is received");
}
catch (System.Exception e)
{
Debug.LogWarning("2:" + e.Message);
Close();
break;
}
}
else
{
break;
}
}
});
Message = message;
return message;
}
public void Write(string message)
{
try
{
serialPort.Write(message);
serialPort.Write("\n");
Debug.Log(message + " is sent");
}
catch (System.Exception e)
{
Debug.LogWarning("3:" + e.Message);
}
}
private void OnApplicationQuit()
{
Write("exit");
Close();
}
private void manualOpen()
{
if(portName == null)
{
portName = "COM3";
OpenToRead();
}
}
public string SendToAnotherScript()
{
return message;
}
}
UnityとArduinoを使用したシリアル通信システムが完成します。システムは自動的に接続されたデバイスを検索し、適切なポートを特定して通信を確立します。Arduinoに接続したロータリーエンコーダを使ってUnity上の車いすを操作できるような、インタラクティブな福祉アプリケーションが実現可能になります。
Unityシリアルポート自動検索通信とは
Unityシリアルポート自動検索通信とは、利用可能なシリアルポートを自動的にスキャンし、特定のデバイスを識別して通信を確立するシステムです。
今回の記事では
- API Compatibility Levelを設定してシリアル通信を有効化する仕組み
- 非同期処理でデバイスを自動検索する方法
- シングルトンパターンで通信データを管理する使い方
を解説していきます。
Unityシリアルポート自動検索通信の作成手順
手順はこちらです。
Edit → Project Setting → Player → Other Setting → Configuration → API Compatibility Level を .NET Frameworkに変更します。これによりSystem.IO.Ports.SerialPortクラスが使用可能になります。
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
MonoBehaviourを継承したSerialHandlerクラスを作成します。
public class SerialHandler : MonoBehaviour
シリアルポート通信に必要なフィールドを定義します。
[SerializeField] private List<string> DeviceList = new List<string>();
private static SerialHandler instance;
[SerializeField] string[] activePorts = null;
public string portName = null;
public int baudRate = 9600;
private SerialPort serialPort;
private Thread thread;
private string message;
public string Message;
private bool isPortOpen;
DeviceList
は接続されたデバイスのリストを保持します。instance
はシングルトンパターンのインスタンスを保持します。activePorts
は利用可能なシリアルポートのリストです。portName
は接続するポートの名前です。baudRate
は通信速度を設定します。serialPort
はシリアルポートのインスタンスです。thread
はデータ読み取り用のスレッドです。message
は読み取ったメッセージを保持します。Message
は他のスクリプトからアクセスするためのプロパティです。isPortOpen
はポートが開かれているかどうかを示すフラグです。
シングルトンパターンを実装し、DontDestroyOnLoadでシーン遷移時も破棄されないようにします。
public static SerialHandler Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<SerialHandler>();
if (instance == null)
{
Debug.LogError("MySerialHandler instance not found in the scene. Please ensure MySerialHandler is attached to a GameObject in the scene.");
}
}
return instance;
}
}
SearchPortメソッドでSerialPort.GetPortNames()を使用して利用可能なポートを取得。各ポートに”areyouDevice”を送信し、”iamDevice”の返答があるポートを特定します。
private async Task<string> SearchPort()
{
Debug.Log("SearchPort Start");
activePorts = SerialPort.GetPortNames();
foreach (string port in activePorts)
{
Debug.Log(port + " is active");
}
for (int i = 0; i < activePorts.Length; i++)
{
//Debug.Log(activePorts[i] + " is active");
portName = activePorts[i]; // activePortの名前をportNameに代入する
message = null; // メッセージをクリアする
try
{
OpenToSearch();
Thread.Sleep(2000);
Write("areyouDevice");//デバイスにシリアル通信で文字列を送信する
await ReadAreYouDevice();// 非同期にデータを読み取る
Close();
if (message != null && message.Contains("iamDevice"))
{
Debug.Log(portName + " is Device!");
DeviceList.Add(portName);// Deviceのリストに追加
Debug.Log(portName);
return portName;// Deviceのポート名を返す
}
else
{
Debug.Log(portName + " is not Device");
Close();
Thread.Sleep(1000);
}
}
catch (System.Exception e)
{
Debug.Log(e);
Close();
}
}
Debug.Log("Device is not found in active ports");
return null;
}
OpenToSearch メソッドでデバイスを検索するためにシリアルポートを開きます。
private void OpenToSearch()
{
Debug.Log(portName + " will be opened to search");
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
{
ReadTimeout = 500//タイムアウトの設定
};
serialPort.Open();//ポートを開く
isPortOpen = true; // ポートが開かれたことを示す
}
デバイスから返答があった場合データを読み取るためにシリアルポートを再度開きます。
private void OpenToRead()
{
Debug.Log(portName + " will be opened to read");
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
{
};
serialPort.Open();//ポートを開く
isPortOpen = true; // ポートが開かれたことを示す
}
OpenToSearch メソッドでシリアルポートとスレッドを閉じ、リソースを解放します。ReadAreYouDevice メソッド
public void Close()
{
if (thread != null && thread.IsAlive)//もしスレッドがあったら
{
thread.Join();//リードの処理が終わるまで待つ
thread = null; // スレッドをクリアする
}
if (serialPort != null && serialPort.IsOpen)//もしシリアルポートが開いていたら
{
serialPort.Close();//ポートを閉じる
Debug.Log(portName + " is closed");
serialPort.Dispose();//リソースの解放
Debug.Log("Resource is cleared");
serialPort = null; // シリアルポートをクリアする
isPortOpen = false; // ポートが閉じられたことを示す
}
}
ReadAreYouDevice メソッドでデバイスへ送ったデバイス判定メッセージを非同期で読み取ります。
private async Task<string> ReadAreYouDevice()
{
message = null;
await Task.Run(() =>
{
if (serialPort != null && serialPort.IsOpen)
{
try
{
message = serialPort.ReadLine();
}
catch (System.Exception e)
{
Debug.LogWarning("1:" + e.Message);
Close();
}
}
});
return message; // 読み取ったメッセージを返す
}
ReadAsyncメソッドでTask.Runを使用して非同期でデータを読み取ります。whileループで継続的にシリアルポートからのデータを監視し、受信したメッセージをMessageプロパティに格納します。
public async Task<string> ReadAsync()
{
message = null;
await Task.Run(() =>
{
while (true)
{
if (serialPort != null && serialPort.IsOpen)
{
try
{
message = serialPort.ReadLine();
//Debug.Log(message + " is received");
}
catch (System.Exception e)
{
Debug.LogWarning("2:" + e.Message);
Close();
break;
}
}
else
{
break;
}
}
});
Message = message;
return message;
}
Writeメソッドでデバイスにメッセージを送信します。
public void Write(string message)
{
try
{
serialPort.Write(message);
serialPort.Write("\n");
Debug.Log(message + " is sent");
}
catch (System.Exception e)
{
Debug.LogWarning("3:" + e.Message);
}
}
OnApplicationQuit メソッドでアプリケーション終了時にシリアルポートを閉じます。
private void OnApplicationQuit()
private void OnApplicationQuit()
{
Write("exit");
Close();
}
manualOpen メソッドでポート名が設定されていない場合に指定したポートを開きます。
private void manualOpen()
{
if(portName == null)
{
portName = "COM~";//任意のポート名
OpenToRead();
}
}
SendToAnotherScript メソッドでシリアル通信で受信したデータを別のスクリプトからも使用できるようにします。
public string SendToAnotherScript()
{
return message;
}
こうしてwhileループで継続的にシリアルポートからのデータを監視し、受信したメッセージをMessageプロパティに格納します。
checkConnected関数でSerial.available()を確認し、”areyouDevice”を受信したら”iamDevice”を返答します。
bool checkConnected() {
if (Serial.available() > 0) { // シリアルバッファにデータがあるか確認
SerialString = readStringUntil('¥n'); // 改行が送られてくるまでシリアルを読み取る
if (SerialString == "areyouDevice") { // シリアルからデバイス確認文が送られてきたら
Serial.println("iamDevice"); // 返答を行う
return true; // trueを返す
}
}
return false; // デフォルトで false を返す
}
setup関数内でconnectingStatusがtrueになるまで接続を待機します。
void setup() {
Serial.begin(9600); // シリアル通信を9600ボーレートで開始
while (!connectingStatus) { // シリアルポートに接続されているかどうか確認
connectingStatus = checkConnected(); // 接続状態を確認
}
}
loop関数の メインループで、ここに任意の処理を追加できます。
void loop() {
// 任意の処理
}
シリアル通信を受信するArduino側のスクリプトの完成
String SerialString = ""; // シリアル通信で受信する文字列
String Commands[3] = { "\0" }; // 分割された文字列を格納する配列
unsigned long searchStartTime = 0;//ポート検索の
unsigned long searchEndTime = 0;
bool connectingStatus = false;//接続状態
void setup() {
Serial.begin(9600);
while (!connectingStatus) { // シリアルポートに接続されているかどうか確認
connectingStatus = checkConnected();
}
}
void loop() {
//任意の処理
}
bool checkConnected() {
if (Serial.available() > 0) { // シリアルバッファにデータがあるか確認
SerialString = readStringUntil('¥n');//改行が送られてくるまでシリアルを読み取る
if (SerialString == "areyouDevice")//シリアルからデバイス確認文が送られてきたら
{
Serial.println("iamDevice");//返答を行う
return true;//trueを返す
}
}
return false;// デフォルトでfalseを返す
}
まとめ
今回はUnityでシリアルポートを自動検索して通信を行う方法を解説しました。とても長い記事になりましたが非同期処理とデバイス識別プロトコルを組み合わせることで、ユーザーが手動でポートを設定する必要がない便利なシステムが構築できましたね。
習得したスキル
- API Compatibility Levelを設定してシリアル通信を有効化する方法
- 非同期処理でシリアルポートからデータを読み取る方法
- シングルトンパターンで通信インスタンスを管理する方法
次に読むべき記事
- Unityでのスレッド処理の最適化について
- Arduinoセンサーデータの活用方法について
- 福祉アプリケーションの開発事例について
以上です。
投稿者プロフィール

-
(株)ゲームガム 代表取締役社長 / Roblox開発スタジオ GameGum 責任者 / 現役大学院生 金属×ロケットの研究従事 / ゲーム配信Mirrativ 配信者 / 国内最大級メタバースクリエーター向けテックブログを運営。
「メタバースクリエーターと1兆円の経済圏を作る」ために活動中。Xのアカウントのフォロワーは2000人弱(2025年4月現在)
最新の投稿
RobloxStudio2025年6月23日【初心者必見!】RobloxのCFrame完全マスターガイド
RobloxStudio2025年6月23日【初心者必見】Robloxでプレイヤーハザードを作る方法
RobloxStudio2025年6月22日【5分で読める!】Robloxレイヤード衣類アクセサリの作り方
RobloxStudio2025年6月22日【初心者必見!】Robloxカメラのカスタマイズ完全ガイド
ぜひ、応援の言葉をお願いします!