Created
April 22, 2024 10:05
-
-
Save unitycoder/3f2c6988f415d5b34e0ce3f5138c3cb7 to your computer and use it in GitHub Desktop.
2D mouse world coordinates in scene view (unity)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://forum.unity.com/threads/2d-mouse-world-coordinates-in-scene-view.1581912/ | |
#nullable enable | |
#if UNITY_EDITOR | |
using System.Globalization; | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEngine.InputSystem; | |
namespace My.Common.Scripts.Editor | |
{ | |
/// <summary> | |
/// A window in the scene view that shows the current world coordinates of the mouse cursor in 2D mode. | |
/// </summary> | |
[InitializeOnLoad] | |
public class MouseWorldSceneWindow | |
{ | |
private static Vector3 _currentMouseWorldPosition; | |
private static GUISkin? _customSkin; | |
private static bool _isWindowVisible; | |
private static Rect _windowRect = new(0, 0, 150, 20); | |
private static readonly Vector2 DefaultPosition = new(50f, 10f); | |
private const string MouseWorldSceneWindowName = nameof(MouseWorldSceneWindow); | |
private const string ResetSceneWindowMenuName = "Tools/Reset " + MouseWorldSceneWindow.MouseWorldSceneWindowName; | |
private const string ToggleSceneWindowMenuName = "Tools/Toggle " + MouseWorldSceneWindow.MouseWorldSceneWindowName; | |
private const string WindowPositionXEditorPrefsName = MouseWorldSceneWindow.MouseWorldSceneWindowName + "/SceneWindowPositionX"; | |
private const string WindowPositionYEditorPrefsName = MouseWorldSceneWindow.MouseWorldSceneWindowName + "/SceneWindowPositionY"; | |
private const string WindowVisibilityEditorPrefsName = MouseWorldSceneWindow.MouseWorldSceneWindowName + "/ShowWindow"; | |
/// <summary> | |
/// Static constructor that initializes the window. | |
/// </summary> | |
static MouseWorldSceneWindow() | |
{ | |
// Load preferences | |
MouseWorldSceneWindow._isWindowVisible = EditorPrefs.GetBool(MouseWorldSceneWindow.WindowVisibilityEditorPrefsName, true); | |
MouseWorldSceneWindow._windowRect.x = EditorPrefs.GetFloat(MouseWorldSceneWindow.WindowPositionXEditorPrefsName, MouseWorldSceneWindow.DefaultPosition.x); | |
MouseWorldSceneWindow._windowRect.y = EditorPrefs.GetFloat(MouseWorldSceneWindow.WindowPositionYEditorPrefsName, MouseWorldSceneWindow.DefaultPosition.y); | |
// Update the menu item checked state | |
Menu.SetChecked(MouseWorldSceneWindow.ToggleSceneWindowMenuName, MouseWorldSceneWindow._isWindowVisible); | |
// Register events | |
SceneView.duringSceneGui += MouseWorldSceneWindow.OnSceneGUI; | |
EditorApplication.update += MouseWorldSceneWindow.Update; | |
} | |
/// <summary> | |
/// Create the GUI skin for the scene window. | |
/// </summary> | |
private static void CreateGUISkin() | |
{ | |
if (MouseWorldSceneWindow._customSkin == null) | |
{ | |
Color semiTransparentBackground = new Color32(0, 0, 0, 160); | |
Color textColor = new Color32(224, 224, 224, 255); | |
var windowTexture = MouseWorldSceneWindow.CreateWindowBackgroundTexture((int)MouseWorldSceneWindow._windowRect.width, (int)MouseWorldSceneWindow._windowRect.height, | |
semiTransparentBackground, 5); | |
var customSkin = ScriptableObject.CreateInstance<GUISkin>(); | |
customSkin.window.normal.background = windowTexture; | |
customSkin.window.onNormal.background = windowTexture; | |
customSkin.window.focused.background = windowTexture; | |
customSkin.window.onFocused.background = windowTexture; | |
var labelStyle = new GUIStyle(); | |
labelStyle.fontSize = 16; | |
labelStyle.normal.textColor = textColor; | |
labelStyle.onFocused.textColor = textColor; | |
labelStyle.hover.textColor = textColor; | |
customSkin.label = labelStyle; | |
MouseWorldSceneWindow._customSkin = customSkin; | |
} | |
} | |
/// <summary> | |
/// Creates the background texture for the window. | |
/// </summary> | |
private static Texture2D CreateWindowBackgroundTexture(int width, int height, Color col, int borderRadius) | |
{ | |
var tex = new Texture2D(width, height); | |
var colors = new Color[width * height]; | |
var cornerReferencePoints = new[] | |
{ | |
new Vector2(borderRadius, borderRadius), | |
new Vector2(width - borderRadius, borderRadius), | |
new Vector2(borderRadius, height - borderRadius), | |
new Vector2(width - borderRadius, height - borderRadius), | |
}; | |
for (var y = 0; y < height; y++) | |
{ | |
for (var x = 0; x < width; x++) | |
{ | |
bool isBorder; | |
var coord = new Vector2(x, y); | |
if (coord.x < cornerReferencePoints[0].x && coord.y < cornerReferencePoints[0].y) | |
{ | |
isBorder = Vector2.Distance(coord, cornerReferencePoints[0]) > borderRadius; | |
} | |
else if (coord.x > cornerReferencePoints[1].x && coord.y < cornerReferencePoints[1].y) | |
{ | |
isBorder = Vector2.Distance(coord, cornerReferencePoints[1]) > borderRadius; | |
} | |
else if (coord.x < cornerReferencePoints[2].x && coord.y > cornerReferencePoints[2].y) | |
{ | |
isBorder = Vector2.Distance(coord, cornerReferencePoints[2]) > borderRadius; | |
} | |
else if (coord.x > cornerReferencePoints[3].x && coord.y > cornerReferencePoints[3].y) | |
{ | |
isBorder = Vector2.Distance(coord, cornerReferencePoints[3]) > borderRadius; | |
} | |
else | |
{ | |
isBorder = false; | |
} | |
colors[y * width + x] = isBorder ? Color.clear : col; | |
} | |
} | |
tex.SetPixels(colors); | |
tex.Apply(); | |
return tex; | |
} | |
private static void DrawWindow(int windowID) | |
{ | |
// Show the mouse world position | |
const string FormatString = "F2"; | |
GUILayout.Space(3); | |
GUILayout.BeginHorizontal(); | |
GUILayout.Space(5); | |
GUILayout.Label(MouseWorldSceneWindow._currentMouseWorldPosition.x.ToString(FormatString, CultureInfo.CurrentCulture), GUILayout.Width(60)); | |
GUILayout.Space(5); | |
GUILayout.Label("/", GUILayout.Width(5)); | |
GUILayout.Space(5); | |
GUILayout.Label(MouseWorldSceneWindow._currentMouseWorldPosition.y.ToString(FormatString, CultureInfo.CurrentCulture)); | |
GUILayout.EndHorizontal(); | |
GUILayout.Space(3); | |
// Make the window draggable | |
GUI.DragWindow(); | |
} | |
/// <summary> | |
/// Draws the scene view window. | |
/// </summary> | |
private static void OnSceneGUI(SceneView sceneView) | |
{ | |
if (MouseWorldSceneWindow._isWindowVisible) | |
{ | |
// Has to be done in the OnSceneGUI method, static ctor does not work | |
MouseWorldSceneWindow.CreateGUISkin(); | |
// Draw the scene window | |
Handles.BeginGUI(); | |
GUI.skin = MouseWorldSceneWindow._customSkin; | |
MouseWorldSceneWindow._windowRect = | |
GUILayout.Window(0, MouseWorldSceneWindow._windowRect, MouseWorldSceneWindow.DrawWindow, string.Empty, MouseWorldSceneWindow._customSkin?.window); | |
GUI.skin = null; | |
Handles.EndGUI(); | |
// Save the window positions | |
EditorPrefs.SetFloat(MouseWorldSceneWindow.WindowPositionXEditorPrefsName, MouseWorldSceneWindow._windowRect.x); | |
EditorPrefs.SetFloat(MouseWorldSceneWindow.WindowPositionYEditorPrefsName, MouseWorldSceneWindow._windowRect.y); | |
} | |
} | |
/// <summary> | |
/// Resets the scene view window to its default position. | |
/// </summary> | |
[MenuItem(MouseWorldSceneWindow.ResetSceneWindowMenuName)] | |
public static void ResetWindowPosition() | |
{ | |
MouseWorldSceneWindow._windowRect.x = MouseWorldSceneWindow.DefaultPosition.x; | |
MouseWorldSceneWindow._windowRect.y = MouseWorldSceneWindow.DefaultPosition.y; | |
EditorPrefs.SetFloat(MouseWorldSceneWindow.WindowPositionXEditorPrefsName, MouseWorldSceneWindow._windowRect.x); | |
EditorPrefs.SetFloat(MouseWorldSceneWindow.WindowPositionYEditorPrefsName, MouseWorldSceneWindow._windowRect.y); | |
} | |
/// <summary> | |
/// Shows the scene view window. | |
/// </summary> | |
[MenuItem(MouseWorldSceneWindow.ToggleSceneWindowMenuName)] | |
public static void ToggleWindowVisibility() | |
{ | |
MouseWorldSceneWindow._isWindowVisible = !MouseWorldSceneWindow._isWindowVisible; | |
EditorPrefs.SetBool(MouseWorldSceneWindow.WindowVisibilityEditorPrefsName, MouseWorldSceneWindow._isWindowVisible); | |
Menu.SetChecked(MouseWorldSceneWindow.ToggleSceneWindowMenuName, MouseWorldSceneWindow._isWindowVisible); | |
} | |
/// <summary> | |
/// Updates the scene view window data. | |
/// </summary> | |
private static void Update() | |
{ | |
// Update mouse position during design-time and play mode | |
MouseWorldSceneWindow.UpdateCurrentMouseWorldPosition(); | |
} | |
/// <summary> | |
/// Update the mouse world position. | |
/// </summary> | |
private static void UpdateCurrentMouseWorldPosition() | |
{ | |
// Gets the current scene view and scene view camera while design-time and play mode | |
var sceneView = SceneView.sceneViews.Count > 0 ? (SceneView)SceneView.sceneViews[0] : null; | |
var sceneViewCamera = sceneView?.camera; | |
if (sceneView != null && sceneViewCamera != null) | |
{ | |
// Determine current mouse position with new input system | |
var mouseScreenPosition = Mouse.current.position.ReadValue(); | |
// Determine offsets and bars of the scene view | |
var sceneViewTopBarsHeight = sceneView.rootVisualElement.worldBound.y; | |
var sceneViewAbsoluteScreenOffset = sceneView.position.min; | |
// Adjust y so it takes the top bars of the scene view into account and flip y-axis (cause of different coordinate systems) | |
// and also adjust x/y regarding the scene view offset | |
// (This is the correct code when calling from Update) | |
mouseScreenPosition.y = sceneViewCamera.pixelHeight - (mouseScreenPosition.y - sceneViewTopBarsHeight - sceneViewAbsoluteScreenOffset.y); | |
mouseScreenPosition.x -= sceneViewAbsoluteScreenOffset.x; | |
// Adjust y so it takes the top bars of the scene view into account and flip y-axis (cause of different coordinate systems) | |
// (This would be the correct code when calling from OnSceneGUI) | |
//mouseScreenPosition.y = sceneViewCamera.pixelHeight - (mouseScreenPosition.y - sceneViewTopBarsHeight); | |
MouseWorldSceneWindow._currentMouseWorldPosition = sceneViewCamera.ScreenToWorldPoint(mouseScreenPosition); | |
// Force repaint the scene view, so the scene window is also repainted with the new data | |
SceneView.RepaintAll(); | |
} | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment