using UnityEngine; using Unity.MLAgents.Sensors; namespace Unity.MLAgents.Extensions.Sensors { /// /// A SensorComponent that creates a . /// [AddComponentMenu("ML Agents/Grid Sensor", (int)MenuGroup.Sensors)] public class GridSensorComponent : SensorComponent { protected GridSensor m_Sensor; [HideInInspector, SerializeField] internal string m_SensorName = "GridSensor"; // /// Name of the generated object. /// Note that changing this at runtime does not affect how the Agent sorts the sensors. /// public string SensorName { get { return m_SensorName; } set { m_SensorName = value; } } [HideInInspector, SerializeField] internal Vector3 m_CellScale = new Vector3(1f, 0.01f, 1f); /// /// The scale of each grid cell. /// Note that changing this after the sensor is created has no effect. /// public Vector3 CellScale { get { return m_CellScale; } set { m_CellScale = value; } } [HideInInspector, SerializeField] internal Vector3Int m_GridNum = new Vector3Int(16, 1, 16); /// /// The number of grid on each side. /// Note that changing this after the sensor is created has no effect. /// public Vector3Int GridNum { get { return m_GridNum; } set { if (value.y != 1) { m_GridNum = new Vector3Int(value.x, 1, value.z); } else { m_GridNum = value; } } } [HideInInspector, SerializeField] internal bool m_RotateWithAgent = true; /// /// Rotate the grid based on the direction the agent is facing. /// public bool RotateWithAgent { get { return m_RotateWithAgent; } set { m_RotateWithAgent = value; } } [HideInInspector, SerializeField] internal int[] m_ChannelDepth = new int[] { 1 }; /// /// Array holding the depth of each channel. /// Note that changing this after the sensor is created has no effect. /// public int[] ChannelDepth { get { return m_ChannelDepth; } set { m_ChannelDepth = value; } } [HideInInspector, SerializeField] internal string[] m_DetectableObjects; /// /// List of tags that are detected. /// Note that changing this after the sensor is created has no effect. /// public string[] DetectableObjects { get { return m_DetectableObjects; } set { m_DetectableObjects = value; } } [HideInInspector, SerializeField] internal LayerMask m_ObserveMask; /// /// The layer mask. /// public LayerMask ObserveMask { get { return m_ObserveMask; } set { m_ObserveMask = value; } } [HideInInspector, SerializeField] internal GridDepthType m_DepthType = GridDepthType.Channel; /// /// The data layout that the grid should output. /// Note that changing this after the sensor is created has no effect. /// public GridDepthType DepthType { get { return m_DepthType; } set { m_DepthType = value; } } [HideInInspector, SerializeField] internal GameObject m_RootReference; /// /// The reference of the root of the agent. This is used to disambiguate objects with the same tag as the agent. Defaults to current GameObject. /// Note that changing this after the sensor is created has no effect. /// public GameObject RootReference { get { return m_RootReference == null ? gameObject : m_RootReference; } set { m_RootReference = value; } } [HideInInspector, SerializeField] internal int m_MaxColliderBufferSize = 500; /// /// The absolute max size of the Collider buffer used in the non-allocating Physics calls. In other words /// the Collider buffer will never grow beyond this number even if there are more Colliders in the Grid Cell. /// Note that changing this after the sensor is created has no effect. /// public int MaxColliderBufferSize { get { return m_MaxColliderBufferSize; } set { m_MaxColliderBufferSize = value; } } [HideInInspector, SerializeField] internal int m_InitialColliderBufferSize = 4; /// /// The Estimated Max Number of Colliders to expect per cell. This number is used to /// pre-allocate an array of Colliders in order to take advantage of the OverlapBoxNonAlloc /// Physics API. If the number of colliders found is >= InitialColliderBufferSize the array /// will be resized to double its current size. The hard coded absolute size is 500. /// Note that changing this after the sensor is created has no effect. /// public int InitialColliderBufferSize { get { return m_InitialColliderBufferSize; } set { m_InitialColliderBufferSize = value; } } [HideInInspector, SerializeField] internal Color[] m_DebugColors; /// /// Array of Colors used for the grid gizmos. /// public Color[] DebugColors { get { return m_DebugColors; } set { m_DebugColors = value; } } [HideInInspector, SerializeField] internal float m_GizmoYOffset = 0f; /// /// The height of the gizmos grid. /// public float GizmoYOffset { get { return m_GizmoYOffset; } set { m_GizmoYOffset = value; } } [HideInInspector, SerializeField] internal bool m_ShowGizmos = false; /// /// Whether to show gizmos or not. /// public bool ShowGizmos { get { return m_ShowGizmos; } set { m_ShowGizmos = value; } } [HideInInspector, SerializeField] internal SensorCompressionType m_CompressionType = SensorCompressionType.PNG; /// /// The compression type to use for the sensor. /// public SensorCompressionType CompressionType { get { return m_CompressionType; } set { m_CompressionType = value; UpdateSensor(); } } [HideInInspector, SerializeField] [Range(1, 50)] [Tooltip("Number of frames of observations that will be stacked before being fed to the neural network.")] internal int m_ObservationStacks = 1; /// /// Whether to stack previous observations. Using 1 means no previous observations. /// Note that changing this after the sensor is created has no effect. /// public int ObservationStacks { get { return m_ObservationStacks; } set { m_ObservationStacks = value; } } /// public override ISensor[] CreateSensors() { m_Sensor = new GridSensor( m_SensorName, m_CellScale, m_GridNum, m_RotateWithAgent, m_ChannelDepth, m_DetectableObjects, m_ObserveMask, m_DepthType, RootReference, m_CompressionType, m_MaxColliderBufferSize, m_InitialColliderBufferSize ); if (ObservationStacks != 1) { return new ISensor[] { new StackingSensor(m_Sensor, ObservationStacks) }; } return new ISensor[] { m_Sensor }; } /// /// Update fields that are safe to change on the Sensor at runtime. /// internal void UpdateSensor() { if (m_Sensor != null) { m_Sensor.CompressionType = m_CompressionType; } } void OnDrawGizmos() { if (m_ShowGizmos) { if (m_Sensor == null) { return; } var cellColors = m_Sensor.PerceiveGizmoColor(); var cellPositions = m_Sensor.GetGizmoPositions(); var rotation = m_Sensor.GetGridRotation(); var scale = new Vector3(m_CellScale.x, 1, m_CellScale.z); var gizmoYOffset = new Vector3(0, m_GizmoYOffset, 0); var oldGizmoMatrix = Gizmos.matrix; for (var i = 0; i < cellPositions.Length; i++) { var cubeTransform = Matrix4x4.TRS(cellPositions[i] + gizmoYOffset, rotation, scale); Gizmos.matrix = oldGizmoMatrix * cubeTransform; var colorIndex = cellColors[i]; var debugRayColor = Color.white; if (colorIndex > -1 && m_DebugColors.Length > colorIndex) { debugRayColor = m_DebugColors[colorIndex]; } Gizmos.color = new Color(debugRayColor.r, debugRayColor.g, debugRayColor.b, .5f); Gizmos.DrawCube(Vector3.zero, Vector3.one); } Gizmos.matrix = oldGizmoMatrix; } } } }