Skip to content

本文记录在 Unity 中通过 MQTT 与真实设备通信 的完整过程。目标是在不涉及游戏开发的前提下,用 Unity 快速实现环境传感器数据的可视化。

适合 会 MQTT / 做嵌入式,但从未使用过 Unity 的工程人员参考。关于Unity的安装,这里不再赘述。

一、工程创建

Unity工程类型选择(3D Core)建立一个基本的工程,我这里工程名字叫做MQTT:

二、Unity快速上手

工程创建好后,建立一个 Canvas :

如图,Hierarchy 里可以看到 Canvas 和 EventSystem :

在这个Canvas下新建按键( UI-->Button (TextMeshPro)):

如果弹窗提示导入 TMP,点 Import。

切换到 Game 视图,屏幕中间有一个按钮,可以选择按键修改文字

接下来为按键编写对应的脚本代码,在 Project(下面)窗口,展开 Assets,右键空白处选择创建MonoBehaviour Script,命名为TestButton

双击 TestButton.cs,打开内容如下:

C#
using UnityEngine;

public class TestButton : MonoBehaviour
{
    void Start()
    {
    }

    void Update()
    {
    }
}

修改为:

C#
using UnityEngine;

public class TestButton : MonoBehaviour
{
    public void OnButtonClick()
    {
        Debug.Log("Button clicked");
    }
}

保存(Ctrl + S),回到 Unity。接下来把脚本拖到 Button 上,在 Button 的 On Click() 里绑定 OnButtonClick:

点击画面上的 Play ▶ ,用鼠标点按钮,Console 窗口出现了Clicked的日志:

说明 UI → 脚本 → 事件链条已经完全打通,我们已经完成了Unity的快速上手。

三、接入MQTT

3.1 引入MQTT库

这里使用M2MqttUnity这个库。虽然这个库快两年没更新了,但是没有更好的替代品,MQTTnet 对Unity的支持也实在一般。

进入 https://github.com/gpvigano/M2MqttUnity 下载源码,把仓库Assets里的内容粘贴到项目Assets目录下:

为了目录简洁整齐,也可以在Assets 里新建ThirdParty--> M2Mqtt目录,把仓库Assets里的内容粘贴到 M2Mqtt目录下,也OK,如下图

3.2 编写MQTT脚本

新建脚本:Assets/Scripts/M2MqttClient.cs

C#

using UnityEngine;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using System.Text;

public class M2MqttClient : MonoBehaviour
{
    public string broker = "www.duruofu.top";
    public int port = 1883;
    public string topic = "env_sense/data";

    private MqttClient client;

    void Start()
    {
        // 最稳妥的构造方式
        client = new MqttClient(broker);

        client.MqttMsgPublishReceived += OnMessageReceived;

        string clientId = System.Guid.NewGuid().ToString();
        client.Connect(clientId);

        if (client.IsConnected)
        {
            Debug.Log("[MQTT] Connected");

            client.Subscribe(
                new string[] { topic },
                new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE }
            );

            Debug.Log("[MQTT] Subscribed: " + topic);
        }
        else
        {
            Debug.LogError("[MQTT] Connect failed");
        }
    }

    void OnMessageReceived(object sender, MqttMsgPublishEventArgs e)
    {
        string msg = Encoding.UTF8.GetString(e.Message);
        Debug.Log("[MQTT RX] " + msg);

        HandleMessage(msg);
    }

    void HandleMessage(string json)
    {
        SensorData data = JsonUtility.FromJson<SensorData>(json);

        Debug.Log(
            $"Temp:{data.temperature} " +
            $"Humi:{data.humidity} " +
            $"Press:{data.pressure} " +
            $"Light:{data.light_intensity} " +
            $"CO2:{data.co2_concentration}"
        );
    }

    void OnDestroy()
    {
        if (client != null && client.IsConnected)
        {
            client.Disconnect();
        }
    }
}

[System.Serializable]
public class SensorData
{
    public float temperature;
    public float humidity;
    public float pressure;
    public float bmp_temperature;
    public float light_intensity;
    public float co2_concentration;
}

这里使用我自己的服务器连接信息:

  • Broker:www.duruofu.top
  • Port:1883
  • Topic:env_sense/data
  • Payload:JSON(已解析)

理论上这个脚本运行后会打印:

csharp
[MQTT] Connected
[MQTT] Subscribed: env_sense/data
[MQTT RX] {"temperature":26.53,...}
Temp: 26.53 °C | Humi: 21.14 % | Press: 805.16 | ...

3.3 挂载并运行脚本

在 Hierarchy 新建一个物体,在 Hierarchy 面板 空白处右键,Create Empty,重命名为MQTTManager,然后把脚本挂上去。

然后点击运行,查看日志:

可以看到已经接收到MQTT的数据了。

四、数据可视化

接下来完成一个简单的UI的数据可视化面板,我们在一开始快速上手部分建立的Canvas 创建一个可视化面板:

text
Canvas
 └── DataPanel
      ├── TemperatureSlider
      ├── HumiditySlider
      ├── PressureSlider
      ├── LightSlider
      └── CO2Slider
  • 每个 Slider 的 Min / Max 对应该传感器的合理范围,例如:
传感器MinMax
Temperature0 ℃50 ℃
Humidity0 %100 %
Pressure900 hPa1100 hPa
Light0 Lux1000 Lux
CO2300 ppm2000 ppm
  • 在 Slider 下可以加一个 TextMeshPro 文本显示数值。


在你现有的 M2MqttClient 脚本里添加引用:

csharp
using TMPro;
using UnityEngine.UI;

public class M2MqttClient : MonoBehaviour
{
    // 数字文本显示
    public TMP_Text temperatureText;
    public TMP_Text humidityText;
    public TMP_Text pressureText;
    public TMP_Text lightText;
    public TMP_Text co2Text;

    // 滑动条显示
    public Slider temperatureSlider;
    public Slider humiditySlider;
    public Slider pressureSlider;
    public Slider lightSlider;
    public Slider co2Slider;

    void HandleMessage(string json)
    {
        SensorData data = JsonUtility.FromJson<SensorData>(json);

        // 更新数字显示
        if (temperatureText) temperatureText.text = $"{data.temperature:F1} ℃";
        if (humidityText) humidityText.text = $"{data.humidity:F1} %";
        if (pressureText) pressureText.text = $"{data.pressure:F1} hPa";
        if (lightText) lightText.text = $"{data.light_intensity:F1} Lux";
        if (co2Text) co2Text.text = $"{data.co2_concentration:F0} ppm";

        // 更新滑动条
        if (temperatureSlider) temperatureSlider.value = data.temperature;
        if (humiditySlider) humiditySlider.value = data.humidity;
        if (pressureSlider) pressureSlider.value = data.pressure;
        if (lightSlider) lightSlider.value = data.light_intensity;
        if (co2Slider) co2Slider.value = data.co2_concentration;
    }
}

最终代码为:

C#
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using System.Text;

[System.Serializable]
public class SensorData
{
    public float temperature;
    public float humidity;
    public float pressure;
    public float bmp_temperature;
    public float light_intensity;
    public float co2_concentration;
}

public class M2MqttClient : MonoBehaviour
{
    [Header("MQTT 设置")]
    public string broker = "www.duruofu.top";
    public int port = 1883;
    public string topic = "env_sense/data";

    private MqttClient client;

    [Header("数字文本显示")]
    public TMP_Text temperatureText;
    public TMP_Text humidityText;
    public TMP_Text pressureText;
    public TMP_Text lightText;
    public TMP_Text co2Text;

    [Header("滑动条显示")]
    public Slider temperatureSlider;
    public Slider humiditySlider;
    public Slider pressureSlider;
    public Slider lightSlider;
    public Slider co2Slider;

    void Start()
    {
        // 连接 MQTT Broker
        client = new MqttClient(broker);

        client.MqttMsgPublishReceived += OnMessageReceived;

        string clientId = System.Guid.NewGuid().ToString();
        client.Connect(clientId);

        if (client.IsConnected)
        {
            Debug.Log("[MQTT] Connected");

            client.Subscribe(
                new string[] { topic },
                new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE }
            );

            Debug.Log("[MQTT] Subscribed: " + topic);
        }
        else
        {
            Debug.LogError("[MQTT] Connect failed");
        }
    }

    void OnMessageReceived(object sender, MqttMsgPublishEventArgs e)
    {
        string msg = Encoding.UTF8.GetString(e.Message);
        Debug.Log("[MQTT RX] " + msg);

        HandleMessage(msg);
    }

    void HandleMessage(string json)
    {
        SensorData data = JsonUtility.FromJson<SensorData>(json);

        // 更新数字显示
        if (temperatureText) temperatureText.text = $"{data.temperature:F1} ℃";
        if (humidityText) humidityText.text = $"{data.humidity:F1} %";
        if (pressureText) pressureText.text = $"{data.pressure:F1} hPa";
        if (lightText) lightText.text = $"{data.light_intensity:F1} Lux";
        if (co2Text) co2Text.text = $"{data.co2_concentration:F0} ppm";

        // 更新滑动条显示
        if (temperatureSlider) temperatureSlider.value = data.temperature;
        if (humiditySlider) humiditySlider.value = data.humidity;
        if (pressureSlider) pressureSlider.value = data.pressure;
        if (lightSlider) lightSlider.value = data.light_intensity;
        if (co2Slider) co2Slider.value = data.co2_concentration;
    }

    void OnDestroy()
    {
        if (client != null && client.IsConnected)
        {
            client.Disconnect();
            Debug.Log("[MQTT] Disconnected");
        }
    }
}

最后,把控件拖到脚本的数据上,运行就可以了:

最终的运行效果是这样的:

这里℃符号显示有点小问题,不过无伤大雅,我们核心的目的是搞定MQTT到UI的通路

至此,我们就完成了MQTT接入到Unity里的基本流程。

五、补充

这里补充一个反向控制的逻辑,我们在面板上增加一个按键,按下后向设备发送信息,主题为 env_sense/control,消息内容随意,就发个 ON/OFF

在 DataPanel 下添加一个 Button,给按钮加上 TextMeshPro - Text 显示文字,例如 “Toggle Device”。如下:

在M2MqttClient 脚本里增加一个控制函数:

C#
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using System.Text;

public class M2MqttClient : MonoBehaviour
{
    [Header("MQTT 设置")]
    public string broker = "www.duruofu.top";
    public int port = 1883;
    public string topic = "env_sense/data";
    public string controlTopic = "env_sense/control";  // 新增控制主题

    private MqttClient client;

    [Header("数字文本显示")]
    public TMP_Text temperatureText;
    public TMP_Text humidityText;
    public TMP_Text pressureText;
    public TMP_Text lightText;
    public TMP_Text co2Text;

    [Header("滑动条显示")]
    public Slider temperatureSlider;
    public Slider humiditySlider;
    public Slider pressureSlider;
    public Slider lightSlider;
    public Slider co2Slider;

    [Header("控制按钮")]
    public Button controlButton;

    private bool deviceOn = false; // 状态记录,可选

    void Start()
    {
        // 连接 MQTT Broker
        client = new MqttClient(broker, port, false, null, null, MqttSslProtocols.None);
        client.MqttMsgPublishReceived += OnMessageReceived;

        string clientId = System.Guid.NewGuid().ToString();
        client.Connect(clientId);

        if (client.IsConnected)
        {
            Debug.Log("[MQTT] Connected");
            client.Subscribe(
                new string[] { topic },
                new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE }
            );
            Debug.Log("[MQTT] Subscribed: " + topic);
        }
        else
        {
            Debug.LogError("[MQTT] Connect failed");
        }

        // 绑定控制按钮点击事件
        if (controlButton != null)
        {
            controlButton.onClick.AddListener(OnControlButtonClick);
        }
    }

    void OnMessageReceived(object sender, MqttMsgPublishEventArgs e)
    {
        string msg = Encoding.UTF8.GetString(e.Message);
        Debug.Log("[MQTT RX] " + msg);
        HandleMessage(msg);
    }

    void HandleMessage(string json)
    {
        SensorData data = JsonUtility.FromJson<SensorData>(json);

        if (temperatureText) temperatureText.text = $"{data.temperature:F1} ℃";
        if (humidityText) humidityText.text = $"{data.humidity:F1} %";
        if (pressureText) pressureText.text = $"{data.pressure:F1} hPa";
        if (lightText) lightText.text = $"{data.light_intensity:F1} Lux";
        if (co2Text) co2Text.text = $"{data.co2_concentration:F0} ppm";

        if (temperatureSlider) temperatureSlider.value = data.temperature;
        if (humiditySlider) humiditySlider.value = data.humidity;
        if (pressureSlider) pressureSlider.value = data.pressure;
        if (lightSlider) lightSlider.value = data.light_intensity;
        if (co2Slider) co2Slider.value = data.co2_concentration;
    }

    // 发送控制消息
    void OnControlButtonClick()
    {
        if (client != null && client.IsConnected)
        {
            deviceOn = !deviceOn;  // 切换状态
            string msg = deviceOn ? "ON" : "OFF";
            client.Publish(controlTopic, Encoding.UTF8.GetBytes(msg), MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, false);
            Debug.Log("[MQTT TX] " + msg);
        }
        else
        {
            Debug.LogWarning("[MQTT] Not connected, cannot send control message.");
        }
    }

    void OnDestroy()
    {
        if (client != null && client.IsConnected)
        {
            client.Disconnect();
            Debug.Log("[MQTT] Disconnected");
        }
    }
}

将 Button 拖到脚本的 controlButton 字段上。

运行 Unity 场景。点击按钮即可发送 "ON" 或 "OFF" 消息到 env_sense/control 主题,设备端订阅此主题即可实现反向控制。