Instantly share code, notes, and snippets.

Embed
What would you like to do?
A debug console for use in Unity games.Features include Command History, Auto-Complete and the use of Attributes to specify Commands.Based on the debug console found here: http://bitflipgames.com/2010/09/16/tips-for-working-with-unity-4-coding-and-general-tips/ For details see here: http://blog.gvnott.com/2013/12/24/the-s-w-a-p-debug-console/
// --------------------------------------------------------------------------------
// Copyright (C)2010 BitFlip Games
// Written by Guy Somberg guy@bitflipgames.com
//
// I, the copyright holder of this work, hereby release it into the public domain.
// This applies worldwide. In case this is not legally possible, I grant any
// entity the right to use this work for any purpose, without any conditions,
// unless such conditions are required by law.
// --------------------------------------------------------------------------------
// Copyright (C)2013 Chaos Theory Games
// Modified by Greg Nott gvnott @ gmail . com
// For info on modifications: http://blog.gvnott.com/2013/12/23/the-s-w-a-p-debug-console/
// --------------------------------------------------------------------------------
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Reflection;
#region Debug Console Class
public class DebugConsole
{
private string ConsoleText = "";
private bool displayConsole = false;
public bool DisplayConsole
{
get { return displayConsole; }
set
{
displayConsole = value;
if (!DisplayConsole)
{
ConsoleText = "";
PreviousCommandIndex = -1;
}
}
}
private List<string> PreviousCommands = new List<string>();
private int PreviousCommandIndex = -1;
private Queue<string> History = new Queue<string>();
private string AutoCompleteBase = "";
private List<string> AutoCompleteOptions = new List<string>();
private int AutoCompleteOptionsIndex = -1;
private Vector2 scrollPosition = new Vector2(0, 0);
private float scrollHeight = 0.0f;
// Constructor will automatically add any commands to the command List:
public DebugConsole()
{
ConsoleCommands.FindCommands();
}
private ConsoleCommands.ConsoleCommand GetCommand(string CommandText)
{
foreach (ConsoleCommands.ConsoleCommand Command in ConsoleCommands.Commands)
{
if (Command.CommandText.Equals(CommandText, StringComparison.CurrentCultureIgnoreCase))
{
return Command;
}
}
return null;
}
private void ExecuteCommand(string CommandText)
{
// safty checks:
if (CommandText.Length == 0)
{
return;
}
CommandText = CommandText.Trim();
PreviousCommands.Add(CommandText);
History.Enqueue(CommandText);
string[] SplitCommandText = CommandText.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
ConsoleCommands.ConsoleCommand Command = GetCommand(SplitCommandText[0]);
if (Command != null)
{
Command.Callback(SplitCommandText);
}
else // if command is invlaid, tell user and print help!
{
Log("Invalid Command!");
ConsoleCommands.GetHelp(null);
}
}
private void AutoComplete()
{
string AutoCompleteText = AutoCompleteBase.Trim().ToLower();
if (AutoCompleteOptionsIndex < 0)
{
AutoCompleteOptions.Clear();
foreach (ConsoleCommands.ConsoleCommand Command in ConsoleCommands.Commands)
{
if (Command.CommandText.ToLower().StartsWith(AutoCompleteText))
{
AutoCompleteOptions.Add(Command.CommandText);
}
}
AutoCompleteOptions.Sort();
if (AutoCompleteOptions.Count > 0)
{
AutoCompleteOptionsIndex = 0;
PreviousCommandIndex = -1;
}
}
else
{
if (AutoCompleteOptions.Count > 0)
{
AutoCompleteOptionsIndex = (AutoCompleteOptionsIndex + 1) % AutoCompleteOptions.Count;
}
else
{
AutoCompleteOptionsIndex = -1;
}
}
if (AutoCompleteOptionsIndex >= 0)
{
ConsoleText = AutoCompleteOptions[AutoCompleteOptionsIndex];
}
}
private void ClearAutoComplete()
{
AutoCompleteBase = "";
AutoCompleteOptions.Clear();
AutoCompleteOptionsIndex = -1;
}
public void OnGUI()
{
if (DisplayConsole)
{
GUI.depth = 1000;
string BaseText = ConsoleText;
if (PreviousCommandIndex >= 0)
{
BaseText = PreviousCommands[PreviousCommandIndex];
}
Event CurrentEvent = Event.current;
if ((CurrentEvent.isKey) &&
(!CurrentEvent.control) &&
(!CurrentEvent.shift) &&
(!CurrentEvent.alt))
{
bool isKeyDown = (CurrentEvent.type == EventType.KeyDown);
if (isKeyDown)
{
if (CurrentEvent.keyCode == KeyCode.Return || CurrentEvent.keyCode == KeyCode.KeypadEnter)
{
ExecuteCommand(BaseText);
//DisplayConsole = true; // set to false to have console disapear after pressing enter.
return;
}
if (CurrentEvent.keyCode == KeyCode.UpArrow)
{
if (PreviousCommandIndex <= -1)
{
PreviousCommandIndex = PreviousCommands.Count - 1;
ClearAutoComplete();
}
else if(PreviousCommandIndex > 0)
{
PreviousCommandIndex--;
ClearAutoComplete();
}
return;
}
if (CurrentEvent.keyCode == KeyCode.DownArrow)
{
if (PreviousCommandIndex == PreviousCommands.Count - 1)
{
PreviousCommandIndex = -1;
ClearAutoComplete();
}
else if (PreviousCommandIndex >= 0)
{
PreviousCommandIndex++;
ClearAutoComplete();
}
return;
}
if (CurrentEvent.keyCode == KeyCode.Tab)
{
if (AutoCompleteBase.Length == 0)
{
AutoCompleteBase = BaseText;
}
AutoComplete();
return;
}
if (CurrentEvent.keyCode == KeyCode.End || CurrentEvent.keyCode == KeyCode.BackQuote)
{
DisplayConsole = false;
return;
}
}
}
GUI.SetNextControlName("ConsoleOutputBox");
string history = FormatHistoryText();
//Rect outputFieldRect = new Rect(0.0f, (float)Screen.height - 100.0f, (float)Screen.width, 80.0f);
GUILayout.BeginArea (new Rect(0.0f, (float)Screen.height - 120.0f, (float)Screen.width, 100.0f));
scrollPosition = GUILayout.BeginScrollView (scrollPosition, GUILayout.Width (Screen.width), GUILayout.Height (100));
GUI.skin.box.wordWrap = true; // set the wordwrap on for box only.
GUI.skin.box.richText = true;
GUI.skin.box.alignment = TextAnchor.UpperLeft;
GUILayout.Box(history); // draw history text.
GUILayout.EndScrollView ();
GUILayout.EndArea();
GUI.SetNextControlName("ConsoleTextBox");
Rect inputFieldRect = new Rect(0.0f, (float)Screen.height - 20.0f, (float)Screen.width, 20.0f);
string CommandText = GUI.TextField(inputFieldRect, BaseText);
if (PreviousCommandIndex == -1)
{
ConsoleText = CommandText;
}
if (CommandText != BaseText)
{
ConsoleText = CommandText;
PreviousCommandIndex = -1;
ClearAutoComplete();
}
GUI.FocusControl("ConsoleTextBox");
}
}
private string FormatHistoryText()
{
float prevScrollHeight = scrollHeight;
scrollHeight = 0.0f;
string history = "";
foreach(string str in History)
{
history += str + "\n";
}
int count = CountNewLines(history) - 4; // minus 4 because we do not need to count the first 5 lines!
if (count > 0)
{
// if we are greater then 0 then count * 20.0f (size of line)
// takes us to the bottom of the debug console window.
scrollHeight = count * 20.0f;
if (count > 100)
{
// We are too big, remove the oldst string:
History.Dequeue();
}
}
if (scrollHeight < prevScrollHeight - 10.0f || scrollHeight > prevScrollHeight + 10.0f)
{
// scroll height has changed, snap down to bottom of the history panel:
scrollPosition.y = scrollHeight;
}
return history;
}
private int CountNewLines(string str)
{
int count = 0;
foreach (char c in str)
{
if (c == '\n')
{
++count;
}
}
return count;
}
public void Log(object message)
{
History.Enqueue(message.ToString());
}
public void LogWarning(object message)
{
History.Enqueue("Warning: " + message.ToString());
}
public void LogError(object message)
{
History.Enqueue("Error: " + message.ToString());
}
}
#endregion
#region Console Commands
public static class ConsoleCommands
{
public delegate void Command(string[] Params);
public class ConsoleCommand
{
public ConsoleCommand(string CommandText, string HelpText, Command Callback)
{
this.CommandText = CommandText;
this.HelpText = HelpText;
this.Callback = Callback;
}
public string CommandText;
public string HelpText;
public Command Callback;
}
// finds any marked commands in the current assembly
// and adds them to the commands list!
static public void FindCommands()
{
WakyCommandAttribute wakyCommandAttrib;
Assembly currAssembly = Assembly.GetCallingAssembly();
foreach(Type type in currAssembly.GetTypes())
{
// check the methods in every class:
if (type.IsClass)
{
foreach (MethodInfo method in type.GetMethods())
{
// if the method meets our requirments for a console command (i.e. is buplic and static).
if (method.IsPublic && method.IsStatic)
{
// check its attributes:
foreach (Attribute attr in method.GetCustomAttributes(true))
{
wakyCommandAttrib = attr as WakyCommandAttribute;
if (wakyCommandAttrib != null)
{
// create and the command based on its attribute data.
ConsoleCommand command = new ConsoleCommand(wakyCommandAttrib.Command, wakyCommandAttrib.HelpText, Delegate.CreateDelegate(typeof(Command), method) as Command);
if (command != null)
{
if (Commands.Contains(command) == false)
{
Commands.Add(command);
}
}
}
} // end attrib loop.
}
} // end method loop
}
} // end Class loop.
}
public static List<ConsoleCommand> Commands = new List<ConsoleCommand>();
// public static ConsoleCommand[] Commands = new ConsoleCommand[]
// {
// // Fill this with your commands:
// new ConsoleCommand("Help", "Prints the Debug Console help info!", GetHelp),
// new ConsoleCommand("Hide", "Hides the Debug Console", HideDevConsole),
// new ConsoleCommand("GetNetworkVersion", "Prints out the network version string.", NetworkController.GetNetworkVersion),
// new ConsoleCommand("GetNetInfo", "Prints out the network information.", NetworkController.GetNetInfo),
// new ConsoleCommand("ToggleWireframe", "Turns wireframe on/off.", WireframeOnOff.ToggleWireframe),
// new ConsoleCommand("test", "test", TestAttrib),
// };
[WakyCommandAttribute("Help", HelpText = "Prints the Debug Console help text")]
public static void GetHelp(string[] Params)
{
string helpText = "Waky Debug Console!!\n";
helpText = helpText + "Press ~ to open and ~ or the 'End' key to close, press enter to enter command text, press tab to autocomplete command, press up/down arror to see command history.\n";
helpText = helpText + "Commands: \n";
foreach (ConsoleCommand command in Commands)
{
helpText = helpText + command.CommandText + " -- " + command.HelpText + "\n";
}
GameController.Log(helpText + "=============================================================================");
}
[WakyCommandAttribute("Hide", HelpText = "Hides the Debug Console")]
public static void HideDevConsole(string[] Params)
{
GameController.Instance.DebugConsoleVisible(false);
}
}
// This Attribute is used to mark a method as a command for the console!!
[AttributeUsage(AttributeTargets.Method)]
public class WakyCommandAttribute : Attribute
{
// The command entered on the console to run this method as a command.
private string command;
public string Command
{
get { return command; }
set { command = value; }
}
// The help text for the command - optional
private string helpText = "No help provided, sorry :(";
public string HelpText
{
get { return helpText; }
set { helpText = value; }
}
public WakyCommandAttribute(string szCommand)
{
command = szCommand;
}
}
#endregion
#region Debug Manager
public class DebugManager : MonoBehaviour
{
private DebugConsole debugConsole = null;
public bool ConsoleEnabled
{
get { return (debugConsole != null) ? debugConsole.DisplayConsole : false; }
}
public bool ConsoleVisible
{
get { return debugConsole.DisplayConsole; }
set { debugConsole.DisplayConsole = value; }
}
void Awake()
{
debugConsole = new DebugConsole();
}
void Update()
{
if (debugConsole == null)
{
return; // do nothing!
}
if (Input.GetKeyUp(KeyCode.BackQuote)) // tilde!
{
debugConsole.DisplayConsole = true;
}
}
void OnGUI()
{
if (debugConsole != null)
{
debugConsole.OnGUI();
}
}
public void Log(object message)
{
if (debugConsole != null)
{
debugConsole.Log(message);
}
}
public void LogWarning(object message)
{
if (debugConsole != null)
{
debugConsole.LogWarning(message);
}
}
public void LogError(object message)
{
if (debugConsole != null)
{
debugConsole.LogError(message);
}
}
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment