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.
- Create a new scene, name it something like
Homeand open it. - Create an empty game object.
- Add an RTI Runtime Control GUI component. This is really handy for development. Notice the Scenario To Load parameter, by default specifying
Demowhich 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.
- Create another new scene, name it
Demo(or whatever you specified in the previous step) and open it. - Put something visible into the scene, like a plane or cube in front of the main camera.
- Open File > Build Settings… and drag your two scenes
HomeandDemointo the Scenes In Build list. Make sureHomehas index 0. - Open the
Homescene again, and press play. - Press the Load button, and your scenario scene should be loaded.
- Press the Start button, notice the clock ticking.
- 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.
- Create a cube in your
Demoscene hierarchy. - Add an RTI Entity component, set its Type property to
cube. - Add an RTI Position component.
- Make it a prefab by dragging the cube from the Hierarchy view into your Project view.
- Delete the cube from the scene.
- Create an empty game object.
- Add an RTI Spawner component.
- For the Spawnable Entities property, add an element with type set to
cubeand 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.publishingandRTIEntity.receivingproperties 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.scenarioslist with names of scenarios that can be loaded - implements delegates for
RTIConnection.OnLoadScenarioandRTIConnection.OnStopevents
See RTITestCustomScenarioLoading.cs for an example.