Skip to main content

Class RemoteControl

Bridges a TCP connection to Unity's event system, letting a remote study operator trigger predefined scene actions during a live session without physical access to the machine.

[RequireComponent(typeof(TCPServer))]
public class RemoteControl : MonoBehaviour, IDataLoggable

Inheritance​

Implements​

IDataLoggable

Examples​

Inspector setup

  • Create signal assets: right-click in the Project window and choose ScriptableObject → Configuration Assets → Remote Control Button Configuration. Give each a human-readable Name (e.g., "StartScenario") and optional Text and Tags. The ID (GUID) field is auto-populated.
  • On the RemoteControl component, add entries to MessageDictionary: drag each signal asset into the key slot and wire the corresponding UnityEvent (e.g., ScenarioManager.StartScenario()).

Wire framing

Every TCP message in both directions is wrapped in a netstring: :,

The numeric prefix is the exact byte length of the payload (ASCII encoding). TCPServer handles all netstring encoding on outgoing messages and decoding on incoming messages automatically — see its documentation for full details. Messages that do not conform to this format are silently discarded by TCPServer. The framing examples below show what the remote client must send and receive on the wire.

Manifest sent to the remote client on connection (Unity → Client)

Immediately after TCP connection is established, RemoteControl sends a netstring whose payload is a JSON array of all registered signals. Each element is a RemoteControlSignalDTO:

264:[{"ID":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","Name":"StartScenario","Text":"Begin the main driving scenario.","Tags":["scenario","start"]},{"ID":"b2c3d4e5-f6a7-8901-bcde-f12345678901","Name":"AbortScenario","Text":"Abort the current scenario.","Tags":["scenario","abort"]}],

Command sent from the remote client to trigger an action (Client → Unity)

To fire a registered UnityEvent, the client sends the signal's ID string wrapped in a netstring. The payload is just the raw GUID — no JSON wrapper:

36:a1b2c3d4-e5f6-7890-abcd-ef1234567890,

Any message whose payload does not exactly match a registered ID is silently ignored.

Minimal Python client

import socket, json

def to_netstring(s):
payload = s.encode("ascii")
return f"{len(payload)}:".encode("ascii") + payload + b","

def from_netstring(data):
raw = data.decode("ascii")
colon = raw.index(":")
length = int(raw[:colon])
return raw[colon + 1 : colon + 1 + length]

with socket.create_connection(("192.168.1.100", 5000)) as sock:
# Receive the manifest netstring and decode it
signals = json.loads(from_netstring(sock.recv(4096)))
target_id = next(s["ID"] for s in signals if s["Name"] == "StartScenario")

# Send the ID wrapped in a netstring to trigger the corresponding UnityEvent
sock.sendall(to_netstring(target_id))

Remarks​

The simulation machine may be inside a driving simulator cab that is physically inaccessible once a participant is seated. A researcher at a control desk connects over TCP, selects a labelled action (e.g., "start scenario", "abort", "go to next waypoint"), and the corresponding UnityEvent fires on the simulation machine — all without interrupting the session.

The MessageDictionary maps RemoteControlSignal assets (which carry a human-readable name and a stable string ID) to UnityEvents, so the operator tool receives a self-describing JSON manifest on connection and can show a labelled button panel without hard-coding signal IDs.

This component is excluded from WebGL builds (#if !UNITY_WEBGL) because TCP sockets are not available in browser sandboxes; WebGL deployments use URL parameters and the PHP backend for equivalent operator control.

Methods​

Collect()​

Returns a RemoteControlSystemDataLog containing all accumulated action timestamps and clears the cache, or returns null if no actions were triggered.

public DataLog Collect()

Returns​

DataLog — A snapshot of all remote control actions, or null if none occurred.

Remarks​

Returning null for an empty log suppresses an unnecessary allocation; the analysis pipeline treats null as "no data this session" and skips the record. On WebGL builds where TCP is unavailable the method always returns null.