|
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]; |
|
} |
|
} |
|
} |
|
|