Arduino 串接 Unity / TouchDesigner:使用 ArduinoJson 實現感測資料 JSON 序列化傳輸

Ted Liou 2025.08.04 實體介面 最後更新 2026.03.17

快速摘要

用 Arduino 串 Unity 或 TouchDesigner 時,真正要先穩住的是資料格式。本文以 ArduinoJson 7 為前提,示範如何把感測資料整理成一行一筆的 JSON,並完成 Arduino、Unity、TouchDesigner 之間的雙向通訊。

Arduino 要串 Unity 或 TouchDesigner,真正容易亂掉的通常是資料格式。今天只有一個光敏電阻時,用逗號分隔字串還撐得住;明天多一個按鈕、再加一個旋鈕、還要讓電腦回傳命令時,整條資料流很快就開始變得難維護。

這時直接改用 JSON 會輕鬆很多。我們把每筆 Serial 訊息固定成「一行一個 JSON 物件」,Arduino 負責序列化,Unity 與 TouchDesigner 負責反序列化,雙向通訊也沿用同一套結構。資料一旦長得規律,後面要接感測器、改欄位、加互動,成本都會低不少。

本文以 ArduinoJson 7 為前提,並沿用目前這個文章包內附的 Arduino、Unity 與 TouchDesigner 範例檔。官方的安裝與 API 說明請參考:ArduinoJson 官方文件。Unity 透過 Git URL 安裝套件的流程仍在官方手冊裡,請參考:Unity Manual - Install a UPM package from a Git URL。TouchDesigner 的 Serial DAT 與 JSON DAT 參數,請參考:Serial DATJSON DAT

先把 Serial 訊息格式固定下來

這篇文章的核心做法很單純:Arduino 每次送出一筆資料時,都整理成一個 JSON 物件,送完立刻換行。只要這個規則固定,後面是 Unity、TouchDesigner,甚至改成別的應用,接法都差不多。

安裝 ArduinoJson 套件

ArduinoJson 可以直接從 Arduino IDE 的 Library Manager 安裝。請開啟 IDE 左側的「函式庫管理員」,搜尋 ArduinoJson,安裝由 Benoit Blanchon 維護的套件即可。

Arduino IDE 中透過 Library Manager 搜尋並安裝 ArduinoJson 套件的畫面

若我們正在維護舊專案,網路上會看到很多 v6 以前的文章還在用 StaticJsonDocumentDynamicJsonDocument。本文不走那條線,因為這裡的範例已經採用 ArduinoJson 7 的 JsonDocument 寫法。

發送 JSON 字串資料

先從最小版本開始。下面這段程式會在 Arduino 端建立一個 JsonDocument,把感測資料塞進 namevalue 兩個欄位,再透過 Serial 送出。

 1#include "ArduinoJson.h";
 2
 3JsonDocument doc;
 4
 5void setup() {
 6  Serial.begin(9600);
 7}
 8
 9void loop() {
10  String dataName = "testData";
11  int dataValue = random(0, 100);
12
13  doc["name"] = dataName;
14  doc["value"] = dataValue;
15
16  serializeJson(doc, Serial);
17  Serial.println();
18  delay(200);
19}

這裡最值得注意的是 Serial.println();。我們把它當作每筆資料的邊界,而不是排版用途。接收端只要採用「一行一筆」的讀法,就能明確知道什麼時候收完整筆資料。

把程式燒錄進 Arduino 後,打開 Serial Monitor,就會看到像下面這樣的輸出:

1{"name":"testData","value":76}
2{"name":"testData","value":12}
3{"name":"testData","value":94}

若看到的畫面和這裡差不多,就代表 Arduino 端的序列化已經站穩。

Arduino Serial Monitor 持續輸出一行一筆 JSON 字串的畫面

接收 JSON 字串資料

只做單向輸出其實還不夠。互動專案很常需要反過來讓 Unity 或 TouchDesigner 把命令送回 Arduino,像是切換模式、改 LED 狀態、觸發馬達或改變感測器行為。

這時我們只要再補一個字串緩衝區,等到讀到換行時,把整筆字串丟給 deserializeJson() 解析即可:

 1#include "ArduinoJson.h";
 2
 3JsonDocument doc;
 4String buffer;
 5
 6void setup() {
 7  Serial.begin(9600);
 8}
 9
10void loop() {
11  if (Serial.available() > 0) {
12    char msg = Serial.read();
13
14    if (msg == '\n'){
15      ParseJSON(buffer);
16      buffer = "";
17    }
18    else {
19      buffer += msg;
20    }
21  }
22  else {
23    String dataName = "testData";
24    int dataValue = random(0, 100);
25
26    doc["name"] = dataName;
27    doc["value"] = dataValue;
28
29    serializeJson(doc, Serial);
30    Serial.println();
31  }
32}
33
34void ParseJSON(String raw){
35  deserializeJson(doc, raw);
36
37  const char* name = doc["name"];
38  long value = doc["value"];
39
40  Serial.print("name: ");
41  Serial.println(name);
42  Serial.print("value: ");
43  Serial.println(value);
44}

這個版本的重點很明確:有輸入就先處理輸入,沒有輸入才繼續送模擬資料。它不花俏,但很像實際專案會採用的骨架。

如果我們要把這段拿去正式專案,我會再補一個 deserializeJson() 的錯誤檢查,至少在字串格式壞掉時,不要讓舊資料殘留在 doc 裡。本文先保留文章包內的原始範例,方便和附檔對照。

Arduino 串列埠同時接收與發送 JSON 資料的測試畫面

Unity 端:把 JSON 變成可用的遊戲資料

Unity 這邊我還是建議把工作拆乾淨:Ardity 負責 Serial 通訊,ArduinoController 只負責接資料和送資料,不要把所有互動邏輯都塞在同一支腳本裡。

安裝 Ardity 套件

本文使用的是我們整理過、可直接用 UPM 安裝的 Ardity 2.0.1。先打開 Unity 的 Package Manager,選擇「Install package from git URL…」,再貼上下列網址:

1https://github.com/tedliou/Ardity.git?path=Packages/ardity#2.0.1

這個 tag 目前仍存在於遠端儲存庫。若 Unity 提示找不到 Git,先確認電腦上的 Git 已安裝且可在 PATH 中被找到。若 Ardity 跳出 API Compatibility Level 不支援的提示,直接依套件內建的檢查器切換即可,不用手動猜設定值。

Unity Package Manager 選擇以 Git URL 安裝套件的畫面

安裝完成後,請在 Project 視窗顯示 Package Folders,將 Ardity > Prefabs > SerialController 拖到場景裡,再把 Port NameBaud RateMax Unread Messages 調成適合你的專案。

Unity 場景中加入 Ardity 的 SerialController Prefab 並設定序列埠參數

接收 JSON 感測資料

接收端我們用一個很單純的資料類別來對應 Arduino 送來的 namevalue

 1using UnityEngine;
 2
 3[System.Serializable]
 4public class InputData {
 5    public string name;
 6    public int value;
 7}
 8
 9public class ArduinoController : MonoBehaviour
10{
11    private void OnConnectionEvent(bool state)
12    {
13        Debug.Log($"連線狀態:{state}");
14    }
15
16    private void OnMessageArrived(string msg)
17    {
18        var data = JsonUtility.FromJson<InputData>(msg);
19        Debug.Log($"接收資料 {data.name}:{data.value}");
20    }
21}

接著建立一個空物件與同名腳本 ArduinoController,把腳本掛上去,再把這個物件拖進 SerialControllerMessage Listener。這樣 Ardity 收到的每一行資料,都會自動送進 OnMessageArrived()

Unity 中建立 ArduinoController 物件並把腳本附加上去

把 ArduinoController 指定給 SerialController 的 Message Listener 欄位

執行後,Console 就會持續看到 Arduino 送來的資料。這個階段先不要急著做互動,先確認「欄位名稱正確、數值有在跳、Baud Rate 一致」這三件事。通訊先穩,後面才好接。

Unity Console 持續顯示從 Arduino 反序列化後的 JSON 資料

發送 JSON 執行命令

當 Unity 也要回頭控制 Arduino 時,做法其實一樣:先把要送的資料包成一個物件,再轉成 JSON 字串。

Arduino 端這裡沿用文章包內的範例,新增一個 mode 字串,讓 Unity 可以透過收到的 name 來改變 Arduino 後續發送的資料名稱:

 1#include "ArduinoJson.h";
 2
 3JsonDocument doc;
 4String buffer;
 5String mode = "testData";
 6
 7void setup() {
 8  Serial.begin(9600);
 9}
10
11void loop() {
12  if (Serial.available() > 0) {
13    char msg = Serial.read();
14
15    if (msg == '\n'){
16      ParseJSON(buffer);
17      buffer = "";
18    }
19    else {
20      buffer += msg;
21    }
22  }
23  else {
24    String dataName = mode;
25    int dataValue = random(0, 100);
26
27    doc["name"] = dataName;
28    doc["value"] = dataValue;
29
30    serializeJson(doc, Serial);
31    Serial.println();
32  }
33}
34
35void ParseJSON(String raw){
36  deserializeJson(doc, raw);
37
38  const char* name = doc["name"];
39  long value = doc["value"];
40
41  mode = name;
42}

Unity 端則只需要保留一個 SerialController 參考,在按下空白鍵時送出 JSON:

 1using UnityEngine;
 2
 3[System.Serializable]
 4public class InputData {
 5    public string name;
 6    public int value;
 7}
 8
 9public class ArduinoController : MonoBehaviour
10{
11    public SerialController serialController;
12
13    private void Update()
14    {
15        if (Input.GetKeyDown(KeyCode.Space))
16        {
17            var send = new InputData
18            {
19                name = $"Hello World! {Time.time}",
20                value = 999999999
21            };
22            var json = JsonUtility.ToJson(send);
23
24            serialController.SendSerialMessage(json);
25        }
26    }
27
28    private void OnConnectionEvent(bool state)
29    {
30        Debug.Log($"連線狀態:{state}");
31    }
32
33    private void OnMessageArrived(string msg)
34    {
35        var data = JsonUtility.FromJson<InputData>(msg);
36        Debug.Log($"接收資料 {data.name}:{data.value}");
37    }
38}

這個範例刻意保持很簡單,因為我們這裡要驗證的是資料格式有沒有在兩端保持一致。只要 Unity 一按鍵,Arduino 送回來的 name 跟著變,就代表雙向通訊已經成立。

Unity Inspector 中把 SerialController 指定給 ArduinoController 腳本欄位

按下空白鍵後,Arduino 發送資料名稱跟著改變的測試畫面

TouchDesigner 端:沿用同一個 JSON 規則

TouchDesigner 這一段的重點和 Unity 一樣,資料邊界要清楚。既然 Arduino 端已經保證「一行一筆 JSON」,TouchDesigner 就很適合照這個規則一路接下去。

接收 JSON 字串資料

接收流程可以拆成四個節點:

  1. Serial DAT:接 Arduino。
  2. Select DAT:只取最新一筆完整資料。
  3. JSON DAT:把字串展開成欄位。
  4. DAT to CHOP:把數值轉成可直接驅動互動的通道。

Serial DAT 中,請把 Row/Callback Format 設成 One Per Line,這樣每讀到一個換行,就會視為一筆完整訊息。這和前面 Arduino 的 Serial.println() 是成對的。

TouchDesigner 的 Serial DAT 設定為一行一筆資料的畫面

接著在 Select DAT 只抓最新一筆完整字串,再把它送進 JSON DATJSON DAT 轉成表格後,我們就能很自然地把 value 欄位轉成 CHOP channel。

TouchDesigner 的 Select DAT 選取最新一筆 JSON 資料列

TouchDesigner 的 JSON DAT 將字串展開為表格欄位

透過 DAT to CHOP 把 JSON 欄位轉成可用的 CHOP 頻道

到這裡,我們就把 Arduino 送來的 value 成功變成 TouchDesigner 裡可以直接拿來驅動視覺或互動的頻道了。

發送 JSON 執行命令

要從 TouchDesigner 回傳命令到 Arduino,也不需要另外發明新格式。做法一樣是先建立資料,再把它轉成 JSON 字串。

這裡用一個 Button COMP 當觸發源,接 Null CHOP,再由 CHOP Execute DAT 監聽 Off to On 事件。按下按鈕時,送出一筆 JSON。

TouchDesigner 以 Button COMP、Null CHOP 和 CHOP Execute DAT 組成命令觸發流程

CHOP Execute DAT 監聽 Off to On 事件的參數設定畫面

程式如下:

 1import json
 2
 3def onOffToOn(channel, sampleIndex, val, prev):
 4	data = {
 5		"name": "Hello World! " + str(absTime.frame),
 6		"value": 99999
 7	}
 8	json_str = json.dumps(data)
 9	op("serial1").send(json_str, terminator="\n")
10	return

這段程式真正關鍵的地方只有兩個。第一個是 json.dumps(data),它讓我們送出去的資料結構和 Unity、Arduino 端完全一致。第二個是 terminator="\n",它把前面整篇文章一直強調的「一行一筆」規則維持到最後。

只要按下按鈕後,Arduino 那邊的模式跟著變,這條雙向通訊就成立了。

TouchDesigner 按鈕觸發後,Arduino 收到 JSON 命令並改變輸出內容的測試畫面

總結

Arduino 串 Unity 或 TouchDesigner 時,最值得先做對的是資料格式。把 Serial 訊息固定成「一行一筆 JSON」,再讓 Arduino、Unity、TouchDesigner 都沿用同一套欄位結構,整條通訊會變得很好維護。後面不管是加感測器、改命令格式,還是把資料接進別的互動系統,成本都會低很多。

常見問題

因為只要欄位一多,逗號分隔字串很快就會變難維護。JSON 的鍵值結構比較清楚,Unity 和 TouchDesigner 端也比較容易直接反序列化。

本文採用 ArduinoJson 7 的 JsonDocument 寫法,不再沿用舊版常見的 StaticJsonDocumentDynamicJsonDocument 範例。若我們還在維護舊專案,語法會不一樣。

最重要的是固定訊息邊界。本文採用「一行一筆 JSON」的做法,也就是每送完一個 JSON 物件就補一個換行,這樣 Unity、TouchDesigner 與 Arduino 都比較容易判斷一筆資料何時結束。

作者

Ted Liou

現職 Unity C# 工程師,主要分享 Unity、C# 與 Vibe Coding 相關技術教學。

上一篇 使用 Docker on WSL 2 快速部署個人 GitLab 服務 下一篇 TreeSize Free 使用教學:快速找出 Windows 硬碟空間被誰占滿