using UnityEngine; using UnityEditor; using System; using System.Linq; using UnityEditor.SceneManagement; namespace MLAgents { /// /// PropertyDrawer for BroadcastHub. Used to display the BroadcastHub in the Inspector. /// [CustomPropertyDrawer(typeof(BroadcastHub))] public class BroadcastHubDrawer : PropertyDrawer { private BroadcastHub _hub; // The height of a line in the Unity Inspectors private const float LineHeight = 17f; // The vertical space left below the BroadcastHub UI. private const float ExtraSpaceBelow = 10f; // The horizontal size of the Control checkbox private const int ControlSize = 80; /// /// Computes the height of the Drawer depending on the property it is showing /// /// The property that is being drawn. /// The label of the property being drawn. /// The vertical space needed to draw the property. public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { LazyInitializeHub(property, label); var numLines = _hub.Count + 2 + (_hub.Count > 0 ? 1 : 0); return (numLines) * LineHeight + ExtraSpaceBelow; } /// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { LazyInitializeHub(property, label); position.height = LineHeight; EditorGUI.LabelField(position, new GUIContent(label.text, "The Broadcast Hub helps you define which Brains you want to expose to " + "the external process")); position.y += LineHeight; EditorGUI.BeginProperty(position, label, property); EditorGUI.indentLevel++; DrawAddRemoveButtons(position); position.y += LineHeight; // This is the labels for each columns var brainWidth = position.width - ControlSize; var brainRect = new Rect( position.x, position.y, brainWidth, position.height); var controlRect = new Rect( position.x + brainWidth, position.y, ControlSize, position.height); if (_hub.Count > 0) { EditorGUI.LabelField(brainRect, "Brains"); brainRect.y += LineHeight; EditorGUI.LabelField(controlRect, "Control"); controlRect.y += LineHeight; controlRect.x += 15; } DrawBrains(brainRect, controlRect); EditorGUI.indentLevel--; EditorGUI.EndProperty(); } /// /// Draws the Add and Remove buttons. /// /// The position at which to draw. private void DrawAddRemoveButtons(Rect position) { // This is the rectangle for the Add button var addButtonRect = position; addButtonRect.x += 20; if (_hub.Count > 0) { addButtonRect.width /= 2; addButtonRect.width -= 24; var buttonContent = new GUIContent( "Add New", "Add a new Brain to the Broadcast Hub"); if (GUI.Button(addButtonRect, buttonContent, EditorStyles.miniButton)) { MarkSceneAsDirty(); AddBrain(); } // This is the rectangle for the Remove button var removeButtonRect = position; removeButtonRect.x = position.width / 2 + 15; removeButtonRect.width = addButtonRect.width - 18; buttonContent = new GUIContent( "Remove Last", "Remove the last Brain from the Broadcast Hub"); if (GUI.Button(removeButtonRect, buttonContent, EditorStyles.miniButton)) { MarkSceneAsDirty(); RemoveLastBrain(); } } else { addButtonRect.width -= 50; var buttonContent = new GUIContent( "Add Brain to Broadcast Hub", "Add a new Brain to the Broadcast Hub"); if (GUI.Button(addButtonRect, buttonContent, EditorStyles.miniButton)) { MarkSceneAsDirty(); AddBrain(); } } } /// /// Draws the Brain and Control checkbox for the brains contained in the BroadCastHub. /// /// The Rect to draw the Brains. /// The Rect to draw the control checkbox. private void DrawBrains(Rect brainRect, Rect controlRect) { for (var index = 0; index < _hub.Count; index++) { var exposedBrains = _hub.broadcastingBrains; var brain = exposedBrains[index]; // This is the rectangle for the brain EditorGUI.BeginChangeCheck(); var newBrain = EditorGUI.ObjectField( brainRect, brain, typeof(Brain), true) as Brain; brainRect.y += LineHeight; if (EditorGUI.EndChangeCheck()) { MarkSceneAsDirty(); _hub.broadcastingBrains.RemoveAt(index); var brainToInsert = exposedBrains.Contains(newBrain) ? null : newBrain; exposedBrains.Insert(index, brainToInsert); break; } // This is the Rectangle for the control checkbox EditorGUI.BeginChangeCheck(); if (brain is LearningBrain) { var isTraining = _hub.IsControlled(brain); isTraining = EditorGUI.Toggle(controlRect, isTraining); _hub.SetControlled(brain, isTraining); } controlRect.y += LineHeight; if (EditorGUI.EndChangeCheck()) { MarkSceneAsDirty(); } } } /// /// Lazy initializes the Drawer with the property to be drawn. /// /// The SerializedProperty of the BroadcastHub /// to make the custom GUI for. /// The label of this property. private void LazyInitializeHub(SerializedProperty property, GUIContent label) { if (_hub != null) { return; } var target = property.serializedObject.targetObject; _hub = fieldInfo.GetValue(target) as BroadcastHub; if (_hub == null) { _hub = new BroadcastHub(); fieldInfo.SetValue(target, _hub); } } /// /// Signals that the property has been modified and requires the scene to be saved for /// the changes to persist. Only works when the Editor is not playing. /// private static void MarkSceneAsDirty() { if (!EditorApplication.isPlaying) { EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); } } /// /// Removes the last Brain from the BroadcastHub /// private void RemoveLastBrain() { if (_hub.Count > 0) { _hub.broadcastingBrains.RemoveAt(_hub.broadcastingBrains.Count - 1); } } /// /// Adds a new Brain to the BroadcastHub. The value of this brain will not be initialized. /// private void AddBrain() { _hub.broadcastingBrains.Add(null); } } }