How can I consume key events to prevent them showing up in GUILayout.TextFields?

I’ve added a debug console to my game to allow for easy testing by, say, putting items into the player’s inventory. It can be toggled open/closed by typing a backtick (`). Unity refers to this as KeyCode.BackQuote. The problem I’m having is that when I open the console, the backtick character ends up in the input field, which I don’t want. I’ve tried to deal with this by using Event.current.Use() on all backtick events in OnGUI, but this doesn’t seem to help. The only solution I’ve come up with is to use a regex to strip out any backtick characters, but this feels like fighting the fire rather than dealing with the problem. Is there a more elegant way of keeping the backtick out of the input field?

Example C# code:

using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using System.Text.RegularExpressions;

public class CommandConsole : MonoBehaviour
{
    private Rect GuiRect;
    public List<string> messages = new List<string>();
    private string inputLine = ""; //This is where text entered into the console ends up.
    private Vector2 scrollPos = Vector2.zero;
    private Color textColour;
    public float fadeoutTimer = 5.0f;
    public float fadeSpeed = 0.5f;
    private float inactiveTimer;
    private GUIStyle labelStyle;

    public void Start()
    {
        GuiRect = new Rect(0, 0, Screen.width, 300);
    }
    
    void OnGUI() {
        //Uncomment this odd bit of code and the problem goes away
        //if (Event.current.type == EventType.KeyDown && Event.current.keyCode==KeyCode.None) {
        //    Event.current.Use();
        //    return;
        //}

        if (Event.current.keyCode == KeyCode.BackQuote) {
            if (Event.current.type == EventType.KeyDown) {
                if (GUI.GetNameOfFocusedControl()=="ConsoleInput") {
                    GUI.FocusControl("");
                }
                else {
                    labelStyle = GUI.skin.label;
                    labelStyle.border = new RectOffset(0, 0, 0, 0);
                    labelStyle.margin = new RectOffset(0, 0, 0, 0);
                    labelStyle.padding = new RectOffset(4, 4, 1, 1);
                    GUI.FocusControl("ConsoleInput");
                    inactiveTimer = fadeoutTimer;
                    textColour = Color.white;
                    inputLine=""; //Reset this here
                }
            }
            Event.current.Use(); //I was hoping this would fix it
            return;              //Returning early doesn't help either
        }
        else {
            //Fade out the console if we're done with it.
            if (GUI.GetNameOfFocusedControl() != "ConsoleInput") {
                if (inactiveTimer <= 0.0f) {
                    if (textColour.a > 0.0f) {
                        textColour.a -= fadeSpeed * Time.deltaTime;
                    }
                }
                else {
                    inactiveTimer -= fadeSpeed * Time.deltaTime;
                }
            }
        }
        Color previousGUIColor = GUI.color;
        GUI.color = textColour;
        GUI.SetNextControlName("");
        GUILayout.BeginArea(this.GuiRect,GUI.skin.box);

        scrollPos = GUILayout.BeginScrollView(scrollPos);
        GUILayout.FlexibleSpace();
        for (int i = messages.Count - 1; i >= 0; i--)
        {
            GUILayout.Label(messages*, labelStyle);*

}
GUILayout.EndScrollView();

GUILayout.BeginHorizontal();
GUI.SetNextControlName(“ConsoleInput”);
Debug.Log ("EventType: “+Event.current.type.ToString()+”, Key: "+Event.current.keyCode.ToString());
Debug.Log ("Before: "+inputLine);
inputLine = GUILayout.TextField(inputLine);
Debug.Log("After: "+inputLine);
GUILayout.EndHorizontal();
GUILayout.EndArea();
GUI.color = previousGUIColor;
//Alternatively, putting this line back in also solves the problem
//inputLine=Regex.Replace(inputLine, “`”, “”); //Ick
}
}
Update: More strange behaviour. With copious debug I’ve noticed that the GUILayout.TextField call adds the backtick to the string when Event.current is a KeyDown event with a keycode of KeyCode.None. That has me scratching my head, it doesn’t seem to make much sense as an event. However, if I Use() that event and return, the backtick doesn’t appear in inputLine at any point.
So that’s another odd-looking solution I could go with. Does anybody know why Unity is producing KeyDown events with no key?

I mentioned this problem in passing to a colleague, who gave me the answer straight away. Pressing the backtick key generates TWO KeyDown events:

  1. The first event has keyCode set to KeyCode.BackQuote - this tells you which physical key on the keyboard was pressed (after being mapped through the keyboard layout, at least in Windows)

  2. The second event has keyCode set to KeyCode.None and character set to ‘`’ - this is the character that will be added to the text string in the GUILayout.TextField call

This is useful when different keys can give the same character. If you press the ‘1’ key above the letters, the first event will have a keyCode of Alpha1, and the second event will have a character of ‘1’. If you press the ‘1’ key on the keypad then the first event will have a keyCode of Keypad1, and the second event will have a character of ‘1’ if NumLock is on, or (at least on my keyboard) ‘End’ if it isn’t.

So although I was using the KeyCode.BackQuote event to toggle the console, it was the later None event with the character field set that was actually putting the ‘`’ in the text field. So I’ve now modified my code to check for KeyDown events with a backtick character and ignore them.

Well that was tough :smiley: - So I stripped it down to the wire and this seems to work, not sure exactly why though…

private bool hasFocus;
private const string CONSOLE_INPUT = "ConsoleInput";

void OnGUI()
{
	if (Event.current.type == EventType.keyDown)
		if (Event.current.keyCode == KeyCode.BackQuote) {
			ToggleFocus();
			inputLine = "";
		}

	GUILayout.BeginArea(GuiRect, GUI.skin.box);
	{
		GUILayout.FlexibleSpace();

		GUILayout.BeginHorizontal();
		{
			GUI.SetNextControlName(CONSOLE_INPUT);
			string value = GUILayout.TextField(inputLine);
			if (hasFocus) inputLine = value; // this was the key solution
		}
		GUILayout.EndHorizontal();
	}
	GUILayout.EndArea();
}

private void ToggleFocus()
{
	hasFocus = GUI.GetNameOfFocusedControl() == "ConsoleInput";
	if (hasFocus) Unfocus();
	else Focus();
}

private void Focus()
{
	GUI.FocusControl(CONSOLE_INPUT);
}

private void Unfocus()
{
	GUI.FocusControl("");
}

I highly recommend that you use another GUI alternative (for very obvious reasons, like you saw…) - I’ve used NGUI and it’s really nice (in fact I’ve done something exactly the same - a small terminal for my inventory - I didn’t face any of this trouble)