| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEngine; |
|
|
| namespace Unity.MLAgents |
| { |
| |
| |
| |
| |
| public class Monitor : MonoBehaviour |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public enum DisplayType |
| { |
| Independent, |
| Proportion |
| } |
|
|
| |
| |
| |
| public static float verticalOffset = 3f; |
|
|
| static bool s_IsInstantiated; |
| static GameObject s_Canvas; |
| static Dictionary<Transform, Dictionary<string, DisplayValue>> s_DisplayTransformValues; |
|
|
| |
| |
| |
| |
| static Dictionary<Transform, Camera> s_TransformCamera; |
|
|
| static Color[] s_BarColors; |
|
|
| struct DisplayValue |
| { |
| public float time; |
| public string stringValue; |
| public float floatValue; |
| public float[] floatArrayValues; |
|
|
| public enum ValueType |
| { |
| Float, |
| FloatarrayIndependent, |
| FloatarrayProportion, |
| String |
| } |
|
|
| public ValueType valueType; |
| } |
|
|
| static GUIStyle s_KeyStyle; |
| static GUIStyle s_ValueStyle; |
| static GUIStyle s_GreenStyle; |
| static GUIStyle s_RedStyle; |
| static GUIStyle[] s_ColorStyle; |
| static bool s_Initialized; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static void Log( |
| string key, |
| string value, |
| Transform target = null, |
| Camera camera = null) |
| { |
| if (!s_IsInstantiated) |
| { |
| InstantiateCanvas(); |
| s_IsInstantiated = true; |
| } |
| if (s_Canvas == null) |
| { |
| return; |
| } |
|
|
| if (target == null) |
| { |
| target = s_Canvas.transform; |
| } |
|
|
| s_TransformCamera[target] = camera; |
|
|
| if (!s_DisplayTransformValues.Keys.Contains(target)) |
| { |
| s_DisplayTransformValues[target] = |
| new Dictionary<string, DisplayValue>(); |
| } |
|
|
| var displayValues = |
| s_DisplayTransformValues[target]; |
|
|
| if (value == null) |
| { |
| RemoveValue(target, key); |
| return; |
| } |
|
|
| if (!displayValues.ContainsKey(key)) |
| { |
| var dv = new DisplayValue(); |
| dv.time = Time.timeSinceLevelLoad; |
| dv.stringValue = value; |
| dv.valueType = DisplayValue.ValueType.String; |
| displayValues[key] = dv; |
| while (displayValues.Count > 20) |
| { |
| var max = ( |
| displayValues |
| .Aggregate((l, r) => l.Value.time < r.Value.time ? l : r) |
| .Key |
| ); |
| RemoveValue(target, max); |
| } |
| } |
| else |
| { |
| var dv = displayValues[key]; |
| dv.stringValue = value; |
| dv.valueType = DisplayValue.ValueType.String; |
| displayValues[key] = dv; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static void Log( |
| string key, |
| float value, |
| Transform target = null, |
| Camera camera = null) |
| { |
| if (!s_IsInstantiated) |
| { |
| InstantiateCanvas(); |
| s_IsInstantiated = true; |
| } |
|
|
| if (target == null) |
| { |
| target = s_Canvas.transform; |
| } |
|
|
| s_TransformCamera[target] = camera; |
|
|
| if (!s_DisplayTransformValues.Keys.Contains(target)) |
| { |
| s_DisplayTransformValues[target] = new Dictionary<string, DisplayValue>(); |
| } |
|
|
| var displayValues = s_DisplayTransformValues[target]; |
|
|
| if (!displayValues.ContainsKey(key)) |
| { |
| var dv = new DisplayValue(); |
| dv.time = Time.timeSinceLevelLoad; |
| dv.floatValue = value; |
| dv.valueType = DisplayValue.ValueType.Float; |
| displayValues[key] = dv; |
| while (displayValues.Count > 20) |
| { |
| var max = ( |
| displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key); |
| RemoveValue(target, max); |
| } |
| } |
| else |
| { |
| var dv = displayValues[key]; |
| dv.floatValue = value; |
| dv.valueType = DisplayValue.ValueType.Float; |
| displayValues[key] = dv; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static void Log( |
| string key, |
| float[] value, |
| Transform target = null, |
| DisplayType displayType = DisplayType.Independent, |
| Camera camera = null |
| ) |
| { |
| if (!s_IsInstantiated) |
| { |
| InstantiateCanvas(); |
| s_IsInstantiated = true; |
| } |
|
|
| if (target == null) |
| { |
| target = s_Canvas.transform; |
| } |
|
|
| s_TransformCamera[target] = camera; |
|
|
| if (!s_DisplayTransformValues.Keys.Contains(target)) |
| { |
| s_DisplayTransformValues[target] = new Dictionary<string, DisplayValue>(); |
| } |
|
|
| var displayValues = s_DisplayTransformValues[target]; |
|
|
| if (!displayValues.ContainsKey(key)) |
| { |
| var dv = new DisplayValue(); |
| dv.time = Time.timeSinceLevelLoad; |
| dv.floatArrayValues = value; |
| if (displayType == DisplayType.Independent) |
| { |
| dv.valueType = DisplayValue.ValueType.FloatarrayIndependent; |
| } |
| else |
| { |
| dv.valueType = DisplayValue.ValueType.FloatarrayProportion; |
| } |
|
|
| displayValues[key] = dv; |
| while (displayValues.Count > 20) |
| { |
| var max = ( |
| displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key); |
| RemoveValue(target, max); |
| } |
| } |
| else |
| { |
| var dv = displayValues[key]; |
| dv.floatArrayValues = value; |
| if (displayType == DisplayType.Independent) |
| { |
| dv.valueType = DisplayValue.ValueType.FloatarrayIndependent; |
| } |
| else |
| { |
| dv.valueType = DisplayValue.ValueType.FloatarrayProportion; |
| } |
|
|
| displayValues[key] = dv; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public static void RemoveValue(Transform target, string key) |
| { |
| if (target == null) |
| { |
| target = s_Canvas.transform; |
| } |
|
|
| if (s_DisplayTransformValues.Keys.Contains(target)) |
| { |
| if (s_DisplayTransformValues[target].ContainsKey(key)) |
| { |
| s_DisplayTransformValues[target].Remove(key); |
| if (s_DisplayTransformValues[target].Keys.Count == 0) |
| { |
| s_DisplayTransformValues.Remove(target); |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public static void RemoveAllValues(Transform target) |
| { |
| if (target == null) |
| { |
| target = s_Canvas.transform; |
| } |
|
|
| if (s_DisplayTransformValues.Keys.Contains(target)) |
| { |
| s_DisplayTransformValues.Remove(target); |
| } |
| } |
|
|
| |
| |
| |
| |
| public static void SetActive(bool active) |
| { |
| if (!s_IsInstantiated) |
| { |
| InstantiateCanvas(); |
| s_IsInstantiated = true; |
| } |
|
|
| if (s_Canvas != null) |
| { |
| s_Canvas.SetActive(active); |
| } |
| } |
|
|
| |
| static void InstantiateCanvas() |
| { |
| s_Canvas = GameObject.Find("AgentMonitorCanvas"); |
| if (s_Canvas == null) |
| { |
| s_Canvas = new GameObject(); |
| s_Canvas.name = "AgentMonitorCanvas"; |
| s_Canvas.AddComponent<Monitor>(); |
| } |
|
|
| s_DisplayTransformValues = new Dictionary<Transform, |
| Dictionary<string, DisplayValue>>(); |
|
|
| s_TransformCamera = new Dictionary<Transform, Camera>(); |
| } |
|
|
| void OnGUI() |
| { |
| if (!s_Initialized) |
| { |
| Initialize(); |
| s_Initialized = true; |
| } |
|
|
| var toIterate = s_DisplayTransformValues.Keys.ToList(); |
| foreach (var target in toIterate) |
| { |
| if (target == null) |
| { |
| s_DisplayTransformValues.Remove(target); |
| continue; |
| } |
|
|
| |
| var cam = s_TransformCamera[target]; |
| if (cam == null) |
| { |
| cam = Camera.main; |
| } |
|
|
| var widthScaler = (Screen.width / 1000f); |
| var keyPixelWidth = 100 * widthScaler; |
| var keyPixelHeight = 20 * widthScaler; |
| var paddingWidth = 10 * widthScaler; |
|
|
| var scale = 1f; |
| var origin = new Vector3( |
| Screen.width / 2.0f - keyPixelWidth, Screen.height); |
| if (!(target == s_Canvas.transform)) |
| { |
| var camTransform = cam.transform; |
| var position = target.position; |
| var cam2Obj = position - camTransform.position; |
| scale = Mathf.Min( |
| 1, |
| 20f / (Vector3.Dot(cam2Obj, camTransform.forward))); |
| var worldPosition = cam.WorldToScreenPoint( |
| position + new Vector3(0, verticalOffset, 0)); |
| origin = new Vector3( |
| worldPosition.x - keyPixelWidth * scale, Screen.height - worldPosition.y); |
| } |
|
|
| keyPixelWidth *= scale; |
| keyPixelHeight *= scale; |
| paddingWidth *= scale; |
| s_KeyStyle.fontSize = (int)(keyPixelHeight * 0.8f); |
| if (s_KeyStyle.fontSize < 2) |
| { |
| continue; |
| } |
|
|
|
|
| var displayValues = s_DisplayTransformValues[target]; |
|
|
| var index = 0; |
| var orderedKeys = displayValues.Keys.OrderBy(x => -displayValues[x].time); |
| foreach (var key in orderedKeys) |
| { |
| s_KeyStyle.alignment = TextAnchor.MiddleRight; |
| GUI.Label( |
| new Rect( |
| origin.x, origin.y - (index + 1) * keyPixelHeight, |
| keyPixelWidth, keyPixelHeight), |
| key, |
| s_KeyStyle); |
| float[] vals; |
| GUIStyle s; |
| switch (displayValues[key].valueType) |
| { |
| case DisplayValue.ValueType.String: |
| s_ValueStyle.alignment = TextAnchor.MiddleLeft; |
| GUI.Label( |
| new Rect( |
| origin.x + paddingWidth + keyPixelWidth, |
| origin.y - (index + 1) * keyPixelHeight, |
| keyPixelWidth, keyPixelHeight), |
| displayValues[key].stringValue, |
| s_ValueStyle); |
| break; |
| case DisplayValue.ValueType.Float: |
| var sliderValue = displayValues[key].floatValue; |
| sliderValue = Mathf.Min(1f, sliderValue); |
| s = s_GreenStyle; |
| if (sliderValue < 0) |
| { |
| sliderValue = Mathf.Min(1f, -sliderValue); |
| s = s_RedStyle; |
| } |
|
|
| GUI.Box( |
| new Rect( |
| origin.x + paddingWidth + keyPixelWidth, |
| origin.y - (index + 0.9f) * keyPixelHeight, |
| keyPixelWidth * sliderValue, keyPixelHeight * 0.8f), |
| GUIContent.none, |
| s); |
| break; |
|
|
| case DisplayValue.ValueType.FloatarrayIndependent: |
| const float histWidth = 0.15f; |
| vals = displayValues[key].floatArrayValues; |
| for (var i = 0; i < vals.Length; i++) |
| { |
| var value = Mathf.Min(vals[i], 1); |
| s = s_GreenStyle; |
| if (value < 0) |
| { |
| value = Mathf.Min(1f, -value); |
| s = s_RedStyle; |
| } |
|
|
| GUI.Box( |
| new Rect( |
| origin.x + paddingWidth + keyPixelWidth + |
| (keyPixelWidth * histWidth + paddingWidth / 2) * i, |
| origin.y - (index + 0.1f) * keyPixelHeight, |
| keyPixelWidth * histWidth, -keyPixelHeight * value), |
| GUIContent.none, |
| s); |
| } |
|
|
| break; |
|
|
| case DisplayValue.ValueType.FloatarrayProportion: |
| var valsSum = 0f; |
| var valsCum = 0f; |
| vals = displayValues[key].floatArrayValues; |
| foreach (var f in vals) |
| { |
| valsSum += Mathf.Max(f, 0); |
| } |
|
|
| if (valsSum < float.Epsilon) |
| { |
| Debug.LogError( |
| $"The Monitor value for key {key} " + |
| "must be a list or array of " + |
| "positive values and cannot " + |
| "be empty."); |
| } |
| else |
| { |
| for (var i = 0; i < vals.Length; i++) |
| { |
| var value = Mathf.Max(vals[i], 0) / valsSum; |
| GUI.Box( |
| new Rect( |
| origin.x + paddingWidth + |
| keyPixelWidth + keyPixelWidth * valsCum, |
| origin.y - (index + 0.9f) * keyPixelHeight, |
| keyPixelWidth * value, keyPixelHeight * 0.8f), |
| GUIContent.none, |
| s_ColorStyle[i % s_ColorStyle.Length]); |
| valsCum += value; |
| } |
| } |
|
|
| break; |
| } |
|
|
| index++; |
| } |
| } |
| } |
|
|
| |
| void Initialize() |
| { |
| s_KeyStyle = GUI.skin.label; |
| s_ValueStyle = GUI.skin.label; |
| s_ValueStyle.clipping = TextClipping.Overflow; |
| s_ValueStyle.wordWrap = false; |
| s_BarColors = new[] |
| { |
| Color.magenta, |
| Color.blue, |
| Color.cyan, |
| Color.green, |
| Color.yellow, |
| Color.red |
| }; |
| s_ColorStyle = new GUIStyle[s_BarColors.Length]; |
| for (var i = 0; i < s_BarColors.Length; i++) |
| { |
| var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false); |
| texture.SetPixel(0, 0, s_BarColors[i]); |
| texture.Apply(); |
| var staticRectStyle = new GUIStyle(); |
| staticRectStyle.normal.background = texture; |
| s_ColorStyle[i] = staticRectStyle; |
| } |
|
|
| s_GreenStyle = s_ColorStyle[3]; |
| s_RedStyle = s_ColorStyle[5]; |
| } |
| } |
| } |
|
|