Unreal Engine Integration

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

If you would like to browse through a finished example, take a look at the client source code, which contains a “sandbox project” along with the plugin source. Otherwise, if this is your first encounter with the RTI, I would recommend starting a new project based on the C++ vehicle template and follow the steps below.

Getting started

Adding the plugin

  1. Make a folder called Plugins in the project root directory
  2. Copy the InhumateRTI folder to the Plugins folder
  3. Right-click your .uproject file and select Generate Visual Studio project files
  4. Open the solution in Visual Studio
  5. Build and run the editor
  6. In the Edit > Project Settings… menu, verify that you have a section called Plugins - Inhumate RTI

Congratulations, your project now has RTI superpowers!

If you start your game with the RTI broker running locally, it should connect. You should see a log message in the Unreal Output Log reading something like:

LogRTI: RTI connect MyFPS to ws://localhost:8000/
LogRTI: RTI connected

Taking control

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

  1. Make a new level, call it something like Home, and open it
  2. Add the blueprint class RTI_Print (search or find it in the content browser at InhumateRTI Content)
  3. Press play. You should see a printout that says “Connected”.
  4. Open the level blueprint (Blueprints > Open Level Blueprint)
  5. From the BeginPlay event, drag out and add a Create Widget node. Select RTI_UI in the Class pin. For the Scenario To Load pin, specify something like Scenario1. Drag out and add a Add to Viewport node, and connect Return Value to Target.

bp_level_rti_ui

Now press play and you should have a simple runtime control UI. That helps a lot during development so you can load, start, stop etc from within the editor/game.

Loading your first scenario

Scenarios are just levels. By default, they are loaded with OpenLevel.

For your scenario level, you can use an existing level in your project, or create a new one with some basic content (so that there is some difference from the home level).

  1. Open the Edit > Project Settings…, select the Game - Inhumate RTI category
  2. In the Home Level property, enter the name of your home level (Home if you did what you’re told)
  3. The Scenarios property is a map of scenario names to level names. Add an element with key Scenario1 and value set to your scenario level name.
  4. In the Project - Maps & Modes category, set Editor Startup Map and Game Default Map to your home level.
  5. Press play, and then hit that Load button. Your scenario level should now load. But it starts paused. That’s intentional, because there’s a Start button that needs purpose.
  6. Do step 5 from above; add the RTI UI to your scenario level blueprint as well.

Well done, 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.

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 new blueprint class, with Actor as parent class, call it BP_Cube, edit it
  2. Add a Static Mesh component, set the Static Mesh property to the 1M_Cube mesh
  3. Add an RTI Entity component, set its Type property to cube
  4. Add an RTI Position component
  5. Close or tab the editor, go back to your scenario level
  6. Add an RTI Spawner to your level (search or find it in the content browser at InhumateRTI C++ Classes / InhumateRTI / Public / RTISpawnerActor
  7. On that actor, there is a property called Entity Type Actor Mapping
  8. Add an element with key cube and set its value to the BP_Cube actor you just created

Now, if you play the level, 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 the pawn

Your scenario level is loaded upon reception of a load scenario message. At that point, no entities should be created. Therefore, you need to spawn the pawn™️. You are also going to need a “remote representation” actor for playback, since Unreal usually doesn’t play nice when you move pawns around without player input.

This is going to be a bit more brief than the instructions above, because you now have RTI ninja skills already.

  1. Create a representation actor, with your mesh and RTI Entity and RTI Position components.
  2. Add the representation actor to your spawner Entity Type Actor Mapping
  3. Add RTI Entity and RTI Position to your pawn blueprint.
  4. Convert the RTI Spawner from the previous step into a blueprint, let’s call it BP_ScenarioSpawner
  5. Add nodes kinda like the blueprint screenshot below

bp_scenariospawner

To summarize the blueprint above, if we’re in state simulating (after we pressed start) or unknown (started the scenario level without load, start), we want to spawn the pawn. For playback, we want to spawn the representation actor.

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)

To make your actors publish and subscribe string or JSON messages, use the RTI Listener component.

It provides functionality for

  • Publishing a string to a specific channel
  • Subscribing and unsubscribing to/from a channel
  • Events on message received, RTI connected, disconnected, errors

The RTI Listener component can be used in either blueprints or C++.

Pubsub custom protobuf messages

To publish and subscribe to protobuf messages, you need to

  1. Make a protobuf definition .proto file
  2. Compile the protobuf; generate a C++ representation, using protoc
  3. Publish the message using RTI()->Publish("MyChannel", myMessage)
  4. Subscribe and receive messages using RTI()->Subscribe<MyMessage>("channel", myCallback)

For a complete example, see the RTITestComponent header and source.

Subscribing is the most tricky part. You can find some examples in the implementation of RTISubsystem or the generic C++ usage example.

Also:

  • Add "InhumateRTI" to PublicDependencyModuleNames in your project’s .Build.cs file.
  • Make sure to use the included protoc binary. The version of the compiler must match that of the library using the protobufs. On Windows it’s located in Plugins\InhumateRTI\ThirdParty\Win64\protoc.exe within your Unreal project folder.

Custom entity state messages

A common use case in co-simulation is to add entity-specific states (in addition to the already included-in-the-box entity metadata and position) that you want to sync across the network, e.g. for a vehicle you might want to publish (and subscribe) steer angle, lights etc.

There’s a pattern for that:

  1. Make a protobuf definition. The first field of the protobuf must be a string, the entity ID.
  2. Compile the protobuf
  3. Make an actor component that derives from URTIEntityBaseComponent.
  4. Implement your state variables, e.g. as properties, and if IsPublishing() is true, publish them periodically or whenever they change
  5. Override the OnMessage(), and if IsReceiving() is true, parse the message and update your state variables
  6. Profit

Scenario loading

Part of the runtime control standard message is a load scenario message, and querying for available scenarios. The default behavior of the Unreal plugin is to load a home level by default, and when a load scenario message is received, lookup the name in the Scenarios map in the RTI subsystem, and then load a level corresponding to this name. This behavior can be overridden, e.g. in a level blueprint or some singleton actor:

  • set the flag Use Custom Scenario Loading to true
  • fill the Scenarios map with something else
  • bind the Load Scenario Event to some new functionality

bp_customscenarioloading


Copyright © Inhumate AB 2024