Help with InputField as Chat System

I’ve been toying around with this script for a few days now and I’ve made little to no headway. What I’m trying to achieve is a chat system similar to that found in MMOs. Pressing some pre-defined key (such as TAB) should select the input field and remove the player’s ability to control their avatar. (This is also achieved simply by clicking on the input field.) Pressing a key such as ESC (or clicking anywhere outside of the input field) should stop allowing the player to type in the input field (but SHOULD NOT delete what they have typed in it), and give them back control of their avatar. I’ve gotten this functionality about 80% complete, but still having a few hiccups. Here’s the script.

using DarkRift;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class Chat : InputField, ISelectHandler, IDeselectHandler
{
    public HUD hud;

    protected override void Awake()
    {
        base.Awake();

        SubmitEvent submitEvent = new SubmitEvent();
        submitEvent.AddListener(SendChatMessage);
        onEndEdit = submitEvent;
    }
    void Update()
    {
        if(Input.GetKeyUp(KeyCode.Tab) && !isFocused)
        {
            ActivateInputField();
            Select();
        }
        if(Input.GetButtonUp("Cancel") && isFocused)
        {
            //DeactivateInputField();
            EventSystem.current.SetSelectedGameObject(null);
        }
    }


    public override void OnSelect(BaseEventData eventData)
    {
        Debug.Log("OnSelect called.");
        base.OnSelect(eventData);

        if(hud.player.components.input.acceptInput)
                hud.player.components.input.AcceptInput(false);

        if(hud.player.Minion&& hud.player.Minion.components.input.acceptInput)
                hud.player.Minion.components.input.AcceptInput(false);
    }
    public override void OnDeselect(BaseEventData eventData)
    {
        Debug.Log("OnDeselect called.");

        //base.OnDeselect(eventData);

        if(hud.player.components.input.acceptInput)
                hud.player.components.input.AcceptInput(true);

        if(hud.player.Minion&& hud.player.Minion.components.input.acceptInput)
                hud.player.Minion.components.input.AcceptInput(true);
    }
    public override void OnSubmit(BaseEventData eventData)
    {
        Debug.Log("OnSubmit called.");

        base.OnSubmit(eventData);

        SendChatMessage(text);
    }

    public void SendChatMessage(string value)
    {
        Debug.Log("SendChatMessage called. The value is " + value);

        if(!string.IsNullOrEmpty(value.Trim()))
        {
            string msg = hud.player.playername + " : " + value;
            DarkRiftAPI.SendMessageToServer(TagIndex.Chat, TagIndex.ChatSubjects.MainChat, msg);

            text = string.Empty;
            DeactivateInputField();
        }
        else
        {
            text = string.Empty;
            DeactivateInputField();
        }
    }
}

Here are the problems I’m facing:

  1. “Submit” (RETURN in my project settings) also calls the onEndEdit (as it should), and sends the chat message, but the player never gets control back, which means OnDeselect isn’t being fired.

  2. Pressing “Cancel” (ESC in my project settings) calls the onEndEdit, but just like with #1, the player never gets control back. All I want pressing “Cancel” to do is stop allowing input in the input field, and give player back control of their avatar, and allow pressing “TAB” in the future to re-enable input in the input field.

  3. If the player begins typing in the input field, and then clicks anywhere else, they DO get control back of their avatar, but they cannot use “TAB” to start typing in the input field again, which means the input field never set “isFocused” to false.

Thanks in advance for any help as this UI InputField system is really frustrating me and holding this project back.

Hey, this was fun. I think I have a solution for you :slight_smile:

 if (Input.GetKeyUp(KeyCode.Escape) && EventSystem.current.currentSelectedGameObject == gameObject)
        {
            print("hit escape.");
           // DeactivateInputField();
         
            EventSystem.current.SetSelectedGameObject(null);
        }
 public override void OnDeselect(BaseEventData eventData)
    {
        Debug.Log("OnDeselect called.");

        base.OnDeselect(eventData);

    }

Those are the parts I changed. (Sure you could put “cancel” back into the GetButton).
(side note: ‘esc’ sends the message, also, which is not something I’d want but if you like it, that’s cool).
Anyways, hopefully that’s helpful :slight_smile:

Okay, so there’s still a couple of problems:

  1. Clicking outside of the input field still causes it to call onEndEdit and send the chat message.
  2. Pressing escape doesn’t actually “send the message”. It does call SendChatMessage, but because the text value of the input field is cleared before SendChatMessage is called, SendChatMessage is sent an empty string, which causes it to not actually send the message.

So the first problem is the real issue right now. How to get deselecting the input field to not actually call onEndEdit, but to still have onEndEdit called when “RETURN” is pressed.

I see (more clearly). You definitely don’t want it to send the message then, or at least not clicking outside. Whatever the case… um… 2 ideas come to mind. You could have the ‘enter’ key call your method (and avoid onendedit), which is more normal for a chat system, imo. The other idea, would be to clear the UnityEvent in your overriden method for OnDeselect, and re-assign it on focusing, if that works.
Those sound possible?

small edit: I kinda wish that was re-written at the core to be 2 different scenarios or a toggle or a diff. event… whatever. :slight_smile: There are times deselecting and submitting make sense as one in the same, but many times they are not, I think. :slight_smile: Oh well.

Thanks for the help @methos5k . Here’s the final script.

using DarkRift;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class Chat : InputField, ISelectHandler, IDeselectHandler
{
    public HUD hud;

    void Update()
    {
        if(Input.GetKeyUp(KeyCode.Tab) && !isFocused)
        {
            ActivateInputField();
            Select();
        }
        if(Input.GetButtonUp("Submit") && EventSystem.current.currentSelectedGameObject == gameObject)
        {
            SendChatMessage(text);
            DeactivateInputField();
            EventSystem.current.SetSelectedGameObject(null);
        }
        if(Input.GetButtonUp("Cancel") && EventSystem.current.currentSelectedGameObject == gameObject)
        {
            EventSystem.current.SetSelectedGameObject(null);
        }
    }


    public override void OnSelect(BaseEventData eventData)
    {
        base.OnSelect(eventData);

        if(hud.player.components.input.acceptInput)
                hud.player.components.input.AcceptInput(false);

        if(hud.player.Minion&& hud.player.Minion.components.input.acceptInput)
                hud.player.Minion.components.input.AcceptInput(false);
    }
    public override void OnDeselect(BaseEventData eventData)
    {
        base.OnDeselect(eventData);

        if(!hud.player.components.input.acceptInput)
            hud.player.components.input.AcceptInput(true);

        if(hud.player.Minion&& !hud.player.Minion.components.input.acceptInput)
            hud.player.Minion.components.input.AcceptInput(true);

    }

    public void SendChatMessage(string value)
    {
        if(!string.IsNullOrEmpty(value.Trim()))
        {
            string msg = hud.player.playername + " : " + value;
            DarkRiftAPI.SendMessageToServer(TagIndex.Chat, TagIndex.ChatSubjects.MainChat, msg);

            text = string.Empty;
            DeactivateInputField();
        }
        else
        {
            text = string.Empty;
            DeactivateInputField();
        }
    }
}

The only problem it doesn’t solve is that using “Esc” or “cancel” still erases the text in the input field. Not so much of a problem as you could simply say it’s an easy way for the player to delete everything they typed in, without highlighting or holding down the “Backspace”. And yeah, I agree that deselecting shouldn’t call OnSubmit or onEndEdit.

perhaps i spoke in haste… I can see how one might have use for an event that said “hey, they’re done editing this field”.
That could be cool. That’s separate, though. Also sending submit when they’re done editing is a ‘sometimes’ thing, imo - certainly not always. Okay, sorry off-track. :slight_smile:
I do not get why ‘esc’ or ‘cancel’ is clearing your text? I can see how you could live with that, but it’s probably not ideal or what you’d prefer?
Maybe I’m missing something, but I’ve scanned your code 2-3 times and I don’t get how it’s erased by that action. :slight_smile:

Anyways… You’re like … 95% good now, right? :slight_smile: lol (up from 80% when you began. heh)

Edit: Ahh, I see. Of course, the ‘esc’ key is “Special” That’s why. It’s not clearing the text, exactly, btw… It’s replacing the text with the text you had when you activated the inputfield.
If the cancel key is pressed, and you want to (keep the esc key as your choice), then save the text string and put it back (later in the frame/next?) I’m trying it out right now.

Yeah… Pressing “Cancel” (escape in this case) calls a method in the source code that resets the text to the original text. I can’t override that method either because it calls some other private methods that I can’t access. I DID try saving the text and putting it back like you said, but couldn’t get it to work. I think you’d have to write a separate method that puts the text back to what it was. Then at the beginning of the OnDeselect override store the text in a temp variable, and then at the end of OnDeselect (after it has called DeactiveInputField (which is where the text is reset to its original state), call that other method and pass the temp variable to it. Heck…you might even have to make it a coroutine and wait a couple frames just to make sure DeactivateInputField has finished before it puts it back (or it’ll get erased again). Here’s the source code snippet so you see what I mean.

public void DeactivateInputField()
        {
            // Not activated do nothing.
            if (!m_AllowInput)
                return;

            m_HasDoneFocusTransition = false;
            m_AllowInput = false;

            if (m_TextComponent != null && IsInteractable())
            {
                if (m_WasCanceled)
                    text = m_OriginalText;

                if (m_Keyboard != null)
                {
                    m_Keyboard.active = false;
                    m_Keyboard = null;
                }

                m_CaretPosition = m_CaretSelectPosition = 0;

                SendOnSubmit();

                Input.imeCompositionMode = IMECompositionMode.Auto;
            }

            MarkGeometryAsDirty();
        }

        public override void OnDeselect(BaseEventData eventData)
        {
            DeactivateInputField();
            base.OnDeselect(eventData);
        }

lol I’m actually reading that stuff right now and knew exactly what you were talking about as I read your post.
Yes, it is a small nuisance. I can think of 2 options that may work. 1 is to override the :
public virtual void OnUpdateSelected(BaseEventData eventData)
method and look through all of the keys yourself. If you get escape, store the text then… restore it after.
the other idea is to always save the text when the value is changed.
and 3rd, and final (probably best option!) is to just let it delete the text!!

The first time I read your post, I had thought that clicking away (deselect) just with the mouse was erasing the text… I later knew that’s not what you meant, and just the escape key was doing that… but I think it was stuck in my head about how the clicking away would be a horrible idea.
I just tried ‘escape’ in a game I play, and it does erase text there, also. (The only difference is that it doesn’t show the old/last written text, if there was some).

Summarized: Let ‘escape’ clear the text, and if desired, always set it to nothing. (text = “” in your cancel statement.)

1 Like