Industrial connectors for Unreal Engine 5.8
Plant data,
as Blueprint tags.
Five connectors that read live values from the equipment you already run — PLCs, brokers and OPC UA servers — and publish them into Unreal as named tags you can bind to. No middleware, no bridge service, no polling loop to write.
| Tag | Value | Quality | Updated |
|---|
The connectors
Pick the protocol your plant speaks
Each is a separate product and installs on its own. Nothing else to buy, nothing else to enable.
Modbus TCP
FC 1 · 2 · 3 · 4 · 5 · 6
Reads coils, discrete inputs and registers straight off a PLC, and writes single coils and registers back. Framing is hand-rolled over Unreal's sockets.
DetailsREST
JSON over HTTP
Polls an HTTP endpoint and flattens the JSON into tags — nested objects become dotted names, arrays become indexed ones.
DetailsMQTT
Topics · Sparkplug B
Subscribes to broker topics and derives tag names from the topic hierarchy. Decodes Sparkplug B payloads, resolving metric aliases against birth messages.
DetailsAMQP
0-9-1 · RabbitMQ
Consumes a queue and publishes each message as tags, taking the tag name from the routing key.
DetailsOPC UA
Subscriptions · Sign & Encrypt
Subscribes to server nodes with polling as a fallback, and supports encrypted channels with client certificate identity.
DetailsIn every connector
The parts nobody enjoys writing twice
Tag registry
A Game Instance Subsystem holding every value the connector has seen. Bind On Tag Updated, or read a tag by name. Connectors are kept alive by the registry, so one created in a Blueprint survives without being stored in a variable.
Reconnect that behaves
Fixed or exponential backoff with a delay ceiling, an optional attempt limit, and jitter — so a hundred clients pointed at one PLC don't retry in lockstep as it reboots. An outage reports twice: when it starts and when it gives up.
Credentials by reference
A config stores where a secret lives — an environment variable name or a file path — never the secret. Configs get exported, diffed and committed; a password inside one leaks the first time that happens.
Deduplication
Suppress broadcasts when a value hasn't changed, with a numeric deadband to absorb sensor noise. The deadband compares against the last broadcast value, so a reading creeping just under the threshold still reports eventually.
Writing, not just reading
Every connector sends as well as receives — Modbus writes registers and coils, OPC UA writes nodes, MQTT and AMQP publish, REST issues a request on demand. Writes are queued from the game thread and executed on the worker, so a Blueprint never blocks on a network round trip, and the call reports whether it was accepted rather than pretending it was delivered.
Runtime log level
Set verbosity from Blueprint using the familiar debug/info/warn/error/critical names. Works in packaged builds, where there is no console to type into.
Readable source
Full C++ ships with every connector, commented to explain why rather than what. Automated tests come with it — run them yourself from the Session Frontend.
Before you buy
What these connectors don't do
Stated here rather than discovered after purchase.
- Win64 only. The vendored libraries are built for that platform.
- MQTT and AMQP have no TLS. Credentials travel in the clear. Fine against a broker on the same machine, unsuitable for a plant network. OPC UA is the exception — it supports encrypted channels.
- Values are stored as double. Integers above 253 lose precision, which affects OPC UA
Int64and Sparkpluglongmetrics. - Tag quality is only as good as the source. It defaults to
Unknown, because Modbus, REST, MQTT and AMQP carry no quality field and claiming otherwise would invent data. OPC UA reports it properly, mapped from the server's status code. Nothing setsStaleyet, and a tag that stops arriving altogether keeps its last value. - OPC UA converts scalars only. Arrays and structs are skipped with a warning.
- AMQP is 0-9-1, not 1.0. It speaks to RabbitMQ, not to Azure Service Bus or Solace.
- Sparkplug B is decode-only. Reading works; issuing
NCMDorDCMDcommands does not, because that needs a protobuf encoder and this release only has a decoder. Raw MQTT publishing is available on the same connector — it is Sparkplug's command semantics specifically that are absent.
Connector
Modbus TCP
Polls coils, discrete inputs, holding registers and input registers from a Modbus TCP device using a register map you define, and publishes each entry as a tag. Writes single coils and single registers back.
| Function codes | Read 0x01 0x02 0x03 0x04, write 0x05 0x06 |
|---|---|
| Framing | MBAP over Unreal's own sockets |
| Data types | UInt16, Int16, UInt32, Int32, Float32, with configurable word order |
| Read strategy | Adjacent registers coalesced into as few requests as the device allows |
| Writes | Queued from the game thread, executed on the worker, echo verified |
| Authentication | None — the protocol carries no identity |
| Third-party code | None redistributed |
// One entry in the register map Address 2 // zero-based protocol address Type Float32 Word Order HighFirst Tag Name oven.temp_c Function ReadHoldingRegisters
40001 is address 0, and the conversion is subtract 40001 rather than 40000. Vendors are inconsistent about this, and some document the raw address already. If every tag reads like its neighbour, the base is the first thing to check.Testing without hardware
Tools/modbus_sim.py ships with the plugin: a Python server that serves changing values and prints what it served — decoded back out of the response bytes rather than recomputed, so the console and Unreal genuinely agree rather than merely appearing to. Standard library only, so there is nothing to install.
It answers reads and writes both. A written value is remembered from then on, so the register stops moving and reads back what you wrote: a simulator that accepted a write and carried on generating would be indistinguishable from one that discarded it.
Public simulators such as diagslave also work, but their register tables start at zero and stay there — which looks exactly like a broken decode.
python modbus_sim.py # listening on 127.0.0.1:502 # holding / input registers 0 UInt16 counter, +1/s 1 Int16 sine, -1000..1000 2-3 Float32 20.0..25.0, high word first 4-5 UInt32 +10/s, high word first # coils / discrete inputs 0 toggles once per second 1 always on 2 always off
Screenshots
Connector
REST
Polls a JSON-over-HTTP endpoint on an interval and flattens the response into tags. For the MES, historian or line-controller that already exposes an API.
| Transport | Unreal's own HTTP stack |
|---|---|
| Flattening | Nested objects to dotted names, arrays to bracketed indices |
| Root path | Scope the walk to a nested object with a dotted path |
| Nulls | Published as an empty value, never dropped — so a source that stops reporting is distinguishable from one that hasn't changed |
| Sending | Send Request — one POST or PUT on demand, outside the polling loop, reusing the configured endpoint and credentials |
| Authentication | Basic, Bearer token, or API key in a named header |
| Third-party code | None redistributed |
// Response { "line1": { "sensors": [ { "temp": 20.4 } ], "vibration": null } } // Tags produced line1.sensors[0].temp 20.4 line1.vibration (empty, quality Unknown)
Unknown rather than Bad: plenty of systems use null to mean something specific — a drive at standstill, a probe not fitted — and calling that a fault would be our inference, not the source's statement.Testing without a server of your own
Tools/rest_sim.py ships with the plugin: a JSON endpoint serving changing plant telemetry. Standard library only, so there is nothing to install on a machine you would rather not install anything on.
It is built around the awkward cases rather than the tidy ones — nested objects and an array for dotted and indexed names, every value type in one payload, a nested path worth aiming Response Root Path at, and optional bearer-token auth that refuses anything else with a 401 naming the header it wanted.
One sensor reports null for eight seconds each minute while the rest of the document keeps going, so you can watch a single tag go empty beside its neighbours. That case is invisible in a connector that drops nulls, which is exactly why it is worth being able to produce on demand.
Any other JSON endpoint works too. Prefer this one for anything you intend to keep: a public API rate-limits, changes its schema without telling you, and eventually stops existing.
# no auth python rest_sim.py # require Authorization: Bearer s3cret python rest_sim.py --token s3cret # listening on http://127.0.0.1:18080 # endpoints / the whole document /api/telemetry the same, for a config that wants a path /health {"ok": true}, no auth # a scoped walk: set Response Root # Path to plant.line1 and the prefix # disappears from every tag name
Screenshots
Connector
MQTT
Subscribes to broker topics and publishes arriving messages as tags, deriving tag names from the topic hierarchy. Decodes Sparkplug B payloads where your edge nodes publish them.
| Client | Eclipse Paho MQTT C, under the Eclipse Distribution License 1.0 |
|---|---|
| Payload formats | Raw, JSON, and Sparkplug B |
| Sparkplug B | Metric aliases resolved against birth messages; sequence gaps reported; alias table dropped when a node reports death |
| Tag naming | Group, node, device and metric — so two devices publishing the same metric name don't collide |
| Publishing | Publish and Publish Bytes — topic, payload, QoS 0–2, optional retain |
| Authentication | Username and password |
| Encryption | None in this release |
// Subscribe to the whole namespace, not just data Topic Filter spBv1.0/# Payload Format SparkplugB // DDATA carries an alias, never a name. // Names arrive only in birth messages, so // subscribing to data alone leaves every // value permanently unidentifiable.
Screenshots
Connector
AMQP
Consumes a queue on a RabbitMQ broker and publishes each message as tags, taking the tag name from the message's routing key.
| Protocol version | AMQP 0-9-1 |
|---|---|
| Library | rabbitmq-c, under the MIT licence |
| Model | Consumes an existing queue; declares no queues or exchanges of its own |
| Payload formats | Raw and JSON |
| Publishing | Publish — exchange, routing key, body, optional persistence |
| Acknowledgement | Automatic, or manual with a prefetch limit when losing a message matters |
| Authentication | Username and password |
| Encryption | None in this release — TLS sources are excluded from the build entirely |
Screenshots
Connector
OPC UA
Connects to OPC UA servers and publishes node values as tags. Subscribes by default, with polling available for servers that limit subscription counts. Implements an OPC UA client using the open62541 stack.
| Stack | open62541 v1.5.6, under MPL-2.0 |
|---|---|
| Data delivery | Subscriptions with configurable publishing interval, sampling interval and queue size; polling as a fallback |
| Security modes | None, Sign, and Sign & Encrypt |
| Security policies | Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss |
| Writing | Write Bool, Write Number, Write String — the numeric type is stated by the caller, because the server checks it |
| Quality | Mapped from the server's status code by severity, including the informational Good_ sub-codes |
| Identity | Anonymous, or username and password; client certificate with trust list and optional revocation lists |
| Certificates | Generate Self Signed Certificate in Blueprint — OPC UA authenticates both ends, so a client needs one of its own |
| Crypto library | The OpenSSL that ships with Unreal Engine; nothing new is redistributed |
| Value conversion | Scalars only; arrays and structs are skipped with a warning |
// Prosys Simulation Server defaults Endpoint opc.tcp://HOST:53530/OPCUA/SimulationServer Security SignAndEncrypt Policy Basic256Sha256 Node Id ns=3;i=1001
Basic128Rsa15 and Basic256 are absent by choice — the stack is built without them. Password-protected private keys are also unsupported, since the password would have to live somewhere the config could reach it, which defeats protecting the key.Screenshots
Licensing
What you're redistributing
Every connector ships full C++ source and a licence inventory naming each third-party component, its version, its upstream commit and its terms. Nothing here requires your project to be licensed under anything.
| Connector | Third-party component | Licence |
|---|---|---|
| Modbus TCP | None | — |
| REST | None | — |
| AMQP | rabbitmq-c | MIT |
| MQTT | Eclipse Paho MQTT C | Eclipse Distribution License 1.0 (BSD-3-Clause) |
| OPC UA | open62541 | MPL-2.0 — file-level, does not extend to your code |
No GPL or LGPL anywhere
Audited specifically, because copyleft of that strength would prevent redistribution inside a proprietary project at all. Two licence documents in the tree contain the words "GNU General Public License" — both inside a definition of the term "Secondary License", not as a dependency.
MPL-2.0 is file-level
Its obligations attach to the open62541 files themselves and do not extend to code that merely links against them — the decisive difference from GPL. The sources ship unmodified, with SHA-256 hashes published so you can verify that.
Built with AI assistance
A material portion of the C++ was written with a large language model, and the Fab listings are tagged accordingly. Every connector has been verified against real servers and brokers, not only against its own tests.