You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Monitor.cs 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. namespace MLAgents
  5. {
  6. /// <summary>
  7. /// Monitor is used to display information about the Agent within the Unity
  8. /// scene. Use the log function to add information to your monitor.
  9. /// </summary>
  10. public class Monitor : MonoBehaviour
  11. {
  12. /// <summary>
  13. /// The type of monitor the information must be displayed in.
  14. /// <slider> corresponds to a single rectangle whose width is given
  15. /// by a float between -1 and 1. (green is positive, red is negative)
  16. /// <hist> corresponds to n vertical sliders.
  17. /// <text> is a text field.
  18. /// <bar> is a rectangle of fixed length to represent the proportions
  19. /// of a list of floats.
  20. /// </summary>
  21. public enum DisplayType
  22. {
  23. INDEPENDENT,
  24. PROPORTION
  25. }
  26. /// <summary>
  27. /// Represents how high above the target the monitors will be.
  28. /// </summary>
  29. [HideInInspector] static public float verticalOffset = 3f;
  30. static bool isInstantiated;
  31. static GameObject canvas;
  32. static Dictionary<Transform, Dictionary<string, DisplayValue>> displayTransformValues;
  33. /// <summary>
  34. /// Camera used to calculate GUI screen position relative to the target
  35. /// transform.
  36. /// </summary>
  37. static Dictionary<Transform, Camera> transformCamera;
  38. static Color[] barColors;
  39. struct DisplayValue
  40. {
  41. public float time;
  42. public string stringValue;
  43. public float floatValue;
  44. public float[] floatArrayValues;
  45. public enum ValueType
  46. {
  47. FLOAT,
  48. FLOATARRAY_INDEPENDENT,
  49. FLOATARRAY_PROPORTION,
  50. STRING
  51. }
  52. public ValueType valueType;
  53. }
  54. static GUIStyle keyStyle;
  55. static GUIStyle valueStyle;
  56. static GUIStyle greenStyle;
  57. static GUIStyle redStyle;
  58. static GUIStyle[] colorStyle;
  59. static bool initialized;
  60. /// <summary>
  61. /// Use the Monitor.Log static function to attach information to a transform.
  62. /// </summary>
  63. /// <returns>The log.</returns>
  64. /// <param name="key">The name of the information you wish to Log.</param>
  65. /// <param name="value">The string value you want to display.</param>
  66. /// <param name="target">The transform you want to attach the information to.
  67. /// </param>
  68. /// <param name="camera">Camera used to calculate GUI position relative to
  69. /// the target. If null, `Camera.main` will be used.</param>
  70. public static void Log(
  71. string key,
  72. string value,
  73. Transform target = null,
  74. Camera camera = null)
  75. {
  76. if (!isInstantiated)
  77. {
  78. InstantiateCanvas();
  79. isInstantiated = true;
  80. }
  81. if (target == null)
  82. {
  83. target = canvas.transform;
  84. }
  85. transformCamera[target] = camera;
  86. if (!displayTransformValues.Keys.Contains(target))
  87. {
  88. displayTransformValues[target] =
  89. new Dictionary<string, DisplayValue>();
  90. }
  91. Dictionary<string, DisplayValue> displayValues =
  92. displayTransformValues[target];
  93. if (value == null)
  94. {
  95. RemoveValue(target, key);
  96. return;
  97. }
  98. if (!displayValues.ContainsKey(key))
  99. {
  100. var dv = new DisplayValue();
  101. dv.time = Time.timeSinceLevelLoad;
  102. dv.stringValue = value;
  103. dv.valueType = DisplayValue.ValueType.STRING;
  104. displayValues[key] = dv;
  105. while (displayValues.Count > 20)
  106. {
  107. string max = (
  108. displayValues
  109. .Aggregate((l, r) => l.Value.time < r.Value.time ? l : r)
  110. .Key
  111. );
  112. RemoveValue(target, max);
  113. }
  114. }
  115. else
  116. {
  117. DisplayValue dv = displayValues[key];
  118. dv.stringValue = value;
  119. dv.valueType = DisplayValue.ValueType.STRING;
  120. displayValues[key] = dv;
  121. }
  122. }
  123. /// <summary>
  124. /// Use the Monitor.Log static function to attach information to a transform.
  125. /// </summary>
  126. /// <returns>The log.</returns>
  127. /// <param name="key">The name of the information you wish to Log.</param>
  128. /// <param name="value">The float value you want to display.</param>
  129. /// <param name="target">The transform you want to attach the information to.
  130. /// </param>
  131. /// <param name="camera">Camera used to calculate GUI position relative to
  132. /// the target. If null, `Camera.main` will be used.</param>
  133. public static void Log(
  134. string key,
  135. float value,
  136. Transform target = null,
  137. Camera camera = null)
  138. {
  139. if (!isInstantiated)
  140. {
  141. InstantiateCanvas();
  142. isInstantiated = true;
  143. }
  144. if (target == null)
  145. {
  146. target = canvas.transform;
  147. }
  148. transformCamera[target] = camera;
  149. if (!displayTransformValues.Keys.Contains(target))
  150. {
  151. displayTransformValues[target] = new Dictionary<string, DisplayValue>();
  152. }
  153. Dictionary<string, DisplayValue> displayValues = displayTransformValues[target];
  154. if (!displayValues.ContainsKey(key))
  155. {
  156. var dv = new DisplayValue();
  157. dv.time = Time.timeSinceLevelLoad;
  158. dv.floatValue = value;
  159. dv.valueType = DisplayValue.ValueType.FLOAT;
  160. displayValues[key] = dv;
  161. while (displayValues.Count > 20)
  162. {
  163. string max = (
  164. displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key);
  165. RemoveValue(target, max);
  166. }
  167. }
  168. else
  169. {
  170. DisplayValue dv = displayValues[key];
  171. dv.floatValue = value;
  172. dv.valueType = DisplayValue.ValueType.FLOAT;
  173. displayValues[key] = dv;
  174. }
  175. }
  176. /// <summary>
  177. /// Use the Monitor.Log static function to attach information to a transform.
  178. /// </summary>
  179. /// <returns>The log.</returns>
  180. /// <param name="key">The name of the information you wish to Log.</param>
  181. /// <param name="value">The array of float you want to display.</param>
  182. /// <param name="displayType">The type of display.</param>
  183. /// <param name="target">The transform you want to attach the information to.
  184. /// </param>
  185. /// <param name="camera">Camera used to calculate GUI position relative to
  186. /// the target. If null, `Camera.main` will be used.</param>
  187. public static void Log(
  188. string key,
  189. float[] value,
  190. Transform target = null,
  191. DisplayType displayType = DisplayType.INDEPENDENT,
  192. Camera camera = null
  193. )
  194. {
  195. if (!isInstantiated)
  196. {
  197. InstantiateCanvas();
  198. isInstantiated = true;
  199. }
  200. if (target == null)
  201. {
  202. target = canvas.transform;
  203. }
  204. transformCamera[target] = camera;
  205. if (!displayTransformValues.Keys.Contains(target))
  206. {
  207. displayTransformValues[target] = new Dictionary<string, DisplayValue>();
  208. }
  209. Dictionary<string, DisplayValue> displayValues = displayTransformValues[target];
  210. if (!displayValues.ContainsKey(key))
  211. {
  212. var dv = new DisplayValue();
  213. dv.time = Time.timeSinceLevelLoad;
  214. dv.floatArrayValues = value;
  215. if (displayType == DisplayType.INDEPENDENT)
  216. {
  217. dv.valueType = DisplayValue.ValueType.FLOATARRAY_INDEPENDENT;
  218. }
  219. else
  220. {
  221. dv.valueType = DisplayValue.ValueType.FLOATARRAY_PROPORTION;
  222. }
  223. displayValues[key] = dv;
  224. while (displayValues.Count > 20)
  225. {
  226. string max = (
  227. displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key);
  228. RemoveValue(target, max);
  229. }
  230. }
  231. else
  232. {
  233. DisplayValue dv = displayValues[key];
  234. dv.floatArrayValues = value;
  235. if (displayType == DisplayType.INDEPENDENT)
  236. {
  237. dv.valueType = DisplayValue.ValueType.FLOATARRAY_INDEPENDENT;
  238. }
  239. else
  240. {
  241. dv.valueType = DisplayValue.ValueType.FLOATARRAY_PROPORTION;
  242. }
  243. displayValues[key] = dv;
  244. }
  245. }
  246. /// <summary>
  247. /// Remove a value from a monitor.
  248. /// </summary>
  249. /// <param name="target">
  250. /// The transform to which the information is attached.
  251. /// </param>
  252. /// <param name="key">The key of the information you want to remove.</param>
  253. public static void RemoveValue(Transform target, string key)
  254. {
  255. if (target == null)
  256. {
  257. target = canvas.transform;
  258. }
  259. if (displayTransformValues.Keys.Contains(target))
  260. {
  261. if (displayTransformValues[target].ContainsKey(key))
  262. {
  263. displayTransformValues[target].Remove(key);
  264. if (displayTransformValues[target].Keys.Count == 0)
  265. {
  266. displayTransformValues.Remove(target);
  267. }
  268. }
  269. }
  270. }
  271. /// <summary>
  272. /// Remove all information from a monitor.
  273. /// </summary>
  274. /// <param name="target">
  275. /// The transform to which the information is attached.
  276. /// </param>
  277. public static void RemoveAllValues(Transform target)
  278. {
  279. if (target == null)
  280. {
  281. target = canvas.transform;
  282. }
  283. if (displayTransformValues.Keys.Contains(target))
  284. {
  285. displayTransformValues.Remove(target);
  286. }
  287. }
  288. /// <summary>
  289. /// Use SetActive to enable or disable the Monitor via script
  290. /// </summary>
  291. /// <param name="active">Value to set the Monitor's status to.</param>
  292. public static void SetActive(bool active)
  293. {
  294. if (!isInstantiated)
  295. {
  296. InstantiateCanvas();
  297. isInstantiated = true;
  298. }
  299. if (canvas != null)
  300. {
  301. canvas.SetActive(active);
  302. }
  303. }
  304. /// Initializes the canvas.
  305. static void InstantiateCanvas()
  306. {
  307. canvas = GameObject.Find("AgentMonitorCanvas");
  308. if (canvas == null)
  309. {
  310. canvas = new GameObject();
  311. canvas.name = "AgentMonitorCanvas";
  312. canvas.AddComponent<Monitor>();
  313. }
  314. displayTransformValues = new Dictionary<Transform,
  315. Dictionary<string, DisplayValue>>();
  316. transformCamera = new Dictionary<Transform, Camera>();
  317. }
  318. /// <summary> <inheritdoc/> </summary>
  319. void OnGUI()
  320. {
  321. if (!initialized)
  322. {
  323. Initialize();
  324. initialized = true;
  325. }
  326. var toIterate = displayTransformValues.Keys.ToList();
  327. foreach (Transform target in toIterate)
  328. {
  329. if (target == null)
  330. {
  331. displayTransformValues.Remove(target);
  332. continue;
  333. }
  334. // get camera
  335. Camera cam = transformCamera[target];
  336. if (cam == null)
  337. {
  338. cam = Camera.main;
  339. }
  340. float widthScaler = (Screen.width / 1000f);
  341. float keyPixelWidth = 100 * widthScaler;
  342. float keyPixelHeight = 20 * widthScaler;
  343. float paddingwidth = 10 * widthScaler;
  344. float scale = 1f;
  345. var origin = new Vector3(
  346. Screen.width / 2 - keyPixelWidth, Screen.height);
  347. if (!(target == canvas.transform))
  348. {
  349. Vector3 cam2obj = target.position - cam.transform.position;
  350. scale = Mathf.Min(
  351. 1,
  352. 20f / (Vector3.Dot(cam2obj, cam.transform.forward)));
  353. Vector3 worldPosition = cam.WorldToScreenPoint(
  354. target.position + new Vector3(0, verticalOffset, 0));
  355. origin = new Vector3(
  356. worldPosition.x - keyPixelWidth * scale, Screen.height - worldPosition.y);
  357. }
  358. keyPixelWidth *= scale;
  359. keyPixelHeight *= scale;
  360. paddingwidth *= scale;
  361. keyStyle.fontSize = (int) (keyPixelHeight * 0.8f);
  362. if (keyStyle.fontSize < 2)
  363. {
  364. continue;
  365. }
  366. Dictionary<string, DisplayValue> displayValues = displayTransformValues[target];
  367. int index = 0;
  368. var orderedKeys = displayValues.Keys.OrderBy(x => -displayValues[x].time);
  369. float[] vals;
  370. GUIStyle s;
  371. foreach (string key in orderedKeys)
  372. {
  373. keyStyle.alignment = TextAnchor.MiddleRight;
  374. GUI.Label(
  375. new Rect(
  376. origin.x, origin.y - (index + 1) * keyPixelHeight,
  377. keyPixelWidth, keyPixelHeight),
  378. key,
  379. keyStyle);
  380. switch (displayValues[key].valueType)
  381. {
  382. case DisplayValue.ValueType.STRING:
  383. valueStyle.alignment = TextAnchor.MiddleLeft;
  384. GUI.Label(
  385. new Rect(
  386. origin.x + paddingwidth + keyPixelWidth,
  387. origin.y - (index + 1) * keyPixelHeight,
  388. keyPixelWidth, keyPixelHeight),
  389. displayValues[key].stringValue,
  390. valueStyle);
  391. break;
  392. case DisplayValue.ValueType.FLOAT:
  393. float sliderValue = displayValues[key].floatValue;
  394. sliderValue = Mathf.Min(1f, sliderValue);
  395. s = greenStyle;
  396. if (sliderValue < 0)
  397. {
  398. sliderValue = Mathf.Min(1f, -sliderValue);
  399. s = redStyle;
  400. }
  401. GUI.Box(
  402. new Rect(
  403. origin.x + paddingwidth + keyPixelWidth,
  404. origin.y - (index + 0.9f) * keyPixelHeight,
  405. keyPixelWidth * sliderValue, keyPixelHeight * 0.8f),
  406. GUIContent.none,
  407. s);
  408. break;
  409. case DisplayValue.ValueType.FLOATARRAY_INDEPENDENT:
  410. float histWidth = 0.15f;
  411. vals = displayValues[key].floatArrayValues;
  412. for (int i = 0; i < vals.Length; i++)
  413. {
  414. float value = Mathf.Min(vals[i], 1);
  415. s = greenStyle;
  416. if (value < 0)
  417. {
  418. value = Mathf.Min(1f, -value);
  419. s = redStyle;
  420. }
  421. GUI.Box(
  422. new Rect(
  423. origin.x + paddingwidth + keyPixelWidth +
  424. (keyPixelWidth * histWidth + paddingwidth / 2) * i,
  425. origin.y - (index + 0.1f) * keyPixelHeight,
  426. keyPixelWidth * histWidth, -keyPixelHeight * value),
  427. GUIContent.none,
  428. s);
  429. }
  430. break;
  431. case DisplayValue.ValueType.FLOATARRAY_PROPORTION:
  432. float valsSum = 0f;
  433. float valsCum = 0f;
  434. vals = displayValues[key].floatArrayValues;
  435. foreach (float f in vals)
  436. {
  437. valsSum += Mathf.Max(f, 0);
  438. }
  439. if (valsSum < float.Epsilon)
  440. {
  441. Debug.LogError(
  442. string.Format("The Monitor value for key {0} " +
  443. "must be a list or array of " +
  444. "positive values and cannot " +
  445. "be empty.", key));
  446. }
  447. else
  448. {
  449. for (int i = 0; i < vals.Length; i++)
  450. {
  451. float value = Mathf.Max(vals[i], 0) / valsSum;
  452. GUI.Box(
  453. new Rect(
  454. origin.x + paddingwidth +
  455. keyPixelWidth + keyPixelWidth * valsCum,
  456. origin.y - (index + 0.9f) * keyPixelHeight,
  457. keyPixelWidth * value, keyPixelHeight * 0.8f),
  458. GUIContent.none,
  459. colorStyle[i % colorStyle.Length]);
  460. valsCum += value;
  461. }
  462. }
  463. break;
  464. }
  465. index++;
  466. }
  467. }
  468. }
  469. /// Helper method used to initialize the GUI. Called once.
  470. void Initialize()
  471. {
  472. keyStyle = GUI.skin.label;
  473. valueStyle = GUI.skin.label;
  474. valueStyle.clipping = TextClipping.Overflow;
  475. valueStyle.wordWrap = false;
  476. barColors = new Color[6]
  477. {
  478. Color.magenta,
  479. Color.blue,
  480. Color.cyan,
  481. Color.green,
  482. Color.yellow,
  483. Color.red
  484. };
  485. colorStyle = new GUIStyle[barColors.Length];
  486. for (int i = 0; i < barColors.Length; i++)
  487. {
  488. var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false);
  489. texture.SetPixel(0, 0, barColors[i]);
  490. texture.Apply();
  491. var staticRectStyle = new GUIStyle();
  492. staticRectStyle.normal.background = texture;
  493. colorStyle[i] = staticRectStyle;
  494. }
  495. greenStyle = colorStyle[3];
  496. redStyle = colorStyle[5];
  497. }
  498. }
  499. }