I’ve isolated the cause and have a workaround. If this has been posted/reported before, I apologize.
In Windows 10, Display Settings:
If you use the Recommended setting of 125% (which it was set to by default), you have to multiply Event.current.mousePosition by 1.25f.
This is the workaround:
float windowsScale = 1.0f; // must be 1.25f if using 125%
Vector2 screenPoint = Event.current.mousePosition * windowsScale;
screenPoint.y = sv.camera.pixelHeight - screenPoint.y;
// Raycast in SceneView will only hit exactly at mouse position with the above.
SceneView sv = SceneView.lastActiveSceneView;
PhysicsScene ps = sv.camera.scene.GetPhysicsScene();
Ray ray = sv.camera.ScreenPointToRay(screenPoint);
if (ps.Raycast(ray.origin, ray.direction, out RaycastHit hit))
{
Handles.DrawSolidDisc(hit.point, hit.normal, 0.25f);
}
camera.ScreenPointToRay - needs the scaling applied to mousePosition
HandleUtility.GUIPointToWorldRay - does NOT need the scaling applied to mousePosition
I was about to post that this was only working in some places and not others and I couldn’t figure out why… then took another look and noticed.
Edit: Note that ScreenPointToRay only has this issue in the Editor with a SceneView camera and Event.current.mousePosition, but it works fine in Play Mode / builds with normal camera and Input.mousePosition.
@TheZombieKiller There have been occasions where it would start returning 0.0f at some random time after working in the Editor for a while. It returns the proper value again as soon as you go change scaling in Windows display settings (without reloading the editor or anything). I added a fallback but do you have any idea why that might happen?
I think GetDpiForWindow will return zero for an invalid or null window handle, so maybe the editorWindow pointer is becoming invalid somehow? Changing the scaling in Windows display settings will cause an editor reload, which would cause the code to search for the window handle again, so it’s a possibility.
You could use this function to check if it’s still a valid window:
And then call Initialize() if it returns false. You can also try just calling that and re-attempting to get the DPI when you get zero.
EDIT: Scratch that, since Initialize uses delayCall, editorWindow wouldn’t be set immediately after. Calling EnumWindows with the appropriate callback and process id instead is what should be done.
The cross-platform solution is to only use the SceneView camera to get the Physics Scene and using HandleUtility.GUIPointToWorldRay instead of ScreenPointToRay during Edit Mode.
First, a generic GetCurrentPhysicsScene function. Works for Edit mode, Prefab Edit mode and Play mode:
public static PhysicsScene GetCurrentPhysicsScene()
{
#if UNITY_EDITOR
if (Application.isPlaying) return Physics.defaultPhysicsScene;
if ((SceneView.lastActiveSceneView != null) && (SceneView.lastActiveSceneView.camera != null) && SceneView.lastActiveSceneView.camera.scene.IsValid()) return SceneView.lastActiveSceneView.camera.scene.GetPhysicsScene();
#endif
return Physics.defaultPhysicsScene;
}
Then:
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
if (GetCurrentPhysicsScene().Raycast(...
Let us know when there’s a publicly available entry in the issue tracker. This bug breaks some tools we have. It’s already clunky enough to get the mouse position in the scene view, so it’s annoying that that’s broken now.
Note that we’re seeing this bug in 2020.1, so it’s not a 2020.2 issue.
The first issue - Physics.defaultPhysicsScene not working in Prefab Edit Mode - is intentional behavior. The PreviewScene used for Prefab Mode uses a local physics scene in order to not ‘pollute’ the default physics world. Prefab Mode has its local editing scene separate from the main scenes (Game scenes). In the repro project from the bug report, raycasting against colliders in Prefab Mode is done in the right way: by getting the local physics and making queries against that. Unity will never instantiate the Prefab Mode objects into the Game world as that would break the game logic there.
The second issue - Event.current.mousePosition giving incorrect coordinates when WindowsScale is not set to 100% - is also expected behavior. In the Game View, the screen coordinates are in pixels, while In the GUI, coordinates are in units independent from the current window scaling. To convert between them, you can multiply/divide by GUIUtility.pixelsPerPoint.
The third issue - Y coordinate of Event.mousePosition is upside down in scene view - is also not a bug, as the point of origin of Event.mousePosition and Camera.ScreenPointToRay is different. As stated in the documentation, Event.mousePosition uses the top-left corner as the origin, while Camera.ScreenPointToRay uses the bottom-left corner as the origin. More information:
Hi @Domas_L - thanks for looking into this and for the detailed explanation. Confirmed it’s all good with a multiplier of EditorGUIUtility.pixelsPerPoint. Thanks again!
Having to multiply by a value every. single. time. we use Event.current.mousePosition is a broken workflow.
Or, rather, we really need a way to get a ray to the mouse in the scene view that’s not dependent on jumping between three different places in the docs to find out how to do that.
@BasteRay ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); was mentioned earlier, I’m not sure if you noticed that post but it’s what I’ve switched to and so far has been working as expected.
Since I came back to this and it might be useful for others;
HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); works, but only during OnSceneGUI. You often want the mouse position at other points - like when you invoke a [MenuItem] or [Shortcut]. At those times, the position is wrong.
The solution I’ve found that has the least hassle is to simply:
[Shortcut("Rain/Play From Mouse Pos", KeyCode.P, ShortcutModifiers.Shift)]
public static void PlayFromMousePos() {
SceneView.duringSceneGui += PlayFromMousePosImpl;
}
private static void PlayFromMousePosImpl(SceneView obj) {
var mousePos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
PlayFrom(mousePos);
SceneView.duringSceneGui -= PlayFromMousePosImpl;
}
Again, it would be very nice if we had a “works at all times, don’t need to think about it” solution for “where is the mouse pos in relation to this scene view’s world space?”.
This is definitely a broken workflow. I ran into a similar issue with drawing IMGUI elements onto the scene view. It would work fine, until you were at a different windows dpi. Then it’d break. So now I have to multiple every single coord x/y by this.
Perhaps even more frustrating is that every single IMGUI example in the help should be telling you to do this. Or perhaps only the screen view is not taking DPI into account when you put IMGUI elements on it, but the inspector/editor windows do. Either way, it’s terrible.