using System.Collections.Generic ;
using System.Linq ;
using Newtonsoft.Json ;
/// <summary>
/// The type of monitor the information must be displayed in.
/// <slider> corresponds to a single rectangle whose width is given
/// by a float between -1 and 1. (green is positive, red is negative)
/// <hist> corresponds to n vertical sliders.
/// <text> is a text field.
/// <bar> is a rectangle of fixed length to represent the proportions
/// of a list of floats.
/// </summary>
public enum MonitorType
{
slider ,
hist ,
text ,
bar
}
/// <summary>
/// Monitor is used to display information about the Agent within the Unity
/// scene. Use the log function to add information to your monitor.
/// <summary>
/// The type of monitor the information must be displayed in.
/// <slider> corresponds to a single rectangle whose width is given
/// by a float between -1 and 1. (green is positive, red is negative)
/// <hist> corresponds to n vertical sliders.
/// <text> is a text field.
/// <bar> is a rectangle of fixed length to represent the proportions
/// of a list of floats.
/// </summary>
public enum DisplayType
{
INDEPENDENT ,
PROPORTION
}
/// <summary>
/// Represents how high above the target the monitors will be.
/// </summary>
[HideInInspector]
struct DisplayValue
{
public float time ;
public object value ;
public MonitorType monitorDisplayType ;
public string stringValue ;
public float floatValue ;
public float [ ] floatArrayValues ;
public enum ValueType
{
FLOAT ,
FLOATARRAY_INDEPENDENT ,
FLOATARRAY_PROPORTION ,
STRING
}
public ValueType valueType ;
}
static GUIStyle keyStyle ;
/// <summary>
/// Use the Monitor.Log static function to attach information to a transform.
/// If displayType is <text>, value can be any object.
/// If sidplayType is <slider>, value must be a float.
/// If sidplayType is <hist>, value must be a List or Array of floats.
/// If sidplayType is <bar>, value must be a list or Array of positive floats.
/// Note that <slider> and <hist> caps values between -1 and 1.
/// <param name="value">The value you want to display.</param>
/// <param name="displayType">The type of display.</param>
/// <param name="target">
/// The transform you want to attach the information to.
/// <param name="value">The string value you want to display.</param>
/// <param name="target">The transform you want to attach the information to.
object value ,
MonitorType displayType = MonitorType . text ,
string value ,
Transform target = null )
{
if ( ! isInstantiated )
}
if ( target = = null )
{
target = canvas . transform ;
}
if ( ! displayTransformValues . Keys . Contains ( target ) )
{
displayTransformValues [ target ] =
new Dictionary < string , DisplayValue > ( ) ;
}
Dictionary < string , DisplayValue > displayValues =
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 > 2 0 )
{
string max = (
displayValues
. Aggregate ( ( l , r ) = > l . Value . time < r . Value . time ? l : r )
. Key
) ;
RemoveValue ( target , max ) ;
}
}
else
{
DisplayValue dv = displayValues [ key ] ;
dv . stringValue = value ;
dv . valueType = DisplayValue . ValueType . STRING ;
displayValues [ key ] = dv ;
}
}
/// <summary>
/// Use the Monitor.Log static function to attach information to a transform.
/// </summary>
/// <returns>The log.</returns>
/// <param name="key">The name of the information you wish to Log.</param>
/// <param name="value">The float value you want to display.</param>
/// <param name="target">The transform you want to attach the information to.
/// </param>
public static void Log (
string key ,
float value ,
Transform target = null )
{
if ( ! isInstantiated )
{
InstantiateCanvas ( ) ;
isInstantiated = true ;
}
if ( target = = null )
Dictionary < string , DisplayValue > displayValues = displayTransformValues [ target ] ;
if ( value = = null )
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 > 2 0 )
{
string max = (
displayValues . Aggregate ( ( l , r ) = > l . Value . time < r . Value . time ? l : r ) . Key ) ;
RemoveValue ( target , max ) ;
}
}
else
RemoveValue ( target , key ) ;
return ;
DisplayValue dv = displayValues [ key ] ;
dv . floatValue = value ;
dv . valueType = DisplayValue . ValueType . FLOAT ;
displayValues [ key ] = dv ;
}
/// <summary>
/// Use the Monitor.Log static function to attach information to a transform.
/// </summary>
/// <returns>The log.</returns>
/// <param name="key">The name of the information you wish to Log.</param>
/// <param name="value">The array of float you want to display.</param>
/// <param name="displayType">The type of display.</param>
/// <param name="target">The transform you want to attach the information to.
/// </param>
public static void Log (
string key ,
float [ ] value ,
Transform target = null ,
DisplayType displayType = DisplayType . INDEPENDENT
)
{
if ( ! isInstantiated )
{
InstantiateCanvas ( ) ;
isInstantiated = true ;
}
if ( target = = null )
{
target = canvas . transform ;
}
if ( ! displayTransformValues . Keys . Contains ( target ) )
{
displayTransformValues [ target ] = new Dictionary < string , DisplayValue > ( ) ;
}
Dictionary < string , DisplayValue > displayValues = displayTransformValues [ target ] ;
dv . value = value ;
dv . monitorDisplayType = displayType ;
dv . floatArrayValues = value ;
if ( displayType = = DisplayType . INDEPENDENT )
{
dv . valueType = DisplayValue . ValueType . FLOATARRAY_INDEPENDENT ;
}
else
{
dv . valueType = DisplayValue . ValueType . FLOATARRAY_PROPORTION ;
}
string max = displayValues . Aggregate ( ( l , r ) = > l . Value . time < r . Value . time ? l : r ) . Key ;
string max = (
displayValues . Aggregate ( ( l , r ) = > l . Value . time < r . Value . time ? l : r ) . Key ) ;
RemoveValue ( target , max ) ;
}
}
dv . value = value ;
dv . floatArrayValues = value ;
if ( displayType = = DisplayType . INDEPENDENT )
{
dv . valueType = DisplayValue . ValueType . FLOATARRAY_INDEPENDENT ;
}
else
{
dv . valueType = DisplayValue . ValueType . FLOATARRAY_PROPORTION ;
}
/// <summary>
/// Remove a value from a monitor.
canvas . name = "AgentMonitorCanvas" ;
canvas . AddComponent < Monitor > ( ) ;
}
displayTransformValues = new Dictionary < Transform , Dictionary < string , DisplayValue > > ( ) ;
}
/// Convert the input object to a float array. Returns a float[0] if the
/// conversion process fails.
float [ ] ToFloatArray ( object input )
{
try
{
return JsonConvert . DeserializeObject < float [ ] > (
JsonConvert . SerializeObject ( input , Formatting . None ) ) ;
}
catch
{
}
try
{
return new float [ 1 ]
{ JsonConvert . DeserializeObject < float > (
JsonConvert . SerializeObject ( input , Formatting . None ) )
} ;
}
catch
{
}
return new float [ 0 ] ;
displayTransformValues = new Dictionary < Transform ,
Dictionary < string , DisplayValue > > ( ) ;
}
/// <summary> <inheritdoc/> </summary>
float paddingwidth = 1 0 * widthScaler ;
float scale = 1f ;
var origin = new Vector3 ( Screen . width / 2 - keyPixelWidth , Screen . height ) ;
var origin = new Vector3 (
Screen . width / 2 - keyPixelWidth , Screen . height ) ;
scale = Mathf . Min ( 1 , 2 0f / ( Vector3 . Dot ( cam2obj , Camera . main . transform . forward ) ) ) ;
Vector3 worldPosition = Camera . main . WorldToScreenPoint ( target . position + new Vector3 ( 0 , verticalOffset , 0 ) ) ;
origin = new Vector3 ( worldPosition . x - keyPixelWidth * scale , Screen . height - worldPosition . y ) ;
scale = Mathf . Min (
1 ,
2 0f / ( Vector3 . Dot ( cam2obj , Camera . main . transform . forward ) ) ) ;
Vector3 worldPosition = Camera . main . WorldToScreenPoint (
target . position + new Vector3 ( 0 , verticalOffset , 0 ) ) ;
origin = new Vector3 (
worldPosition . x - keyPixelWidth * scale , Screen . height - worldPosition . y ) ;
}
keyPixelWidth * = scale ;
keyPixelHeight * = scale ;
Dictionary < string , DisplayValue > displayValues = displayTransformValues [ target ] ;
int index = 0 ;
foreach ( string key in displayValues . Keys . OrderBy ( x = > - displayValues [ x ] . time ) )
var orderedKeys = displayValues . Keys . OrderBy ( x = > - displayValues [ x ] . time ) ;
float [ ] vals ;
GUIStyle s ;
foreach ( string key in orderedKeys )
GUI . Label ( new Rect ( origin . x , origin . y - ( index + 1 ) * keyPixelHeight , keyPixelWidth , keyPixelHeight ) , key , keyStyle ) ;
if ( displayValues [ key ] . monitorDisplayType = = MonitorType . text )
GUI . Label (
new Rect (
origin . x , origin . y - ( index + 1 ) * keyPixelHeight ,
keyPixelWidth , keyPixelHeight ) ,
key ,
keyStyle ) ;
switch ( displayValues [ key ] . valueType )
valueStyle . alignment = TextAnchor . MiddleLeft ;
GUI . Label ( new Rect (
origin . x + paddingwidth + keyPixelWidth ,
origin . y - ( index + 1 ) * keyPixelHeight ,
keyPixelWidth , keyPixelHeight ) ,
JsonConvert . SerializeObject ( displayValues [ key ] . value , Formatting . None ) , valueStyle ) ;
}
else if ( displayValues [ key ] . monitorDisplayType = = MonitorType . slider )
{
float sliderValue = 0f ;
if ( displayValues [ key ] . value is float )
{
sliderValue = ( float ) displayValues [ key ] . value ;
}
else
{
Debug . LogError ( string . Format ( "The value for {0} could not be displayed as " +
"a slider because it is not a number." , key ) ) ;
}
sliderValue = Mathf . Min ( 1f , sliderValue ) ;
GUIStyle s = greenStyle ;
if ( sliderValue < 0 )
{
sliderValue = Mathf . Min ( 1f , - sliderValue ) ;
s = redStyle ;
}
GUI . Box ( new Rect (
origin . x + paddingwidth + keyPixelWidth ,
origin . y - ( index + 0.9f ) * keyPixelHeight ,
keyPixelWidth * sliderValue , keyPixelHeight * 0.8f ) ,
GUIContent . none , s ) ;
}
else if ( displayValues [ key ] . monitorDisplayType = = MonitorType . hist )
{
float histWidth = 0.15f ;
float [ ] vals = ToFloatArray ( displayValues [ key ] . value ) ;
for ( int i = 0 ; i < vals . Length ; i + + )
{
float value = Mathf . Min ( vals [ i ] , 1 ) ;
GUIStyle s = greenStyle ;
if ( value < 0 )
case DisplayValue . ValueType . STRING :
valueStyle . alignment = TextAnchor . MiddleLeft ;
GUI . Label (
new Rect (
origin . x + paddingwidth + keyPixelWidth ,
origin . y - ( index + 1 ) * keyPixelHeight ,
keyPixelWidth , keyPixelHeight ) ,
displayValues [ key ] . stringValue ,
valueStyle ) ;
break ;
case DisplayValue . ValueType . FLOAT :
float sliderValue = displayValues [ key ] . floatValue ;
sliderValue = Mathf . Min ( 1f , sliderValue ) ;
s = greenStyle ;
if ( sliderValue < 0 )
value = Mathf . Min ( 1f , - value ) ;
sliderValue = Mathf . Min ( 1f , - sliderValue ) ;
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 ) ;
}
GUI . Box (
new Rect (
origin . x + paddingwidth + keyPixelWidth ,
origin . y - ( index + 0.9f ) * keyPixelHeight ,
keyPixelWidth * sliderValue , keyPixelHeight * 0.8f ) ,
GUIContent . none ,
s ) ;
break ;
}
else if ( displayValues [ key ] . monitorDisplayType = = MonitorType . bar )
{
float [ ] vals = ToFloatArray ( displayValues [ key ] . value ) ;
float valsSum = 0f ;
float valsCum = 0f ;
foreach ( float f in vals )
{
valsSum + = Mathf . Max ( f , 0 ) ;
}
if ( valsSum = = 0 )
{
Debug . LogError ( string . Format ( "The Monitor value for key {0} must be "
+ "a list or array of positive values and cannot be empty." , key ) ) ;
}
else
{
case DisplayValue . ValueType . FLOATARRAY_INDEPENDENT :
float histWidth = 0.15f ;
vals = displayValues [ key ] . floatArrayValues ;
float 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 , colorStyle [ i % colorStyle . Length ] ) ;
valsCum + = value ;
float value = Mathf . Min ( vals [ i ] , 1 ) ;
s = greenStyle ;
if ( value < 0 )
{
value = Mathf . Min ( 1f , - value ) ;
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 . FLOATARRAY_PROPORTION :
float valsSum = 0f ;
float valsCum = 0f ;
vals = displayValues [ key ] . floatArrayValues ;
foreach ( float f in vals )
{
valsSum + = Mathf . Max ( f , 0 ) ;
}
if ( valsSum < float . Epsilon )
{
Debug . LogError (
string . Format ( "The Monitor value for key {0} " +
"must be a list or array of " +
"positive values and cannot " +
"be empty." , key ) ) ;
else
{
for ( int i = 0 ; i < vals . Length ; i + + )
{
float 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 ,
colorStyle [ i % colorStyle . Length ] ) ;
valsCum + = value ;
}
}
}
break ;
}
index + + ;
valueStyle = GUI . skin . label ;
valueStyle . clipping = TextClipping . Overflow ;
valueStyle . wordWrap = false ;
barColors = new Color [ 6 ] { Color . magenta , Color . blue , Color . cyan , Color . green , Color . yellow , Color . red } ;
barColors = new Color [ 6 ] {
Color . magenta ,
Color . blue ,
Color . cyan ,
Color . green ,
Color . yellow ,
Color . red } ;
colorStyle = new GUIStyle [ barColors . Length ] ;
for ( int i = 0 ; i < barColors . Length ; i + + )
{