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:
public Texture2D normalCursor;
public Texture2D clickableCursor;
public Texture2D doorwayCursor;
void Update()
{
RaycastHit hit;
Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, 50, clickableLayer))
{
var isDoor = false;
if (hit.collider.gameObject.CompareTag("Doorway"))
{
Cursor.SetCursor(doorwayCursor, new Vector2(16, 16), CursorMode.Auto);
isDoor = true;
}
else
{
Cursor.SetCursor(clickableCursor, new Vector2(16, 16),CursorMode.Auto);
}
}
else
{
Cursor.SetCursor(normalCursor, Vector2.zero, CursorMode.Auto);
}
}
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.
I haven’t used the Cursor class but from the API description it sounded potentially fraught with platform-specific quirky behaviors:
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:
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!
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?
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.
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.
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:
void SetCursor() {
// Set cursor
if (_useDefaultCursor) {
Cursor.SetCursor(pointer, Vector2.zero, CursorMode.Auto);
return;
}
// Raycast into scene
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 50, clickableLayer.value)) {
if (hit.collider.gameObject.tag == "Doorway") {
Cursor.SetCursor(doorway, new Vector2(16, 16), CursorMode.Auto);
return;
}
if (hit.collider.gameObject.tag == "Chest") {
Cursor.SetCursor(pointer, new Vector2(16, 16), CursorMode.Auto);
return;
}
bool isAttackable = hit.collider.GetComponent(typeof(IAttackable)) != null;
if (isAttackable) {
Cursor.SetCursor(sword, new Vector2(16, 16), CursorMode.Auto);
return;
}
// Override cursor
Cursor.SetCursor(target, new Vector2(16, 16), CursorMode.Auto);
return;
}
Cursor.SetCursor(pointer, Vector2.zero, CursorMode.Auto);
}
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.
void Update()
{
Ray ray = cam.ViewportPointToRay(cam.ScreenToViewportPoint(Input.mousePosition));
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity, ~2))
{
if (hit.collider.gameObject.CompareTag("Button"))
{
if (!cursorSet)
{
cursorSet = true;
Cursor.SetCursor(crosshairOver, hotSpot, cursorMode);
}
}
else if (hit.collider.gameObject.CompareTag("Hyperlink"))
{
if (!cursorSet)
{
cursorSet = true;
Cursor.SetCursor(crosshairOver, hotSpot, cursorMode);
}
}
else
{
if (cursorSet)
{
cursorSet = false;
Cursor.SetCursor(crosshair, hotSpot, cursorMode);
}
}
}
}