1
\$\begingroup\$

Today I learned some cool tricks about sizing containers and its children, I mainly using this, because I need to build some forms.

Forms

But now... It is some preffered way how to handle forms? Or some unity component? I imagine something like get values of all InputFields, when the button is clicked.

Originaly I had script, which cycled through all children of container, registered them and I could from the script call method to get values in array. But now I have input fields in the more complex structure, for using of unity scripts for sizing and placement, so the cycle is now not so easy.

So, does unity have some script, or there is some unified way how to do this? How do you handle the forms? And of course, the biggest question, how do you handle standard behaviour as tabing through input fields and submit all on enter press?

\$\endgroup\$
1
\$\begingroup\$

Usually I just create a class for that specific form. Using your example login dialog above, that might look something like this:

[System.Serializable] public class LoginDialogEvent : UnityEvent<string, string> {}

public class LoginDialog : MonoBehaviour {
    [SerializeField] private InputField username;
    [SerializeField] private InputField password;
    [SerializeField] private LoginDialogEvent submitEvent = new LoginDialogEvent();
    public LoginDialogEvent SubmitEvent => submitEvent;

    public string Username { get => username.text; set => username.text = value; }
    public string Password { get => password.text; set => password.text = value; }

    void Start() {
        Assert.IsNotNull(username);
        Assert.IsNotNull(password);
    }

    //call this from the click event on the button in the inspector
    public void Submit() {
        submitEvent.Invoke(Username, Password);
    }
}

Then you'd use it something like this:

LoginDialog dialog = Instantiate(loginDialogPrefab);
dialog.SubmitEvent.AddListener(OnLoginDialogSubmit);
void OnLoginDialogSubmit(string username, string password) {
    loginService.Login(username, password);
}

If your application has a limited number of forms, it doesn't take very long to write up the custom classes for them. If you're going to have a huge number of forms, highly complex forms, or forms that are dynamically generated at runtime, you may need something more sophisticated.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Yep, that what was my script doing, getting automatically all the elements. I was mainly curious if unity has something or how handle it others. And what tabing through input? Did you make it? \$\endgroup\$ – Zechy Aug 1 at 14:09
0
\$\begingroup\$

So, thanks to @Kevin I have approached to custom solution, which I think is the best for me now. I have created FormBehaviour script, which I am linking to Form Container Game Object, where I have the array of inputs, and object for submit and cancel button (all assagnable from editor). And of course submit event, assignable.

Also the Tab navigation is less easier solved, on tab select I find in EventSystem, if is there selected game object and found next or previous selectable.

Here's my Behaviour code.

EDIT: the TabSelectable should be seperated to some custom script, for the scene. Especially when I will have more forms in one screen.

[Serializable]
public class SubmitForm : UnityEvent<GameObject, IDictionary<string, string>>
{
}

public class FormBehaviour : MonoBehaviour
{
    public SubmitForm OnSubmit;
    public GameObject[] Inputs;
    public GameObject SubmitButton;
    public GameObject CancelButton;
    
    private readonly IDictionary<string, string> _values = new Dictionary<string, string>();
    private EventSystem _eventSystem;

    public void Start()
    {
        if (SubmitButton == null) throw new InvalidOperationException("The SubmitButton is not set.");

        _eventSystem = EventSystem.current;
        SubmitButton.GetComponent<Button>()
            .onClick
            .AddListener(Submit);

        if (CancelButton != null)
        {
            // Add cancel listener
        }

        BindInputEvents();
    }

    public void Update()
    {
        if (Input.GetButtonUp("Submit") && gameObject.activeSelf)
        {
            Submit();
        }
        
        TabSelectable();
    }
    
    public void Submit()
    {
        OnSubmit.Invoke(gameObject, _values);
    }

    private void BindInputEvents()
    {
        foreach (var input in Inputs)
        {
            var inputField = input.GetComponent<TMP_InputField>();
            if (inputField == null) continue;
            
            _values.Add(input.name, "");
            inputField.onValueChanged.AddListener(value =>
            {
                _values[input.name] = value;
            });
        }
    }

    private void TabSelectable()
    {
        if (!Input.GetKeyUp(KeyCode.Tab)) return;

        Selectable element;
        // Tab to previous element.
        if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
        {
            element = GetCurrentSelectable().FindSelectableOnUp();
        }
        // Tab to next element.
        else
        {
            element = GetCurrentSelectable().FindSelectableOnDown();
        }
            
        if(element != null) element.Select();
    }

    private Selectable GetCurrentSelectable()
    {
        return _eventSystem.currentSelectedGameObject.GetComponent<Selectable>();
    }
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.