Skip to content

Instantly share code, notes, and snippets.

@tantalum7
Last active July 4, 2018 14:20
Show Gist options
  • Save tantalum7/ba617167351fbe9ce1b6dfa796955199 to your computer and use it in GitHub Desktop.
Save tantalum7/ba617167351fbe9ce1b6dfa796955199 to your computer and use it in GitHub Desktop.
unity-quake-console
#region Imports
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
#endregion
//
// I got fed up trying to use other people's bloated/messy/buggy debug consoles,
// so I made my own. Simple, easy to use and less than 250 lines (including comments).
//
// 1. Make sure there is a child object underneath the script with a Unity.UI.Text
// component attached named "ConsoleText"
// 2. Find this script in unity GetComponent<DebugConsole>(), and add new commands
// Debug.Console.RegisterCommand(string name, Action<string> action, string help="")
// 3. Fire up the console with `
//
namespace Ta7.UI
{
public class DebugConsole: MonoBehaviour
{
#region Fields
public bool isOpen { get { return _isOpen; } private set { OpenConsole(value); } }
private bool _isOpen = true;
private Text logText;
private Patterns.LimitedStack<string> logStack = new Patterns.LimitedStack<string>(10);
private string currentLine = "";
private Dictionary<string, Command> commandDict = new Dictionary<string, Command>();
private string prompt = "> ";
private KeyCode toggleKey = KeyCode.BackQuote;
private char toggleChar = '`';
private bool autoCompleteMode = false;
private List<string> autoCompleteMatches = new List<string>();
private int autoCompleteIndex = 0;
private Vector3 showPosition;
private int recallIndex = 0;
#endregion
/// <summary>
/// Small embedded container class to store command name, help string and action together
/// </summary>
class Command
{
public string name;
public string help;
public Action<string> action;
public Command(string name, string help, Action<string> action)
{
this.name = name.ToLower();
this.help = help;
this.action = action;
}
}
public void Awake()
{
// Add our handler to grab unity log messages
Application.logMessageReceived += HandleLog;
// Register the builtin commands
RegisterCommand("echo", x => WriteLine(x), "Echo back command");
RegisterCommand("help", HelpCmd, "List all commands");
RegisterCommand("help2", x => WriteLine("No help!"), "not grreat");
RegisterCommand("warning", x => Debug.Log("oh noes!"), "stuf");
// Grab the logText component
logText = transform.Find("ConsoleText").GetComponent<Text>();
// Set the show position to where it is now, then hide it
showPosition = transform.position;
OpenConsole(false);
}
public void Update()
{
// If toggleKey is pressed, toggle isOpen
if(Input.GetKeyUp(toggleKey)) { isOpen = !isOpen; }
// Check if console is open
if (isOpen)
{
// Start autocomplete if tab is pressed
if (Input.GetKeyUp(KeyCode.Tab)) { StartAutoComplete(); }
else if (Input.GetKeyUp(KeyCode.DownArrow))
{
currentLine = logStack[recallIndex];
recallIndex = ++recallIndex >= logStack.Count ? logStack.Count -1 : recallIndex;
}
else if (Input.GetKeyUp(KeyCode.UpArrow))
{
recallIndex = --recallIndex < 0 ? 0 : recallIndex;
currentLine = logStack[recallIndex];
}
else
{
// Iterate through each character in the new input string
foreach (char c in Input.inputString)
{
// If backspace is pressed, remove the last char from currentLine
if (c == '\b') { currentLine = (currentLine.Length > 0) ? currentLine.Substring(0, currentLine.Length - 1) : ""; }
// If its the togglekey, do nothing
else if (c == toggleChar) { }
// If newline or linefeed is pressed, execute the currentLine buffer
else if (c == '\n' | c == '\r') { HandleLine(); }
// Any other characters
else
{
// If autocomplete is enabled, end it
if (autoCompleteMode) { EndAutoComplete(); }
// Append char to currentline
currentLine += c;
}
}
}
// Update the screen contents
UpdateScreen();
}
}
/// <summary>
/// Register command with the console at any time. When the action is invoked, it will be passed the line buffer
/// e.g. "mycommand myvars and junk"
/// </summary>
public void RegisterCommand(string name, Action<string> action, string help="")
{
commandDict[name] = new Command(name: name, help: help, action: action);
}
/// <summary>
/// Unregister a command
/// </summary>
public void UnregisterCommand(string name)
{
commandDict.Remove(name);
}
/// <summary>
/// Write to the console. Unity Debug.Log messages will also appear on console, but with "Unity: Log:" in front
/// </summary>
public void WriteLine(string line)
{
logStack.Push(line);
}
#region Private
private void UpdateScreen()
{
// Join all the logStack items into a single string, separated by newlines
string text = String.Join("\r\n", logStack.Reversed().ToList());
// Add the current incomplete buffer, and the auto the autocomplete buffer to the end
text = String.Format("{0}\r\n{1}{2}_", text, prompt, currentLine);
// Update the ui text
logText.text = text;
}
private void OpenConsole(bool enable)
{
// If enable is true, put the console in the show position
if (enable) { transform.position = showPosition; }
// If enable is false, put the console miles away
else { transform.position = new Vector3(10000f, 10000f); }
// Set private backed var to match enable
_isOpen = enable;
}
private void StartAutoComplete()
{
// We aren't in autoCompleteMode
if (!autoCompleteMode)
{
// Set the autocomplete mode flag
autoCompleteMode = true;
// Set the autoComplete index to zero
autoCompleteIndex = 0;
// Grab a list of commandline matches
autoCompleteMatches = commandDict.Keys.ToList().FindAll(s => s.Contains(currentLine)).ToList();
}
// Make sure there is at least one autoComplete match
if (autoCompleteMatches.Count > 0)
{
// If autoCompleteIndex has gone over the number of possible matches, reset it to zero
if (autoCompleteMatches.Count <= autoCompleteIndex) { autoCompleteIndex = 0; }
// Set the autocomplete buffer to the commandMatch at the autoCompleteIndex
currentLine = autoCompleteMatches[autoCompleteIndex];
// Increment autoCompleteIndex
autoCompleteIndex++;
}
}
private void EndAutoComplete()
{
// Clear matches
autoCompleteMatches.Clear();
// Reset auto complete mode flag
autoCompleteMode = false;
}
private void HandleLog(string message, string stackTrace, LogType type)
{
// Write the unity message to the console
WriteLine(string.Format("Unity: {0}: {1}", type.ToString(), message));
}
private void HandleLine()
{
// Split the command out of the currentLine
string command = currentLine.Split(' ').FirstOrDefault() ?? "";
// If the command string matches a command in the dict, invoke it
if (commandDict.ContainsKey(command)) { commandDict[command].action(currentLine); }
// Command not found, print error to console
else { WriteLine(String.Format("Command '{0}' not found", command)); }
// Clear the currentLine
currentLine = "";
}
private void HelpCmd(string input)
{
// Iterate through each command in the dict, and print the name and help string
foreach (Command cmd in commandDict.Values) { WriteLine(string.Format("{0,-20} {1}", cmd.name, cmd.help)); }
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment