IoT Eğitimleri

Unity MQTT Client: 3D Sahneleri Canlı Sensör Verisiyle Yönetin

MQTTnet ile Unity'ye bir MQTT client ekleyin, broker.connectmq.com üzerindeki sensör topic'lerine abone olun ve GameObject'leri canlı veriyle ana thread üzerinde güvenle hareket ettirin; bir dijital ikizin temeli.

Bir unity mqtt client tarafından yönetilen dönen bir fan ve renk kodlu bir tank içeren Unity editör sahnesi
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Bir Unity MQTT client, statik bir 3D sahneyi gerçek ekipmanın canlı görünümüne dönüştürür: bir fan ölçülen devirde döner, bir tank sıcaklığa göre renk değiştirir, bir sensör söylediğinde bir kapı açılır. Bu eğitimde bir Unity projesine MQTTnet ekleyecek, broker.connectmq.com üzerindeki sensör topic'lerine abone olacak ve GameObject'leri Unity'nin ana thread'inden güvenle güncelleyeceksiniz. Bu, tarayıcı tabanlı ve masaüstü dijital ikizlerin kullandığı temelin aynısıdır.

Canlı Veri Bir Unity Sahnesine Nasıl Ulaşır?

Akışın üç parçası vardır ve ilk denemelerin çoğu ortadaki parçada hata yapar:

Sensor or simulator --MQTT--> Broker --MQTT--> MQTTnet (background thread)
                                                  |
                                       ConcurrentDictionary (latest value per topic)
                                                  |
                                       MonoBehaviour.Update() (main thread) --> Transforms, materials

Unity'nin API'si thread-safe değildir. MQTTnet'teki ağ callback'leri thread havuzu thread'lerinde çalışır ve oradan bir Transform veya Material nesnesine dokunmak UnityException: ... can only be called from the main thread hatası fırlatır ya da daha kötüsü sessizce başarısız olur. Çözüm, gelen değerleri thread-safe bir yapıda saklamak ve bunları Update() içinde uygulamaktır.

Görselleştirme için genellikle her mesajla değil, yalnızca her topic'in son değeriyle ilgilenirsiniz. Topic'e göre anahtarlanmış bir sözlük size "son değer kazanır" davranışını hazır olarak sunar ve bir sensör kare hızınızdan daha hızlı yayın yapsa bile kare süresini sabit tutar.

Unity'ye MQTTnet Eklemek

Unity'nin betik çalışma zamanı .NET Standard 2.1'i hedefler (Project Settings > Player > Api Compatibility Level). .NET Standard derlemesiyle gelen bir MQTTnet 4.x sürümü kullanın; daha yeni ana sürümler yalnızca modern .NET'i hedefleyebilir ve Unity'de yüklenmez.

İki kurulum yolu vardır:

  1. NuGetForUnity: paketi kurun, NuGet > Manage NuGet Packages menüsünü açın, MQTTnet'i arayın ve bir 4.x sürümü seçin.
  2. Manuel: .nupkg dosyasını nuget.org'dan indirin, uzantısını .zip olarak değiştirin, lib/netstandard2.1/MQTTnet.dll dosyasını çıkarın ve Assets/Plugins/ klasörüne bırakın.

IL2CPP ile derliyorsanız (iOS için zorunlu, Android ve konsollar için yaygın), kod ayıklayıcının (stripper) MQTTnet'in reflection ile kullanılan kısımlarını kaldırmaması için Assets/ klasörüne bir link.xml ekleyin:

<linker>
  <assembly fullname="MQTTnet" preserve="all" />
</linker>

MQTT Manager Betiği

Assets/Scripts/MqttManager.cs dosyasını oluşturun. Betik TLS üzerinden bağlanır, bir topic önekine abone olur, her topic için son payload'ı tutar ve geri çekilmeyle yeniden bağlanır.

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using UnityEngine;

public class MqttManager : MonoBehaviour
{
    public static MqttManager Instance { get; private set; }

    [SerializeField] private string host = "broker.connectmq.com";
    [SerializeField] private int port = 8883;
    [SerializeField] private bool useTls = true;
    [SerializeField] private string topicPrefix = "connectmq/demo/u9n1ty";
    [SerializeField] private string username = "";
    [SerializeField] private string password = "";

    private readonly ConcurrentDictionary<string, string> _latest = new();
    private IMqttClient _client;
    private CancellationTokenSource _cts;
    private string _clientId;

    public bool IsConnected => _client != null && _client.IsConnected;

    public bool TryGet(string relativeTopic, out string payload) =>
        _latest.TryGetValue($"{topicPrefix}/{relativeTopic}", out payload);

    private void Awake()
    {
        if (Instance != null) { Destroy(gameObject); return; }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        // Application gibi Unity API'leri ana thread üzerinde okunmalıdır
        _clientId = $"unity-{Application.platform}-{Guid.NewGuid().ToString("N").Substring(0, 8)}";
        _cts = new CancellationTokenSource();
        _client = new MqttFactory().CreateMqttClient();
        _client.ApplicationMessageReceivedAsync += e =>
        {
            _latest[e.ApplicationMessage.Topic] = e.ApplicationMessage.ConvertPayloadToString();
            return Task.CompletedTask;
        };
        _ = Task.Run(() => ConnectionLoop(_cts.Token));
    }

    private async Task ConnectionLoop(CancellationToken ct)
    {
        var builder = new MqttClientOptionsBuilder()
            .WithTcpServer(host, port)
            .WithClientId(_clientId)
            .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
            .WithKeepAlivePeriod(TimeSpan.FromSeconds(30));
        if (useTls) builder.WithTlsOptions(o => o.UseTls());
        if (!string.IsNullOrEmpty(username)) builder.WithCredentials(username, password);
        var options = builder.Build();

        var delay = 1;
        while (!ct.IsCancellationRequested)
        {
            try
            {
                if (!_client.IsConnected)
                {
                    await _client.ConnectAsync(options, ct);
                    await _client.SubscribeAsync(new MqttClientSubscribeOptionsBuilder()
                        .WithTopicFilter(f => f.WithTopic($"{topicPrefix}/#")
                            .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce))
                        .Build(), ct);
                    Debug.Log("MQTT connected");
                    delay = 1;
                }
            }
            catch (OperationCanceledException) { break; }
            catch (Exception ex)
            {
                Debug.LogWarning($"MQTT connect failed: {ex.Message}");
                delay = Math.Min(delay * 2, 30);
            }
            try { await Task.Delay(TimeSpan.FromSeconds(delay), ct); }
            catch (OperationCanceledException) { break; }
        }
    }

    public Task PublishAsync(string relativeTopic, string payload, bool retain = false)
    {
        if (!IsConnected) return Task.CompletedTask;
        var msg = new MqttApplicationMessageBuilder()
            .WithTopic($"{topicPrefix}/{relativeTopic}")
            .WithPayload(payload)
            .WithRetainFlag(retain)
            .Build();
        return _client.PublishAsync(msg);
    }

    private async void OnDestroy()
    {
        if (Instance != this) return;
        _cts?.Cancel();
        if (_client != null && _client.IsConnected)
        {
            try { await _client.DisconnectAsync(); } catch { }
        }
        _client?.Dispose();
    }
}

Tercihlerle ilgili notlar:

  • QoS 0, yüksek hızlı görselleştirme verisi için doğru seçimdir. Bir karenin değeri kaybolursa bir sonraki değer kısa süre sonra onun yerini alır. Komutlar ve alarmlar için QoS 1 kullanın.
  • Client ID, Start() içinde ana thread üzerinde oluşturulur; çünkü ConnectionLoop, Application ve SystemInfo gibi Unity API'lerinin çağrılmaması gereken bir thread havuzu thread'inde çalışır. Rastgele sonek, iki editör örneğinin veya iki cihazın birbirini broker'dan düşürmesini önler.
  • DontDestroyOnLoad, sahne yüklemelerinde her seferinde yeniden bağlanmak yerine tek bir bağlantıyı korur.
  • OnDestroy içinde bağlantıyı kesmek editörde önemlidir: bu olmadan her Play/Stop döngüsü keep-alive süresi dolana kadar hayalet bir bağlantı bırakır ve bağlantı kotası olan bir broker'da kotayı hızla tüketebilirsiniz.

Topic'leri GameObject'lere Bağlamak

Şimdi eğlenceli kısma geçelim. Bir RPM topic'ine göre bir transform'u döndüren yeniden kullanılabilir bir bileşen:

using System.Globalization;
using UnityEngine;

public class SpinFromTopic : MonoBehaviour
{
    [SerializeField] private string topic = "fan1/rpm";
    [SerializeField] private Vector3 axis = Vector3.up;
    private float _targetRpm, _currentRpm;

    private void Update()
    {
        if (MqttManager.Instance != null &&
            MqttManager.Instance.TryGet(topic, out var raw) &&
            float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var rpm))
        {
            _targetRpm = rpm;
        }
        // Mesajlar arasındaki sıçramalar doğal görünsün diye hedefe doğru yumuşakça yaklaş
        _currentRpm = Mathf.Lerp(_currentRpm, _targetRpm, Time.deltaTime * 2f);
        transform.Rotate(axis, _currentRpm * 6f * Time.deltaTime); // rpm * 360 / 60
    }
}

Ve JSON sıcaklık payload'ına göre bir renderer'ı renklendiren bir bileşen:

using UnityEngine;

public class ColorFromTemperature : MonoBehaviour
{
    [System.Serializable] private class Reading { public float temperature; }

    [SerializeField] private string topic = "tank1/telemetry";
    [SerializeField] private float min = 20f, max = 90f;
    [SerializeField] private Gradient gradient;
    private Renderer _renderer;
    private string _lastRaw;

    private void Awake() => _renderer = GetComponent<Renderer>();

    private void Update()
    {
        if (MqttManager.Instance == null || !MqttManager.Instance.TryGet(topic, out var raw)) return;
        if (raw == _lastRaw) return; // yeni bir şey yok
        _lastRaw = raw;
        var reading = JsonUtility.FromJson<Reading>(raw);
        var t = Mathf.InverseLerp(min, max, reading.temperature);
        _renderer.material.color = gradient.Evaluate(t);
    }
}

Kopyalamaya değer iki ayrıntı: sayıları CultureInfo.InvariantCulture ile ayrıştırın (aksi takdirde ondalık ayırıcısı virgül olan makinelerde 12.5 başarısız olur) ve payload değişmediğinde işi atlayın. JsonUtility hızlıdır ve az bellek ayırır, ancak yalnızca serileştirilebilir sınıflardaki alanları işler; iç içe veya dinamik JSON için Newtonsoft Json (Unity paketi olarak mevcuttur) daha esnektir.

Test Verisiyle Besleyin

MqttManager içeren boş bir GameObject, SpinFromTopic içeren bir silindir ve ColorFromTemperature içeren bir kapsül oluşturun. Play'e basın, ardından bir terminalden sensörleri simüle edin:

# Fanı önce 120 RPM, sonra 600 RPM ile döndür
mosquitto_pub -h broker.connectmq.com -p 1883 -i unity-sim-1 -t connectmq/demo/u9n1ty/fan1/rpm -m 120
mosquitto_pub -h broker.connectmq.com -p 1883 -i unity-sim-1 -t connectmq/demo/u9n1ty/fan1/rpm -m 600

# Tankı bir döngüde ısıt (bash)
for t in $(seq 20 5 90); do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i unity-sim-2 \
    -t connectmq/demo/u9n1ty/tank1/telemetry -m "{\"temperature\": $t}"
  sleep 0.5
done

Fan yavaş yavaş hızlanmalı ve tank gradyanınız boyunca renk değiştirmelidir. Herkese açık broker istemci başına saniyede 10 mesaja izin verir; bu nedenle simülatörleri bunun altında tutun ve herkese açık alanın herkes tarafından görülebildiğini unutmayın. Daha fazla yayın tekniği mosquitto_pub ve mosquitto_sub örneklerinde yer alıyor.

Sahneden Komut Göndermek

Bir ikiz, kullanıcılar onun üzerinden işlem yapabildiğinde bir görüntüleyiciden fazlası haline gelir. Bir UI düğmesini veya bir 3D nesneye tıklamayı MqttManager.Instance.PublishAsync("pump1/cmd", "{\"action\":\"stop\"}") çağrısına bağlayın ve gerçek cihazın (veya simülatörünüzün) yeni durumunu yayınlayarak doğrulamasını sağlayın. Animasyonu tıklamaya göre değil doğrulanmış duruma göre yapın; böylece sahne asla ekipmanın yapmadığı bir şeyi göstermez. Komutlar için QoS 1 kullanın ve çok daha sonra yeniden bağlanan bir cihazın güncelliğini yitirmiş bir talimatı yürütmemesi için bir mesaj sona erme süresi düşünün.

Unity MQTT Client için Platform Notları

Hedef MQTTnet TCP/TLS üzerinden çalışır mı? Notlar
Windows, macOS, Linux standalone Evet En basit hedef; önce burada test edin
Android Evet IL2CPP için link.xml gerekir; Player ayarlarında internet erişimine izin verin
iOS Evet IL2CPP ve link.xml; 8883'te TLS'yi tercih edin
WebGL Hayır Tarayıcılar ham TCP soketlerine izin vermez. Bir JavaScript köprüsü (bir tarayıcı MQTT kütüphanesini saran bir .jslib) üzerinden wss://broker.connectmq.com:8084/mqtt adresine WebSocket üzerinden MQTT kullanın
VR gözlükleri (Android tabanlı) Evet Android ile aynı; kare süresine dikkat edin, handler'ları çok küçük tutun

İnsanları şaşırtan WebGL'dir. Nihai hedefiniz tarayıcı tabanlı bir ikizse, web tabanlı bir 3D yığının daha uygun olup olmadığını değerlendirin; WebSocket üzerinden MQTT rehberi tarayıcı tarafını açıklar.

Canlı Sahneden Dijital İkize

Nesneleri topic'lerle hareket ettirmek ilk adımdır. Kullanışlı bir ikiz ayrıca tutarlı bir topic yapısına, sahne yüklendiği anda doğru olması için retained duruma ve geri komut göndermenin bir yoluna ihtiyaç duyar. ConnectMQ'nun kendi 3D dijital ikiz web demosu; CNC tezgâhı, robot kol, fırın, tank, konveyör ve pompa içeren bir fabrika, güvenli WebSocket üzerinden canlı veriyle tam olarak bu yaklaşımı izler. Topic'leri ISA-95 seviyeleriyle (enterprise/site/area/line/cell) düzenlemek, topic'leri sahne hiyerarşisine eşlemeyi de kolaylaştırır; bu yapı için MQTT Sparkplug B ve Unified Namespace yazısını okuyun. Sahnenin etrafında .NET araçları da geliştiriyorsanız MQTTnet eğitimi MQTT 5 özelliklerini ve TLS'yi daha ayrıntılı ele alır.

Üretime Taşımak

Bir demonun ötesindeki her şey için Unity'yi özel bir broker'a bağlayın. ConnectMQ gibi bir yönetilen MQTT broker, Let's Encrypt sertifikalı TLS, MQTT 5 ve üretilmiş kimlik bilgileri sunar; host, username ve password alanlarını inspector'da doldurun (ya da daha iyisi, bunları çalışma zamanında derlemenin dışındaki bir yapılandırma dosyasından yükleyin). Decompile edilmiş bir derlemenin sahte sensör verisi enjekte edememesi için mümkün olduğunda görüntüleyici uygulamalara yayın yetkisi vermeyin. Broker'ın nerede olacağına karar veriyorsanız MQTT broker'ın ne yaptığına dair genel bakış ve MQTT istemci kütüphaneleri rehberi seçenekleri çerçevelemenize yardımcı olur.

ConnectMQ ile Başlayın

Unity sahnenizi dakikalar içinde özel bir broker'a yöneltin: app.connectmq.com adresinde kredi kartı gerekmeden ücretsiz Serverless plana kaydolun ve geliştirme yaparken sahnenizin tam olarak nelere abone olduğunu görmek için portalın canlı topic gezginini kullanın.

Sık Sorulan Sorular

Unity'de hangi MQTT kütüphanesini kullanmalıyım?

MQTTnet 4.x sağlam bir seçimdir; çünkü aktif olarak kullanılır, MQTT 5 ve TLS'yi destekler ve Unity ile uyumlu bir .NET Standard derlemesiyle gelir. Ana thread'i bloklayan veya Unity'nin desteklemediği API'lere dayanan kütüphanelerden kaçının.

Unity MQTT client'ım neden "can only be called from the main thread" hatası veriyor?

Mesaj handler'ı içinde Unity nesnelerine dokunuyorsunuz. Bu eğitimde gösterildiği gibi payload'ı thread-safe bir koleksiyonda saklayın ve Update() içinde uygulayın.

Bir Unity WebGL derlemesi MQTT'ye bağlanabilir mi?

TCP soketleriyle bağlanamaz. Bir WebGL derlemesi, bir JavaScript eklentisi aracılığıyla güvenli WebSocket üzerinden MQTT kullanmalı ve 8084 portu ile /mqtt yolu gibi bir WSS uç noktasına bağlanmalıdır.

Bir Unity sahnesi saniyede kaç mesaj işleyebilir?

Sınır genellikle MQTT değil, mesaj başına yaptığınız işlemdir. Topic başına son değer deseniyle sahne, mesaj hızından bağımsız olarak kare başına sabit iş yapar; bu nedenle pratik sınırlar broker planı ve ağ olur.

İlgili yazılar

Test, Betik ve Hata Ayıklama için En İyi MQTT CLI Araçları

mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, MQTT.js CLI, curl ve mqttui'nin uygulamalı karşılaştırması; betik yazma ve hata ayıklama için doğru aracı seçebilmeniz için aynı test broker.connectmq.com üzerinde çalıştırıldı.

7 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.