Unity UI - Mouse Clicks Clearing Selected Object in Eventsystem

Hi,

I was wondering if anyone knows of a method to prevent mouse clicks deselecting objects in a scene when the click doesnt hit a UI element.

When I select a UI element and then click into the empty space next to it, the currently selected object becomes deselected.

Any advice on how to prevent this would be appreciated.

Cheers,

John

Similar problem here, I want to navigate through the UI by only using the keyboard. However, because the mouse click is being detected (mouse is hidden and locked) the first selected object (button) becomes unselected and it results in me not being able to navigate niether with mouse nor keyboard.

5 Answers

5

You need to create your own custom Input Module to change this behavior.

  1. Create a new C# script and name it “MyInputModule”.
  2. Copy & paste the StandaloneInputModule source code into this new script and replace all occurrences of “StandaloneInputModule” with “MyInputModule” in the newly copied code.
  3. To stop mouse clicks from deselecting objects, just delete or comment-out line 322 in the new script: DeselectIfSelectionChanged(currentOverGo, pointerEvent);
  4. Or to disable pointer input completely, just delete or comment-out line 179 in the new script: ProcessMouseEvent();
  5. Finally, remove the StandaloneInputModule script attached to the EventSystem object and replace it with your new [64244-myinputmodule.zip|64244] script to use it.

@FerrenReve

Thanks, this worked great. In my case I still wanted objects to be deselected if there was a left click on nothing so I changed line 322 to: if (data.buttonData.button == PointerEventData.InputButton.Left && !EventSystem.current.IsPointerOverGameObject()) { DeselectIfSelectionChanged(currentOverGo, pointerEvent); }

Thanks for posting this, it worked great for me. However (@Unity) It's really annoying that there's no way to accomplish this without replacing the StandaloneInputModule.

If any of you are lucky enough to be using Rewired, and you want to disable mouse clicks completely, you can use the RewiredStandaloneInputModule. It has a "Allow Mouse Input" checkbox that can be turned off. Just make sure that if you use this, you need to remove/disable any regular StandaloneInputModule components that happen to be in the scene.

Thank you!!

The link to the Unity UI source code is no longer available, and the .zip download doesn't work. If anyone has the code, please tell me. EDIT: I think I found it https://pastebin.com/MLEHvTVN ANOTHER EDIT: This is just supported with the new input system now. Scroll down to see more details in my answer.

With the new Input System, you can just uncheck this box:
167709-deselect-on-background-click.png

Perfect solution. For those who have the same problem and needs to fix it and use old EventSystems, there is an article which describes how to configure project: https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/

Thank you so much. This is exactly what im looking for

You could try using SetSelectedGameObject in the EventSystem to manually set the button as selected when the mouse is clicked like this:

using UnityEngine.EventSystems;

GameObject currentButton;
EventSystem eventSystem;

void Start ()
{
    eventSystem = GameObject.Find("EventSystem").GetComponent<EventSystem>();

    GameObject buttonObj = GameObject.Find("Button");
    buttonObj.GetComponent<Button>().onClick.AddListener(() => { currentButton = buttonObj; });
}

void Update()
{
    if (Input.GetMouseButtonDown(0))
        eventSystem.SetSelectedGameObject(currentButton);
}

Hi, I have tried out the script and I am getting this error. http://prntscr.com/8820ep

Sorry the using UnityEngine.EventSystems; goes above the class name, I just didn't want to include the whole class to make the answer shorter.

I have added the class name however, now a new error poped up. http://prntscr.com/882vi5 Sorry, I am not very good with programming and big thanks for helping out.

Oops, you'll also need using UnityEngine.UI; If you like the interface I'd recommend using Visual Studio, it has a feature where you can right click and resolve to automatically put the using statement up top. I sometimes take that for granted.

For some reason the button still becomes unhighlighted when i left click. Is there a way to completely remove mouse functionality from the game and block any mouse related input? I think first selected is only relevant when the scene is loaded and after that it doesn't matter what happens with it it will not re highlight that button or effect the scene in any way.

While the Deselect On Background Click setting resolves this issue for clicks on objects which don’t implement ISelectHandler, it will still clear the selection if the click is on an object which does have a component implementing this interface.

This causes unwanted behavior for me with disabled buttons, which while they of course implement ISelectHandler, are not reacting to it while interactable is set to false. However, the input system logic will clear the UI selection because the button press was on an ISelectHandler.

I would like the disabled button input to be fully ignored and not clear the current UI selection elsewhere. It also seems like strange behavior that a right click on a selectable, which does not cause it to be selected, should clear selection elsewhere.

The only way I’ve found to work around this is to mark all Graphics under the button to not be raycast targets at the same time I set the button’s interactable to false, but this is a giant pain to have to add to every button’s logic, especially if it has many raycast target children.

You can use a low level mouse hook like so. This is not global (do NOT use a global hook for a video game). This is only if you’re completely and utterly not going to use the mouse or touch screen in your entire game. Just create an instace of this object and do myMouse.EnableClicks() or myMouse.DisableClicks().

public class Mouse
{
 
    #if UNITY_STANDALONE_WIN
    
    public WindowsLowLevelHook windowsLowLevelHook = new WindowsLowLevelHook();
    
    public void DisableClicks()
    {
        windowsLowLevelHook.Enable();
    }
    public void EnableClicks()
    {
        windowsLowLevelHook.Disable();
    }

    public class WindowsLowLevelHook
    {
        public bool enabled = false;

        public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
        public HookProc hookProc;
        
        public delegate void MouseHookCallback(MOUSEHOOKSTRUCT mouseHookStruct);
		
        #region Events
        public event MouseHookCallback LeftButtonDown;
        public event MouseHookCallback LeftButtonUp;
        public event MouseHookCallback RightButtonDown;
        public event MouseHookCallback RightButtonUp;
        public event MouseHookCallback MouseMove;
        public event MouseHookCallback MouseWheel;
        public event MouseHookCallback DoubleClick;
        public event MouseHookCallback MiddleButtonDown;
        public event MouseHookCallback MiddleButtonUp;
        #endregion
        
        public IntPtr hookID = IntPtr.Zero;
        public void Enable()
        {
            hookProc = MouseProc;
            hookID = SetHook(hookProc);
            enabled             = true;
        }
        public void Disable()
        {
            if (hookID == IntPtr.Zero)
                return;

            UnhookWindowsHookEx(hookID);
            hookID                  = IntPtr.Zero;
            enabled                 = false;
        }
        ~WindowsLowLevelHook()
        {
            Enable();
        }

        private IntPtr SetHook(HookProc hookProc)
        {
            using (ProcessModule module = Process.GetCurrentProcess().MainModule)
                return SetWindowsHookEx(WH_MOUSE, hookProc, GetModuleHandle(module.ModuleName), GetCurrentThreadId());
        }

        private IntPtr MouseProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
			MOUSEHOOKSTRUCT lParamStruct = (MOUSEHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MOUSEHOOKSTRUCT));
            
            if (nCode >= 0)
            {

                if (MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
                {
                    return (IntPtr)1;
                }

            }
            return CallNextHookEx(hookID, nCode, wParam, lParam);
        }

        #region WinAPI
        private const int WH_MOUSE = 7;

        private enum MouseMessages
        {
            WM_LBUTTONDOWN = 0x0201,
            WM_LBUTTONUP = 0x0202,
            WM_MOUSEMOVE = 0x0200,
            WM_MOUSEWHEEL = 0x020A,
            WM_RBUTTONDOWN = 0x0204,
            WM_RBUTTONUP = 0x0205,
            WM_LBUTTONDBLCLK = 0x0203,
            WM_MBUTTONDOWN = 0x0207,
            WM_MBUTTONUP = 0x0208
        }

        private Int32 GetWindowThreadProcessID(IntPtr hwnd)
        {
            Int32 pid = 1;
            GetWindowThreadProcessId(hwnd, out pid);
            return pid;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        public struct MOUSEHOOKSTRUCT
        {
            public POINT pt;
            public IntPtr hwnd;
            public uint wHitTestCode;
            public IntPtr dwExtraInfo;
        }
        

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, Int32 dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("kernel32.dll")]
        private static extern Int32 GetCurrentThreadId();

        #endregion
    }

    #endif

}