2020/DebugConsole
Author: Serge Billault
Contents |
Description
A more up to date, as of 2020 (Unity 5.6.1f1 through 2019.3.8.f1), a little more professional, minimal debug console.
Archive File
Archive content
A c# script.
A Resources folder containing a GUI skin.
Usage
. Installation: Unzip the archive content in your Standard Assets folder (required).
. Setup: no action required.
. Scripting: no action required.
. Unity Legacy Compatibility (old VS new Unity Input System): no action required.
Professionals
. Net compatibility level: 2.0 minimum - no dynamic methods.
. Unsafe code: None.
. Reflectivity: Some.
. Networking: None.
. QA Teams functions support: None.
. Advanced features: None.
. Performances: Suitable for productions (translation for students: High).
C# - DebugConsole.cs
The script must be named DebugConsole.cs. It has no real use. It is for compliance with other wiki articles only. The script is available through the archive linked at the top of this page.
using System.Reflection; using System.Diagnostics; using System.Collections.Generic; //******************************************************************************************************** // //******************************************************************************************************** using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; //******************************************************************************************************** // //******************************************************************************************************** namespace DEBUG { //**************************************************************************************************** // //**************************************************************************************************** public enum TRACE { NONE, SHORT, FULL, DEFAULT = NONE } //**************************************************************************************************** // //**************************************************************************************************** static public class Debug { //************************************************************************************************ // //************************************************************************************************ private const string FMT_TRACE = " * {0} : line {1} : {2}()\r\n"; private const char MEMBER_SEP = '.'; //************************************************************************************************ // //************************************************************************************************ static private string QualifiedMethodName( StackFrame frame ) { return frame.GetMethod().DeclaringType.Name + MEMBER_SEP + frame.GetMethod().Name; } //************************************************************************************************ // //************************************************************************************************ static private string Msg( string msg, TRACE trace = TRACE.DEFAULT ) { switch( trace ) { //**************************************************************************************** // //**************************************************************************************** case TRACE.FULL: { StackTrace full = new StackTrace ( 2 , true ); StackFrame[] frames = full.GetFrames(); string output = string.Empty; for( int frame = frames.Length - 1; frame >= 0; --frame ) { StackFrame Frame = frames[ frame ]; output += string.Format( FMT_TRACE, Frame.GetFileName(), Frame.GetFileLineNumber(), QualifiedMethodName( Frame ) ); } return output + msg; } //**************************************************************************************** // //**************************************************************************************** case TRACE.SHORT: { StackTrace stack = new StackTrace( 2 , true ); StackFrame Frame = stack.GetFrame( 0 ); return string.Format( FMT_TRACE, Frame.GetFileName(), Frame.GetFileLineNumber(), QualifiedMethodName( Frame ) ) + msg; } //**************************************************************************************** // //**************************************************************************************** default : { return msg; } } } //************************************************************************************************ // //************************************************************************************************ static public void Log ( object msg, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.Log ( Msg( msg.ToString(), trace ) ); } static public void LogWarning( object msg, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogWarning( Msg( msg.ToString(), trace ) ); } static public void LogError ( object msg, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogError ( Msg( msg.ToString(), trace ) ); } //************************************************************************************************ // //************************************************************************************************ static public void Log ( object msg, UnityEngine.Object context, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.Log ( Msg( msg.ToString(), trace ), context ); } static public void LogWarning( object msg, UnityEngine.Object context, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogWarning( Msg( msg.ToString(), trace ), context ); } static public void LogError ( object msg, UnityEngine.Object context, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogError ( Msg( msg.ToString(), trace ), context ); } } //**************************************************************************************************** // //**************************************************************************************************** public struct Entry { public int category; public string date; public string msg; } //**************************************************************************************************** // //**************************************************************************************************** public class Buffer { //************************************************************************************************ // //************************************************************************************************ private Entry[] m_entries = null; private int m_capacity = 0; private int m_start = 0; private int m_end = 0; private int m_count = 0; //************************************************************************************************ // //************************************************************************************************ public int capacity { get { return m_capacity; } } public int start { get { return m_start; } } public int end { get { return m_end; } } public int count { get { return m_count; } } //************************************************************************************************ // //************************************************************************************************ static public ulong Align( ulong s, ulong a ) { return ( s + ( a - 1 ) ) & ~( a - 1 ); } //************************************************************************************************ // //************************************************************************************************ public Buffer( int paramCapacity = 0 ) { m_capacity = ( paramCapacity != 0 ) ? ( int )Align( ( ulong )Mathf.Max( 4, paramCapacity ), 4 ) : 1024; m_entries = new Entry[ m_capacity ]; } //************************************************************************************************ // //************************************************************************************************ public void Clear( ) { m_start = 0; m_end = 0; m_count = 0; } //************************************************************************************************ // //************************************************************************************************ public void Push( string date, string msg, int category ) { if( m_count < m_capacity ) ++m_count; else m_start = ( m_start + 1 ) & ( m_capacity - 1 ); m_end = ( m_start + ( m_count - 1 ) ) & ( m_capacity - 1 ); m_entries[ m_end ].category = category; m_entries[ m_end ].date = date; m_entries[ m_end ].msg = msg; } //************************************************************************************************ // //************************************************************************************************ public Entry this[ int index ] { get { return m_entries[ index & ( m_capacity - 1 ) ]; } } } //**************************************************************************************************** // //**************************************************************************************************** static public class GUIStacks { static private readonly Stack< GUISkin > m_skin = new Stack< GUISkin >( 16 ); static private readonly Stack< Color > m_color = new Stack< Color >( 16 ); static private readonly Stack< Color > m_cont = new Stack< Color >( 16 ); static private readonly Stack< Color > m_bgnd = new Stack< Color >( 16 ); static public void PushSkin ( GUISkin skin ) { m_skin.Push ( GUI.skin ); GUI.skin = skin; } static public void PushColor ( Color color ) { m_color.Push( GUI.color ); GUI.color = color; } static public void PushContentColor ( Color color ) { m_cont.Push ( GUI.contentColor ); GUI.contentColor = color; } static public void PushBackgroundColor( Color color ) { m_bgnd.Push ( GUI.backgroundColor ); GUI.backgroundColor = color; } static public void PushSkin () { m_skin.Push ( GUI.skin ); } static public void PushColor () { m_color.Push( GUI.color ); } static public void PushContentColor () { m_cont.Push ( GUI.contentColor ); } static public void PushBackgroundColor() { m_bgnd.Push ( GUI.backgroundColor ); } static public void PopSkin () { GUI.skin = m_skin.Pop (); } static public void PopColor () { GUI.color = m_color.Pop(); } static public void PopContentColor () { GUI.contentColor = m_cont.Pop (); } static public void PopBackgroundColor () { GUI.backgroundColor = m_bgnd.Pop (); } } //**************************************************************************************************** // //**************************************************************************************************** public class GUIDropDown { //************************************************************************************************ // //************************************************************************************************ static private Color COLOR_NORMAL = new Color( 0.6f, 0.6f, 0.6f, 1.0f ); static private Color COLOR_SELECTED = new Color( 1.0f, 1.0f, 1.0f, 1.0f ); private const int MAX_LINES = 8; private const float LINE_H = 32.0f; //************************************************************************************************ // //************************************************************************************************ private Rect m_selectionRect = default( Rect ); private Rect m_scrollRect = default( Rect ); private Rect m_viewRect = default( Rect ); private object[] m_options = null; private int m_nbOptions = 0; private int m_nbDisplayed = 0; private bool m_expanded = false; private Vector2 m_scroll = Vector2.zero; private int m_selected = 0; //************************************************************************************************ // //************************************************************************************************ public GUIDropDown( params object[] options ) { m_options = options; } //************************************************************************************************ // //************************************************************************************************ private void UpdateLayout( Rect rect, int selected, object[] options ) { if( Event.current.type != EventType.Layout ) return; m_options = options; m_nbOptions = ( options != null ) ? options.Length : 0; m_nbDisplayed = Mathf.Min( m_nbOptions, MAX_LINES ); m_selected = Mathf.Min( m_nbOptions - 1, selected ); m_selectionRect = rect; if( m_expanded ) { m_scrollRect = new Rect ( rect.x, rect.y + rect.height, rect.width, m_nbDisplayed * LINE_H ); m_viewRect = new Rect ( 0.0f, 0.0f, m_scrollRect.width - ( m_nbDisplayed < m_nbOptions ? 15.0f : 0.0f ), m_nbOptions * LINE_H ); } } //************************************************************************************************ // //************************************************************************************************ private void DrawOptions( GUISkin skin ) { if( m_expanded ) { GUI.skin = skin; int firstVisible = ( int )( m_scroll.y / LINE_H ); int lastVisible = Mathf.Clamp( firstVisible + m_nbDisplayed, 0, m_nbOptions - 1 ); GUIStacks.PushContentColor(); m_scroll = GUI.BeginScrollView( m_scrollRect, m_scroll, m_viewRect, false, false ); for( int option = firstVisible; option <= lastVisible; ++option ) { GUI.contentColor = ( option == m_selected ) ? COLOR_SELECTED : COLOR_NORMAL; if( GUI.Button( new Rect( 0.0f, option * LINE_H, m_viewRect.width, LINE_H ), m_options[ option ].ToString() ) ) { m_selected = option; m_expanded = false; } } GUI.EndScrollView(); GUIStacks.PopContentColor(); } } //************************************************************************************************ // //************************************************************************************************ private void DrawSelection( GUISkin skin ) { GUI.skin = skin; object selection = ( m_nbOptions > 0 ) ? m_options[ m_selected ] : null; string selectionStr = ( selection != null ) ? selection.ToString() : "<NULL>"; if( GUI.Button( m_selectionRect, selectionStr ) && ( m_nbOptions > 0 ) ) { m_expanded = ! m_expanded; } } //************************************************************************************************ // //************************************************************************************************ public int Draw( GUISkin btn_skin, GUISkin drop_down_skin, Rect rect, int selected, object[] options = null ) { GUIStacks.PushSkin(); UpdateLayout ( rect, selected, ( options != null ) ? options : m_options ); DrawSelection( btn_skin ); DrawOptions ( drop_down_skin ); GUIStacks.PopSkin(); return m_selected; } } //**************************************************************************************************** // //**************************************************************************************************** static public class Console { //************************************************************************************************ // //************************************************************************************************ static public List< MethodInfo > GetApplicationProperties() { List< MethodInfo > result = new List< MethodInfo >( 64 ); System.Type type = typeof( Application ); PropertyInfo[] props = type.GetProperties( BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static ); for( int pr = 0; pr < props.Length; ++pr ) { PropertyInfo prop = props[ pr ]; MethodInfo func = prop.GetGetMethod(); if( func != null ) result.Add( func ); } return result; } //************************************************************************************************ // //************************************************************************************************ [ RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.BeforeSceneLoad ) ] static private void OnBeforeSceneLoadRuntimeMethod() { if( UnityEngine.Debug.isDebugBuild ) { SceneManager.sceneLoaded += OnSceneLoaded; Scene scene = SceneManager.GetActiveScene(); string scene_name = ( scene != null ) ? scene.name : "None"; Console.LogSystem( string.Format( "Console init from scene: {0}", scene_name ) ); List< MethodInfo > properties = GetApplicationProperties(); for( int pr = 0; pr < properties.Count; ++pr ) { MethodInfo prop = properties[ pr ]; string value = prop.Invoke( null, null ).ToString(); Console.LogSystem( string.Format( "Application.{0}: {1}", prop.Name.Substring( 4 ), value ) ); } } } //************************************************************************************************ // //************************************************************************************************ static private void OnSceneLoaded( Scene scene, LoadSceneMode mode ) { if( DebugConsole.Instance == null ) { GameObject console = new GameObject( "DebugConsole", typeof( DebugConsole ) ); if( console != null ) UnityEngine.Object.DontDestroyOnLoad( console ); } } //************************************************************************************************ // //************************************************************************************************ public enum CATEGORY { INFO, WARNING, ERROR, SYSTEM, COUNT } private const string STR_HIDE = "HIDE"; private const string STR_SHOW = "SHOW"; private const string STR_SORT_ASC = "RECENT FIRST"; private const string STR_SORT_DSC = "OLDER FIRST"; private const string STR_CLEAR = "CLEAR"; private const int CLIPING = 1024; static private string[] SPLIT_SEMS = new string[ 3 ] { "\r\n", "\r", "\n" }; //************************************************************************************************ // //************************************************************************************************ static private Color m_colDate = new Color( 0.7f, 0.7f, 0.7f, 1.0f ); static private Color m_colInfo = new Color( 1.0f, 1.0f, 1.0f, 1.0f ); static private Color m_colWarn = new Color( 0.8f, 0.8f, 0.4f, 1.0f ); static private Color m_colErr = new Color( 1.0f, 0.4f, 0.4f, 1.0f ); static private Color m_colSys = new Color( 0.7f, 0.7f, 1.0f, 1.0f ); static private Color[] m_cols = new Color[ ( int )CATEGORY.COUNT ] { m_colInfo, m_colWarn, m_colErr, m_colSys }; //************************************************************************************************ // //************************************************************************************************ static private object m_lock = new object(); static private Buffer m_buffer = new Buffer(); static private Buffer[] m_catBuffers = new Buffer[ ( int )CATEGORY.COUNT ] { new Buffer(), new Buffer(), new Buffer(), new Buffer() }; //************************************************************************************************ // //************************************************************************************************ static private EventSystem m_eventSystem = null; static private GUISkin m_skin = null; static private GUISkin m_skin_dd = null; static private bool m_layout_dirty = false; static private Vector2 m_pos = Vector2.zero; static private Vector2 m_size = Vector2.zero; static private Vector2 m_headerSize = Vector2.zero; static private Vector2 m_scrollSize = Vector2.zero; static private Vector2 m_scrollPos = Vector2.zero; static private Rect m_globalRect = default( Rect ); static private Rect m_headerRect = default( Rect ); static private Rect m_scrollRect = default( Rect ); static private bool m_expanded = true; static private bool m_ascending = true; static private CATEGORY m_filter = CATEGORY.COUNT; static private GUIDropDown m_dropDown = new GUIDropDown( "INFOS", "WARNINGS", "ERRORS", "SYSTEM", "ALL" ); //************************************************************************************************ // //************************************************************************************************ static Console() { LoadResources(); if( UnityEngine.Debug.isDebugBuild ) { Application.logMessageReceivedThreaded += LogCallback; Application.lowMemory += LowMemoryCallback; } } //************************************************************************************************ // //************************************************************************************************ static private void LoadResources() { if( m_skin == null ) { m_skin = Resources.Load< GUISkin >( "DebugConsoleSkin" ); } if( m_skin_dd == null ) { m_skin_dd = Resources.Load< GUISkin >( "DebugConsoleDropDownSkin" ); } } //************************************************************************************************ // //************************************************************************************************ static private void Clear() { lock( m_lock ) { m_buffer.Clear(); for( int buf = 0; buf < m_catBuffers.Length; ++buf ) m_catBuffers[ buf ].Clear(); } } //************************************************************************************************ // //************************************************************************************************ static private void LowMemoryCallback() { Log( "LOW MEMORY", CATEGORY.ERROR ); } //************************************************************************************************ // //************************************************************************************************ static private void LogCallback( string condition, string stackTrace, LogType type ) { bool trace = ( type != LogType.Log ); string entry = ( trace ) ? stackTrace + condition : condition; if ( type == LogType.Log ) Log( entry, CATEGORY.INFO ); else if( type == LogType.Warning ) Log( entry, CATEGORY.WARNING ); else Log( entry, CATEGORY.ERROR ); } //************************************************************************************************ // //************************************************************************************************ static public void LogInfo ( string entry ) { Log( entry, CATEGORY.INFO ); } static public void LogWarning( string entry ) { Log( entry, CATEGORY.WARNING ); } static public void LogError ( string entry ) { Log( entry, CATEGORY.ERROR ); } static public void LogSystem ( string entry ) { Log( entry, CATEGORY.SYSTEM ); } //************************************************************************************************ // //************************************************************************************************ static public void Log( string entry, CATEGORY category = CATEGORY.INFO ) { if( UnityEngine.Debug.isDebugBuild == false ) return; if( string.IsNullOrEmpty( entry ) ) return; if( category >= CATEGORY.COUNT ) return; string[] entries = entry.Split( SPLIT_SEMS, System.StringSplitOptions.RemoveEmptyEntries ); for( int ent = 0; ent < entries.Length; ++ent ) LogEntry( entries[ ent ], category ); } //************************************************************************************************ // //************************************************************************************************ static private void LogEntry( string entry, CATEGORY category ) { if( entry.Length > CLIPING ) entry = entry.Substring( 0, CLIPING ); string date = System.DateTime.Now.ToString(); lock( m_lock ) { m_buffer.Push ( date, entry, ( int )category ); m_catBuffers[ ( int )category ].Push( date, entry, ( int )category ); } } //************************************************************************************************ // //************************************************************************************************ static private void ConsumeMouseEvents() { #if ENABLE_INPUT_SYSTEM Vector2 pointerPos = UnityEngine.InputSystem.Pointer.current.position.ReadValue(); #else Vector2 pointerPos = Input.mousePosition; #endif Vector2 mousePos = new Vector2( pointerPos.x, Screen.height - pointerPos.y ); bool focused = m_globalRect.Contains( mousePos ); if( ( Event.current.isMouse ) && ( focused ) ) Event.current.Use(); if( EventSystem.current != null ) m_eventSystem = EventSystem.current; if( m_eventSystem != null ) m_eventSystem.enabled = ( focused == false ); } //************************************************************************************************ // //************************************************************************************************ static private void UpdateLayout() { if( Event.current.type != EventType.Layout ) return; Vector2 size = new Vector2( Screen.width * ( Application.isMobilePlatform ? 1.0f : 0.5f ), Screen.height * 0.5f ); if( ( m_size != size ) || ( m_layout_dirty ) ) { m_size = size; m_layout_dirty = false; m_headerSize = new Vector2( m_size.x, 24.0f ); m_scrollSize = new Vector2( m_size.x, ( m_expanded ) ? ( m_size.y - m_headerSize.y ) : 0.0f ); m_pos = new Vector2( 0.0f, ( m_expanded ) ? ( Screen.height - m_size.y ) : Screen.height - m_headerSize.y ); m_headerRect = new Rect ( m_pos.x, m_pos.y, m_headerSize.x, m_headerSize.y ); m_scrollRect = new Rect ( m_pos.x, m_pos.y + m_headerSize.y, m_scrollSize.x, m_scrollSize.y ); m_globalRect = new Rect ( m_pos.x, m_pos.y, m_size.x, m_size.y ); } } //************************************************************************************************ // //************************************************************************************************ static private void DrawHeader() { const int nbButtons = 5; float buttonsW = m_headerSize.x / nbButtons; Rect butRect = new Rect( m_headerRect.x, m_headerRect.y, buttonsW, m_headerSize.y ); if( GUI.Button( butRect, ( m_expanded ) ? STR_HIDE : STR_SHOW ) ) { m_expanded = ( ! m_expanded ); m_layout_dirty = true; } if( m_expanded ) { butRect.x += buttonsW; if( GUI.Button( butRect, STR_SORT_ASC ) ) m_ascending = true; butRect.x += buttonsW; if( GUI.Button( butRect, STR_SORT_DSC ) ) m_ascending = false; butRect.x += buttonsW; if( GUI.Button( butRect, STR_CLEAR ) ) Clear(); butRect.x += buttonsW; m_filter = ( CATEGORY )m_dropDown.Draw( m_skin, m_skin_dd, butRect, ( int )m_filter ); } } //************************************************************************************************ // //************************************************************************************************ static private void DrawMessages() { const float dateW = 140.0f; const float lineH = 21.0f; Buffer buffer = ( ( m_filter < 0 ) || ( m_filter >= CATEGORY.COUNT ) ) ? m_buffer : m_catBuffers[ ( int )m_filter ]; int count = buffer.count; Rect viewRect = new Rect( 0.0f, 0.0f, Screen.width, lineH * count ); GUI.Box( m_scrollRect, ( Texture )null ); m_scrollPos = GUI.BeginScrollView( m_scrollRect, m_scrollPos, viewRect, false, true ); if( Event.current.type == EventType.Repaint ) { Rect dateRect = new Rect( 0.0f, 0.0f, dateW, lineH ); Rect lineRect = new Rect( dateW, 0.0f, viewRect.width - dateW, lineH ); int maxVisibles = ( int )( m_scrollRect.height / lineH ) + 1; int firstVisible = ( int )( m_scrollPos.y / lineH ); int lastVisible = firstVisible + ( Mathf.Min( maxVisibles, count ) - 1 ); int start = buffer.start; GUIStacks.PushColor(); for( int entry = firstVisible; entry <= lastVisible; ++entry ) { dateRect.y = lineRect.y = entry * lineH; int index = ( m_ascending ) ? start + ( ( count - 1 ) - entry ) : start + entry; Entry Entry = buffer[ index ]; GUI.color = m_colDate; GUI.Label( dateRect, Entry.date ); GUI.color = m_cols[ ( int )Entry.category ]; GUI.Label( lineRect, Entry.msg ); } GUIStacks.PopColor(); } GUI.EndScrollView (); } //************************************************************************************************ // //************************************************************************************************ static public void OnGUI() { if( UnityEngine.Debug.isDebugBuild == false ) return; GUIStacks.PushSkin( m_skin ); UpdateLayout(); if( m_expanded ) { lock( m_lock ) { DrawMessages(); } } DrawHeader(); ConsumeMouseEvents(); GUIStacks.PopSkin (); } } //**************************************************************************************************** // //**************************************************************************************************** public class DebugConsole : MonoBehaviour { static public DebugConsole Instance { get; private set; } private void Awake () { if( Instance == null ) Instance = this; } private void OnDestroy() { if( Instance == this ) Instance = null; } public void OnGUI () { if( Instance == this ) DEBUG.Console.OnGUI(); } } }