using UnityEngine; using UnityEditor; namespace MLAgents { /// /// PropertyDrawer for BrainParameters. Defines how BrainParameters are displayed in the /// Inspector. /// [CustomPropertyDrawer(typeof(BrainParameters))] public class BrainParametersDrawer : PropertyDrawer { // The height of a line in the Unity Inspectors private const float k_LineHeight = 17f; private const int k_VecObsNumLine = 3; private const string k_CamResPropName = "cameraResolutions"; private const string k_ActionSizePropName = "vectorActionSize"; private const string k_ActionTypePropName = "vectorActionSpaceType"; private const string k_ActionDescriptionPropName = "vectorActionDescriptions"; private const string k_VecObsPropName = "vectorObservationSize"; private const string k_NumVecObsPropName = "numStackedVectorObservations"; private const string k_CamWidthPropName = "width"; private const string k_CamHeightPropName = "height"; private const string k_CamGrayPropName = "blackAndWhite"; private const int k_DefaultCameraWidth = 84; private const int k_DefaultCameraHeight = 84; private const bool k_DefaultCameraGray = false; /// public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { if (property.isExpanded) { return k_LineHeight + GetHeightDrawVectorObservation() + GetHeightDrawVisualObservation(property) + GetHeightDrawVectorAction(property) + GetHeightDrawVectorActionDescriptions(property); } return k_LineHeight; } /// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; position.height = k_LineHeight; property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, label); position.y += k_LineHeight; if (property.isExpanded) { EditorGUI.BeginProperty(position, label, property); EditorGUI.indentLevel++; // Vector Observations DrawVectorObservation(position, property); position.y += GetHeightDrawVectorObservation(); //Visual Observations DrawVisualObservations(position, property); position.y += GetHeightDrawVisualObservation(property); // Vector Action DrawVectorAction(position, property); position.y += GetHeightDrawVectorAction(property); // Vector Action Descriptions DrawVectorActionDescriptions(position, property); position.y += GetHeightDrawVectorActionDescriptions(property); EditorGUI.EndProperty(); } EditorGUI.indentLevel = indent; } /// /// Draws the Vector Observations for the Brain Parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the BrainParameters /// to make the custom GUI for. private static void DrawVectorObservation(Rect position, SerializedProperty property) { EditorGUI.LabelField(position, "Vector Observation"); position.y += k_LineHeight; EditorGUI.indentLevel++; EditorGUI.PropertyField(position, property.FindPropertyRelative(k_VecObsPropName), new GUIContent("Space Size", "Length of state " + "vector for brain (In Continuous state space)." + "Or number of possible values (in Discrete state space).")); position.y += k_LineHeight; EditorGUI.PropertyField(position, property.FindPropertyRelative(k_NumVecObsPropName), new GUIContent("Stacked Vectors", "Number of states that will be stacked before " + "being fed to the neural network.")); position.y += k_LineHeight; EditorGUI.indentLevel--; } /// /// The Height required to draw the Vector Observations paramaters /// /// The height of the drawer of the Vector Observations private static float GetHeightDrawVectorObservation() { return k_VecObsNumLine * k_LineHeight; } /// /// Draws the Visual Observations parameters for the Brain Parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the BrainParameters /// to make the custom GUI for. private static void DrawVisualObservations(Rect position, SerializedProperty property) { EditorGUI.LabelField(position, "Visual Observations"); position.y += k_LineHeight; var quarter = position.width / 4; var resolutions = property.FindPropertyRelative(k_CamResPropName); DrawVisualObsButtons(position, resolutions); position.y += k_LineHeight; // Display the labels for the columns : Index, Width, Height and Gray var indexRect = new Rect(position.x, position.y, quarter, position.height); var widthRect = new Rect(position.x + quarter, position.y, quarter, position.height); var heightRect = new Rect(position.x + 2 * quarter, position.y, quarter, position.height); var bwRect = new Rect(position.x + 3 * quarter, position.y, quarter, position.height); EditorGUI.indentLevel++; if (resolutions.arraySize > 0) { EditorGUI.LabelField(indexRect, "Index"); indexRect.y += k_LineHeight; EditorGUI.LabelField(widthRect, "Width"); widthRect.y += k_LineHeight; EditorGUI.LabelField(heightRect, "Height"); heightRect.y += k_LineHeight; EditorGUI.LabelField(bwRect, "Gray"); bwRect.y += k_LineHeight; } // Iterate over the resolutions for (var i = 0; i < resolutions.arraySize; i++) { EditorGUI.LabelField(indexRect, "Obs " + i); indexRect.y += k_LineHeight; var res = resolutions.GetArrayElementAtIndex(i); var w = res.FindPropertyRelative("width"); w.intValue = EditorGUI.IntField(widthRect, w.intValue); widthRect.y += k_LineHeight; var h = res.FindPropertyRelative("height"); h.intValue = EditorGUI.IntField(heightRect, h.intValue); heightRect.y += k_LineHeight; var bw = res.FindPropertyRelative("blackAndWhite"); bw.boolValue = EditorGUI.Toggle(bwRect, bw.boolValue); bwRect.y += k_LineHeight; } EditorGUI.indentLevel--; } /// /// Draws the buttons to add and remove the visual observations parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the resolution array /// to make the custom GUI for. private static void DrawVisualObsButtons(Rect position, SerializedProperty resolutions) { var widthEighth = position.width / 8; var addButtonRect = new Rect(position.x + widthEighth, position.y, 3 * widthEighth, position.height); var removeButtonRect = new Rect(position.x + 4 * widthEighth, position.y, 3 * widthEighth, position.height); if (resolutions.arraySize == 0) { addButtonRect.width *= 2; } // Display the buttons if (GUI.Button(addButtonRect, "Add New", EditorStyles.miniButton)) { resolutions.arraySize += 1; var newRes = resolutions.GetArrayElementAtIndex(resolutions.arraySize - 1); newRes.FindPropertyRelative(k_CamWidthPropName).intValue = k_DefaultCameraWidth; newRes.FindPropertyRelative(k_CamHeightPropName).intValue = k_DefaultCameraHeight; newRes.FindPropertyRelative(k_CamGrayPropName).boolValue = k_DefaultCameraGray; } if (resolutions.arraySize > 0) { if (GUI.Button(removeButtonRect, "Remove Last", EditorStyles.miniButton)) { resolutions.arraySize -= 1; } } } /// /// The Height required to draw the Visual Observations parameters /// /// The height of the drawer of the Visual Observations private static float GetHeightDrawVisualObservation(SerializedProperty property) { var visObsSize = property.FindPropertyRelative(k_CamResPropName).arraySize + 2; if (property.FindPropertyRelative(k_CamResPropName).arraySize > 0) { visObsSize += 1; } return k_LineHeight * visObsSize; } /// /// Draws the Vector Actions parameters for the Brain Parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the BrainParameters /// to make the custom GUI for. private static void DrawVectorAction(Rect position, SerializedProperty property) { EditorGUI.LabelField(position, "Vector Action"); position.y += k_LineHeight; EditorGUI.indentLevel++; var bpVectorActionType = property.FindPropertyRelative(k_ActionTypePropName); EditorGUI.PropertyField( position, bpVectorActionType, new GUIContent("Space Type", "Corresponds to whether state vector contains a single integer (Discrete) " + "or a series of real-valued floats (Continuous).")); position.y += k_LineHeight; if (bpVectorActionType.enumValueIndex == 1) { DrawContinuousVectorAction(position, property); } else { DrawDiscreteVectorAction(position, property); } } /// /// Draws the Continuous Vector Actions parameters for the Brain Parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the BrainParameters /// to make the custom GUI for. private static void DrawContinuousVectorAction(Rect position, SerializedProperty property) { var vecActionSize = property.FindPropertyRelative(k_ActionSizePropName); vecActionSize.arraySize = 1; var continuousActionSize = vecActionSize.GetArrayElementAtIndex(0); EditorGUI.PropertyField( position, continuousActionSize, new GUIContent("Space Size", "Length of continuous action vector.")); } /// /// Draws the Discrete Vector Actions parameters for the Brain Parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the BrainParameters /// to make the custom GUI for. private static void DrawDiscreteVectorAction(Rect position, SerializedProperty property) { var vecActionSize = property.FindPropertyRelative(k_ActionSizePropName); vecActionSize.arraySize = EditorGUI.IntField( position, "Branches Size", vecActionSize.arraySize); position.y += k_LineHeight; position.x += 20; position.width -= 20; for (var branchIndex = 0; branchIndex < vecActionSize.arraySize; branchIndex++) { var branchActionSize = vecActionSize.GetArrayElementAtIndex(branchIndex); EditorGUI.PropertyField( position, branchActionSize, new GUIContent("Branch " + branchIndex + " Size", "Number of possible actions for the branch number " + branchIndex + ".")); position.y += k_LineHeight; } } /// /// The Height required to draw the Vector Action parameters /// /// The height of the drawer of the Vector Action private static float GetHeightDrawVectorAction(SerializedProperty property) { var actionSize = 2 + property.FindPropertyRelative(k_ActionSizePropName).arraySize; if (property.FindPropertyRelative(k_ActionTypePropName).enumValueIndex == 0) { actionSize += 1; } return actionSize * k_LineHeight; } /// /// Draws the Vector Actions descriptions for the Brain Parameters /// /// Rectangle on the screen to use for the property GUI. /// The SerializedProperty of the BrainParameters /// to make the custom GUI for. private static void DrawVectorActionDescriptions(Rect position, SerializedProperty property) { var bpVectorActionType = property.FindPropertyRelative(k_ActionTypePropName); var vecActionSize = property.FindPropertyRelative(k_ActionSizePropName); var numberOfDescriptions = 0; if (bpVectorActionType.enumValueIndex == 1) { numberOfDescriptions = vecActionSize.GetArrayElementAtIndex(0).intValue; } else { numberOfDescriptions = vecActionSize.arraySize; } EditorGUI.indentLevel++; var vecActionDescriptions = property.FindPropertyRelative(k_ActionDescriptionPropName); vecActionDescriptions.arraySize = numberOfDescriptions; if (bpVectorActionType.enumValueIndex == 1) { //Continuous case : EditorGUI.PropertyField( position, vecActionDescriptions, new GUIContent("Action Descriptions", "A list of strings used to name the available actionsm for the Brain."), true); position.y += k_LineHeight; } else { // Discrete case : EditorGUI.PropertyField( position, vecActionDescriptions, new GUIContent("Branch Descriptions", "A list of strings used to name the available branches for the Brain."), true); position.y += k_LineHeight; } } /// /// The Height required to draw the Action Descriptions /// /// The height of the drawer of the Action Descriptions private static float GetHeightDrawVectorActionDescriptions(SerializedProperty property) { var descriptionSize = 1; if (property.FindPropertyRelative(k_ActionDescriptionPropName).isExpanded) { var descriptions = property.FindPropertyRelative(k_ActionDescriptionPropName); descriptionSize += descriptions.arraySize + 1; } return descriptionSize * k_LineHeight; } } }