Engine plugins
Control Unreal Engine, Unity and Godot projects from Tangle with triggers, properties and game events.
Tangle can drive real-time engines in installations, kiosks, virtual production and interactive shows: fire named triggers, set and watch named properties, and react to game events. Unity and Godot need a small Tangle plugin in the game. Unreal needs no Tangle plugin: it uses Epic's built-in Remote Control API.
| Engine | Tangle module | What goes in the game | Port |
|---|---|---|---|
| Unreal Engine 5 (and 4.26+) | epic.unreal.engine | Epic's Remote Control API plugin | HTTP 30010, WebSocket 30020 |
| Unity 2021.3+ | unity.unity.engine | Tangle Unity package (dev.tangle.engine) | TCP 9420 |
| Godot 4.1+ | misc.godot.engine | Tangle Godot addon | TCP 9420 |
The Unity package and the Godot addon are MIT licensed and live in the Tangle source tree under tangle-connect/plugins/unity and tangle-connect/plugins/godot.
What you get in Tangle
All three modules work the same way from the show's side:
- Triggers (in Unreal: exposed Blueprint functions) become buttons. The buttons are one
sceneswidget whose options are the game's list, so they follow what the game exposes without editing the dashboard. - Properties can be set and watched. Numeric properties get sliders, with the range set in the game. Four slider slots are available; the
sliderssetting picks which properties go on them (default: the first numeric properties). - Game events become device events named
game.<name>, with their data, so automations can react to them. - Layouts:
compact(status, last event, trigger buttons),operator(adds the 4 sliders) andfull(adds the game or preset name, slider property names and a reload button).
Statuses:
| Situation | Status |
|---|---|
| Game or editor not running (port closed) | offline, reconnects on its own (1 s, 2 s... 30 s) |
| Running but not answering (a long load, stuck, paused in the debugger, a firewall dropping packets) | timeout |
| Wrong token (Unity, Godot) or passphrase (Unreal) | error, not retried until you change the device |
| Unreal preset not found (its level is not loaded) | degraded, retried at every poll |
Each module has a setup step (configurator) with the install instructions and a final check that the game answers.
Unity
Install the package
- In Unity: Window > Package Manager > + > Add package from disk..., and pick
tangle-connect/plugins/unity/package.json. Or use Add package from git URL... with the repository URL and?path=/tangle-connect/plugins/unity. - Create an empty GameObject in your first scene, name it
Tangle, and add Tangle > Tangle Server. It keeps itself across scene loads. - Press Play. The console shows
[Tangle] Listening on 0.0.0.0:9420. - In Tangle, add a device with the module Unity (
unity.unity.engine),host= this computer's IP address. Run the device's Install the Tangle Unity package check: it should pass.
Windows asks to allow the game (or the Unity editor) through the firewall the first time. Allow it on private networks, or Tangle cannot reach it.
The package works on any platform with TCP sockets (Windows, macOS, Linux, Android, iOS), but not WebGL: browsers cannot listen on a port.
Triggers
Add Tangle > Tangle Trigger to any GameObject, set Trigger Name (for example intro), and wire On Trigger to what should happen: play a Timeline, start an Animator state, call your method. On Trigger With Argument gets the first argument Tangle sends, as text. Several components with the same name all run.
From code:
GetComponent<Tangle.TangleTrigger>().Triggered += args => Debug.Log($"intro with {args.Count} args");
Tangle.TangleServer.TriggerReceived += (name, args) => Debug.Log($"Tangle fired {name}");
Properties
Add Tangle > Tangle Property and set Property Name (speed), then either:
- set Target to a component and Member to one of its fields or properties (
intensityon a Light,speedon your script). Types: float, double, int, long, bool, string, enum (as text); - or leave Target empty: the component's own Value (float) is the property. Wire On Value Changed to whatever should follow it.
Tick Use Range and set Min and Max to clamp numbers and give Tangle's sliders their range (the default is 0 to 1). Untick Writable for values Tangle should only watch, such as a score. Changes made by the game are pushed to Tangle about 10 times per second.
IL2CPP builds with managed code stripping may remove engine properties nothing else uses. If a property works in the editor but not in a build, add a link.xml that preserves that type.
Game events
Call Tangle.TangleServer.SendEvent("door-opened", 2); from code, or add Tangle > Tangle Event and call its Send() from any UnityEvent (a UI button, an animation event, a Timeline signal). Tangle receives the device event game.door-opened.
Server settings
| Field | Default | Notes |
|---|---|---|
| Port | 9420 | Must match the device's port in Tangle |
| Bind Address | 0.0.0.0 | 127.0.0.1 accepts only Tangle on the same computer |
| Token | empty | When set, the device's token setting must match |
| Keep Across Scenes | on | DontDestroyOnLoad |
| Run In Background | on | The game answers while unfocused |
| Property Checks Per Second | 10 | How often values are compared for changes |
| Verbose | off | Logs connections and requests |
Requests are handled on the main thread, once per frame, so triggers and properties can use any Unity API. A game at 60 fps answers within about 16 ms.
Actions
| Action | Parameters |
|---|---|
unity.trigger.fire | name, optional args (array) |
unity.property.set | name, value (converted to the property's type; the game may clamp it) |
unity.property.setSlot | slot (1 to 4), value |
unity.property.get | name |
unity.game.refresh | Read the trigger and property list again |
unity.game.ping | The setup check |
State unity.property.values holds every property, for example { "speed": 4, "lights": true }.
Godot
Install the addon
- Copy
addons/tangleinto your project'saddons/folder, keeping the pathres://addons/tangle/. - Project > Project Settings > Plugins: enable Tangle. This adds the
Tangleautoload and its settings. - Run the project. The output shows
[Tangle] Listening on *:9420. - In Tangle, add a device with the module Godot (
misc.godot.engine),host= this computer's IP address. Run the device's Install the Tangle Godot addon check: it should pass.
The addon works in desktop and mobile exports, not in the web export.
Triggers
Add a TangleTrigger node anywhere in a scene, set Trigger Name (intro), and connect its triggered(args) signal to what should happen (an AnimationPlayer, a cutscene). Several nodes with the same name all emit. From code, without a node:
func _ready():
Tangle.add_trigger("intro", "Starts the intro")
Tangle.trigger_received.connect(_on_tangle_trigger)
func _on_tangle_trigger(trigger_name: String, args: Array):
if trigger_name == "intro":
$AnimationPlayer.play("intro")
Properties
Add a TangleProperty node and set Property Name (speed), then either:
- set Target to a node and Property to one of its properties (
speedon your script,light_energyon a light,position:xfor a sub-property). Types: float, int, bool, String; other types are shown as text and read-only; - or leave Target empty: the node's own Value (float) is the property. Follow it with the
value_changed_by_tangle(new_value)signal.
Use Range with Min Value and Max Value clamps numbers and sets the slider range (default 0 to 1). Untick Writable for watch-only values.
Game events
Tangle.send_event("door-opened", {"door": 2})
Settings
In Project Settings > General > Tangle > Server (turn on Advanced Settings to see them):
| Setting | Default | Notes |
|---|---|---|
tangle/server/port | 9420 | Must match the device's port in Tangle |
tangle/server/bind_address | * | 127.0.0.1 accepts only Tangle on the same computer |
tangle/server/token | empty | When set, the device's token setting must match |
tangle/server/property_checks_per_second | 10 | How often values are compared for changes |
tangle/server/verbose | false | Prints connections and requests |
Exported builds also take overrides on the command line after --:
game.exe -- --tangle-port=9500 --tangle-token=secret --tangle-bind=127.0.0.1
The addon runs on the main thread once per frame and keeps running while the scene tree is paused.
Actions
The Godot module has the same actions as Unity, under godot.: godot.trigger.fire, godot.property.set, godot.property.setSlot, godot.property.get, godot.game.refresh and godot.game.ping. Its state is godot.property.values.
Unreal Engine
The epic.unreal.engine module uses the Remote Control API that ships with Unreal Engine, in the editor or in a packaged game.
Enable the Remote Control API
Edit > Plugins, search "Remote Control", enable Remote Control API (and Remote Control Web Interface if you want Epic's browser control page). Restart the editor.
Project Settings > Plugins > Remote Control: check that the web server starts automatically, and note the HTTP and WebSocket ports. In UE 5.1 and later, look at the passphrase and allowed-clients settings: either allow the Tangle computer, or set a passphrase and put it in the device's
passphrasesetting.In the editor, the console command
WebControl.StartServerstarts the server if it is not running. Test it from the Tangle computer:curl http://<unreal-pc>:30010/remote/infoPackaged games (nDisplay, kiosks): keep both plugins enabled for the build, start the game with
-RCWebControlEnable -RCWebInterfaceEnable, and allow ports 30010 and 30020 in the Windows firewall.
Expose what the show needs
Recommended: a Remote Control Preset.
- Content Browser > Add > Remote Control > Remote Control Preset, and name it (for example
Show). - Open it and expose:
- functions: Blueprint functions of level actors ("Play Intro", "Reset"). Each becomes a button in Tangle, under its display name;
- properties: numbers, booleans and text (a light's intensity, a speed variable). Numbers become sliders; set their UI min and max so the sliders get the right range.
- In Tangle, set the device's
presetsetting to the preset's name. Keep the preset's level loaded.
With a preset, the module opens the WebSocket and Unreal pushes value changes. If the WebSocket server is off (or websocket is false), values are polled every 5 s.
Without a preset, list objects directly in the device settings:
functions:{ name, objectPath, function, parameters }properties:{ name, objectPath, property, type, min, max }
The object path is what the editor's right-click Copy Reference gives for a level actor, for example /Game/Maps/Main.Main:PersistentLevel.BP_Show_C_1. Both ways can be combined.
Game events from Unreal
The Remote Control WebSocket pushes changes of preset properties. To tell Tangle that something happened:
- Add a String variable
LastEventto a level actor and expose it in the preset (display name, for example, "Last Event"). - In Blueprints, set it to the event name (
intro-finished) when the thing happens. Set it to an empty string first if the same event can happen twice in a row. - Set the device's
eventPropertysetting toLast Event. Each new value becomes the device eventgame.<value>.
Settings and actions
| Setting | Default |
|---|---|
host | required |
port | 30010 (HTTP) |
wsPort | 30020 (WebSocket) |
websocket | true |
passphrase | secret, stored in the OS credential store |
preset, eventProperty, functions, properties, sliders |
| Action | Parameters |
|---|---|
unreal.function.call | name, optional parameters. The return value is in unreal.function.lastResult |
unreal.property.set | name, value (converted to the property's type and clamped to its range) |
unreal.property.setSlot | slot, value |
unreal.property.get | name |
unreal.function.callObject | objectPath, function, parameters (any object, for scripts) |
unreal.property.setObject | objectPath, property, value |
unreal.info.refresh | Read the preset again |
unreal.info.ping | The setup check |
The module checks GET /remote/info every 5 s to tell whether Unreal is there.
Tangle engine protocol
The Unity package and Godot addon speak the Tangle engine protocol, version 1. You only need it to write a plugin for another engine or to debug.
- TCP: the game is the server, Tangle connects to it. Default port 9420. Several clients may connect at once (a main and a backup Tangle).
- Messages are UTF-8 JSON objects, one per line, ended by
\n. - Each client message is a request with a numeric
idand anop; the game answers each with one reply carrying the sameid:{"id": 3, "ok": true, ...}or{"id": 3, "ok": false, "error": "unknown trigger: intro2"}. - The game also sends pushes: messages with an
eventfield and noid.
| op | Fields | Reply | Purpose |
|---|---|---|---|
hello | protocol (1), client, clientVersion, optional token | protocol, engine, engineVersion, app, appVersion | Must be first. A wrong token answers bad token and closes the connection |
list | triggers, properties | What the game exposes now | |
trigger | name, optional args | Fire a trigger | |
set | name, value | value (after clamping and conversion) | Set a property |
get | name | value | Read a property |
ping | time (game clock, seconds) | Keep-alive; Tangle sends it every few seconds |
Properties in a list reply have name, type (number, integer, boolean or string), value, optional min and max, and writable.
Pushes:
{"event": "property", "name": "speed", "value": 2.5}
{"event": "game", "name": "level-complete", "data": {"level": 3}}
{"event": "list"}
list means triggers or properties appeared or disappeared (a scene was loaded); Tangle reads the list again.
Example session:
> {"id":1,"op":"hello","protocol":1,"client":"tangle","clientVersion":"1.0.0"}
< {"id":1,"ok":true,"protocol":1,"engine":"unity","engineVersion":"2022.3.20f1","app":"Lobby Show","appVersion":"1.2"}
> {"id":2,"op":"list"}
< {"id":2,"ok":true,"triggers":[{"name":"intro"}],"properties":[{"name":"speed","type":"number","value":1,"min":0,"max":10,"writable":true}]}
> {"id":3,"op":"trigger","name":"intro"}
< {"id":3,"ok":true}
Warning: The engine protocol has no encryption. The optional token only prevents accidental control from another Tangle or a stray tool. Use it on a show network only, never expose the port to the internet, and set the bind address to
127.0.0.1when Tangle runs on the same computer.