├── Assets ├── Plugins │ ├── DebugGUI │ │ ├── Attributes │ │ │ ├── DebugGUIPrintAttribute.cs │ │ │ ├── DebugGUIGraphAttribute.cs.meta │ │ │ ├── DebugGUIPrintAttribute.cs.meta │ │ │ └── DebugGUIGraphAttribute.cs │ │ ├── Attributes.meta │ │ ├── Examples.meta │ │ ├── Examples │ │ │ ├── DebugGUIExample.unity.meta │ │ │ ├── DebugGUIExamples.cs.meta │ │ │ ├── DebugGUIExamples.cs │ │ │ └── DebugGUIExample.unity │ │ ├── Resources.meta │ │ ├── Windows.meta │ │ ├── Resources │ │ │ ├── DebugGUISettings.asset.meta │ │ │ └── DebugGUISettings.asset │ │ ├── DebugGUISettings.cs.meta │ │ ├── Windows │ │ │ ├── LogWindow.cs.meta │ │ │ ├── DebugGUIWindow.cs.meta │ │ │ ├── GraphWindow.cs.meta │ │ │ ├── DebugGUIWindow.cs │ │ │ ├── LogWindow.cs │ │ │ └── GraphWindow.cs │ │ ├── DebugGUI.cs.meta │ │ ├── DebugGUISettings.cs │ │ └── DebugGUI.cs │ └── DebugGUI.meta └── Plugins.meta ├── .gitignore └── README.md /Assets/Plugins/DebugGUI/Attributes/DebugGUIPrintAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 4 | public class DebugGUIPrintAttribute : Attribute { } -------------------------------------------------------------------------------- /Assets/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4bc7c7a2efb55f44e8d0f2d5399aa6e7 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ad068340d5f33444b9c6e328a733d305 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Attributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8489cfc6e7b87f458a33db5fe7b53ae 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Examples.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53f3e246a0b018d40bd95948e0e62384 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Examples/DebugGUIExample.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 433e3a874a431e44c9f29c876e471cce 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ed26b9be30a2694a9dfb8da90cac4e8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0465c9995c2ea340a682ce39c965592 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Resources/DebugGUISettings.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5634d44c17e37d94a9ae81481656b2a8 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 11400000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/DebugGUISettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6113b998a039c1d478fae3c7e29f0dd6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows/LogWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d0f580c0f21ccf449a9635d3dda2b98 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows/DebugGUIWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f65bab18e1108ac44b7836b6d23ba6c0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows/GraphWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82b18bc7e7578cb469bcc327b1e9778f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Examples/DebugGUIExamples.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2e7d4119f4284164b9a4012cb41ed080 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Attributes/DebugGUIGraphAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c70f402a170b9d64bbd6a5561aa82ae1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Attributes/DebugGUIPrintAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b587a62e654d164b9c61e74d1cddbd9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/DebugGUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 08a3245b7cfea604b93682e005169e6f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: 7 | - _settings: {instanceID: 0} 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/DebugGUISettings.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | // [CreateAssetMenu(fileName = "DebugGUISettings", menuName = "DebugGUI/Settings", order = 1)] 4 | public class DebugGUISettings : ScriptableObject 5 | { 6 | [SerializeField] public bool enableGraphs = true; 7 | [SerializeField] public bool enableLogs = true; 8 | 9 | [SerializeField] public Color backgroundColor = new Color(0f, 0f, 0f, 0.7f); 10 | [SerializeField] public Color scrubberColor = new Color(1f, 1f, 0f, 0.7f); 11 | [SerializeField] public int graphWidth = 300; 12 | [SerializeField] public int graphHeight = 100; 13 | [SerializeField] public float temporaryLogLifetime = 5; 14 | } 15 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Resources/DebugGUISettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: 6113b998a039c1d478fae3c7e29f0dd6, type: 3} 13 | m_Name: DebugGUISettings 14 | m_EditorClassIdentifier: Assembly-CSharp-firstpass::DebugGUI/Settings 15 | enableGraphs: 1 16 | enableLogs: 1 17 | backgroundColor: {r: 0, g: 0, b: 0, a: 0.7} 18 | scrubberColor: {r: 1, g: 1, b: 0, a: 0.7} 19 | graphWidth: 300 20 | graphHeight: 100 21 | temporaryLogLifetime: 5 22 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Attributes/DebugGUIGraphAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | 4 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 5 | public class DebugGUIGraphAttribute : Attribute 6 | { 7 | public float min { get; private set; } 8 | public float max { get; private set; } 9 | public Color color { get; private set; } 10 | public int group { get; private set; } 11 | public bool autoScale { get; private set; } 12 | 13 | public DebugGUIGraphAttribute( 14 | // Line color 15 | float r = 1, 16 | float g = 1, 17 | float b = 1, 18 | // Values at top/bottom of graph 19 | float min = 0, 20 | float max = 1, 21 | // Offset position on screen 22 | int group = 0, 23 | // Auto-adjust min/max to fit the values 24 | bool autoScale = true 25 | ) 26 | { 27 | color = new Color(r, g, b, 0.9f); 28 | this.min = min; 29 | this.max = max; 30 | this.group = group; 31 | this.autoScale = autoScale; 32 | } 33 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Recordings can get excessive in size 18 | /[Rr]ecordings/ 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | *.app 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | # Packed Addressables 68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 69 | 70 | # Temporary auto-generated Android Assets 71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 72 | /[Aa]ssets/[Ss]treamingAssets/aa/* 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Debug GUI Graph - Unity


3 |

4 | DebugGUI Unity Icon 5 |

6 | 7 |

8 | Simple and easy to use graphing debug utility. 9 |

10 | 11 |
12 | 13 | ## Description 14 | DebugGUI Graph provides a way to debug continuous systems by providing an inspectable graphing GUI and logging overlay. It also provides an optional attribute-based abstraction for a one line injection into your existing code. 15 | 16 |
17 | 18 | ```csharp 19 | // Works with regular fields 20 | [DebugGUIGraph(min: -1, max: 1, r: 0, g: 1, b: 0, autoScale: true)] 21 | float SinField; 22 | 23 | // As well as properties 24 | [DebugGUIGraph(min: -1, max: 1, r: 0, g: 1, b: 1, autoScale: true)] 25 | float CosProperty { get { return Mathf.Cos(Time.time * 6); } } 26 | 27 | // Also works for expression-bodied properties 28 | [DebugGUIGraph(min: -1, max: 1, r: 1, g: 0.3f, b: 1)] 29 | float SinProperty => Mathf.Sin((Time.time + Mathf.PI / 2) * 6); 30 | 31 | // User inputs, print and graph in one! 32 | [DebugGUIPrint, DebugGUIGraph(group: 1, r: 1, g: 0.3f, b: 0.3f)] 33 | float mouseX; 34 | [DebugGUIPrint, DebugGUIGraph(group: 1, r: 0, g: 1, b: 0)] 35 | float mouseY; 36 | ``` 37 | 38 |

39 | DebugGUI Screenshot 40 |

41 | 42 | ## Installation 43 | 44 | ### Git 45 | 1. Download the latest commit or the stable Release version. 46 | 2. Place the `Plugins` folder into your project asset folder. 47 | 48 | ## Overview 49 | 50 | ### UI Interaction 51 | 52 | - **Dragging** - Windows can be dragged with middle mouse button. 53 | - **Scrubbing** - You can check the values at any point on the graph by hovering over it with the mouse. 54 | - **Freezing** - Holding left mouse button on the graph window stops graph updates for easier scrubbing. 55 | - **Disabling** - Graphs can be toggled on/off by clicking their names. 56 | 57 | Settings for changing colors and graph sizes are available in the `Plugins\DebugGUI\Resources\DebugGUISettings` ScriptableObject. 58 | 59 | ### Graphing 60 | 61 | A graph can be generated by adding the `DebugGUIGraph` attribute above any property or variable castable to `float`. Here you can specify the range of the graph (i.e. values at the top and bottom), the RGB color, and whether it should expand its range if a variable goes outside the range. 62 | 63 | ```cs 64 | [DebugGUIGraph(group: 2, min: -200, max: 200, r: 0, g: 1, b: 0, autoScale: true)] 65 | float playerXVelocity => velocity.x; 66 | ``` 67 | 68 |
69 | 70 | Alternatively, for more control, you can define and manage a graph manually. Graphs are referenced via an `object` key, which you then use to push new values to the graph. 71 | 72 | ```cs 73 | object myGraphKey = new object(); 74 | 75 | void Awake() 76 | { 77 | // Graph using this component as the key 78 | DebugGUI.SetGraphProperties(this, "My Graph", 0, 1, 0, Color.red, false); 79 | 80 | // Another graph with an arbitrary object key 81 | DebugGUI.SetGraphProperties(myGraphKey, "My Other Graph", 0, 1, 0, Colorr.blue, false); 82 | 83 | // Strings also work as keys 84 | DebugGUI.SetGraphProperties("my graph", "My Other Graph", 0, 1, 0, Color.green, false); 85 | } 86 | 87 | void Update() 88 | { 89 | DebugGUI.Graph(this, Time.deltaTime); 90 | DebugGUI.Graph(myGraphKey, Time.deltaTime); 91 | DebugGUI.Graph("my graph", Time.deltaTime); 92 | } 93 | ``` 94 | 95 |
96 | 97 | Graphs can be exported to json via `DebugGUI.ExportGraphs()`. 98 | 99 | ### Logging 100 | 101 | Similar to the graphs, a persistent logged value can be generated by adding the `DebugGUIPrint` attribute above any property or variable. This attribute has no configurable settings. Attribute-derived logs will include the object and field name in its output. 102 | 103 | ```cs 104 | [DebugGUIPrint] 105 | float playerXVelocity => velocity.x; 106 | ``` 107 | 108 |
109 | 110 | Persistent logs may also be managed manually in the same way as graphs using arbitrary keys. 111 | 112 | ```cs 113 | public override void Update() 114 | { 115 | DebugGUI.LogPersistent(this, $"Velocity: {velocity}"); 116 | DebugGUI.LogPersistent("deltatime", $"Last delta: {Time.deltaTime}"); 117 | DebugGUI.Log("This is a temporary log entry!"); 118 | } 119 | ``` 120 | 121 | ## Contribution 122 | If you spot a bug while using this, please create an [Issue](https://github.com/WeaverDev/DebugGUIGraph-Unity/issues). 123 | 124 | ## Acknowledgements 125 | Special thanks to [TheSniperFan](https://github.com/TheSniperFan) for the 2.0 conversion from texture to GL lines for the graph rendering. 126 | 127 | ## License 128 | 129 | [MIT License](LICENSE) 130 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Examples/DebugGUIExamples.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | public class DebugGUIExamples : MonoBehaviour 6 | { 7 | /* * * * 8 | * 9 | * [DebugGUIGraph] 10 | * Renders the variable in a graph on-screen. Attribute based graphs will updates every Update. 11 | * Lets you optionally define: 12 | * max, min - The range of displayed values 13 | * r, g, b - The RGB color of the graph (0~1) 14 | * group - Graphs can be grouped into the same window and overlaid 15 | * autoScale - If true the graph will readjust min/max to fit the data 16 | * 17 | * [DebugGUIPrint] 18 | * Draws the current variable continuously on-screen as 19 | * $"{GameObject name} {variable name}: {value}" 20 | * 21 | * For more control, these features can be accessed manually. 22 | * DebugGUI.SetGraphProperties(key, ...) - Set the properties of the graph with the provided key 23 | * DebugGUI.Graph(key, value) - Push a value to the graph 24 | * DebugGUI.LogPersistent(key, value) - Print a persistent log entry on screen 25 | * DebugGUI.Log(value) - Print a temporary log entry on screen 26 | * 27 | * See DebugGUI.cs for more info 28 | * 29 | * * * */ 30 | 31 | // Disable Field Unused warning 32 | #pragma warning disable 0414 33 | 34 | // Works with regular fields 35 | [DebugGUIGraph(min: -1, max: 1, r: 0, g: 1, b: 0, autoScale: true)] 36 | float SinField; 37 | 38 | // As well as properties 39 | [DebugGUIGraph(min: -1, max: 1, r: 0, g: 1, b: 1, autoScale: true)] 40 | float CosProperty { get { return Mathf.Cos(Time.time * 6); } } 41 | 42 | // Also works for expression-bodied properties 43 | [DebugGUIGraph(min: -1, max: 1, r: 1, g: 0.3f, b: 1)] 44 | float SinProperty => Mathf.Sin((Time.time + Mathf.PI / 2) * 6); 45 | 46 | // User inputs, print and graph in one! 47 | [DebugGUIPrint, DebugGUIGraph(group: 1, r: 1, g: 0.3f, b: 0.3f)] 48 | float mouseX; 49 | [DebugGUIPrint, DebugGUIGraph(group: 1, r: 0, g: 1, b: 0)] 50 | float mouseY; 51 | 52 | Queue deltaTimeBuffer = new(); 53 | float smoothDeltaTime => deltaTimeBuffer.Sum() / deltaTimeBuffer.Count; 54 | 55 | void Awake() 56 | { 57 | // Init smooth DT 58 | for (int i = 0; i < 10; i++) 59 | { 60 | deltaTimeBuffer.Enqueue(0); 61 | } 62 | 63 | // Log (as opposed to LogPersistent) will disappear automatically after some time. 64 | DebugGUI.Log("Hello! I will disappear after some time!"); 65 | 66 | // Set up graph properties using our graph keys 67 | DebugGUI.SetGraphProperties("smoothFrameRate", "SmoothFPS", 0, 200, 2, new Color(0, 1, 1), false); 68 | DebugGUI.SetGraphProperties("frameRate", "FPS", 0, 200, 2, new Color(1, 0.5f, 1), false); 69 | DebugGUI.SetGraphProperties("fixedFrameRateSin", "FixedSin", -1, 1, 3, new Color(1, 1, 0), true); 70 | } 71 | 72 | void Update() 73 | { 74 | // Update smooth delta time queue 75 | deltaTimeBuffer.Dequeue(); 76 | deltaTimeBuffer.Enqueue(Time.deltaTime); 77 | 78 | // Update the fields our attributes are graphing 79 | SinField = Mathf.Sin(Time.time * 6); 80 | 81 | // Update graphed mouse XY values 82 | var mousePos = Input.mousePosition; 83 | var screenSize = Screen.currentResolution; 84 | mouseX = Mathf.Clamp(mousePos.x, 0, screenSize.width); 85 | mouseY = Mathf.Clamp(mousePos.y, 0, screenSize.height); 86 | 87 | // Manual persistent logging 88 | DebugGUI.LogPersistent("smoothFrameRate", "SmoothFPS: " + (1 / smoothDeltaTime).ToString("F3")); 89 | DebugGUI.LogPersistent("frameRate", "FPS: " + (1 / Time.deltaTime).ToString("F3")); 90 | 91 | // Manual logging of mouse clicks 92 | if (Input.GetMouseButton(0)) 93 | { 94 | DebugGUI.Log(string.Format( 95 | "Mouse down ({0}, {1})", 96 | mouseX.ToString("F3"), 97 | mouseY.ToString("F3") 98 | )); 99 | } 100 | 101 | if (smoothDeltaTime != 0) 102 | { 103 | DebugGUI.Graph("smoothFrameRate", 1 / smoothDeltaTime); 104 | } 105 | if (Time.deltaTime != 0) 106 | { 107 | DebugGUI.Graph("frameRate", 1 / Time.deltaTime); 108 | } 109 | 110 | if (Input.GetKeyDown(KeyCode.Space)) 111 | { 112 | Destroy(this); 113 | } 114 | 115 | if (Input.GetKeyDown(KeyCode.E)) 116 | { 117 | Debug.Log(DebugGUI.ExportGraphs()); 118 | } 119 | } 120 | 121 | void FixedUpdate() 122 | { 123 | // Manual graphing 124 | DebugGUI.Graph("fixedFrameRateSin", Mathf.Sin(Time.fixedTime * 6)); 125 | } 126 | 127 | void OnDestroy() 128 | { 129 | // Clean up our logs and graphs when this object leaves tree 130 | DebugGUI.RemoveGraph("frameRate"); 131 | DebugGUI.RemoveGraph("fixedFrameRateSin"); 132 | DebugGUI.RemoveGraph("smoothFrameRate"); 133 | 134 | DebugGUI.RemovePersistent("frameRate"); 135 | DebugGUI.RemovePersistent("smoothFrameRate"); 136 | } 137 | } -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows/DebugGUIWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace WeavUtils 4 | { 5 | // Draggable window clamped to the corners 6 | public class DebugGUIWindow : MonoBehaviour 7 | { 8 | protected const int outOfScreenClampPadding = 30; 9 | protected readonly Vector2 Padding = new Vector2(5, 5); 10 | 11 | static bool dragInProgress; 12 | bool dragged; 13 | 14 | protected Rect rect; 15 | 16 | Vector3 lastMousePos; 17 | 18 | static Material drawMat; 19 | Material CreateMaterial() 20 | { 21 | // Unity has a built-in shader that is useful for drawing 22 | // simple colored things. 23 | Shader shader = Shader.Find("Hidden/Internal-Colored"); 24 | Material mat = new Material(shader); 25 | mat.hideFlags = HideFlags.HideAndDontSave; 26 | // Turn on alpha blending 27 | mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); 28 | mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); 29 | // Turn backface culling off 30 | mat.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off); 31 | // Turn off depth writes 32 | mat.SetInt("_ZWrite", 0); 33 | 34 | return mat; 35 | } 36 | 37 | public virtual Rect GetDraggableRect() 38 | { 39 | return new Rect(rect.position, rect.size + Padding * 2); 40 | } 41 | 42 | public virtual void Init() 43 | { 44 | if (drawMat == null) 45 | { 46 | drawMat = CreateMaterial(); 47 | } 48 | } 49 | 50 | void Update() 51 | { 52 | // Flip mouse Y 53 | var mousePos = Input.mousePosition; 54 | mousePos.y = Screen.height - mousePos.y; 55 | 56 | if (Input.GetMouseButtonDown(2)) 57 | { 58 | if (!dragInProgress) 59 | { 60 | var rect = GetDraggableRect(); 61 | if (rect.Contains(mousePos)) 62 | { 63 | dragged = true; 64 | dragInProgress = true; 65 | } 66 | } 67 | } 68 | else if (Input.GetMouseButtonUp(2)) 69 | { 70 | if (dragged) dragInProgress = false; 71 | dragged = false; 72 | } 73 | 74 | if (dragged) 75 | { 76 | var mouseDelta = mousePos - lastMousePos; 77 | Move(mouseDelta); 78 | } 79 | lastMousePos = mousePos; 80 | } 81 | 82 | protected void Move(Vector2 delta = default) 83 | { 84 | rect.position += delta; 85 | 86 | var viewportRect = new Rect(Vector2.zero, new Vector2(Screen.width, Screen.height)); 87 | 88 | var min = -GetDraggableRect().size + Vector2.one * outOfScreenClampPadding; 89 | var max = viewportRect.size - Vector2.one * outOfScreenClampPadding; 90 | 91 | // Limit graph window offset so we can't get lost off screen 92 | rect.position = new Vector2( 93 | Mathf.Clamp(rect.position.x, min.x, max.x), 94 | Mathf.Clamp(rect.position.y, min.y, max.y) 95 | ); 96 | } 97 | 98 | protected virtual void OnGUI() 99 | { 100 | // Only draw once per frame 101 | if (Event.current.type != EventType.Repaint) 102 | { 103 | return; 104 | } 105 | 106 | drawMat.SetPass(0); 107 | } 108 | 109 | GUIContent tmpGuiContent = new(); 110 | protected Vector2 GetMultilineStringSize(GUIStyle style, in string str) 111 | { 112 | tmpGuiContent.text = str; 113 | style.CalcMinMaxWidth( 114 | tmpGuiContent, out _, out float width 115 | ); 116 | var height = style.CalcHeight(tmpGuiContent, width); 117 | 118 | return new Vector2( 119 | width, 120 | height 121 | ); 122 | } 123 | 124 | protected void DrawRect(Rect rect, Color color, Vector2 padding = default) 125 | { 126 | rect.position += this.rect.position; 127 | rect.size += padding * 2; 128 | 129 | GL.Begin(GL.QUADS); 130 | { 131 | GL.Color(color); 132 | 133 | GL.Vertex3(rect.x, rect.y, 0.0f); 134 | GL.Vertex3(rect.x, rect.y + rect.height, 0.0f); 135 | GL.Vertex3(rect.x + rect.width, rect.y + rect.height, 0.0f); 136 | GL.Vertex3(rect.x + rect.width, rect.y, 0.0f); 137 | } 138 | GL.End(); 139 | } 140 | 141 | protected void DrawLine(Vector2 start, Vector2 end, Color color) 142 | { 143 | start += rect.position; 144 | end += rect.position; 145 | 146 | GL.Begin(GL.LINES); 147 | { 148 | GL.Color(color); 149 | 150 | GL.Vertex(start); 151 | GL.Vertex(end); 152 | } 153 | GL.End(); 154 | } 155 | 156 | protected void DrawLabel(Vector2 pos, string label, Vector2 padding = default, GUIStyle style = null) 157 | { 158 | DrawLabel(new Rect(pos, GetMultilineStringSize(GUIStyle.none, in label)), label, padding, style); 159 | } 160 | protected void DrawLabel(Rect rect, string label, Vector2 padding = default, GUIStyle style = null) 161 | { 162 | rect.position += this.rect.position; 163 | tmpGuiContent.text = label; 164 | GUI.Label(new Rect(rect.position + padding, rect.size + padding), tmpGuiContent, style ?? GUIStyle.none); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/DebugGUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.IO; 3 | using WeavUtils; 4 | using System; 5 | 6 | public partial class DebugGUI : MonoBehaviour 7 | { 8 | static bool quitting; 9 | bool initialized; 10 | 11 | // Ensure a singleton is always present when used 12 | private static DebugGUI _instance; 13 | private static DebugGUI Instance 14 | { 15 | get 16 | { 17 | if (_instance == null && !quitting) 18 | { 19 | _instance = FindObjectOfType(); 20 | 21 | if (_instance == null && Application.isPlaying) 22 | { 23 | _instance = new GameObject("DebugGUI").AddComponent(); 24 | } 25 | 26 | if (!_instance.initialized) 27 | { 28 | _instance.Init(); 29 | } 30 | } 31 | return _instance; 32 | } 33 | } 34 | 35 | public DebugGUISettings _settings; 36 | public static DebugGUISettings Settings => Instance._settings; 37 | 38 | #region Graph 39 | 40 | /// 41 | /// Set the properties of a graph. 42 | /// 43 | /// The graph's key 44 | /// The graph's label 45 | /// Value at the bottom of the graph box 46 | /// Value at the top of the graph box 47 | /// The graph's ordinal position on screen 48 | /// The graph's color 49 | public static void SetGraphProperties(object key, string label, float min, float max, int group, Color color, bool autoScale) 50 | { 51 | if (Settings.enableGraphs) 52 | Instance.graphWindow.SetGraphProperties(key, label, min, max, group, color, autoScale); 53 | } 54 | 55 | /// 56 | /// Add a data point to a graph. 57 | /// 58 | /// The graph's key 59 | /// Value to be added 60 | public static void Graph(object key, float val) 61 | { 62 | if (Settings.enableGraphs) 63 | Instance.graphWindow.Graph(key, val); 64 | } 65 | 66 | /// 67 | /// Remove an existing graph. 68 | /// 69 | /// The graph's key 70 | public static void RemoveGraph(object key) 71 | { 72 | if (Settings.enableGraphs) 73 | Instance.graphWindow.RemoveGraph(key); 74 | } 75 | 76 | /// 77 | /// Resets a graph's data. 78 | /// 79 | /// The graph's key 80 | public static void ClearGraph(object key) 81 | { 82 | if (Settings.enableGraphs) 83 | Instance.graphWindow.ClearGraph(key); 84 | } 85 | 86 | /// 87 | /// Export graphs to a json file. Returns file path. 88 | /// 89 | public static string ExportGraphs() 90 | { 91 | if (Instance == null || !Settings.enableGraphs) 92 | return null; 93 | 94 | string dateTimeStr = DateTime.Now.ToString("YYY-mm-ddTHH-mm-ss"); 95 | string filename = $"debuggui_graph_export_{dateTimeStr}.json"; 96 | 97 | string filePath = Path.Combine(Application.persistentDataPath, filename); 98 | 99 | try 100 | { 101 | using StreamWriter writer = new StreamWriter(filePath); 102 | writer.Write(Instance.graphWindow.ToJson()); 103 | 104 | Debug.Log($"[DebugGUI] Wrote graph data to {filePath}"); 105 | return filePath; 106 | } 107 | catch (Exception e) 108 | { 109 | Debug.LogError("[DebugGUI] Graph export failed"); 110 | Debug.LogException(e); 111 | return null; 112 | } 113 | } 114 | 115 | #endregion 116 | 117 | #region Log 118 | 119 | /// 120 | /// Create or update an existing message with the same key. 121 | /// 122 | public static void LogPersistent(object key, string message) 123 | { 124 | if (Settings.enableLogs) 125 | Instance.logWindow.LogPersistent(key, message); 126 | } 127 | 128 | /// 129 | /// Remove an existing persistent message. 130 | /// 131 | public static void RemovePersistent(object key) 132 | { 133 | if (Settings.enableLogs) 134 | Instance.logWindow.RemovePersistent(key); 135 | } 136 | 137 | /// 138 | /// Clears all persistent logs. 139 | /// 140 | public static void ClearPersistent() 141 | { 142 | if (Settings.enableLogs) 143 | Instance.logWindow.ClearPersistent(); 144 | } 145 | 146 | /// 147 | /// Print a temporary message. 148 | /// 149 | public static void Log(object message) 150 | { 151 | if (Settings.enableLogs) 152 | Instance.logWindow.Log(message.ToString()); 153 | } 154 | 155 | #endregion 156 | 157 | /// 158 | /// Re-scans for DebugGUI attribute holders (i.e. [DebugGUIGraph] and [DebugGUIPrint]) 159 | /// 160 | public static void ForceReinitializeAttributes() 161 | { 162 | if (Instance == null) return; 163 | 164 | Instance.graphWindow.ReinitializeAttributes(); 165 | Instance.logWindow.ReinitializeAttributes(); 166 | } 167 | 168 | GraphWindow graphWindow; 169 | LogWindow logWindow; 170 | 171 | void Awake() 172 | { 173 | if (!initialized) 174 | Init(); 175 | } 176 | 177 | void Init() 178 | { 179 | Application.quitting += () => quitting = true; 180 | initialized = true; 181 | _settings = Resources.Load("DebugGUISettings"); 182 | 183 | DontDestroyOnLoad(gameObject); 184 | if (Settings.enableGraphs) 185 | { 186 | graphWindow = new GameObject("Graph").AddComponent(); 187 | graphWindow.Init(); 188 | graphWindow.transform.parent = transform; 189 | } 190 | if (Settings.enableLogs) 191 | { 192 | logWindow = new GameObject("Log").AddComponent(); 193 | logWindow.Init(); 194 | logWindow.transform.parent = transform; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Examples/DebugGUIExample.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 0 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 0} 41 | m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 12 47 | m_GIWorkflowMode: 1 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 1 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 12 58 | m_Resolution: 2 59 | m_BakeResolution: 40 60 | m_AtlasSize: 1024 61 | m_AO: 0 62 | m_AOMaxDistance: 1 63 | m_CompAOExponent: 1 64 | m_CompAOExponentDirect: 0 65 | m_ExtractAmbientOcclusion: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 512 79 | m_PVRBounces: 2 80 | m_PVREnvironmentSampleCount: 256 81 | m_PVREnvironmentReferencePointCount: 2048 82 | m_PVRFilteringMode: 1 83 | m_PVRDenoiserTypeDirect: 1 84 | m_PVRDenoiserTypeIndirect: 1 85 | m_PVRDenoiserTypeAO: 1 86 | m_PVRFilterTypeDirect: 0 87 | m_PVRFilterTypeIndirect: 0 88 | m_PVRFilterTypeAO: 0 89 | m_PVREnvironmentMIS: 1 90 | m_PVRCulling: 1 91 | m_PVRFilteringGaussRadiusDirect: 1 92 | m_PVRFilteringGaussRadiusIndirect: 5 93 | m_PVRFilteringGaussRadiusAO: 2 94 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 95 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 96 | m_PVRFilteringAtrousPositionSigmaAO: 1 97 | m_ExportTrainingData: 0 98 | m_TrainingDataDestination: TrainingData 99 | m_LightProbeSampleCountMultiplier: 4 100 | m_LightingDataAsset: {fileID: 0} 101 | m_LightingSettings: {fileID: 0} 102 | --- !u!196 &4 103 | NavMeshSettings: 104 | serializedVersion: 2 105 | m_ObjectHideFlags: 0 106 | m_BuildSettings: 107 | serializedVersion: 3 108 | agentTypeID: 0 109 | agentRadius: 0.5 110 | agentHeight: 2 111 | agentSlope: 45 112 | agentClimb: 0.4 113 | ledgeDropHeight: 0 114 | maxJumpAcrossDistance: 0 115 | minRegionArea: 2 116 | manualCellSize: 0 117 | cellSize: 0.16666667 118 | manualTileSize: 0 119 | tileSize: 256 120 | buildHeightMesh: 0 121 | maxJobWorkers: 0 122 | preserveTilesOutsideBounds: 0 123 | debug: 124 | m_Flags: 0 125 | m_NavMeshData: {fileID: 0} 126 | --- !u!1 &675168579 127 | GameObject: 128 | m_ObjectHideFlags: 0 129 | m_CorrespondingSourceObject: {fileID: 0} 130 | m_PrefabInstance: {fileID: 0} 131 | m_PrefabAsset: {fileID: 0} 132 | serializedVersion: 6 133 | m_Component: 134 | - component: {fileID: 675168583} 135 | - component: {fileID: 675168582} 136 | - component: {fileID: 675168581} 137 | m_Layer: 0 138 | m_Name: Main Camera 139 | m_TagString: MainCamera 140 | m_Icon: {fileID: 0} 141 | m_NavMeshLayer: 0 142 | m_StaticEditorFlags: 0 143 | m_IsActive: 1 144 | --- !u!81 &675168581 145 | AudioListener: 146 | m_ObjectHideFlags: 0 147 | m_CorrespondingSourceObject: {fileID: 0} 148 | m_PrefabInstance: {fileID: 0} 149 | m_PrefabAsset: {fileID: 0} 150 | m_GameObject: {fileID: 675168579} 151 | m_Enabled: 1 152 | --- !u!20 &675168582 153 | Camera: 154 | m_ObjectHideFlags: 0 155 | m_CorrespondingSourceObject: {fileID: 0} 156 | m_PrefabInstance: {fileID: 0} 157 | m_PrefabAsset: {fileID: 0} 158 | m_GameObject: {fileID: 675168579} 159 | m_Enabled: 1 160 | serializedVersion: 2 161 | m_ClearFlags: 1 162 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 163 | m_projectionMatrixMode: 1 164 | m_GateFitMode: 2 165 | m_FOVAxisMode: 0 166 | m_Iso: 200 167 | m_ShutterSpeed: 0.005 168 | m_Aperture: 16 169 | m_FocusDistance: 10 170 | m_FocalLength: 50 171 | m_BladeCount: 5 172 | m_Curvature: {x: 2, y: 11} 173 | m_BarrelClipping: 0.25 174 | m_Anamorphism: 0 175 | m_SensorSize: {x: 36, y: 24} 176 | m_LensShift: {x: 0, y: 0} 177 | m_NormalizedViewPortRect: 178 | serializedVersion: 2 179 | x: 0 180 | y: 0 181 | width: 1 182 | height: 1 183 | near clip plane: 0.3 184 | far clip plane: 1000 185 | field of view: 60 186 | orthographic: 0 187 | orthographic size: 5 188 | m_Depth: -1 189 | m_CullingMask: 190 | serializedVersion: 2 191 | m_Bits: 4294967295 192 | m_RenderingPath: -1 193 | m_TargetTexture: {fileID: 0} 194 | m_TargetDisplay: 0 195 | m_TargetEye: 3 196 | m_HDR: 1 197 | m_AllowMSAA: 1 198 | m_AllowDynamicResolution: 0 199 | m_ForceIntoRT: 0 200 | m_OcclusionCulling: 1 201 | m_StereoConvergence: 10 202 | m_StereoSeparation: 0.022 203 | --- !u!4 &675168583 204 | Transform: 205 | m_ObjectHideFlags: 0 206 | m_CorrespondingSourceObject: {fileID: 0} 207 | m_PrefabInstance: {fileID: 0} 208 | m_PrefabAsset: {fileID: 0} 209 | m_GameObject: {fileID: 675168579} 210 | serializedVersion: 2 211 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 212 | m_LocalPosition: {x: 0, y: 1, z: -10} 213 | m_LocalScale: {x: 1, y: 1, z: 1} 214 | m_ConstrainProportionsScale: 0 215 | m_Children: [] 216 | m_Father: {fileID: 0} 217 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 218 | --- !u!1 &2026908710 219 | GameObject: 220 | m_ObjectHideFlags: 0 221 | m_CorrespondingSourceObject: {fileID: 0} 222 | m_PrefabInstance: {fileID: 0} 223 | m_PrefabAsset: {fileID: 0} 224 | serializedVersion: 6 225 | m_Component: 226 | - component: {fileID: 2026908712} 227 | - component: {fileID: 2026908711} 228 | - component: {fileID: 2026908713} 229 | m_Layer: 0 230 | m_Name: Directional Light 231 | m_TagString: Untagged 232 | m_Icon: {fileID: 0} 233 | m_NavMeshLayer: 0 234 | m_StaticEditorFlags: 0 235 | m_IsActive: 1 236 | --- !u!108 &2026908711 237 | Light: 238 | m_ObjectHideFlags: 0 239 | m_CorrespondingSourceObject: {fileID: 0} 240 | m_PrefabInstance: {fileID: 0} 241 | m_PrefabAsset: {fileID: 0} 242 | m_GameObject: {fileID: 2026908710} 243 | m_Enabled: 1 244 | serializedVersion: 10 245 | m_Type: 1 246 | m_Shape: 0 247 | m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} 248 | m_Intensity: 1 249 | m_Range: 10 250 | m_SpotAngle: 30 251 | m_InnerSpotAngle: 21.80208 252 | m_CookieSize: 10 253 | m_Shadows: 254 | m_Type: 2 255 | m_Resolution: -1 256 | m_CustomResolution: -1 257 | m_Strength: 1 258 | m_Bias: 0.05 259 | m_NormalBias: 0.4 260 | m_NearPlane: 0.2 261 | m_CullingMatrixOverride: 262 | e00: 1 263 | e01: 0 264 | e02: 0 265 | e03: 0 266 | e10: 0 267 | e11: 1 268 | e12: 0 269 | e13: 0 270 | e20: 0 271 | e21: 0 272 | e22: 1 273 | e23: 0 274 | e30: 0 275 | e31: 0 276 | e32: 0 277 | e33: 1 278 | m_UseCullingMatrixOverride: 0 279 | m_Cookie: {fileID: 0} 280 | m_DrawHalo: 0 281 | m_Flare: {fileID: 0} 282 | m_RenderMode: 0 283 | m_CullingMask: 284 | serializedVersion: 2 285 | m_Bits: 4294967295 286 | m_RenderingLayerMask: 1 287 | m_Lightmapping: 4 288 | m_LightShadowCasterMode: 0 289 | m_AreaSize: {x: 1, y: 1} 290 | m_BounceIntensity: 1 291 | m_ColorTemperature: 6570 292 | m_UseColorTemperature: 0 293 | m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} 294 | m_UseBoundingSphereOverride: 0 295 | m_UseViewFrustumForShadowCasterCull: 1 296 | m_ShadowRadius: 0 297 | m_ShadowAngle: 0 298 | --- !u!4 &2026908712 299 | Transform: 300 | m_ObjectHideFlags: 0 301 | m_CorrespondingSourceObject: {fileID: 0} 302 | m_PrefabInstance: {fileID: 0} 303 | m_PrefabAsset: {fileID: 0} 304 | m_GameObject: {fileID: 2026908710} 305 | serializedVersion: 2 306 | m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} 307 | m_LocalPosition: {x: 0, y: 3, z: 0} 308 | m_LocalScale: {x: 1, y: 1, z: 1} 309 | m_ConstrainProportionsScale: 0 310 | m_Children: [] 311 | m_Father: {fileID: 0} 312 | m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} 313 | --- !u!114 &2026908713 314 | MonoBehaviour: 315 | m_ObjectHideFlags: 0 316 | m_CorrespondingSourceObject: {fileID: 0} 317 | m_PrefabInstance: {fileID: 0} 318 | m_PrefabAsset: {fileID: 0} 319 | m_GameObject: {fileID: 2026908710} 320 | m_Enabled: 1 321 | m_EditorHideFlags: 0 322 | m_Script: {fileID: 11500000, guid: 2e7d4119f4284164b9a4012cb41ed080, type: 3} 323 | m_Name: 324 | m_EditorClassIdentifier: 325 | --- !u!1660057539 &9223372036854775807 326 | SceneRoots: 327 | m_ObjectHideFlags: 0 328 | m_Roots: 329 | - {fileID: 675168583} 330 | - {fileID: 2026908712} 331 | -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows/LogWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Reflection; 6 | 7 | namespace WeavUtils 8 | { 9 | public partial class LogWindow : DebugGUIWindow 10 | { 11 | List transientLogs = new(); 12 | List attributeContainers = new(); 13 | Dictionary typeCache = new(); 14 | Dictionary typeInstanceCounts = new(); 15 | Dictionary persistentLogs = new(); 16 | Dictionary> attributeKeys = new(); 17 | Dictionary> debugGUIPrintFields = new(); 18 | Dictionary> debugGUIPrintProperties = new(); 19 | 20 | StringBuilder persistentLogStringBuilder = new(); 21 | 22 | GUIStyle textStyle; 23 | 24 | public override void Init() 25 | { 26 | base.Init(); 27 | RegisterAttributes(); 28 | textStyle = new GUIStyle(); 29 | textStyle.normal.textColor = Color.white; 30 | } 31 | 32 | void LateUpdate() 33 | { 34 | // Clean up expired logs 35 | int expiredCt = 0; 36 | for (int i = 0; i < transientLogs.Count; i++) 37 | { 38 | if (transientLogs[i].expiryTime <= Time.time) 39 | { 40 | expiredCt++; 41 | } 42 | } 43 | transientLogs.RemoveRange(0, expiredCt); 44 | 45 | CleanUpDeletedAttributes(); 46 | } 47 | 48 | protected override void OnGUI() 49 | { 50 | base.OnGUI(); 51 | 52 | // Only draw once per frame 53 | if (Event.current.type != EventType.Repaint) 54 | { 55 | return; 56 | } 57 | 58 | if (persistentLogs.Count + transientLogs.Count == 0) 59 | { 60 | return; 61 | } 62 | 63 | GUI.color = Color.white; 64 | GUI.backgroundColor = DebugGUI.Settings.backgroundColor; 65 | 66 | var lineHeight = GetMultilineStringSize(textStyle, in string.Empty).y; 67 | 68 | persistentLogStringBuilder.Clear(); 69 | 70 | 71 | foreach (var mb in attributeContainers) 72 | { 73 | Type type = typeCache[mb]; 74 | if (debugGUIPrintFields.ContainsKey(type)) 75 | { 76 | foreach (var field in debugGUIPrintFields[type]) 77 | { 78 | persistentLogStringBuilder.AppendLine($"{mb.name} {field.Name}: {field.GetValue(mb)}"); 79 | } 80 | } 81 | if (debugGUIPrintProperties.ContainsKey(type)) 82 | { 83 | foreach (var property in debugGUIPrintProperties[type]) 84 | { 85 | persistentLogStringBuilder.AppendLine($"{mb.name} {property.Name}: {property.GetValue(mb, null)}"); 86 | } 87 | } 88 | } 89 | 90 | foreach (var log in persistentLogs.Values) 91 | { 92 | persistentLogStringBuilder.AppendLine(log); 93 | } 94 | 95 | var persistentLogStr = persistentLogStringBuilder.ToString(); 96 | var textSize = GetMultilineStringSize(textStyle, in persistentLogStr); 97 | // Min size in case of no entries 98 | textSize.x = Mathf.Max(100, textSize.x); 99 | rect.size = textSize; 100 | 101 | textSize.y += lineHeight; 102 | float transientLogY = textSize.y; 103 | 104 | // Calculate size 105 | for (int i = 0; i < transientLogs.Count; i++) 106 | { 107 | var log = transientLogs[i]; 108 | var size = GetMultilineStringSize(textStyle, in log.text); 109 | textSize = new Vector2( 110 | Mathf.Max(size.x, textSize.x), 111 | textSize.y + size.y 112 | ); 113 | } 114 | 115 | var backgroundRect = new Rect(default, new Vector2(textSize.x, textSize.y)); 116 | DrawRect(backgroundRect, DebugGUI.Settings.backgroundColor, Padding); 117 | // Draw a little bit extra for the draggable area 118 | DrawRect(new Rect(0, 0, rect.width, rect.height), new Color(1, 1, 1, 0.05f), Padding); 119 | 120 | // Draw persistent logs 121 | DrawLabel(new Rect(default, textSize), persistentLogStr, Padding, textStyle); 122 | 123 | // Draw transient logs 124 | for (int i = transientLogs.Count - 1; i >= 0; i--) 125 | { 126 | // Clear up transient logs going off screen 127 | if (transientLogY > Screen.height) 128 | { 129 | transientLogs.RemoveRange(0, i + 1); 130 | break; 131 | } 132 | 133 | var log = transientLogs[i]; 134 | DrawLabel(Vector2.up * transientLogY, log.text, Padding, textStyle); 135 | transientLogY += lineHeight; 136 | } 137 | } 138 | 139 | public void Log(string str) 140 | { 141 | transientLogs.Add(new TransientLog(str, Time.time + DebugGUI.Settings.temporaryLogLifetime)); 142 | } 143 | 144 | public void LogPersistent(object key, string message) 145 | { 146 | if (persistentLogs.ContainsKey(key)) 147 | persistentLogs[key] = message; 148 | else 149 | persistentLogs.Add(key, message); 150 | } 151 | 152 | public void RemovePersistent(object key) 153 | { 154 | if (persistentLogs.ContainsKey(key)) 155 | { 156 | persistentLogs.Remove(key); 157 | } 158 | } 159 | 160 | public void ClearPersistent() 161 | { 162 | persistentLogs.Clear(); 163 | } 164 | 165 | public void ReinitializeAttributes() 166 | { 167 | // Clean up graphs 168 | List toRemove = new List(); 169 | foreach (var key in persistentLogs.Keys) 170 | { 171 | if (key is PersistentLogAttributeKey) 172 | toRemove.Add(key); 173 | } 174 | foreach (var key in toRemove) 175 | { 176 | persistentLogs.Remove(key); 177 | } 178 | 179 | attributeContainers = new(); 180 | debugGUIPrintFields = new(); 181 | debugGUIPrintProperties = new(); 182 | typeInstanceCounts = new(); 183 | attributeKeys = new(); 184 | } 185 | 186 | private void RegisterAttributes() 187 | { 188 | foreach (var mb in FindObjectsOfType()) 189 | { 190 | Type mbType = mb.GetType(); 191 | 192 | HashSet uniqueAttributeContainers = new(); 193 | 194 | // Fields 195 | { 196 | // Retreive the fields from the mono instance 197 | FieldInfo[] objectFields = mbType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 198 | 199 | // search all fields/properties for the [DebugGUIVar] attribute 200 | for (int i = 0; i < objectFields.Length; i++) 201 | { 202 | DebugGUIPrintAttribute printAttribute = Attribute.GetCustomAttribute(objectFields[i], typeof(DebugGUIPrintAttribute)) as DebugGUIPrintAttribute; 203 | 204 | if (printAttribute != null) 205 | { 206 | uniqueAttributeContainers.Add(mb); 207 | typeCache[mb] = mb.GetType(); 208 | if (!debugGUIPrintFields.ContainsKey(mbType)) 209 | { 210 | debugGUIPrintFields.Add(mbType, new HashSet()); 211 | } 212 | 213 | debugGUIPrintFields[mbType].Add(objectFields[i]); 214 | } 215 | } 216 | } 217 | 218 | // Properties 219 | { 220 | PropertyInfo[] objectProperties = mbType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 221 | 222 | for (int i = 0; i < objectProperties.Length; i++) 223 | { 224 | if (Attribute.GetCustomAttribute(objectProperties[i], typeof(DebugGUIPrintAttribute)) is DebugGUIPrintAttribute) 225 | { 226 | uniqueAttributeContainers.Add(mb); 227 | typeCache[mb] = mb.GetType(); 228 | 229 | if (!debugGUIPrintProperties.ContainsKey(mbType)) 230 | { 231 | debugGUIPrintProperties.Add(mbType, new HashSet()); 232 | } 233 | debugGUIPrintProperties[mbType].Add(objectProperties[i]); 234 | } 235 | } 236 | } 237 | 238 | foreach (var attributeContainer in uniqueAttributeContainers) 239 | { 240 | attributeContainers.Add(attributeContainer); 241 | Type type = attributeContainer.GetType(); 242 | if (!typeInstanceCounts.ContainsKey(type)) 243 | typeInstanceCounts.Add(type, 0); 244 | typeInstanceCounts[type]++; 245 | } 246 | } 247 | } 248 | 249 | private void CleanUpDeletedAttributes() 250 | { 251 | for (int i = 0; i < attributeContainers.Count; i++) 252 | { 253 | var mb = attributeContainers[i]; 254 | if (attributeContainers[i] == null) 255 | { 256 | attributeKeys.Remove(mb); 257 | typeCache.Remove(mb); 258 | 259 | Type type = mb.GetType(); 260 | typeInstanceCounts[type]--; 261 | if (typeInstanceCounts[type] == 0) 262 | { 263 | if (debugGUIPrintFields.ContainsKey(type)) 264 | debugGUIPrintFields.Remove(type); 265 | if (debugGUIPrintProperties.ContainsKey(type)) 266 | debugGUIPrintProperties.Remove(type); 267 | } 268 | attributeContainers.RemoveAt(i); 269 | 270 | i--; 271 | } 272 | } 273 | } 274 | 275 | private struct TransientLog 276 | { 277 | public string text; 278 | public float expiryTime; 279 | 280 | public TransientLog(string text, float expiryTime) 281 | { 282 | this.text = text; 283 | this.expiryTime = expiryTime; 284 | } 285 | } 286 | 287 | // Wrapper to differentiate attributes from 288 | // manually created logs 289 | public class PersistentLogAttributeKey 290 | { 291 | public MemberInfo memberInfo; 292 | public PersistentLogAttributeKey(MemberInfo memberInfo) 293 | { 294 | this.memberInfo = memberInfo; 295 | } 296 | } 297 | } 298 | } -------------------------------------------------------------------------------- /Assets/Plugins/DebugGUI/Windows/GraphWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using static DebugGUI; 6 | 7 | namespace WeavUtils 8 | { 9 | public class GraphWindow : DebugGUIWindow 10 | { 11 | const int graphLabelFontSize = 12; 12 | const int graphLabelPadding = 5; 13 | const int graphBlockPadding = 3; 14 | const int scrubberBackgroundWidth = 55; 15 | 16 | List graphs = new(); 17 | HashSet attributeContainers = new(); 18 | Dictionary typeInstanceCounts = new(); 19 | Dictionary graphDictionary = new(); 20 | Dictionary> attributeKeys = new(); 21 | Dictionary> debugGUIGraphFields = new(); 22 | Dictionary> debugGUIGraphProperties = new(); 23 | SortedDictionary> graphGroups = new(); 24 | 25 | bool freezeGraphs; 26 | float graphLabelBoxWidth; 27 | 28 | GUIStyle graphLabelStyle; 29 | 30 | protected void InitializeGUIStyles() 31 | { 32 | graphLabelStyle = new GUIStyle(); 33 | graphLabelStyle.fontSize = graphLabelFontSize; 34 | } 35 | 36 | public override void Init() 37 | { 38 | base.Init(); 39 | 40 | InitializeGUIStyles(); 41 | RegisterAttributes(); 42 | 43 | // Default to top right 44 | rect.position = new Vector2(Screen.width - GetDraggableRect().width, 0); 45 | } 46 | 47 | void LateUpdate() 48 | { 49 | if (!Input.GetMouseButton(0)) 50 | { 51 | freezeGraphs = false; 52 | } 53 | 54 | if (!freezeGraphs) 55 | { 56 | CleanUpDeletedAttributes(); 57 | PollGraphAttributes(); 58 | } 59 | } 60 | 61 | protected override void OnGUI() 62 | { 63 | base.OnGUI(); 64 | 65 | int groupNum = 0; 66 | foreach (var group in graphGroups.Values) 67 | { 68 | DrawGraphGroup(group, groupNum); 69 | groupNum++; 70 | } 71 | 72 | foreach (var label in deferredLabels) 73 | { 74 | graphLabelStyle.normal.textColor = label.color; 75 | DrawLabel(label.position, label.label, style: graphLabelStyle); 76 | } 77 | deferredLabels.Clear(); 78 | } 79 | 80 | public void Graph(object key, float val) 81 | { 82 | if (!graphDictionary.ContainsKey(key)) 83 | { 84 | CreateGraph(key); 85 | } 86 | 87 | if (freezeGraphs) return; 88 | 89 | graphDictionary[key].Push(val); 90 | // Todo: optimize away? 91 | RecalculateGraphLabelWidth(); 92 | } 93 | 94 | public void CreateGraph(object key) 95 | { 96 | AddGraph(key, new GraphContainer(Settings.graphWidth)); 97 | RecalculateGraphLabelWidth(); 98 | } 99 | 100 | public void ClearGraph(object key) 101 | { 102 | if (graphDictionary.ContainsKey(key)) 103 | graphDictionary[key].Clear(); 104 | } 105 | 106 | public void RemoveGraph(object key) 107 | { 108 | if (graphDictionary.ContainsKey(key)) 109 | { 110 | var graph = graphDictionary[key]; 111 | graphs.Remove(graph); 112 | graphDictionary.Remove(key); 113 | graphGroups[graph.group].Remove(graph); 114 | if (graphGroups[graph.group].Count == 0) 115 | { 116 | graphGroups.Remove(graph.group); 117 | } 118 | RecalculateGraphLabelWidth(); 119 | } 120 | } 121 | 122 | public void SetGraphProperties(object key, string label, float min, float max, int group, Color color, bool autoScale) 123 | { 124 | if (graphDictionary.ContainsKey(key)) 125 | { 126 | RemoveGraph(key); 127 | } 128 | 129 | var graph = new GraphContainer(Settings.graphWidth, group); 130 | AddGraph(key, graph); 131 | 132 | graph.name = label; 133 | graph.SetMinMax(min, max); 134 | graph.color = color; 135 | graph.autoScale = autoScale; 136 | } 137 | 138 | public void ReinitializeAttributes() 139 | { 140 | // Clean up graphs 141 | List toRemove = new List(); 142 | foreach (var key in graphDictionary.Keys) 143 | { 144 | if (key is GraphAttributeKey) 145 | toRemove.Add(key); 146 | } 147 | foreach (var key in toRemove) 148 | { 149 | RemoveGraph(key); 150 | } 151 | 152 | attributeContainers = new(); 153 | debugGUIGraphFields = new(); 154 | debugGUIGraphProperties = new(); 155 | typeInstanceCounts = new(); 156 | attributeKeys = new(); 157 | 158 | RegisterAttributes(); 159 | } 160 | 161 | [Serializable] 162 | class DataExport 163 | { 164 | public GraphContainer.DataExport[] data; 165 | } 166 | public string ToJson() 167 | { 168 | var dataExport = new GraphContainer.DataExport[graphs.Count]; 169 | 170 | for (int i = 0; i < graphs.Count; i++) 171 | { 172 | dataExport[i] = graphs[i].AsDataExport(); 173 | } 174 | 175 | var json = JsonUtility.ToJson(new DataExport() { data = dataExport }); 176 | return json; 177 | } 178 | 179 | public override Rect GetDraggableRect() 180 | { 181 | RefreshRect(); 182 | return base.GetDraggableRect(); 183 | } 184 | 185 | private void AddGraph(object key, GraphContainer graph) 186 | { 187 | graph.OnLabelSizeChange += RefreshRect; 188 | 189 | graphDictionary.Add(key, graph); 190 | graphs.Add(graph); 191 | 192 | if (!graphGroups.ContainsKey(graph.group)) 193 | { 194 | graphGroups.Add(graph.group, new List()); 195 | } 196 | 197 | graphGroups[graph.group].Add(graph); 198 | 199 | RecalculateGraphLabelWidth(); 200 | } 201 | 202 | private void PollGraphAttributes() 203 | { 204 | foreach (var node in attributeContainers) 205 | { 206 | if (node != null && attributeKeys.ContainsKey(node)) 207 | { 208 | foreach (var key in attributeKeys[node]) 209 | { 210 | if (key.memberInfo is FieldInfo fieldInfo) 211 | { 212 | float? val = fieldInfo.GetValue(node) as float?; 213 | if (val != null) 214 | graphDictionary[key].Push(val.Value); 215 | } 216 | else if (key.memberInfo is PropertyInfo propertyInfo) 217 | { 218 | float? val = propertyInfo.GetValue(node, null) as float?; 219 | if (val != null) 220 | graphDictionary[key].Push(val.Value); 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | GraphContainer lastPressedGraphLabel; 228 | private void DrawGraphGroup(List group, int groupNum) 229 | { 230 | Vector2 relativeMousePos = Input.mousePosition; 231 | relativeMousePos.y = Screen.height - relativeMousePos.y; 232 | relativeMousePos -= rect.position; 233 | 234 | Vector2 graphBlockSize = new Vector2(Settings.graphWidth + graphBlockPadding, Settings.graphHeight + graphBlockPadding); 235 | 236 | var groupOrigin = new Vector2(0, graphBlockSize.y * groupNum); 237 | var groupGraphRect = new Rect( 238 | groupOrigin.x + graphLabelBoxWidth + graphBlockPadding, 239 | groupOrigin.y, 240 | Settings.graphWidth, 241 | Settings.graphHeight 242 | ); 243 | 244 | // Label background 245 | DrawRect(new Rect( 246 | groupOrigin.x, 247 | groupOrigin.y, 248 | graphLabelBoxWidth, 249 | Settings.graphHeight), 250 | Settings.backgroundColor); 251 | 252 | // Graph background 253 | DrawRect(new Rect( 254 | groupOrigin.x + graphBlockPadding + graphLabelBoxWidth, 255 | groupOrigin.y, 256 | graphBlockSize.x, 257 | Settings.graphHeight), 258 | Settings.backgroundColor); 259 | 260 | // Magic padding offsets 261 | Vector2 textOrigin = groupOrigin + new Vector2(0, 14); 262 | Vector2 minMaxOrigin = groupOrigin + new Vector2(graphLabelBoxWidth - 10, 0); 263 | foreach (var graph in group) 264 | { 265 | var textSize = GetMultilineStringSize(graphLabelStyle, in graph.name); 266 | textOrigin.y += textSize.y; 267 | var maxWidthOfMinMaxStrings = Mathf.Max( 268 | GetMultilineStringSize(graphLabelStyle, graph.minString).x, 269 | GetMultilineStringSize(graphLabelStyle, graph.maxString).x 270 | ); 271 | minMaxOrigin += Vector2.left * (maxWidthOfMinMaxStrings + graphLabelPadding); 272 | 273 | // Label button logic 274 | var labelRect = new Rect(textOrigin - textSize + new Vector2(graphLabelBoxWidth - (graphLabelPadding * 2), graphLabelPadding), textSize); 275 | // Enable disable 276 | var isHovered = labelRect.Contains(relativeMousePos); 277 | var isPressed = isHovered && Input.GetMouseButton(0); 278 | 279 | // Button click 280 | if (lastPressedGraphLabel == graph && !isPressed && isHovered) 281 | { 282 | graph.visible = !graph.visible; 283 | } 284 | 285 | if (isPressed) 286 | { 287 | lastPressedGraphLabel = graph; 288 | } 289 | else if (lastPressedGraphLabel == graph) 290 | { 291 | lastPressedGraphLabel = null; 292 | } 293 | 294 | var graphColor = graph.GetModifiedColor(isHovered); 295 | 296 | // Name 297 | DrawLabelDeferred( 298 | labelRect.position, 299 | graph.name, 300 | graphColor 301 | ); 302 | 303 | // Max 304 | DrawLabelDeferred( 305 | minMaxOrigin, 306 | graph.maxString, 307 | graphColor 308 | ); 309 | 310 | // Min 311 | DrawLabelDeferred( 312 | minMaxOrigin + new Vector2(0, Settings.graphHeight - 20), 313 | graph.minString, 314 | graphColor 315 | ); 316 | 317 | // Graph 318 | if (graph.visible) 319 | { 320 | graph.Draw(new Rect( 321 | groupGraphRect.position + rect.position, 322 | groupGraphRect.size 323 | )); 324 | } 325 | } 326 | 327 | // Scrubber 328 | if (groupGraphRect.Contains(relativeMousePos)) 329 | { 330 | if (Input.GetMouseButton(0)) 331 | { 332 | freezeGraphs = true; 333 | } 334 | 335 | // Background 336 | Vector2 scrubberOrigin = new Vector2(relativeMousePos.x, groupOrigin.y); 337 | if (relativeMousePos.x > groupGraphRect.max.x - scrubberBackgroundWidth) 338 | { 339 | scrubberOrigin.x -= scrubberBackgroundWidth; 340 | } 341 | 342 | var rect = new Rect( 343 | scrubberOrigin.x, 344 | scrubberOrigin.y, 345 | scrubberBackgroundWidth, 346 | Settings.graphHeight 347 | ); 348 | DrawRect(rect, Settings.backgroundColor); 349 | 350 | DrawLine( 351 | new Vector2( 352 | relativeMousePos.x, 353 | groupOrigin.y 354 | ), new Vector2( 355 | relativeMousePos.x, 356 | groupOrigin.y + Settings.graphHeight 357 | ), 358 | Settings.scrubberColor 359 | ); 360 | 361 | // Scrubber labels 362 | Vector2 textPos = scrubberOrigin + new Vector2(graphLabelPadding, graphLabelPadding * 3); 363 | var groupMousePosX = (relativeMousePos.x - groupOrigin.x); 364 | int sampleIndex = (int)(groupGraphRect.width - groupMousePosX + graphLabelBoxWidth + graphBlockPadding); 365 | foreach (GraphContainer graph in group) 366 | { 367 | var text = graph.GetValue(sampleIndex).ToString("F3"); 368 | DrawLabelDeferred( 369 | textPos, 370 | text, 371 | color: graph.color 372 | ); 373 | 374 | textPos.y += GetMultilineStringSize(graphLabelStyle, in string.Empty).y; 375 | } 376 | } 377 | } 378 | 379 | private Rect GetGraphWindowRect() 380 | { 381 | return new Rect( 382 | new Vector2(-graphLabelBoxWidth, 0) + rect.position, 383 | new Vector2( 384 | Settings.graphWidth + graphLabelBoxWidth + graphBlockPadding, 385 | (Settings.graphHeight + graphBlockPadding) * graphGroups.Count 386 | ) 387 | ); 388 | } 389 | 390 | private void RegisterAttributes() 391 | { 392 | foreach (var mb in FindObjectsOfType()) 393 | { 394 | Type mbType = mb.GetType(); 395 | 396 | HashSet uniqueAttributeContainers = new(); 397 | 398 | // Fields 399 | { 400 | // Retreive the fields from the mono instance 401 | FieldInfo[] objectFields = mbType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 402 | 403 | // search all fields/properties for the [DebugGUIVar] attribute 404 | for (int i = 0; i < objectFields.Length; i++) 405 | { 406 | DebugGUIGraphAttribute graphAttribute = Attribute.GetCustomAttribute(objectFields[i], typeof(DebugGUIGraphAttribute)) as DebugGUIGraphAttribute; 407 | 408 | if (graphAttribute != null) 409 | { 410 | // Can't cast to float so we don't bother registering it 411 | if (objectFields[i].GetValue(mb) as float? == null) 412 | { 413 | Debug.LogError($"Cannot cast {mbType.Name}.{objectFields[i].Name} to float. This member will be ignored."); 414 | continue; 415 | } 416 | 417 | uniqueAttributeContainers.Add(mb); 418 | if (!debugGUIGraphFields.ContainsKey(mbType)) 419 | debugGUIGraphFields.Add(mbType, new HashSet()); 420 | if (!debugGUIGraphProperties.ContainsKey(mbType)) 421 | debugGUIGraphProperties.Add(mbType, new HashSet()); 422 | 423 | debugGUIGraphFields[mbType].Add(objectFields[i]); 424 | GraphContainer graph = 425 | new GraphContainer(Settings.graphWidth, graphAttribute.group) 426 | { 427 | name = objectFields[i].Name, 428 | max = graphAttribute.max, 429 | min = graphAttribute.min, 430 | autoScale = graphAttribute.autoScale 431 | }; 432 | graph.OnLabelSizeChange += RefreshRect; 433 | if (!graphAttribute.color.Equals(default(Color))) 434 | graph.color = graphAttribute.color; 435 | 436 | var key = new GraphAttributeKey(objectFields[i]); 437 | if (!attributeKeys.ContainsKey(mb)) 438 | attributeKeys.Add(mb, new List()); 439 | attributeKeys[mb].Add(key); 440 | 441 | AddGraph(key, graph); 442 | } 443 | } 444 | } 445 | 446 | // Properties 447 | { 448 | PropertyInfo[] objectProperties = mbType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 449 | 450 | for (int i = 0; i < objectProperties.Length; i++) 451 | { 452 | if (Attribute.GetCustomAttribute(objectProperties[i], typeof(DebugGUIGraphAttribute)) is DebugGUIGraphAttribute graphAttribute) 453 | { 454 | // Can't cast to float so we don't bother registering it 455 | if (objectProperties[i].GetValue(mb, null) as float? == null) 456 | { 457 | Debug.LogError($"Cannot cast {mbType.Name}.{objectProperties[i].Name} to float. This member will be ignored."); 458 | continue; 459 | } 460 | 461 | uniqueAttributeContainers.Add(mb); 462 | 463 | if (!debugGUIGraphFields.ContainsKey(mbType)) 464 | debugGUIGraphFields.Add(mbType, new HashSet()); 465 | if (!debugGUIGraphProperties.ContainsKey(mbType)) 466 | debugGUIGraphProperties.Add(mbType, new HashSet()); 467 | 468 | debugGUIGraphProperties[mbType].Add(objectProperties[i]); 469 | 470 | GraphContainer graph = 471 | new GraphContainer(Settings.graphWidth, graphAttribute.group) 472 | { 473 | name = objectProperties[i].Name, 474 | max = graphAttribute.max, 475 | min = graphAttribute.min, 476 | autoScale = graphAttribute.autoScale 477 | }; 478 | graph.OnLabelSizeChange += RefreshRect; 479 | if (!graphAttribute.color.Equals(default(Color))) 480 | graph.color = graphAttribute.color; 481 | 482 | var key = new GraphAttributeKey(objectProperties[i]); 483 | if (!attributeKeys.ContainsKey(mb)) 484 | attributeKeys.Add(mb, new List()); 485 | attributeKeys[mb].Add(key); 486 | 487 | AddGraph(key, graph); 488 | } 489 | } 490 | } 491 | 492 | foreach (var attributeContainer in uniqueAttributeContainers) 493 | { 494 | attributeContainers.Add(attributeContainer); 495 | Type type = attributeContainer.GetType(); 496 | if (!typeInstanceCounts.ContainsKey(type)) 497 | typeInstanceCounts.Add(type, 0); 498 | typeInstanceCounts[type]++; 499 | } 500 | } 501 | } 502 | 503 | private void CleanUpDeletedAttributes() 504 | { 505 | // Clear out associated keys 506 | foreach (var mb in attributeContainers) 507 | { 508 | if (mb == null) 509 | { 510 | var keys = attributeKeys[mb]; 511 | foreach (var key in keys) 512 | { 513 | RemoveGraph(key); 514 | } 515 | attributeKeys.Remove(mb); 516 | 517 | Type type = mb.GetType(); 518 | typeInstanceCounts[type]--; 519 | if (typeInstanceCounts[type] == 0) 520 | { 521 | if (debugGUIGraphFields.ContainsKey(type)) 522 | debugGUIGraphFields.Remove(type); 523 | if (debugGUIGraphProperties.ContainsKey(type)) 524 | debugGUIGraphProperties.Remove(type); 525 | } 526 | } 527 | } 528 | 529 | // Finally clear out removed nodes 530 | attributeContainers.RemoveWhere(node => node == null); 531 | } 532 | 533 | void RefreshRect() 534 | { 535 | var lastWidth = rect.width; 536 | RecalculateGraphLabelWidth(); 537 | rect.size = new Vector2( 538 | Settings.graphWidth + graphLabelBoxWidth + graphBlockPadding, 539 | (Settings.graphHeight + graphBlockPadding) * graphGroups.Count); 540 | // Grow to the left instead of right 541 | rect.position += new Vector2(lastWidth - rect.width, 0); 542 | } 543 | 544 | void RecalculateGraphLabelWidth() 545 | { 546 | float width = 0; 547 | foreach (var group in graphGroups.Values) 548 | { 549 | float minMaxWidth = graphLabelPadding; 550 | foreach (var graph in group) 551 | { 552 | // Names 553 | width = Mathf.Max(GetMultilineStringSize(graphLabelStyle, in graph.name).x, width); 554 | 555 | // Minmax labels per group 556 | var maxWidthOfMinMaxStrings = Mathf.Max( 557 | GetMultilineStringSize(graphLabelStyle, graph.minString).x, 558 | GetMultilineStringSize(graphLabelStyle, graph.maxString).x 559 | ); 560 | minMaxWidth += maxWidthOfMinMaxStrings + graphLabelPadding; 561 | } 562 | width = Mathf.Max(minMaxWidth, width); 563 | } 564 | graphLabelBoxWidth = width + graphLabelPadding * 2; 565 | } 566 | 567 | // GUI calls will break later GL calls, so we defer the label drawing 568 | 569 | List deferredLabels = new(); 570 | struct DeferredLabel 571 | { 572 | public Vector2 position; 573 | public string label; 574 | public Color color; 575 | } 576 | 577 | void DrawLabelDeferred(Vector2 pos, string label, Color color) 578 | { 579 | deferredLabels.Add(new DeferredLabel() { position = pos, label = label, color = color }); 580 | } 581 | 582 | private class GraphContainer 583 | { 584 | public Action OnLabelSizeChange; 585 | 586 | public string name = ""; 587 | 588 | // Value at the top of the graph 589 | public float max = 1; 590 | // Value at the bottom of the graph 591 | public float min = 0; 592 | public bool autoScale; 593 | public Color color; 594 | // Graph order on screen 595 | public readonly int group; 596 | 597 | private int currentIndex; 598 | private readonly float[] values; 599 | private readonly Vector2[] graphPoints; 600 | 601 | public string minString = null; 602 | public string maxString = null; 603 | public bool visible = true; 604 | 605 | public GraphContainer(int width, int group = 0) 606 | { 607 | this.group = group; 608 | values = new float[width]; 609 | graphPoints = new Vector2[width]; 610 | SetMinMax(min, max); 611 | } 612 | 613 | public Color GetModifiedColor(bool highlighted) 614 | { 615 | if (!highlighted && visible) return color; 616 | 617 | Color.RGBToHSV(color, out float h, out float s, out float v); 618 | 619 | if (!visible) v *= 0.3f; 620 | if (highlighted) v *= (v > 0.9f ? 0.7f : 1.2f); 621 | return Color.HSVToRGB(h, s, v); 622 | } 623 | 624 | public void SetMinMax(float min, float max) 625 | { 626 | OnLabelSizeChange?.Invoke(); 627 | this.min = min; 628 | this.max = max; 629 | 630 | minString = min.ToString("F2"); 631 | maxString = max.ToString("F2"); 632 | } 633 | 634 | // Add a data point to the beginning of the graph 635 | public void Push(float val) 636 | { 637 | if (autoScale && (val > max || val < min)) 638 | { 639 | SetMinMax(Mathf.Min(val, min), Mathf.Max(val, max)); 640 | } 641 | else 642 | { 643 | // Prevent drawing outside frame 644 | val = Mathf.Clamp(val, min, max); 645 | } 646 | 647 | values[currentIndex] = val; 648 | currentIndex = (currentIndex + 1) % values.Length; 649 | } 650 | 651 | public void Clear() 652 | { 653 | for (int i = 0; i < values.Length; i++) 654 | { 655 | values[i] = 0; 656 | } 657 | } 658 | 659 | public void Draw(Rect rect) 660 | { 661 | GL.Begin(GL.LINE_STRIP); 662 | { 663 | GL.Color(color); 664 | 665 | int num = values.Length; 666 | for (int i = 0; i < num; i++) 667 | { 668 | float value = values[Mod(currentIndex - i - 1, values.Length)]; 669 | // Note flipped inverse lerp min max to account for y = down in GL 670 | GL.Vertex3( 671 | rect.x + (rect.width * ((float)i / num)), 672 | rect.y + (Mathf.InverseLerp(max, min, value) * Settings.graphHeight), 673 | 0.0f); 674 | } 675 | } 676 | GL.End(); 677 | } 678 | 679 | public float GetValue(int index) 680 | { 681 | return values[Mod(currentIndex + index, values.Length)]; 682 | } 683 | 684 | [Serializable] 685 | public class DataExport 686 | { 687 | public string name; 688 | public float[] values; 689 | 690 | public DataExport(string name, float[] values) 691 | { 692 | this.name = name; 693 | this.values = values; 694 | } 695 | } 696 | 697 | public DataExport AsDataExport() 698 | { 699 | return new DataExport(name, values); 700 | } 701 | 702 | private static int Mod(int n, int m) 703 | { 704 | return ((n % m) + m) % m; 705 | } 706 | } 707 | 708 | public class GraphAttributeKey 709 | { 710 | public MemberInfo memberInfo; 711 | public GraphAttributeKey(MemberInfo memberInfo) 712 | { 713 | this.memberInfo = memberInfo; 714 | } 715 | } 716 | } 717 | } --------------------------------------------------------------------------------