Communicate with Vortex using VHL interfaces
This page describes how to use data from a Vortex VHL interface in a custom Unity script. This is a simple way to implement custom behavior or logic in Unity using data in a distributed Vortex simulation.
See Working With VHL interfaces on mechanisms for more information about VHL interfaces.
Overview
When associating a Vortex Scene to a Unity Scene, VortexVHL components are created to allow communication between Unity and Vortex.
For example, if the Vortex Scene contains several VHL extensions:
The Unity Scene will have the same objects, ready to be used.
Using VortexVHL component
Any script can be added to a Game Object with a VortexVHL component and will be able to communicate with Vortex.
To use the data in a script, the prefab must contain a script component: it is in such a script that the VHL interface's data can be accessed. To do this, you first need to get a reference to the VortexVHL component. It is recommended to do this in the Start or Awake method, then re-use it as needed. The reference can also be set directly from the Inspector.
Accessing VHL interface
public class DisplayVHLData : MonoBehaviour
{
private Vortex.VortexVHL vhl = null;
public UnityEngine.UI.Text speedText = null;
public bool isVisible = false;
void Awake()
{
vhl = GetComponent<Vortex.VortexVHL>();
}
}
A series of getters VortexVHL.GetXXXX() and setters VortexVHL.SetXXXX() allows to set and get values for several types.
OnEnable() on a MonoBehaviour is too early to get or set values to VortexVHL. Initialization should be done in the Start() function.
Get a value from a VHL field
The following methods can be used to access the value of the data from a VHL interface's field. All these methods take the same argument: the string corresponding to the field path. When using these methods, if a field is not found, an error will appear in the log and the value returned will be a default value.
A field path name is simply the path to a Field in a VHL extension. A field path is built by concatenating the names of the field in the Field Container hierarchy until you get to the target field.
All field paths begin with either Inputs, Outputs, or Parameters, depending on what type of field they are. Examples of field paths are
Inputs.Vehicle.Speed
Inputs.Active
Outputs.Engine.RPM
Parameters.Vehicle.Engine.MaxRPM
Return Data Type | Get Value from Field Method | Additional Notes |
---|---|---|
bool | VortexVHL.GetBool(path) | |
int | VortexVHL.GetInt(path) | |
double | VortexVHL.GetReal(path) | |
string | VortexVHL.GetString(path) | |
UnityEngine.Vector2 | VortexVHL.GetVector2(path) | Vortex Vector 2 of double values. |
UnityEngine.Vector3 | VortexVHL.GetVector3(path) | Vortex Vector 3 of double values. |
UnityEngine.Vector3 | VortexVHL.GetVector3AsPosition(path) | Vector 3, assumed as position information. It is converted from Vortex to Unity coordinate system. |
UnityEngine.Vector4 | VortexVHL.GetVector4(path) | Vortex Vector 4 of double values. |
UnityEngine.Color | VortexVHL.GetColor(path) | Vortex Color of float values |
UnityEngine.Matrix4x4 | VortexVHL.GetMatrix(path) | Vortex Matrix44 represented by Unity's Matrix4x4 type. No conversion is done whatsoever. |
Vortex.Types.PositionRotation | VortexVHL.GetMatrixAsPositionRotation(path) | Matrix4x4, interpreted as transform information into the PositionRotation class, which contains a position vector, rotation quaternion, and scale vector adapted from Vortex to Unity coordinate system. These can be used directly to assign to Unity Transform component's position, rotation and scale properties. |
string | VortexVHL.GetExtensionName(path) |
Set a value to a VHL field
In this integration, it is also possible to set values of a VHL extension through Unity. However, this feature is only available on standalone simulators (single node) or on the master node of a multi-node simulator. From a script, it is possible to know if the application is on the master node using the IsMaster method. See this page: How to get information about the Vortex simulation.
For more information about nodes, please refer to Vortex Integration SDK.
The following methods can be used to set the value of the data from a VHL interface's field. All these methods take two arguments: the string corresponding to the field path and the value to be set in the VHL. When using these methods, if a field is not found, an error will appear in the log and the value returned will be a default value. These methods will also fail and return an error if the code is not running on the Master node.
Data Type | Set value on VHL Field Method | Additional Notes |
---|---|---|
bool | VortexVHL.SetBool(path, value) | |
int | VortexVHL.SetInt(path, value) | |
double | VortexVHL.SetReal(path, value) | |
string | VortexVHL.SetString(path, value) | |
UnityEngine.Vector2 | VortexVHL.SetVector2(path, value) | Vortex Vector 2 of double values. |
UnityEngine.Vector3 | VortexVHL.SetVector3(path, value) | Vortex Vector 3 of double values. |
UnityEngine.Vector3 | VortexVHL.SetVector3AsPosition(path, value) | Vector 3, assumed as position information. It is converted from Unity to Vortex coordinate system. |
UnityEngine.Vector4 | VortexVHL.SetVector4(path, value) | Vortex Vector 4 of double values. |
UnityEngine.Color | VortexVHL.SetColor(path, value) | Vortex Color of float values |
UnityEngine.Matrix4x4 | VortexVHL.SetMatrix(path, value) | Unity's Matrix44 represented by Vortex Matrix4x4 type. No conversion is done whatsoever. |
UnityEngine.Transform | VortexVHL.SetMatrixAsPositionRotation(path, value) | Unity's Transform converted to Vortex coordinate system as a Vortex Maxtrix4x4. |
VHL Interface Script Example
Here is an example of a script that uses a VHL interface to display some Vortex data in Unity.
Sample Script
using UnityEngine;
using Vortex;
public class DisplayVHLData : MonoBehaviour
{
private Vortex.VortexVHL vhl = null;
public UnityEngine.UI.Text speedText = null;
public UnityEngine.UI.Text distanceText = null;
public UnityEngine.UI.Image engineImage = null;
public bool isVisible = false;
void Awake()
{
vhl = GetComponent<Vortex.VortexVHL>();
}
void FixedUpdate()
{
if (speedText != null)
{
double speed = vhl.GetReal("Inputs.Speed");
// Displays speed with 2 decimals format
speedText.text = $"Current Speed: { speed.ToString("F2") }";
}
if (engineImage != null)
{
bool engineOn = vhl.GetBool("Inputs.Engine Active");
engineImage.enabled = engineOn;
}
if (distanceText != null)
{
double distance = vhl.GetReal("Outputs.Distance");
// Displays distance with 2 decimals format
distanceText.text = $"Distance Moved: { distance.ToString("F2")}";
}
vhl.SetBool("Parameters.Visibility", isVisible);
}
}
Here is how this script is attached to a prefab with the proper components and references to them:
And here is the VHL interface as defined in a mechanism in the Vortex Editor:
Using VortexVHL dynamic fields
For ease of use, the VortexVHL component exposes dynamic fields. The member Inputs
, Outputs
and Parameters
are object extending C# System.Dynamic.DynamicObject
. For example, a VHL input property named "Velocity" can be access by writing vhl.Inputs.Velocity
. There is a few things to keep in mind while using this feature.
The dynamic fields are subject to the same limitations as C# DynamicObject
This means the properties can't be reserved words such asdouble
,return
,bool
, etc. This also means you can't use extension methods directly.// double myDouble = vhl.Inputs.double; // ERROR: double is a C# reserved keyword // Vector3 myPosition = vhl.Outputs.Position.MyExtensionMethod(); // ERROR: CS1973: Vector3<dynamic> has no applicable method named MyExtensionMethod() Vector3 myPosition = vhl.Outputs.Position; myPosition = myPosition.MyExtensionMethod(); // OK
Property's names are case sensitive
Non-alphanumeric characters are replaced with underscores
Vortex allows field's properties to contain character that can't be used to form valid C# identifiers such as space, parenthesis, punctuation to name a few. These properties can be access by replacing the non-alphanumeric values with underscores. For example, the VHL input property "Desired Velocity" can be access withvhl.Inputs.Desired_Velocity
, and the VHL output property "My output (5)" can be access withvhl.Outpus.My_output__5_
. Notice the double underscore betweenoutput
and5
: this is because the space and the opening parenthesis both account for one underscore.Values are returned as raw values
Objects like Vector3 and Matrix4x4 are returned as raw values. A Vector3 representing a position in Vortex frame of reference would need to be converted to Unity referential withConversionUtilities.VortexToUnityVector
. Similarly, a Matrix4x4 transformation matrix in the Vortex frame of reference also needs to be converted withConversionUtilities.VortexToUnityPositionRotation
.
Dynamic Field Usage Example
The following figures shows a VHL interface configuration and a script sample demonstrating how to use the dynamic fields.
Dynamic Fields
using UnityEngine;
using Vortex;
public class DynamicVHLData : MonoBehaviour
{
private Vortex.VortexVHL vhl = null;
public bool myBool = false;
public Color myColor = null;
public string myString = null;
void Awake()
{
vhl = GetComponent<Vortex.VortexVHL>();
}
void FixedUpdate()
{
myBool = vhl.Inputs.isActive; // yields true
myColor = vhl.Outputs.My_color; // yields a new UnityEngine.Color(1,1,1,1)
myString = vhl.Parameters.Texture; // yields "default texture"
}
}
Using an observer on a VHL field
The observer pattern is a very useful way to simplify logic in the code. A regular check for value change in a VHL field, usually implement by polling the value at every Update(), becomes can quickly become a processing bottleneck in a complex system.
When using the Observer on VHL field, the observer callback function will be notified at each value change on the VHL field. This should be used on VHL fields that are not modified very often, e.g. turning on or off of a light. It should not be used for fields that change value continually, e.g. the position of an object.
Adding and removing an observer
First, to create an observer, you need to define a callback function. The delegate definition in this case is
public delegate void Vortex.VortexVHL.ObserverDelegate(string fieldPath);
The callback function will receive the field name that was changed. This way, the same observer callback can be used for several fields if needed.
The API to add or remove an observer is simple:
namespace Vortex
{
public class VortexVHL
{
// Add an observer to a VHL field
// Arguments:
// fieldPath: the path to any field in the VHL
// callback: the delegate to call when the field has changed value
// Return value
// The Observer object that can be used to destroy the observer, null on failure to add the observer
public Observer AddObserver(string fieldPath, ObserverDelegate callback);
// Remove an observer from a VHL field
// Arguments:
// observer: the Observer object to remove (was obtained when adding the observer)
public void RemoveObserver(Observer obsever);
// Object representing an observer
public class Observer
{
// Destroy this observer from the VHL
public void Destroy();
}
}
}
An observer is added with the VortexVHL.AddObserver on a VortexVHL object. The argument fieldPath is a generic way to point to any field in the VHL. It supports any inputs, outputs or parameters, and any level of data Container in the VHL.
The syntax of a Field Path is simple. It is just the name of the fields in the hierarchy of Containers. It always starts with either Inputs, Outputs, or Parameters, followed by the name of the fields in the hierarchy
// add an observer to the Input "LightOn" in the vhl
observer = vhl.AddObserver("Inputs.LightOn", onLightSwitchChanged);
// add an observer to the field "LightOn" that is in the Container "LightControl", that is in the Container "ControlBoard", that is in the Ouputs of the vhl
obsever = vhl.AddObserver("Outputs.ControlBoard.LightControl.LightOn", onLightSwitchChanged);
The AddObserver method returns an Observer object that needs to be kept. It is used to remove the Observer.
There are two ways of destroying an observer, both need the object returned from AddObserver, both are equivalent.
Observer myObserver = vhl.AddObserver("Inputs.LightOn", onLightSwitchChanged);
// if you have access to the VHL, you can do
vhl.RemoveObserver(myObserver);
// or you can just do it on the observer itself
myObserver.Destroy();
If an observer is not destroyed when the callback is no longer available, the application is likely to crash.
The best method to avoid any issues is to use AddObserver in OnEnable() of a MonoBehavior, and DeleteObserver in OnInactive()