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.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Point & Click game : IsPointerOverGameObject don't work properly with clickables active distance

Discussion in 'Scripting' started by NormanBates31, Jun 29, 2022.

  1. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Hello all !

    I m making a point and click game in the old Lucas Art style and i'm stuck on an problem with IsPointerOverGameObject and i'm becoming mad at it. I will try to explain clearly :

    So i have an inventory, and i want to be able to click an inventory item and then use it on a clickable object of the game. For the moment, the game is designed this way : if you toggle inventory, it activates a var inDialog = true and the player cannot move. He only can interact with the inventory. If he click on an inventory item, then close the inventory, and click on a game object, it works.
    So in the inventory script i removed those lines in green in order to give the player ability to move while inventory is toggled :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Fungus;
    5. using System.Linq;
    6. using UnityEngine.EventSystems;
    7.  
    8. public class Inventory : MonoBehaviour
    9. {
    10.     private MenuDialog[] menuDialogs;
    11.     private SayDialog[] sayDialogs;
    12.     public CanvasGroup canvasGroup;
    13.     private PlayerMovement playerMovement;
    14.  
    15.     public InventoryItem[] inventoryItems;
    16.     public ItemSlot[] itemSlots;
    17.  
    18.     private Flowchart[] flowcharts;
    19.  
    20.     void Start()
    21.     {
    22.         menuDialogs = FindObjectsOfType<MenuDialog>();
    23.         sayDialogs = FindObjectsOfType<SayDialog>();
    24.         canvasGroup = FindObjectOfType<CanvasGroup>();
    25.         playerMovement = FindObjectOfType<PlayerMovement>();
    26.         flowcharts = FindObjectsOfType<Flowchart>();
    27.     }
    28.  
    29.     void Update()
    30.     {
    31.         if (Input.GetButtonDown("Inventory"))
    32.         {
    33.             ToggleInventory(!canvasGroup.interactable);
    34.         }
    35.     }
    36.     private void ToggleInventory(bool setting)
    37.     {
    38.         ToggleCanvasGroup(canvasGroup, setting);
    39.         InitializeItemSlots();
    40.  
    41.   [COLOR=#00b300]     // if (!playerMovement.cutSceneInProgress)
    42.        // {
    43.        //   playerMovement.inDialog = setting;
    44.        // }[/COLOR]
    So now it works but when you click an inventory item, the player follow the click and move, which i don't like.
    So in the PlayerMovement script, i added this line :

    Code (CSharp):
    1.   void Update()
    2.     {
    3.         if(!inDialog)
    4.         {
    5.             var mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    6.             if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
    7.         {
    8.         //if (EventSystem.current.IsPointerOverGameObject())
    9.           //  return;
    10.        
    11.             lastClickedPos = new Vector2(mousePosition.x, mousePosition.y);


    So now, when you click on inventory item, the player don't move, as expected, and the inventory system works. But now, i cannot click any Clickable object of the game... I looked into the event system, and even if the click response is true, the object clicked don't react. Even more strange, when i walk toward the clickable gameobject, it suddenly interact with it. So with further investigation, the problem seems to be connected with the active distance of my clickables, which is settled in a Clickable2D script.



    If i put it very high, everything works. If i set it lower, it seem like the character won't move to it instantly. But if i go to the object by myself and enter the active distance, then it clicked the object.

    Any idea to solve my problem ? i'm really a beginner

    Thanks a lot for your help, i m becoming crazy on this !

    Cheers all
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,963
    Don't get your click input from two separate streams.

    The UI is forced to accept input through the EventSystem input stream.

    But your code is directly asking Input.GetMouseButtonDown(), which knows nothing about your inventory UI. It's a much lower-level stream of data.

    Two possible ways to fix:

    - have your game ALSO respond via the UI's mechanism in 100% of the cases (eg, no more using Input.GetMouseButtonDown()

    OR

    - inhibit all game input whenever any UI is shown (extremely gross way to do it, meaning you can never have clickable UI present while game-clickable areas are present)

    One tactical way to move towards the first option is to just wrap up all the current Input.GetMouseButtonDown() calls into your own static class that simply calls the same function.

    Once tat works you can move towards reading clicks through the UI and injecting them through your own layer to the former game call sites asking if something was clicked.

    I would suggest this might not be a useful response. It can be helpful to remember that if you get mad at code, the code simply laughs at you. :)
     
  3. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    Can you show the Clickable2D script as well?
     
  4. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Yes of course ! Here you are :
    (i forgot to precise that i use Fungus to display dialogs etc... and use the object clicked event in the flowchart)

    Code (CSharp):
    1. // This code is part of the Fungus library (https://github.com/snozbot/fungus)
    2. // It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)
    3.  
    4. using UnityEngine;
    5. using UnityEngine.EventSystems;
    6.  
    7. namespace Fungus
    8. {
    9.     /// <summary>
    10.     /// Detects mouse clicks and touches on a Game Object, and sends an event to all Flowchart event handlers in the scene.
    11.     /// The Game Object must have a Collider or Collider2D component attached.
    12.     /// Use in conjunction with the ObjectClicked Flowchart event handler.
    13.     /// </summary>
    14.     public class Clickable2D : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
    15.     {
    16.         [Tooltip("Is object clicking enabled")]
    17.         [SerializeField] protected bool clickEnabled = true;
    18.  
    19.         [Tooltip("Mouse texture to use when hovering mouse over object")]
    20.         [SerializeField] protected Texture2D hoverCursor;
    21.  
    22.         [Tooltip("Use the UI Event System to check for clicks. Clicks that hit an overlapping UI object will be ignored. Camera must have a PhysicsRaycaster component, or a Physics2DRaycaster for 2D colliders.")]
    23.         [SerializeField] protected bool useEventSystem;
    24.  
    25.         public float activeDistance;
    26.         public string clickableName;
    27.  
    28.         private Verb verb;
    29.  
    30.         private void Start()
    31.         {
    32.             verb = FindObjectOfType<Verb>();
    33.         }
    34.  
    35.         protected virtual void ChangeCursor(Texture2D cursorTexture)
    36.         {
    37.             if (!clickEnabled)
    38.             {
    39.                 return;
    40.             }
    41.  
    42.             Cursor.SetCursor(cursorTexture, Vector2.zero, CursorMode.Auto);
    43.         }
    44.  
    45.         protected virtual void DoPointerClick()
    46.         {
    47.             if (!clickEnabled)
    48.             {
    49.                 return;
    50.             }
    51.  
    52.             var eventDispatcher = FungusManager.Instance.EventDispatcher;
    53.  
    54.             eventDispatcher.Raise(new ObjectClicked.ObjectClickedEvent(this));
    55.         }
    56.  
    57.         protected virtual void DoPointerEnter()
    58.         {
    59.             ChangeCursor(hoverCursor);
    60.             verb.UpdateVerbTextBox(clickableName);
    61.         }
    62.  
    63.         protected virtual void DoPointerExit()
    64.         {
    65.             // Always reset the mouse cursor to be on the safe side
    66.             SetMouseCursor.ResetMouseCursor();
    67.             verb.UpdateVerbTextBox(null);
    68.         }
    69.  
    70.         protected void OnDisable()
    71.         {
    72.             SetMouseCursor.ResetMouseCursor();
    73.         }
    74.  
    75.         #region Legacy OnMouseX methods
    76.  
    77.         protected virtual void OnMouseDown()
    78.         {
    79.             if (!useEventSystem)
    80.             {
    81.                 DoPointerClick();
    82.             }
    83.         }
    84.  
    85.         protected virtual void OnMouseEnter()
    86.         {
    87.             if (!useEventSystem)
    88.             {
    89.                 DoPointerEnter();
    90.             }
    91.         }
    92.  
    93.         protected virtual void OnMouseExit()
    94.         {
    95.             if (!useEventSystem)
    96.             {
    97.                 DoPointerExit();
    98.             }
    99.         }
    100.  
    101.         #endregion
    102.  
    103.         #region Public members
    104.  
    105.         /// <summary>
    106.         /// Is object clicking enabled.
    107.         /// </summary>
    108.         public bool ClickEnabled { set { clickEnabled = value; } }
    109.  
    110.         #endregion
    111.  
    112.         #region IPointerClickHandler implementation
    113.  
    114.         public void OnPointerClick(PointerEventData eventData)
    115.         {
    116.             if (useEventSystem)
    117.             {
    118.                 DoPointerClick();
    119.             }
    120.         }
    121.  
    122.         #endregion
    123.  
    124.         #region IPointerEnterHandler implementation
    125.  
    126.         public void OnPointerEnter(PointerEventData eventData)
    127.         {
    128.             if (useEventSystem)
    129.             {
    130.                 DoPointerEnter();
    131.             }
    132.         }
    133.  
    134.         #endregion
    135.  
    136.         #region IPointerExitHandler implementation
    137.  
    138.         public void OnPointerExit(PointerEventData eventData)
    139.         {
    140.             if (useEventSystem)
    141.             {
    142.                 DoPointerExit();
    143.             }
    144.         }
    145.  
    146.         #endregion
    147.     }
    148. }
    149.  
     
  5. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Maybe you also need the ObjectClicked script. I'm not sure but maybe the answer is there :

    Code (CSharp):
    1. // This code is part of the Fungus library (https://github.com/snozbot/fungus)
    2. // It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)
    3.  
    4. using UnityEngine;
    5. using UnityEngine.AI;
    6. using System.Collections;
    7.  
    8. namespace Fungus
    9. {
    10.     /// <summary>
    11.     /// The block will execute when the user clicks or taps on the clickable object.
    12.     /// </summary>
    13.     [EventHandlerInfo("Sprite",
    14.                       "Object Clicked",
    15.                       "The block will execute when the user clicks or taps on the clickable object.")]
    16.     [AddComponentMenu("")]
    17.     public class ObjectClicked : EventHandler
    18.     {  
    19.         public class ObjectClickedEvent
    20.         {
    21.             public Clickable2D ClickableObject;
    22.             public ObjectClickedEvent(Clickable2D clickableObject)
    23.             {
    24.                 ClickableObject = clickableObject;
    25.             }
    26.         }
    27.  
    28.         [Tooltip("Object that the user can click or tap on")]
    29.         [SerializeField] protected Clickable2D clickableObject;
    30.  
    31.         [Tooltip("Wait for a number of frames before executing the block.")]
    32.         [SerializeField] protected int waitFrames = 1;
    33.  
    34.         protected EventDispatcher eventDispatcher;
    35.  
    36.         private PlayerMovement playerMovement;
    37.  
    38.         private NavMeshAgent agent;
    39.         private Verb verb;
    40.  
    41.         private void Start()
    42.         {
    43.             playerMovement = FindObjectOfType<PlayerMovement>();
    44.             agent = playerMovement.GetComponent<NavMeshAgent>();
    45.             verb = FindObjectOfType<Verb>();
    46.         }
    47.  
    48.  
    49.         protected virtual void OnEnable()
    50.         {
    51.             eventDispatcher = FungusManager.Instance.EventDispatcher;
    52.  
    53.             eventDispatcher.AddListener<ObjectClickedEvent>(OnObjectClickedEvent);
    54.         }
    55.  
    56.         protected virtual void OnDisable()
    57.         {
    58.             eventDispatcher.RemoveListener<ObjectClickedEvent>(OnObjectClickedEvent);
    59.  
    60.             eventDispatcher = null;
    61.         }
    62.  
    63.         void OnObjectClickedEvent(ObjectClickedEvent evt)
    64.         {
    65.             OnObjectClicked(evt.ClickableObject);
    66.         }
    67.  
    68.         /// <summary>
    69.         /// Executing a block on the same frame that the object is clicked can cause
    70.         /// input problems (e.g. auto completing Say Dialog text). A single frame delay
    71.         /// fixes the problem.
    72.         /// </summary>
    73.         protected virtual IEnumerator DoExecuteBlock(int numFrames)
    74.         {
    75.             while(Vector3.Distance(clickableObject.transform.position, playerMovement.transform.position) > clickableObject.activeDistance)
    76.             {
    77.                 yield return new WaitForSeconds(0.1f);
    78.             }
    79.  
    80.             if (Vector3.Distance(clickableObject.transform.position, playerMovement.transform.position) <= clickableObject.activeDistance)  
    81.             {
    82.                 playerMovement.inDialog = true;
    83.                 playerMovement.cutSceneInProgress = true;
    84.                 playerMovement.EnterDialogue();
    85.                 agent.SetDestination(playerMovement.transform.position);
    86.                 playerMovement.lastClickedPos = playerMovement.transform.position;
    87.                 playerMovement.animator.SetFloat("distance", 0f);
    88.  
    89.                 verb.gameObject.SetActive(false);
    90.  
    91.                 if (numFrames == 0)
    92.                 {
    93.                 ExecuteBlock();
    94.                     yield break;
    95.                 }
    96.  
    97.                 int count = Mathf.Max(waitFrames, 1);
    98.                 while (count > 0)
    99.                 {
    100.                     count--;
    101.                     yield return new WaitForEndOfFrame();
    102.                 }
    103.  
    104.                 ExecuteBlock();
    105.         }
    106.         }
    107.  
    108.         #region Public members
    109.  
    110.         /// <summary>
    111.         /// Called by the Clickable2D object when it is clicked.
    112.         /// </summary>
    113.         public virtual void OnObjectClicked(Clickable2D clickableObject)
    114.         {
    115.             if (clickableObject == this.clickableObject)
    116.             {
    117.                 StartCoroutine(DoExecuteBlock(waitFrames));
    118.             }
    119.         }
    120.  
    121.         public override string GetSummary()
    122.         {
    123.             if (clickableObject != null)
    124.             {
    125.                 return clickableObject.name;
    126.             }
    127.  
    128.             return "None";
    129.         }
    130.  
    131.         #endregion
    132.     }
    133. }
    134.  
     
  6. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Thanks for your answer ! I m kind of understanding the theory but to do it, it's another matter...

    I m looking for first option.

    Not sure to understand where to do it... In which script am i supposed to do this ?
    Do i keep it in the void update ? Then create a static class ?

    Thanks a lot for your help :)
     
  7. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    Does the Clickable2D never work correctly or does it not work only after the changes with IsPointerOverGameObject? I ask this because it's not clear if it is a general problem or one related to the changes you described.
     
  8. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Yes everything was working perfectly before the IsPointerOverGameObject ! The game is the one below my signature.

    I'm investigating the ObjectClicked script right now, the one i reposted above. I'm kinda feel the solution is in there but not sure ofc

    thanks a lot !!
     
  9. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    In addition, i realized that the Last Clicked Pos don't refresh when i click on a clickable object. So that's why the player don't go into the active distance i bet stuck.jpg

    Maybe he looks at my clickables as UI ? but why they are settled on Clickables
     
  10. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    What definitely will cause problems is the first while loop inside your coroutine. I don't think you need the loop. Either the click is valid or it isn't. At the moment you are starting the coroutine independently from activateDistance and waiting until the distance is valid + you could start the coroutine with multiple clicks multiple times making all of them waiting until the distance is valid. Why not just checking if the distance is valid or not the moment you click? For what is the loop? If you really need the loop then you need a boolean member variable that blocks the coroutine from starting again in case it's currently running - but like i said, I don't see why you need the first waitloop at all..
     
  11. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    If the clickables are UI elements then it won't set the position. Do you even want to update the position when a clickable is clicked?
     
  12. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    So i get rid of the while loop but still doesn't work... and Last Clicked Pos don't refresh...
     
  13. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    I think i want it for the player to move toward the object and enter the active distance. Then the dialog box appear.

    My clickables are classics game objects, on Layer Clickables, with this kind of stuff on it :
     

    Attached Files:

  14. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    And you want this to happen after the click event is executed?
     
  15. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    Can you put a Debug.Log at line 82 of the ObjectClicked class to check it is being ever executed at all?
     
  16. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Yes exactly. I click on a clickable. Last Clicked Pos refresh. The player goes to it. Enter the active distance. Then the Object clicked event start in fungus flowchart and dialog box appear.

    When i add the PointerOverGameObject line : the Last Clicked Pos don't refresh when i click on a clickable. So nothing appear. But if i enter the active distance by clicking somewhere else close to the clickable, the player goes to it, enter the active distance and the dialog box appear.
     
  17. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    So i tried it but when i click on a clickable nothing appear. But as I explained above, if i walk toward the clickable and enter the active distance, then the Debug.Log appear.

    Code (CSharp):
    1. if (Vector3.Distance(clickableObject.transform.position, playerMovement.transform.position) <= clickableObject.activeDistance)  
    2.             {
    3.                 Debug.Log("Hello!");
    4.                 playerMovement.inDialog = true;
    5.                 playerMovement.cutSceneInProgress = true;
    6.                 playerMovement.EnterDialogue();
    7.                 agent.SetDestination(playerMovement.transform.position);
    8.                 playerMovement.lastClickedPos = playerMovement.transform.position;
    9.                 playerMovement.animator.SetFloat("distance", 0f);
    10.  
     
  18. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    Alright. I don't know why the clickable reacts to IsPointerOverGameObject and I can't test it because I am not at home. Anyway, does your inventory close after the first inventory interaction? If yes you could try to fix the issue by only checking for IsPointerOverGameObject when the inventory is open and otherwise you don't make the check at all. If not, something from the inventory may block your click.
     
  19. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    I understand that's smart. Unfortunately my goal by doing this is to give the player the ability to combine items in the inventory without moving, and also giving him the ability to interact between inventory items and clickables like in every point and click game. Without the IsPointerOverGameObject line, it works.
    Maybe the inventory is blocking that's an idea. I will look into that direction. Thx
     
  20. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    So if your inventory stays open, maybe something from the inventory triggers IsPointerOverGameObject. Maybe a transparent image or text?
     
  21. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    I tried but the problem is there even if you don't open inventory. Even if i deactivate it before running the game, the problem is the same...
     
  22. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    I see. You could also try to make a boundary check with the inventory when it's open using mouse position and avoid IsPointerOverGameObject all together. This would definitely solve the problem however you should still try to find out why IsPointerOverGameObject is returning true for your clickable object..
     
  23. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    Maybe the reason why it's interacting with IsPointerOverGameObject is because it implementes:

    public class Clickable2D : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler

    I guess that's the reason..
     
  24. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Hum interesting. I'm not sure i understand why. Do you have an idea of the reason and how to fix it ?
     
  25. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    This is probably because of OnPointerClick -Enter and -Exit. I recommend using a custom bounding check instead of IsPointerOverGameObject to fix this issue in this case because I don't know how this script is integrated in the tool you are using (i don't know fungus).
     
  26. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    I tried to remove IPointerEnterHandler and IPointerExitHandler and it makes some graphical changes, but the problem is still there (when i enter the active distance, the object is suddenly clicked)

    For the bounding box, what is the method exactly ? Is that in the playermovement script or inventory script ?

    I understand that you want to create a box to put the inventory inside but i have never done that... It was my primary idea before discovering what IsPointerOverGameObject.
     
    Last edited: Jun 29, 2022
  27. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    You basically have to get the RectTransform component of your inventory and do _inventoryRectTrans.rect.Contains(mousePosition) instead of IsPointerOverGameObject().
     
  28. NormanBates31

    NormanBates31

    Joined:
    Feb 22, 2022
    Posts:
    16
    Allright i will try it !
    I finally had solve the problem with this code :

    Code (CSharp):
    1. void Update()
    2.     {
    3.         if(!inDialog)
    4.         {
    5.             var mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    6.             if (Input.GetMouseButtonDown(0) && !IsMouseOverUIWithIgnores())
    7.         {
    8.             lastClickedPos = new Vector2(mousePosition.x, mousePosition.y);
    9.  
    10.         }
    11.  
    12.             agent.SetDestination(new Vector3(lastClickedPos.x, lastClickedPos.y, transform.position.z));
    13.        
    14.             UpdateAnimation();
    15.         }
    16.         AdjustPerspective();
    17.         AdjustSortingLayer();
    18.     }
    19.  
    20.     private bool IsMouseOverUI()
    21.     {
    22.         return EventSystem.current.IsPointerOverGameObject();
    23.     }
    24.  
    25.     private bool IsMouseOverUIWithIgnores()
    26.     {
    27.         PointerEventData pointerEventData = new PointerEventData(EventSystem.current);
    28.         pointerEventData.position = Input.mousePosition;
    29.  
    30.         List<RaycastResult> raycastResultList = new List<RaycastResult>();
    31.         EventSystem.current.RaycastAll(pointerEventData, raycastResultList);
    32.         for (int i = 0; i < raycastResultList.Count; i++)
    33.         {
    34.             if (raycastResultList[i].gameObject.GetComponent<MouseUIClickthrough>() != null)
    35.             {
    36.                 raycastResultList.RemoveAt(i);
    37.                 i--;
    38.             }
    39.         }
    40.  
    41.         return raycastResultList.Count > 0;
    It's a bit gross because now i have to add a MouseUIClickthrough empty script as a component on all my clickables, but it works and give me control over everything so it's nice.
    As i read a ton of posts on the subject, I couldn't figure if the problem was coming from me or from unity.

    Thanks a lot man for your help ! It's so cool to be helped as a beginner.

    Cheers man
     
    Kreshi likes this.
  29. Kreshi

    Kreshi

    Joined:
    Jan 12, 2015
    Posts:
    434
    Well done, looks great :)