Hardware Cursor Plugin

Discussion in 'Showcase' started by calveit, Dec 9, 2010.

  1. calveit

    calveit

    New Member

    Joined:
    Jul 4, 2009
    Messages:
    42
    Hi there,

    I needed to have custom hardware cursos in my PC/MAC game, due to low FPS on slow machines. I found no solution, so I've written this very simple plugin. It enables custom hardware cursor support in Unity standalone applications for PC/MAC.

    All you have to do is download the package, and follow the tutorial provided in PDF.

    The plugin requires Unity Pro. Minimum Unity version is 3.0.

    Drop me a line if you find it useful, or in case you had problems or questions.

    Calveit

    Attached Files:

    Last edited: Mar 9, 2011
  2. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Oh man I've wanted this for a while! Never had the Cocoa/Carbon/MFC or whatever knowledge to attempt it, though. I'll put it in our project now and let you know how it goes.
  3. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Can you recommend a .cur editor for Mac OS? The cursor editing market seems to be mostly malware and crapware.
  4. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Ok, so I think I've followed your instructions:

    1. I added CP 0.1.unitypackage to my project
    2. I put a cursor file at Resources/Cursors/aero_unavail.cur
    3. I added the following script to an object in the first scene:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [AddComponentMenu("Cursors/Custom Cursors")]
    5.  
    6. public class CustomCursors : MonoBehaviour {
    7.    
    8.     protected void Awake() {
    9.         string cursorPath = Application.dataPath + "/Cursors/aero_unavail.cur";
    10.         Debug.Log("Loading custom cursor from: " + cursorPath);
    11.         CursorPlugin.InitializeCursor(0, cursorPath);
    12.         CursorPlugin.SetCurrentCursor(0);
    13.     }
    14. }
    Aside from the debug statement, I can see no change in my build. The standard Mac OS cursor is there from the beginning. No errors or anything.

    This is an Intel-only build on Mac OS X 10.6.5. What might I be doing wrong?
  5. calveit

    calveit

    New Member

    Joined:
    Jul 4, 2009
    Messages:
    42
    Hi Daniel,

    Thanks for trying the plugin, and your feedback!

    I guess there's a bug in the document, which says cursors are copied to Application.dataPath / Cursors, but in fact the path should be: Application.dataPath/. Please try the same code with the path:
    Code (csharp):
    1. string cursorPath = Application.dataPath + "/aero_unavail.cur";
    I will change the document if that works for you.

    Maciek
    Last edited: Dec 10, 2010
  6. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Oops, that was my mistake. I just put my cursor in Resources/Cursors for organizational purposes. I didn't realize that the directory would be flattened in the build.

    However, now that I've confirmed that the logged path and the path to the cursor in the build are the same, it still doesn't seem to do anything. Any ideas?
  7. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Ok, I think I understand what was happening. I had forgotten that the contents of Resources is not just copied into the build: only assets in that directory are copied automatically. Your PostprocessBuildPlayer scripts copy the cursor files, and they expect to find the cursors in Resources/Cursors. I must have messed something up the time I thought I was doing it properly.

    In any case, it seems to work fine if you do exactly as you say:

    1. Put the cursor files in Assets/Resources/Cursors
    2. Load files from Application.dataPath + "/" + cursorNameIncludingExtension
    The only problem I've found so far is that under Mac OS, the cursor reverts to the system cursor when you click on the menu bar.
  8. Cameron.

    Cameron.

    Member

    Joined:
    Jun 1, 2009
    Messages:
    714
    What's the benefit of using this as opposed to hiding the cursor and using an in-game asset?
  9. calveit

    calveit

    New Member

    Joined:
    Jul 4, 2009
    Messages:
    42
    Cameron, if you use this you don't have to rely on your game's FPS in matter of displaying the cursor. In other words, if your game runs smoothly (ie. at 60 FPS), there's no need to use this plugin. But if the FPS drops below 20 - 25 FPS, you will notice that your cursor starts to float and does not update its position instantly. That's because it depends on your Update call frequency. This plugin delegates cursor handling to the OS, so it does not depend on your FPS.

    The problem in most cases is: why does your game run so slowly? But there are situations in which you are obliged to support very old machines, or where FPS does not impact the rest of the gameplay (ie. Hidden Object / Puzzle games).

    Daniel, I will look into the problem you've found and post a soulution as soon as I manage to fix it.
  10. the_gnoblin

    the_gnoblin

    New Member

    Joined:
    Jan 10, 2009
    Messages:
    721
  11. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    I find cursor lag very noticeable. Even 16-33ms (which is about what you get when running at 60fps) feels slightly off when compared to hardware cursors.
  12. Cameron.

    Cameron.

    Member

    Joined:
    Jun 1, 2009
    Messages:
    714
    Thanks for that explanation Calveit, makes perfect sense now!
  13. calveit

    calveit

    New Member

    Joined:
    Jul 4, 2009
    Messages:
    42
    I think I've fixed this issue. New version (0.2) is available as attachment to the first post of this thread.
    You do not need to to change your code, only switch OS X library (.bundle) to new version.
  14. immFX

    immFX

    Member

    Joined:
    Mar 20, 2010
    Messages:
    95
    Hey, thanks for this contribution.

    However, the doc/pdf need to be corrected: the call to CursorPlugin.InitializeCursor CursorPlugin.SetCurrentCursor should be:

    HardwareCursors.InitializeCursor
    HardwareCursors.SetCurrentCursor

    Even so, I didn't manage to make it to work. :confused:
    I have a cur file in Resources/Cursors and I have a GO in my scene with a .cs script like this:

    Code (csharp):
    1.  
    2.     void Start()
    3.     {
    4.         HardwareCursors.InitializeCursor((int)0, Application.dataPath + "/hand.cur");
    5.         HardwareCursors.SetCurrentCursor((int)0);
    6.         Screen.showCursor = true;
    7.     }
    8.  
    I Build and Run and see nothing...
    What could be wrong?
    Last edited: Dec 22, 2010
  15. calveit

    calveit

    New Member

    Joined:
    Jul 4, 2009
    Messages:
    42
    Thanks for your feedback immFX!

    You're right, I will correct the tutorial document.

    Your script seems ok and the plugin should work. Could you provide some more details on your situation? What OS you're running? Has Postprocess script copied the .cur files to the Application.dataPath of your build folder?

    Also, have you tried to build sample project?
  16. immFX

    immFX

    Member

    Joined:
    Mar 20, 2010
    Messages:
    95
    Oh, that was it.
    The Postprocess script didn't copy the .cur file (as expected).
    I manually copied it in Application.dataPath and this fixed it.

    Lovely, thanks!
  17. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Hi Calveit,

    I just had a chance to test the new version on a Mac. It definitely behaves better when in windowed mode (going to the system cursor when the cursor leaves the game window is a good idea), but it seems to have problems with fullscreen:

    1. Switching to or from fullscreen changes the cursor back to the system default. It will be changed again when SetCurrentCursor() is called.

    2. If you switch from fullscreen to windowed mode (windowed>fullscreen>windowed or fullscreen>windowed and then call SetCurrentCursor(), the behaviour reverts back to what I saw in version 1: the cursor switches properly, but does not change at the edges of the window. However, clicking on a menu item will cause the cursor to switch back to the system cursor.

    3. Finally, if the game starts in fullscreen then no cursor changes will be respected at all until you switch back to windowed mode.

    If you can fix 2. and 3. then I can work around 1.

    Thanks for your continued work!
  18. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    I've tested this plugin further, and on Windows XP I'm getting a DllNotFoundException. It provides the path where it is looking, and I double-checked it to make sure it is correct. Apparently this can happen if the plugin has a dependency that cannot be satisfied. The plugin works fine on Windows Vista and 7.
  19. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Having looked further into this issue, I might be able to give you more specific information after trying Dependency Walker. I'll do that sometime soon.
  20. RyuMaster

    RyuMaster

    Member

    Joined:
    Sep 13, 2010
    Messages:
    348
    Thanks, Calveit, very nice start!
    I wonder, is there possibility for some Generic GUI class, which will detect when mouse cursor is over some button? Will try looking into it right now.
  21. mrSaig

    mrSaig

    New Member

    Joined:
    Jan 14, 2010
    Messages:
    11
    Very Nice! Thanks for sharing! ... might it also be possible to set the cursors position to a specific location on screen?
  22. taoa

    taoa

    New Member

    Joined:
    Dec 10, 2009
    Messages:
    88
    Hi Calvert, and kudos for this! It'll help a lot of people.

    However, we came up with our own solution some time ago on Windows by relying on the cursor-controlling functions offered by user32.dll.

    We not simply replace the mouse cursor icon but also get/set its position, clipping rectangle (clipped to the game window, so that the mouse doesn't leave it in either windowed mode or if the user has more that one screen) and manage different behaviors if the game isn't in the foreground (we then release the clipping rectangle so that the mouse isn't locked to inside the game's window if the player alt-tabbed to find cheat codes on the web or whatever ^_^).

    I looked in doing the same kind of things on OSX, but I haven't been lucky there, and couldn't find anything I can use to have as much control over the mouse cursor.
    May I enquire as to how you managed to work on the cursor in OSX, if you used a particular library or did it another way?
    Would you be ok with showing us the source code of your Mac bundle?

    In advance, thank you.
  23. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Would you be interested in providing your source code, taoa?
  24. calveit

    calveit

    New Member

    Joined:
    Jul 4, 2009
    Messages:
    42
    Hey guys,

    I'm glad that you find the plugin helpful. I'm afraid I don't have as much time as I'd like to, to work on the plugin, so I decided to share the bundle and dll code. I've included the packages in the first post of this thread. Feel free to use, modify, redistribute it.

    Best,
    Calveit
  25. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    Thank you very much! I'll see if I can removed that DLL dependency myself.
  26. taoa

    taoa

    New Member

    Joined:
    Dec 10, 2009
    Messages:
    88
    There's nothing amazingly difficult once you've found and put user32.dll into your plugin directory.
    Actually I'm not even sure you need to put it in there as this file is available to any program running on Windows.

    Anyway, in user32.dll, you'll find these functions (code copied/pasted from our CS script):

    Code (csharp):
    1.     [DllImport( "user32.dll" )]
    2.     static extern System.IntPtr GetForegroundWindow();
    3.    
    4.     [DllImport( "user32.dll", CharSet = CharSet.Auto, ExactSpelling = true )]
    5.     public static extern System.IntPtr SetCursor( System.IntPtr hCursor );
    6.    
    7.     [DllImport( "user32.dll", CharSet = CharSet.Auto, ExactSpelling = true )]
    8.     public static extern System.IntPtr SetClassLong( HandleRef hWnd, int nIndex, long dwNewLong );
    9.    
    10.     [DllImport( "user32.dll", EntryPoint = "SetClassLong" )]
    11.     public static extern uint SetClassLongPtr32( HandleRef hWnd, int nIndex, uint dwNewLong );
    12.    
    13.     [DllImport( "user32.dll", CharSet = CharSet.Auto, SetLastError = true, ThrowOnUnmappableChar = true, BestFitMapping = false )]
    14.     public static extern System.IntPtr LoadImage( System.IntPtr hInstance, string lpImageName, uint uType, int cxDesired, int cyDesired, uint fuLoad );
    15.    
    16.     [DllImport( "user32.dll" )]
    17.     [return: MarshalAs( UnmanagedType.Bool )]
    18.     static extern bool SetCursorPos( int X, int Y );
    19.    
    20.     [DllImport( "user32.dll" )]
    21.     [return: MarshalAs( UnmanagedType.Bool )]
    22.     static extern bool GetCursorPos( out POINT lpPoint );
    23.    
    24.     [DllImport( "user32.dll" )]
    25.     [return: MarshalAs( UnmanagedType.Bool )]
    26.     public static extern bool GetClipCursor( out RECT rcClip );
    27.    
    28.     [DllImport( "user32.dll", CharSet = CharSet.Auto, ExactSpelling = true )]
    29.     [return: MarshalAs( UnmanagedType.Bool )]
    30.     public static extern bool ClipCursor( ref RECT rcClip );
    31.    
    32.     [DllImport( "user32.dll" )]
    33.     [return: MarshalAs( UnmanagedType.Bool )]
    34.     static extern bool GetWindowRect( int hWnd, ref RECT lpRect );
    35.  
    36.     [DllImport( "user32.dll", CharSet = CharSet.Auto, ExactSpelling = true )]
    37.     public static extern int GetDesktopWindow( );
    Once you've declared them, you just need to use them!

    For a few of them, you'll also need those structures:

    Code (csharp):
    1.  
    2.     [System.Runtime.InteropServices.StructLayout( LayoutKind.Sequential )]
    3.     private struct POINT
    4.     {
    5.         public int X;
    6.         public int Y;
    7.     }
    8.  
    9.     [System.Runtime.InteropServices.StructLayout( LayoutKind.Sequential )]
    10.     public struct RECT
    11.     {
    12.         public int Left;
    13.         public int Top;
    14.         public int Right;
    15.         public int Bottom;
    16.     }
    17.  
    That's about it really.
  27. rasik

    rasik

    New Member

    Joined:
    Oct 8, 2010
    Messages:
    3
    You need to install "Microsoft Visual C++ 2008 Redistributable Package (x86)" on Windows XP in order to use this utility.
  28. Daniel Brauer

    Daniel Brauer

    Member

    Joined:
    Aug 11, 2006
    Messages:
    3,158
    That's not really something I can explain to hundreds of thousands of customers. Surely the same functionality can be accomplished through MFC or something else that is standard with XP?
  29. lubo3d

    lubo3d

    New Member

    Joined:
    Mar 23, 2011
    Messages:
    75
  30. arzi

    arzi

    Member

    Joined:
    Apr 6, 2009
    Messages:
    153
    I've been considering using this one, but the 15 cursor slots are not enough for our purposes. Has anyone managed to raise the limit?

    I'd probably do it myself, but I have no experience on building dlls or osx bundles so just learning that bit would take a lot of time.

    EDIT: We can work around the limitation but changing the UI design a bit, but the cursor's hotspot seems to be on the bottom of the icon rather than on top of it.
    Last edited: Nov 22, 2011
  31. Marrt

    Marrt

    Member

    Joined:
    Feb 7, 2012
    Messages:
    224
  32. Tim C

    Tim C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Messages:
    913
    Hardware cursor should be rendered exactly where the OS thinks the cursor is... because the OS is rendering it. If you are seeing something that is not like this please raise a bug.
  33. Marrt

    Marrt

    Member

    Joined:
    Feb 7, 2012
    Messages:
    224
    it is not a question of its rendering position, the render-position is on spot (you can see it if you use a long vertical line as HW cursor and moving the cursor out of the webplayer)

    it is a question of getting the very exact position of this cursor for use in update, it seems that the SW-Cursor of unity and its Input.mousePosition is lagging behind way more than 1 frame
  34. koyima

    koyima

    Member

    Joined:
    Mar 19, 2009
    Messages:
    1,547
    Just a side note: this is pretty common practice. Nearly every game I have installed from Steam (and this is on Win7) has required some additional install. The trick is to either bundle it or prompt the user during installation. I think most setup creation tools can do this.
  35. Pangamini

    Pangamini

    Member

    Joined:
    Aug 8, 2012
    Messages:
    17
    Hello there.

    First, thanks very much for sharing.
    I have a problem though, it seems that cursor is always flipped back to the default windows arrow when i move the mouse. Setting it in LateUpdate won't help as the hw cursor refreshes faster than the update is called. Any idea?

    PS: Plugin works with *.ani too