docs.unity3d.com
    Warning

    Warning: Unity Simulation is deprecated as of December 2023, and is no longer available.

    Create a custom sensor

    Now that you have learned about the out-of-the-box sensors available in the Unity Simulation Authoring tools, let's look at the basics of creating a new custom sensor. We'll walk you through all the steps using a simple example: a pose publisher.

    1. Write the sensor code

    Each sensor in SimulationSensors is packaged as a Unity Prefab. The logic for each sensor is implemented through a derivation of the Sensor class. The package provides several "node" classes that can be used to perform sensor functionality, such as publishing data or collecting IMU observations.

    Let's start by writing the logic for a sensor that takes a character string as an input parameter, finds the GameObject with the corresponding name, and creates a message containing the pose of the corresponding GameObject.

    Create a new C# script and name the file GameObjectPoseNode.cs. Paste the following code into the script.

    GameObjectPoseNode.cs

    /*
    1. Import namespaces
    Add the using directives for the sensor-related namespaces in order to access the APIs.
    */
    using System;
    using Unity.Simulation.Foundation.GeometryMessages;
    using Unity.Simulation.Foundation;
    using Unity.Simulation.Sensors;
    using UnityEngine;
    
    /*
    2. Declare the sensor node
    The node derives from SynchronizedSensorNode so it will be ticked by the Clock.
    The [Serializable] attribute is added so that the node will show up in the inspector.
    */
    [Serializable]
    public class GameObjectPoseNode : SynchronizedSensorNode
    {
    /*
    3. Add an input port
    Declares an input port for the GameObject name string. It is a simple property
    that is also exposed to the inspector with the [field: SerializeField] attribute..
    */
        //------------ Input ports --------------
        [field: SerializeField]
        public string GameObjectName { get; set; }
    
    /*
    4. Add an output port
    Declares an output port for the TransformMsg that is created after observing the specified
    GameObject. It is a SensorPort of type TransformMsg, and a property.
    */
        //------------ Output ports -------------
        public SensorPort<TransformMsg> GameObjectPoseOutputPort { get; private set; } = new();
    
        GameObject m_GameObject;
    
    /*
    5. Initialize the node
    Subscribes the node to the Scheduler with SynchronizedSensorNode.Initialize(), and 
    Finds the game object with the name specified by the property GameObjectName.
    */
        public override void Initialize(Sensor sensor)
        {
            base.Initialize(sensor);
            m_GameObject = GameObject.Find(GameObjectName);
        }
    
    /*
    6. Implement CollectObservations()
    Function called on every update of the sensor by the Scheduler.
    */
        public override void CollectObservations()
        {
    /*
    7. Create the output message
    Creates a TransformMsg and writes it to the output port m_MessagePort. 
    The To<FLU>() calls are to convert the position vector and rotation quaternion from 
    Unity`s coordinate system (Right-Up-Forward) to ROS's coordinate system (Forward-Left-Up).
    */
            if (m_GameObject)
            {
                GameObjectPoseOutputPort.SetData(new TransformMsg
                {
                    translation = m_GameObject.transform.position.To<FLU>(),
                    rotation = m_GameObject.transform.rotation.To<FLU>()
                }, Clock.Now);
            }
        }
    }
    

    The GameObjectPoseNode can now be implemented in a new Sensor. Create a new C# script and name the file GameObjectPoseSensor.cs. Paste the following code into the script.

    GameObjectPoseSensor.cs

    /*
    1. Import namespaces
    Add the using directives for the sensor-related namespaces in order to access the APIs.
    */
    using System;
    using Unity.Simulation.Sensors;
    using UnityEngine;
    
    /*
    2. Declare the sensor
    The sensor derives from Sensor to provide it some base functionality, such as 
    InitializeSensor, InitializeProperties, and SetPropertyValue.
    */
    public class GameObjectPoseSensor : Sensor
    {
    /*
    3. Add node properties
    The sensor needs the new GameObjectPostNode to collect and output observations. It also
    needs the PublisherNode to publish the observations to a connection such as ROS.
    */
        [SerializeField]
        GameObjectPoseNode m_GameObjectPoseNode = new();
    
        [SerializeField]
        PublisherNode m_PublisherNode = new();
    
    /*
    4. Initialize the sensor
    Initializes the nodes and subscribes the PublisherNode to the output port of the GameObjectPoseNode.
    */
        public override void InitializeSensor()
        {
            m_GameObjectPoseNode.Initialize(this);
            m_PublisherNode.Initialize(this);
    
            m_GameObjectPoseNode.GameObjectPoseOutputPort.OnData += value =>
            {
                if (value != null)
                {
                    m_PublisherNode?.Send(value);
                }
            };
        }
    
    /*
    5. Initialize the properties
    The sensors provided in SimulationSensors all have properties that can be set via the SetPropertyValue()
    function. This allows you to set properties from the URDF Importer. Add the properties to the 
    m_Properties dictionary like so in order to access them from SetPropertyValue().
    */
        protected override void InitializeProperties()
        {
            try
            {
                m_Properties.Add("sensor/topic", str => m_PublisherNode.Topic = str);
                m_Properties.Add("sensor/update_rate", str => m_GameObjectPoseNode.UpdateRate = double.Parse(str));
                m_Properties.Add("sensor/gameobject_name", str => m_GameObjectPoseNode.GameObjectName = str);
            }
            catch (Exception e)
            {
                Debug.Log($"Could not initialize properties for sensor {name}. Exception: {e}");
            }
        }
    }
    

    The property names follow the xpath syntax and determine how they can be specified in the robot's description file. For example the values for the properties above can be specified as:

    <sensor>
      <gameobject_name> OBJECT_NAME </gameobject_name> 
      <topic> TOPIC_NAME </topic>
      <update_rate> UPDATE_RATE </update_rate>
    </sensor>
    

    2. Test the new sensor

    You can now test your new sensor by setting it to publish the pose of a game object.

    1. Add a new GameObject to a scene, and add the GameObjectPoseSensor script to it.
    2. Set its properties in the Inspector window:
      • Game Object Pose Node/Update Rate to a number (e.g. 1.0)
      • Game Object Pose Node/Game Object Name to the name of any object in the scene
      • Publisher Node/Topic to a topic name to publish to (e.g. /unity/objectpose)
    3. In Unity Editor, click on the Play (▶) button to enter the Play mode. The GameObjectPoseSensor will send the pose of the specified game object to the /unity/objectpose topic, which can be sent through a connection.
    Copyright © 2023 Unity Technologies
    • Legal
    • Privacy Policy
    • Cookies
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)
    "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
    Generated by DocFX.