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:
- 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.
- Manuel:
.nupkgdosyasını nuget.org'dan indirin, uzantısını.zipolarak değiştirin,lib/netstandard2.1/MQTTnet.dlldosyasını çıkarın veAssets/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,ApplicationveSystemInfogibi 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.OnDestroyiç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.



