Как получить экранный ввод из любого места с пользовательским атрибутом редактора? - PullRequest
0 голосов
/ 02 ноября 2018

TL; DR: Как реализовать функциональность Unity «цвет с экрана», но с векторами?

Image of Unity's color picker


Хорошо, так что название довольно упрощено для того, что я пытаюсь сделать:

  1. Пользователь должен нажать кнопку, а затем щелкнуть позицию на экране, чтобы сохранить эту [мировую] позицию в качестве вектора. - В основном это работает, за исключением того, что он не обнаруживает левые щелчки за пределами инспектора.

  2. Отключить щелчок левой кнопкой мыши для всего остального в редакторе единиц (поэтому, когда вы щелкаете позицию, она не меняет фокус на другой GameObject). - Это главная проблема.

Отслеживать мышь и определять мировую позицию было довольно просто, просто сохранять логическое значение, если мышь отслеживается, и SerializedProperty для сохранения того значения, в которое сохраняется позиция мыши.

Вот как выглядит мой атрибут:

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;
    }
}

Вот 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);
        }
    }
}

И вот что он делает до сих пор:

Image of VectorPickerAttribute in action.

Вы нажимаете кнопку справа, и он будет обновлять вектор до положения мыши, пока не обнаружит щелчок левой кнопкой мыши с помощью Input.GetMouseButtonDown(0).

Проблемы с этим:

  • Он будет обнаруживать щелчок только тогда, когда он фактически находится в окне инспектора.

  • Когда вы щелкаете за пределами окна инспектора, он либо ничего не изменит, либо выберет что-то другое, поэтому закроет инспектор (но, поскольку он сохраняет положение мыши каждые OnGUI(), точка, в которой вы щелкнули, будет сохраняется в вектор, так что, я думаю, это работает ??).

Я пытался накрыть экран пустым окном, но мне не удалось заставить GUI.Window или GUI.ModalWindow что-либо сделать в PropertyDrawer. Я также пытался использовать GUI.UnfocusWindow(), но либо он не работает в PropertyDrawer, либо он предназначен только для окон Unity или чего-то подобного.

1 Ответ

0 голосов
/ 03 ноября 2018

Основные аспекты:

  • перезаписать SceneView.onSceneGUIDelegate, чтобы перехватить любые события мыши в SceneView

  • используйте ActiveEditorTracker.sharedTracker.isLocked для блокировки и разблокировки инспектора, чтобы предотвратить потерю фокуса (, из-за которого OnGUI больше не будет вызываться )

  • используйте Selection.activeGameObject и установите его на GameObject, когда ящик включен, чтобы предотвратить потерю фокуса на GameObject (, особенно в тот момент, когда ActiveEditorTracker.sharedTracker.isLocked установлен в значение false, кажется, что он автоматически очищается Selection.activeGameObject)

  • Разрешить возврат значения к предыдущему с помощью клавиши Escape

  • Используйте Event.current.Use(); и / или Event.current = null; ( Я просто хотел быть очень уверенным ), чтобы предотвратить распространение события и его обработку кем-то другим

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Attribute for setting a vector by clicking on the screen in editor mode.
/// </summary>
public class VectorPickerAttribute : PropertyAttribute {
    public readonly bool relative;

    /// <summary>Works a lot like the color picker, except for vectors.</summary>
    /// <param name="relative">Make the final vector relative the transform?</param>
    public VectorPickerAttribute(bool relative = false) {
        this.relative = relative;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(VectorPickerAttribute))]
public class VectorPickerDrawer : PropertyDrawer {
    #region Variables
    bool _trackMouse;
    SerializedProperty _property;
    MonoBehaviour script;

    ///<summary>Keep the currently selected object to avoid loosing focus while/after tracking</summary>
    GameObject _mySelection;

    ///<summary>For reverting if tracking canceled</summary>
    Vector2 _originalPosition;

    ///<summary>Flag for doing Setup only once</summary>
    bool _setup;

    /// <summary>Mouse position from scene view into the world.</summary>
    Vector2 worldPoint;
    #endregion

    /// <summary>
    /// Catch a click event while over the SceneView
    /// </summary>
    /// <param name="sceneView">The current scene view => might not work anymore with multiple SceneViews</param>
    private void UpdateSceneView(SceneView sceneView) {

        Camera cam = SceneView.lastActiveSceneView.camera;
        worldPoint = Event.current.mousePosition;
        worldPoint.y = Screen.height - worldPoint.y - 36.0f; // ??? Why that offset?!
        worldPoint = cam.ScreenToWorldPoint(worldPoint);

        VectorPickerAttribute vectorPicker = attribute as VectorPickerAttribute;
        if(script != null && vectorPicker.relative) worldPoint -= (Vector2)script.transform.position;

        // get current event
        var e = Event.current;

        // Only check while tracking
        if(_trackMouse) {
            if((e.type == EventType.MouseDown || e.type == EventType.MouseUp) && e.button == 0) {
                OnTrackingEnds(false, e);
            }
            else {
                // Prevent losing focus
                Selection.activeGameObject = _mySelection;
            }
        }
        else {
            // Skip if event is Layout or Repaint
            if(e.type == EventType.Layout || e.type == EventType.Repaint) return;

            // Prevent Propagation
            Event.current.Use();
            Event.current = null;

            // Unlock Inspector
            ActiveEditorTracker.sharedTracker.isLocked = false;

            // Prevent losing focus
            Selection.activeGameObject = _mySelection;

            // Remove SceneView callback
            SceneView.onSceneGUIDelegate -= UpdateSceneView;

        }
    }

    /// <summary>
    /// Called when ending Tracking
    /// </summary>
    /// <param name="revert">flag whether to revert to previous value or not</param>
    /// <param name="e">event that caused the ending</param>
    /// <returns>Returns the vector value of the property that we are modifying.</returns>
    private Vector2 OnTrackingEnds(bool revert, Event e) {
        e.Use();
        Event.current = null;
        //Debug.Log("Vector Picker finished");

        if(revert) {
            // restore previous value
            _property.vector2Value = _originalPosition;
            //Debug.Log("Reverted");
        }

        // disable tracking
        _trackMouse = false;

        // Apply changes
        _property.serializedObject.ApplyModifiedProperties();

        return _property.vector2Value;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        script = (MonoBehaviour)property.serializedObject.targetObject;

        if(property.propertyType != SerializedPropertyType.Vector2) {
            EditorGUI.HelpBox(position, "This Attribute requires Vector2", MessageType.Error);
            return;
        }

        var e = Event.current;

        if(!_setup) {
            // store the selected Object (should be the one with this drawer active)
            _mySelection = Selection.activeGameObject;
            _property = property;

            _setup = true;
        }


        // load current value into serialized properties
        _property.serializedObject.Update();

        //specific to the ONE property we are updating
        bool trackingThis = _trackMouse && property.propertyPath == _property.propertyPath;

        GUI.enabled = !trackingThis;
        EditorGUI.PropertyField(position, property, label);
        GUI.enabled = true;


        // Write manually changed values to the serialized fields
        _property.serializedObject.ApplyModifiedProperties();



        if(!trackingThis) {
            var button = new Rect(position) {
                x = position.width - 2,
                width = position.height
            };

            // if button wasn't pressed do nothing
            if(!GUI.Button(button, "")) return;

            // store current value in case of revert
            _originalPosition = _property.vector2Value;

            // enable tracking
            _property = property;
            _trackMouse = true;

            // Lock the inspector so we cannot lose focus
            ActiveEditorTracker.sharedTracker.isLocked = true;

            // Prevent event propagation
            e.Use();

            //Debug.Log("Vector Picker started");
            return;
        }

        // <<< This section is only reached if we are in tracking mode >>>

        // Overwrite the onSceneGUIDelegate with a callback for the SceneView
        SceneView.onSceneGUIDelegate = UpdateSceneView;

        // Set to world position
        _property.vector2Value = worldPoint;

        // Track position until either Mouse button 0 (to confirm) or Escape (to cancel) is clicked
        var mouseUpDown = (e.type == EventType.MouseUp || e.type == EventType.MouseDown) && e.button == 0;
        if(mouseUpDown) {
            // End the tracking, don't revert
            property.vector2Value = OnTrackingEnds(false, e);
        }
        else if(e.type == EventType.KeyUp && _trackMouse && e.keyCode == KeyCode.Escape) {
            // Cancel tracking via Escape => revert value
            property.vector2Value = OnTrackingEnds(true, e);
        }

        property.serializedObject.ApplyModifiedProperties();

        //This fixes "randomly stops updating for no reason".
        EditorUtility.SetDirty(property.serializedObject.targetObject);
    }
}

Я пытался объяснить все в комментариях. Конечно, это все еще имеет некоторые недостатки и может не работать в некоторых особых случаях, но я надеюсь, что оно идет в правильном направлении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...