EventSystem Override / Custom InputModule?

In short, Im working on a menu system that prevents the current selected gameobject from being null (so that controller-only input wont have to rely on a mouse to regain control). I started with a simple script attached to the EventSystem:

void Update()
{
        if (system.currentSelectedGameObject == null)
            system.SetSelectedGameObject(lastSelected);
        else
            lastSelected = system.currentSelectedGameObject;
}

For the most part, this works fine, except that the gameobject gets Deselected and then re-Selected every time the selection would become null (ie, when clicking on nothing). I’d like to prevent that from happening by intercepting whatever ultimately invokes those methods (OnSelect and OnDeselect). In other words, I want to prevent the possibility of selecting a null GO, rather than fixing it after the fact.

Looking at Unity’s source reference for the EventSystem, it looks like the SetSelectedGameObject method is where OnSelect and OnDeselect gets invoked, so my first thought was to override it and return early if it gets called with a null GameObject. However, this method is not virtual and thus cannot be overridden. So I went looking for what calls this method to begin with.

I think the only other solution is to define an override for the StandAloneInputModule or one of its related systems and prevent it from trying to select an unselectable/null GO, but that has left me with a bit of a dead end.

I feel like Im probably massively over-complicating things, so if anyone has any input or advice, Im all ears.

Feeling a bit frustated as well, because even if you do override the BaseInputModule, other code in Unity UI call SetSelectedGameObject, often with a null target.

Please let us override EventSystem !!

The only other way to get desired behaviour is to modify StandaloneInputModule AND all selectables so that no one ever calls SetSelectedGameObject(null)…

This is how I dealt with that problem:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections;

using com.spacepuppy;
using com.spacepuppy.Utils;

namespace com.mansion.UserInput
{

    public class MansionInputModule : StandaloneInputModule
    {

        public static GameObject MostRecentlySelectedGameObject
        {
            get;
            private set;
        }

        #region Fields

        [SerializeField]
        public bool IgnoreMouse;

        #endregion

        #region CONSTRUCTOR

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

            this.m_InputOverride = this.AddComponent<MansionInput>();
        }

        #endregion

        #region Methods

        public override void Process()
        {
            if (!this.enabled) return;

            var ev = EventSystem.current;
            if (ev != null)
            {
                if (ev.currentSelectedGameObject != null)
                {
                    MostRecentlySelectedGameObject = ev.currentSelectedGameObject;
                }
                else if (MostRecentlySelectedGameObject != null && MostRecentlySelectedGameObject.activeInHierarchy)
                {
                    ev.SetSelectedGameObject(MostRecentlySelectedGameObject);
                }
            }

            base.Process();

            if(ev != null && ev.currentSelectedGameObject != null)
            {
                ExecuteEvents.ExecuteHierarchy<IUpdateSelectedHandler>(ev.currentSelectedGameObject, this.GetBaseEventData(), (o, e) => o.OnUpdateSelected(e));
            }
        }

        #endregion

        #region Special Types

        public class MansionInput : BaseInput
        {

            #region Fields

            private MansionInputModule _module;

            #endregion

            #region CONSTRUCTOR

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

                _module = this.GetComponent<MansionInputModule>();
            }

            #endregion

            #region Overrides

            public override Vector2 mousePosition
            {
                get
                {
                    if (!UnityEngine.Cursor.visible || (!object.ReferenceEquals(_module, null) && _module.IgnoreMouse))
                        return Vector2.zero;
                    else
                        return base.mousePosition;
                }
            }
          
            public override float GetAxisRaw(string axisName)
            {
                var input = Services.Get<IInputManager>().GetDevice<MansionInputDevice>(Game.MAIN_INPUT);
                if (input == null) return base.GetAxisRaw(axisName);
              
                switch(axisName)
                {
                    case "Horizontal":
                        return input.GetDualAxleState(MansionInputs.Move).x;
                    case "Vertical":
                        return input.GetDualAxleState(MansionInputs.Move).y;
                    default:
                        if (input.InputSignatures.Contains(axisName))
                            return input.GetAxleState(axisName);
                        else
                            return base.GetAxisRaw(axisName);
                }
            }

            public override bool GetButtonDown(string buttonName)
            {
                var manager = Services.Get<IInputManager>();
                if (manager == null) return base.GetButtonDown(buttonName);
                var input = manager.GetDevice<MansionInputDevice>(Game.MAIN_INPUT);
                if (input == null) return base.GetButtonDown(buttonName);

                switch (buttonName)
                {
                    case "Horizontal":
                        return false;
                    case "Vertical":
                        return false;
                    case "Submit":
                        return input.GetButtonState(MansionInputs.Submit) == spacepuppy.SPInput.ButtonState.Down && !Input.GetMouseButtonDown(0);
                    default:
                        if (input.InputSignatures.Contains(buttonName))
                            return input.GetButtonState(buttonName) == spacepuppy.SPInput.ButtonState.Down;
                        else
                            return base.GetButtonDown(buttonName);
                }
            }

            #endregion

        }

        #endregion

    }

}

See the Process method; since I have other stuff going on in here as well.

Note it deals with it after the fact, that we can’t easily avoid. But it deals with it as soon as possible (when it starts processing inputs) so that the inputs don’t notice it was ever null.

The other code which you can mostly ignore is the custom MansionInput that inherits from BaseInput. This code exists because I use a custom input system and this allows me to hook into it. Hence the Services.Get() and what not.