TL;DR: How do I implement Unity's 'color from screen' functionality but with vectors?
Ok so title is pretty simplified for what I'm trying to do:
Have the user click a button, then click a position on the screen to have that [world] position be saved as the vector. - This is mostly working, except it won't detect left clicks outside of the inspector.
Disable left click for everything else on the unity editor (so when you click a position it doesn't change focus to another GameObject). - This is the main problem.
Tracking the mouse and getting the world position was pretty easy, it's just a bool to save if the mouse is being tracked and a SerializedProperty to save which value the mouse position is being saved to.
Here's what my attribute looks like:
public class VectorPickerAttribute : PropertyAttribute {
readonly bool relative;
/// <summary>
/// Works a lot like the color picker, except for vectors.
/// </summary>
/// <param name="relative">Make the final vector relative to the transform?</param>
public VectorPickerAttribute(bool relative = false) {
this.relative = relative;
}
}
Here is the PropertyDrawer:
[CustomPropertyDrawer(typeof(VectorPickerAttribute))]
public class VectorPickerDrawer : PropertyDrawer {
bool trackMouse = false;
SerializedProperty v;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if(property.propertyType == SerializedPropertyType.Vector2) {
Rect button = new Rect(position);
button.x = position.width - 2;
button.width = position.height;
bool pressed = GUI.Button(button, "");
if(pressed) {
trackMouse = true;
v = property;
}
else if(Input.GetMouseButtonDown(0)) trackMouse = false;
bool tracking = trackMouse && v.propertyPath == property.propertyPath;
if(tracking) {
property.vector2Value =
Camera.main.ScreenToWorldPoint(
GUIUtility.GUIToScreenPoint(
Event.current.mousePosition
));
}
GUI.enabled = !tracking;
EditorGUI.Vector2Field(position, label.text, property.vector2Value);
GUI.enabled = true;
EditorUtility.SetDirty(property.serializedObject.targetObject);
}
}
}
And here's what it does so far:
You click the button on the right, and it will update the vector to the mouse position until it detects a left click with Input.GetMouseButtonDown(0)
.
Problems with this:
It will only detect a click when it's actually on the inspector window.
When you click outside the inspector window it will either not change anything or it will select something else so it will close the inspector (but since it saves the mouse position every
OnGUI()
that point where you clicked will be saved to the vector, so I guess it works??).
I've tried covering the screen with a blank window, but I couldn't get GUI.Window
or GUI.ModalWindow
to do anything in the PropertyDrawer. I've also tried using GUI.UnfocusWindow()
, but either it doesn't work in PropertyDrawer or it's only meant for Unity's windows or something.
Core aspects:
overwrite
SceneView.onSceneGUIDelegate
in order to catch any mouse events on the SceneViewuse
ActiveEditorTracker.sharedTracker.isLocked
to lock and unlock the inspector to prevent losing the focus (which would cause theOnGUI
not getting called anymore)use
Selection.activeGameObject
and set it to the GameObject the drawer is on in order to prevent losing the focus on the GameObject (especially in the momentActiveEditorTracker.sharedTracker.isLocked
is set to false it seems to automatically clearSelection.activeGameObject
)Allow reverting the value to previous using the Escape key
Use
Event.current.Use();
and/orEvent.current = null;
(I just wanted to be very sure) in order to prevent the event to propagate and being handled by someone elseI tried to explain everything in the comments. Ofcourse this still has some flaws and might not work in some special cases but I hope it gets in the correct direction.