Search Unity

Bug How to fix cursor flickering when Cursor.SetCursor with hotspot is used

Discussion in 'Scripting' started by MikeMnD, Jul 9, 2020.

  1. MikeMnD

    MikeMnD

    Joined:
    Jan 17, 2014
    Posts:
    13
    I followed a tutorial: https://learn.unity.com/tutorial/pr...9fbeedbc2a468bd6bcf4#5cfb9c0eedbc2a4571e4b23e

    And I found a problem/bug or strange issue with the custom mouse cursor.

    All the cursors are 32x32px size textures imported as cursors (check the attachments) and two of the are arrow and sword which are pointing to the top left corner of the image, but the other two are circle aim and door exit sign so the need to be positioned to the center of where the normal cursor is.

    So the issue is if I set the second property of SetCursor to anything than Vector2.zero then in game mode the cursor flickers.

    This works fine, but of course, the cursors which are not an arrow but a circle or square are pivoted to the top left corner instead of the center, which is the correct one.

    This is the code with the flickering:

    Code (CSharp):
    1.  
    2.  
    3. public Texture2D normalCursor;
    4. public Texture2D clickableCursor;
    5. public Texture2D doorwayCursor;
    6.  
    7. void Update()
    8. {
    9.  
    10.     RaycastHit hit;
    11.     Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
    12.  
    13.     if (Physics.Raycast(ray, out hit, 50, clickableLayer))
    14.     {
    15.         var isDoor = false;
    16.         if (hit.collider.gameObject.CompareTag("Doorway"))
    17.         {
    18.             Cursor.SetCursor(doorwayCursor, new Vector2(16, 16), CursorMode.Auto);
    19.             isDoor = true;
    20.         }
    21.         else
    22.         {
    23.             Cursor.SetCursor(clickableCursor, new Vector2(16, 16),CursorMode.Auto);
    24.         }
    25.     }
    26.     else
    27.     {
    28.         Cursor.SetCursor(normalCursor, Vector2.zero, CursorMode.Auto);
    29.     }
    30.  
    31. }
    32.  
    33.  
    If all of the SetCursors are with Vector2.zero there is no flickering.

    That's with the latest Unity 2019 4.2f1 on Windows 10. cursor-flickering.gif
     

    Attached Files:

    Last edited: Jul 9, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    I haven't used the Cursor class but from the API description it sounded potentially fraught with platform-specific quirky behaviors:

    https://docs.unity3d.com/ScriptReference/Cursor.html

    That sorta stuff always scares me because it leads to errors like "works fine except on Windows 10 with service back B" or "Cursor disappears with two monitor setup," things you are NEVER going to be able to solve.

    Also, I have seen a lot of this type of post, where it takes up some 1/3rd of the guys' CPU time:

    https://forum.unity.com/threads/setcursor-alternative.624202/

    I think if I was gonna have a custom cursor like what you're doing, I'd just make my own sprite and move it around the screen based on where Input.mousePosition is each frame. Then you can make it whatever you want, just overlay it with its own separate orthographic camera and you're done. Performant and unbounded to anything else in your game. Just make a cursor scene and additively load it!
     
    MendProj and MikeMnD like this.
  3. MikeMnD

    MikeMnD

    Joined:
    Jan 17, 2014
    Posts:
    13
    Thank you for the information @Kurt-Dekker

    Could you expand a little bit more on: "just overlay it with its own separate orthographic camera".
    How this related to the standard perspective Main Camera?
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    If you were making a general purpose cursor to use in various screens, the cursor wouldn't want to care about how the rest of the scene's cameras are set up, so it could use its own camera set just the way it expects it, and thus will behave the same regardless of other game camera settings. You don't usually use perspective cameras for 2D stuff, but I guess you could, you just gotta get the math right.
     
    MendProj and MikeMnD like this.
  5. Shizola

    Shizola

    Joined:
    Jun 29, 2014
    Posts:
    475
    If anyone else comes across this the flicker only seems to happen when used within Update. Set the desired hotspot on Start and it should be fine.

    The problem with using Input.mousePosition is that you will get some latency. This might be ok, but in my fast moving 2D scenario it was too annoying, so hardware cursor seems like the way to go.
     
    MendProj likes this.
  6. wlblanchette

    wlblanchette

    Joined:
    Jan 19, 2015
    Posts:
    2
    In case someone else comes across this in 2021+, it can be solved by not calling `Cursor.SetCursor` so many times inside Update.

    Here's a basic example of fixed code (from the combat system portion of Swords and Shovels), pulling the offending logic into a function and using returns to highlight the point:
    Code (CSharp):
    1. void SetCursor() {
    2.     // Set cursor
    3.  
    4.     if (_useDefaultCursor) {
    5.       Cursor.SetCursor(pointer, Vector2.zero, CursorMode.Auto);
    6.       return;
    7.     }
    8.  
    9.     // Raycast into scene
    10.     RaycastHit hit;
    11.     if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 50, clickableLayer.value)) {
    12.       if (hit.collider.gameObject.tag == "Doorway") {
    13.         Cursor.SetCursor(doorway, new Vector2(16, 16), CursorMode.Auto);
    14.         return;
    15.       }
    16.  
    17.       if (hit.collider.gameObject.tag == "Chest") {
    18.         Cursor.SetCursor(pointer, new Vector2(16, 16), CursorMode.Auto);
    19.         return;
    20.       }
    21.  
    22.       bool isAttackable = hit.collider.GetComponent(typeof(IAttackable)) != null;
    23.       if (isAttackable) {
    24.         Cursor.SetCursor(sword, new Vector2(16, 16), CursorMode.Auto);
    25.         return;
    26.       }
    27.  
    28.       // Override cursor
    29.       Cursor.SetCursor(target, new Vector2(16, 16), CursorMode.Auto);
    30.       return;
    31.     }
    32.  
    33.     Cursor.SetCursor(pointer, Vector2.zero, CursorMode.Auto);
    34.   }
     
    Hero101 likes this.
  7. Hero101

    Hero101

    Joined:
    Jul 14, 2015
    Posts:
    158
    Person above me is correct. The issue is setting the cursor (Cursor.SetCursor) too many times in Update. I got around this by still setting my cursor within Update, but using a boolean to check if it is already set. Their method might be more optimized though.
    Code (CSharp):
    1.  
    2. void Update()
    3.     {
    4.         Ray ray = cam.ViewportPointToRay(cam.ScreenToViewportPoint(Input.mousePosition));
    5.         RaycastHit hit;
    6.         if (Physics.Raycast(ray, out hit, Mathf.Infinity, ~2))
    7.         {
    8.             if (hit.collider.gameObject.CompareTag("Button"))
    9.             {
    10.                 if (!cursorSet)
    11.                 {
    12.                     cursorSet = true;
    13.                     Cursor.SetCursor(crosshairOver, hotSpot, cursorMode);
    14.                 }
    15.             }
    16.             else if (hit.collider.gameObject.CompareTag("Hyperlink"))
    17.             {
    18.                 if (!cursorSet)
    19.                 {
    20.                     cursorSet = true;
    21.                     Cursor.SetCursor(crosshairOver, hotSpot, cursorMode);
    22.                 }
    23.             }
    24.             else
    25.             {
    26.                 if (cursorSet)
    27.                 {
    28.                     cursorSet = false;
    29.                     Cursor.SetCursor(crosshair, hotSpot, cursorMode);
    30.                 }
    31.             }
    32.         }
    33.     }
    34.