Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Question OnMouseDown with new input system?

Discussion in 'Input System' started by Munkey, Aug 20, 2020.

  1. Munkey

    Munkey

    Joined:
    Oct 26, 2013
    Posts:
    23
    What is the way of calling a method on an object that is clicked when using the new input system? In the past I could just have OnMouseDown() or the like in a component on the game object which had a collider. But with the new input system, these events aren't called. While it is listed as a known limitation that these events aren't called, I would hope that Unity's devs have an alternative available given that the input system is in its 1.0 release.
     
    Sam-034 and Stevens-R-Miller like this.
  2. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    if it's a UI control use the eventsystem
     
  3. brunopj1

    brunopj1

    Joined:
    Jun 25, 2020
    Posts:
    4
    Did you manage to find an answer? I'm having the same issue and I can't seem to find a way to get arround this issue.
     
  4. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @Munkey
    @obsidianz
    Here is the method I use.

    Code (CSharp):
    1.  
    2. private void MenuSceneAccept_performed(InputAction.CallbackContext context)
    3.   {
    4.    if (this.CurrentSelectableObject != null)
    5.      {
    6.       if (this.CurrentSelectableObject.GetType() == typeof(Button))
    7.        ((Button)this.CurrentSelectableObject).onClick.Invoke();
    8.       else if (this.CurrentSelectableObject.GetType() == typeof(Toggle))    
    9.        ((Toggle)this.CurrentSelectableObject).isOn ^= true;
    10.      }
    11.   }
    12.  
    I've not found a way for the ComboBox/Dropdown as of yet, you need to make sure that the button is selected, and here is another method to use the EventSystem to that

    Code (CSharp):
    1.  
    2. public Selectable CurrentSelectableObject
    3.   {
    4.    get { return mCurrentSelectableObject; }
    5.    set {
    6.          mCurrentSelectableObject = value;        
    7.          EventSystem.current.SetSelectedGameObject(null);
    8.  
    9.          if (mCurrentSelectableObject != null)
    10.           EventSystem.current.SetSelectedGameObject(mCurrentSelectableObject.gameObject);
    11.        }
    12.   }
    13.  
     
    brunopj1 likes this.
  5. brunopj1

    brunopj1

    Joined:
    Jun 25, 2020
    Posts:
    4
    @piggybank1974

    For the UI the IPointerDownHandler works fine. My problem (and I believe this is @Munkey 's problem as well) is trying to click on sprites/colliders. MonoBehaviour.OnMouseDown() doesn't work with the new Input System.

    I found a post on StackOverflow suggesting the use of a PhysicsRaycaster. Here's the link to the post: https://stackoverflow.com/questions/41391708/how-to-detect-click-touch-events-on-ui-and-gameobjects

    The Raycasting is probably going to work, I just think it's sad that the old method is completly ignored by the new Input System.
     
    ckimport and trombonaut like this.
  6. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @obsidianz
    I'm assuming that you have the button script attached, this is a UI gameobject.

    What about using UnityEngine.InputSystem.Mouse.current?
     
  7. brunopj1

    brunopj1

    Joined:
    Jun 25, 2020
    Posts:
    4
    MonoBehaviour.OnMouseDown() is called whenever the mouse is pressed over a collider attatched to the same gameobject. It also works with UI elements but they are not needed.
    In this case, no button is used. We are just detecting mouse clicks over the collider.
     
    ckimport likes this.
  8. Munkey

    Munkey

    Joined:
    Oct 26, 2013
    Posts:
    23
    @obsidianz I did end up finding a work around, but it feels hacky. The game objects I have which I wish to be clickable actually do have Canvases on them, set to World Space, so that they can display things like the unit's health and status effects. I basically just added an invisible UI Button to the canvas, which I turn on/off depending if the player is in "Selection Mode". This way I can use the UI click handlers like any other UI button.

    @piggybank1974 's answer inspired me to do it. When he said "if it's a UI control..." really got me thinking: What is UI in this situation? But I doubt Unity's devs think that every clickable object in a scene should have a Button component. I imagine a game like Starcraft; does every unit have a little Button game object between the unit and the camera? Each unit needing it's own WorldSpace Canvas? I highly doubt it.

    While creating your own physics raycast to detect clicks would likely work, having to resort to that just to click a gameobject is a real step backwards for prototyping and new users, even for experienced devs. But, I suppose this is why it is shipped as an optional package and not included by default. Still, it really should be easier to do such a common interaction with the new Input System.
     
  9. brunopj1

    brunopj1

    Joined:
    Jun 25, 2020
    Posts:
    4
    @Munkey
    The raycasting is suprisingly easy to do. Just add a Physics2DRaycaster to your main camera and check for colliders doing something like this:

    Code (CSharp):
    1. private void MyMouseClick()
    2. {
    3. Vector2 mousePos = Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue());
    4. RaycastHit2D hit = Physics2D.Raycast(mousePos, Vector2.zero);
    5.         if (hit)
    6.         {
    7.             // Call methods here
    8.             Debug.Log("Raycast Hit -> " + hit.transform.name);
    9.         }
    10. }
    Then just subscribe this function to your mouse click event.
     
  10. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,239
    When you switch to the new input system you need to switch the event system you use with your UI from the "Standalone Input Module" (which is based around the old input system) to the "Input System UI Input Module" (which is based around the new input system) and configure the actions for it. Once you've done that everything will work again.
     
    Iggy101 likes this.
  11. vanderFeest

    vanderFeest

    Joined:
    Apr 12, 2017
    Posts:
    8
    The old OnMouseDown and OnMouseUp messages aren't actually called anymore, even when you do what @Ryiah said here.

    Make sure you have a an EventSystem with an InputSystemUIInputModule in the scene and a PhysicsRaycaster or Physics2DRaycaster on your Camera, and then use the IPointerClickHandler interface on the object with a collider on itself or its children:
    Code (CSharp):
    1.      using UnityEngine;
    2.      using UnityEngine.EventSystems;
    3.      public class MyClass : MonoBehaviour, IPointerClickHandler {
    4.         public void OnPointerClick (PointerEventData eventData)
    5.         {
    6.             Debug.Log ("clicked");
    7.         }
    8.      }
     
    blisz, stonstad, Lad-Ty and 15 others like this.
  12. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    You can use the EventSystem without Implementing InputSystemUIInputModule, if your willing to handle it yourself.

    Last week I posted a UI idea that has fours players that I wanted independently to connect and for example choose their Characters etc.

    https://forum.unity.com/threads/new-input-system-idea-your-thought-please.984833/

    Although I did not get any replies, I've converted the idea to a canvas within the scene and I've got it working with two controllers "I have three but I only have two connected so far on my PC" without using InputSystemUIInputModule, as you can see in the screenshot this is not a simple canvas and with the new Input system it makes this possible although I'm not sure if it is easier as I never did this in the older version, for this type of thing.
     
  13. Tentakero

    Tentakero

    Joined:
    Mar 5, 2017
    Posts:
    17
    Probably the most painless response in this thread. The only thing to constantly remember is to add the raycaster between scenes. Thanks!
     
    mcroswell, bindon and brunopj1 like this.
  14. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    673
    I don't think that will work. Implicitly casting the Vector2 returned from ReadValue to the Vector3 ScreenToWorldPoint expects gives the Vector3 a z value of zero, which is always going to make ScreenToWorldPoint return the coordinates of the camera.
     
  15. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    673
    This works when I run it, and it does not require a PhysicsRaycaster nor an EventSystem:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.InputSystem;
    3.  
    4. public class MyMouseReader : MonoBehaviour
    5. {
    6.     public void ReadMouse(InputAction.CallbackContext ctx)
    7.     {
    8.         if (ctx.phase == InputActionPhase.Performed &&
    9.             Physics.Raycast(
    10.                 Camera.main.ScreenPointToRay(
    11.                     Mouse.current.position.ReadValue()),
    12.                 out RaycastHit hit))
    13.         {
    14.             Debug.LogFormat("You hit [{0}]", hit.collider.gameObject.name);
    15.         }
    16.     }
    17. }
    To get something like OnMouseDown, you could create a component with a public OnMouseDown method, like this:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class MouseDown : MonoBehaviour
    4. {
    5.     public void OnMouseDown()
    6.     {
    7.         Debug.LogFormat("GameObject [{0}] doing mouse down.", gameObject.name);
    8.     }
    9. }
    When your Raycast detects a hit, you would query the object for a MouseDown component and call its OnMouseDown method:
    Code (CSharp):
    1.                 if (Physics.Raycast(
    2.                         Camera.main.ScreenPointToRay(
    3.                             Mouse.current.position.ReadValue()),
    4.                         out RaycastHit hit))
    5.                 {
    6.                     Debug.LogFormat("You hit [{0}]", hit.collider.gameObject.name);
    7.  
    8.                     MouseDown mouseDown = hit.collider.gameObject.GetComponent<MouseDown>();
    9.  
    10.                     mouseDown?.OnMouseDown();
    11.                 }
    Note that this keeps the objects decoupled. That is, the object doing the raycast need not have any prior relationship to the object it hits with the raycast, and the object being hit need not have any prior relationship to the one doing the raycast. When a hit is detected, the object that detected it queries the other object for a MouseDown component and, if it finds one, it runs that component's OnMouseDown method. This way, as you develop and test, either object can be in a scene alone and it will still run fine, with no dependency on the other one.

    You could implement this as an interface so you could have a lot of different components with OnMouseDown methods that did different things, but still allowed the raycasting component to use the same code:
    Code (CSharp):
    1. public interface IMouseDown
    2. {
    3.     void OnMouseDown();
    4. }
    5.  
    6. public class MouseDown : MonoBehaviour, IMouseDown
    7. {
    8.     public void OnMouseDown()
    9.     {
    10.         Debug.LogFormat("GameObject [{0}] doing mouse down.", gameObject.name);
    11.     }
    12. }
    13.  
    14. //...
    15.  
    16.                     IMouseDown mouseDown = hit.collider.gameObject.GetComponent<IMouseDown>();
    17.  
    18.                     mouseDown?.OnMouseDown();
     
    Last edited: Mar 1, 2021
  16. jacksonw7007

    jacksonw7007

    Joined:
    Feb 8, 2018
    Posts:
    1
    this works perfectly for me. Thanks
     
    Stevens-R-Miller likes this.
  17. AndreGulbis

    AndreGulbis

    Joined:
    May 6, 2021
    Posts:
    1
    vanderFeest's solution was perfect for me.
    I had lots of onMouseUpAsButton functions in my code across a number of scenes, and everything was working fine.
    Then I needed to use the new First Person controller and it forced me over into the new input scheme - then none of the clicks were working.
    I noticed I was already using the IPointerEnterHandler and IPointerExitHandler interfaces(?) class extensions(?) for a hover affect, so I only had to add the IPointerClickHandler and change over to the OnPointerClick method and I was right!

    So in the end the basic class ended up looking like the following and all works well!

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5.  
    6.  
    7. public class MyInteractiveObject : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
    8. {
    9.     void Start()
    10.     {
    11.  
    12.     }
    13.  
    14.     void Update()
    15.     {
    16.  
    17.     }
    18.  
    19.     public void OnPointerEnter(PointerEventData eventData)
    20.     {
    21.  
    22.     }
    23.  
    24.     public void OnPointerExit(PointerEventData eventData)
    25.     {
    26.  
    27.     }
    28.  
    29.     public void OnPointerClick(PointerEventData eventData)
    30.     {
    31.  
    32.     }
    33. }
    Cheers!
     
  18. Mike900125

    Mike900125

    Joined:
    Nov 12, 2022
    Posts:
    1
    It's just confusing they make it more complicacted interacting with not-ui game object doing just a click or tap.
     
  19. UnityIco

    UnityIco

    Joined:
    Jan 5, 2017
    Posts:
    18
    This seems to be the easiest way for me

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.InputSystem;
    4.  
    5. public class DragAndDrop : MonoBehaviour, IDragHandler, IBeginDragHandler
    6. {
    7.     public float speed;
    8.     Vector3 dragOffset;
    9.     Camera cam;
    10.     Vector3 mousePos;
    11.     bool isDraging = false;
    12.  
    13.     // Start is called before the first frame update
    14.     void Start()
    15.     {
    16.         cam = Camera.main;
    17.     }
    18.  
    19.     public void OnDrag(PointerEventData eventData)
    20.     {
    21.         transform.position = Vector3.MoveTowards(transform.position, GetMousePos() + dragOffset, speed * Time.deltaTime);
    22.     }
    23.  
    24.     public void OnBeginDrag(PointerEventData eventData)
    25.     {
    26.         dragOffset = transform.position - GetMousePos();
    27.     }
    28.  
    29.     private Vector3 GetMousePos()
    30.     {
    31.         mousePos = Mouse.current.position.ReadValue();
    32.  
    33. // I am moving the 2d objects, so the z needs to be opposite of the camera
    34.         mousePos.z = -cam.transform.position.z;    
    35.  
    36.         mousePos = cam.ScreenToWorldPoint(mousePos);
    37.  
    38.         return mousePos;
    39.     }
    40. }
     
  20. plasol

    plasol

    Joined:
    Dec 20, 2019
    Posts:
    2
    This works fine, but I find out that children game objects trigger pointer events in parent scripts and I don't know how to avoid that.