Unity Integration

This guide is a walk-through of adding the Inhumate RTI Unity integration to a project and getting it up and running for co-simulation purposes.

Getting started

Adding the package

TODO Package Manager…

Scaffold a Unity-based RTI simulator

Let’s make a home scene, which is a scene to be loaded when the simulator is not running. Kind of like a lobby.

  1. Create a new scene, name it something like Home and open it.
  2. Create an empty game object.
  3. Add an RTI Runtime Control GUI component. This is really handy for development. Notice the Scenario To Load parameter, by default specifying Demo which refers to the next step.

Now let’s make a scenario scene, which is a scene to be loaded when the simulator is requested to load a specific scenario.

  1. Create another new scene, name it Demo (or whatever you specified in the previous step) and open it.
  2. Put something visible into the scene, like a plane or cube in front of the main camera.
  3. Open File > Build Settings… and drag your two scenes Home and Demo into the Scenes In Build list. Make sure Home has index 0.
  4. Open the Home scene again, and press play.
  5. Press the Load button, and your scenario scene should be loaded.
  6. Press the Start button, notice the clock ticking.
  7. Press the Stop button, and you should get back to your home scene.

Congratulations, you now have a well-behaved RTI-enabled simulator! You can fire it up, load, start, pause, stop, and do it all over again as often as you please. Try controlling it from the RTI control panel or command line.

Spawn co-simulated entities

Entities are objects/actors that exist in the co-simulation “world”, and their life cycle (create -> destroy) and state (more on that later) are synchronized via the RTI.

  1. Create a cube in your Demo scene hierarchy.
  2. Add an RTI Entity component, set its Type property to cube.
  3. Add an RTI Position component.
  4. Make it a prefab by dragging the cube from the Hierarchy view into your Project view.
  5. Delete the cube from the scene.
  6. Create an empty game object.
  7. Add an RTI Spawner component.
  8. For the Spawnable Entities property, add an element with type set to cube and drag your cube prefab into the prefab property.

Now, if you play the scene, you can spawn and destroy a cube using the RTI CLI:

# create a cube
rti entity create --id thecube cube 0 1 2.5
# look around and find it with the editor camera, then move it
rti entity position thecube 2 2 2.5
# then destroy it
rti entity destroy thecube

Spawn a player

Your scenario scene is loaded upon reception of a load scenario message. At that point, no entities should be created. You need to spawn the player when the simulation starts, not when the scene is loaded. You also need to either:

  • create a “remote representation” prefab that the RTI Spawner can use for playback, or
  • use the same prefab but adapt your scripts to differentiate between simulation and playback behavior. Use the RTIEntity.publishing and RTIEntity.receiving properties for that.

Custom (project-specific) messages

Now let’s start digging into adding more functionality than just runtime control, entities and their whereabouts.

Pubsub custom messages (string or JSON)

You can publish and subscribe to simple text or JSON messages.

RTIConnection.Instance.Publish("mychannel", "my message");
RTICOnnection.Instance.PublishJson("mychannel", mySerializableObject);

Pubsub protobuf messages representing entity state

A common pattern for Unity-based simulations is to exchange entity state beyond position, for example a vehicle that needs to publish its vehicle-specific state (e.g. steering angle and brake lights) so that it can be subscribed to and visualized by other simulation clients.

Let’s say you have a component Vehicle that manages your vehicle steering, lights etc.

Create a .proto with the protobuf definition of the state to be transferred:

syntax = "proto3";

message VehicleState {
    string id = 1; // entity id
    float steering_angle = 2; // degrees, positive right
    bool headlight = 3;
    bool brakelight = 4;
    bool blinker_left = 5;
    bool blinker_right = 6;
    bool reverselight = 7;
    // bool engine_running
    // int gear
    // int rpm
    // etc...
}

Use the protobuf compiler to generate a C# representation, typically placed in a Generated/ subfolder of your scripts.

Then make a component deriving from RTIEntityStateBehaviour, something along the lines of:

public class RTIVehicleState : RTIEntityStateBehaviour<VehicleState> {
   public string ChannelName => "vehiclestate";

   public float updateInterval = 1f;

   private Vehicle vehicle;

   protected override void Start() {
      base.Start();
      vehicle = GetComponent<Vehicle>();
   }

   void Update() {
      if (entity.published && publishing && Time.time - lastPublishTime > updateInterval) {
         lastPublishTime = Time.time;
         Publish(new VehicleState {
            Id = entity.id,
            SteeringAngle = vehicle.steeringAngle,
            Headlight = vehicle.headlight,
            // ...
         });
      }
   }

   protected override void OnMessage(VehicleState message) {
      if (receiving && enabled) {
         vehicle.steeringAngle = message.SteeringAngle;
         vehicle.headlight = message.Headlight;
         // ...
      }
   }
}

Scenario loading

Part of the runtime control standard message is a load scenario message, and querying for available scenarios. The default behavior of the Unity package is to load a home level on startup and stop message, and when a load scenario message is received, lookup the name in the scenarios list (which is populated with the scenes in build settings by default, or can be specified with Scenario objects in the editor) in the RTIConnection, and then load a scene with the same name as the scenario. This behavior can be overridden in a script that:

  • fills the RTIConnection.scenarios list with names of scenarios that can be loaded
  • implements delegates for RTIConnection.OnLoadScenario and RTIConnection.OnStop events

See RTITestCustomScenarioLoading.cs for an example.


Copyright © Inhumate AB 2026