Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Custom PropertyDrawer tracking Transform updates

Discussion in 'UI Toolkit' started by victorluccas, Aug 27, 2023.

  1. victorluccas

    victorluccas

    Joined:
    Sep 11, 2019
    Posts:
    7
    Hello guys!

    I'm trying to learn how to use the UI Toolkit to create editor tools, and currently i'm trying to create a property drawer for a Vector3 that represents a world/local position. My goal is to have a Vector3 field that updates when:
    1. Switching between pivot rotation mode (global/local).
    2. Modifying gameObject transform.
    3. Gets directly modified.
    When modifying this vector3 field, it always sets the serialzied value to local position, but if it displays a global or local value on the editor depends on the Scene -> Pivot Rotation mode.

    My problems right now are:
    • I'm not being able to properly refresh the shown value when changing the Pivot Rotation Mode. ( Though it works if i switch the mode, deselect gameobject, and select it again )
    • I have no clue how to track transform modifications within my code
    I would appreciate some help if possible! This is my code right now:

    Code (PositionPropertyDrawer):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3. using UnityEditor;
    4. using UnityEditor.UIElements;
    5.  
    6.  
    7. [CustomPropertyDrawer(typeof(Position))]
    8. public class PositionPropertyDrawer : PropertyDrawer
    9. {
    10.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    11.     {
    12.         VisualElement root = new VisualElement();
    13.  
    14.         if (property.serializedObject.targetObject is MonoBehaviour mb)
    15.         {
    16.             SerializedProperty positionProperty = property.FindPropertyRelative("value");
    17.  
    18.             SerializedObject transformSerializedObject = new SerializedObject(mb.transform);
    19.             SerializedProperty localPositionSerializedProperty = transformSerializedObject.FindProperty("m_LocalPosition");
    20.  
    21.             Vector3Field vector3Field = new Vector3Field();
    22.             if (Tools.pivotRotation == PivotRotation.Local)
    23.             {
    24.                 vector3Field.value = positionProperty.vector3Value;
    25.             }
    26.             else
    27.             {
    28.                 vector3Field.value = mb.transform.TransformPoint(positionProperty.vector3Value);
    29.             }
    30.  
    31.             Tools.pivotModeChanged += () =>
    32.             {
    33.                 if (Tools.pivotRotation == PivotRotation.Local)
    34.                 {
    35.                     vector3Field.value = positionProperty.vector3Value;
    36.                 }
    37.                 else
    38.                 {
    39.                     vector3Field.value = mb.transform.TransformPoint(positionProperty.vector3Value);
    40.                 }
    41.                 root.MarkDirtyRepaint();
    42.             };
    43.  
    44.             root.Add(vector3Field);
    45.  
    46.             vector3Field.RegisterCallback<ChangeEvent<Vector3>>((changeEvent) =>
    47.             {
    48.                 vector3Field.value = changeEvent.newValue;
    49.  
    50.                 if (Tools.pivotRotation == PivotRotation.Local)
    51.                 {
    52.                     positionProperty.vector3Value = changeEvent.newValue;
    53.                 }
    54.                 else
    55.                 {
    56.                     positionProperty.vector3Value = mb.transform.InverseTransformPoint(changeEvent.newValue);
    57.                 }
    58.  
    59.                 positionProperty.serializedObject.ApplyModifiedProperties();
    60.             });
    61.         }
    62.  
    63.         return root;
    64.     }
    65. }
    Code (Position.cs):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable]
    5. public struct Position
    6. {
    7.     public Vector3 value;
    8. }
    Code (GizmosCustomHandles.cs):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public class GizmosCustomHandles : MonoBehaviour
    5. {
    6.     public Position position;
    7.  
    8.     private void OnDrawGizmos()
    9.     {
    10.         Gizmos.color = Color.blue;
    11.  
    12.         Gizmos.DrawSphere(transform.TransformPoint(position.value), 0.03f);
    13.     }
    14. }
    15.  
     
  2. rawna

    rawna

    Joined:
    Aug 13, 2015
    Posts:
    33
    victorluccas likes this.
  3. victorluccas

    victorluccas

    Joined:
    Sep 11, 2019
    Posts:
    7
    Thank you very much rawna! this is precisely what i needed for the transform part, i was able to get that working ( and soon i can post it here in case anyone want to no exacly how ). Now my only problem is being able trigger the draw field draw when the pivot rotation mode changes :/
     
  4. rawna

    rawna

    Joined:
    Aug 13, 2015
    Posts:
    33
    victorluccas likes this.
  5. victorluccas

    victorluccas

    Joined:
    Sep 11, 2019
    Posts:
    7
    Omg, you are completely right. That was a lack of attention on my part :eek: It is working now
     
    rawna likes this.
  6. victorluccas

    victorluccas

    Joined:
    Sep 11, 2019
    Posts:
    7
    Just in case anyone is interested on the final version:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable]
    5. public struct LocalPosition
    6. {
    7.     public Vector3 value;
    8. }
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class LocalPositionTester : MonoBehaviour
    4. {
    5.     public LocalPosition position;
    6.  
    7.     private void OnDrawGizmos()
    8.     {
    9.         Gizmos.color = Color.blue;
    10.  
    11.         Gizmos.DrawSphere(transform.TransformPoint(position.value), 0.03f);
    12.     }
    13. }
    14.  
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3. using UnityEditor;
    4. using UnityEditor.UIElements;
    5.  
    6. [CustomPropertyDrawer(typeof(LocalPosition))]
    7. public class LocalPositionPropertyDrawer : PropertyDrawer
    8. {
    9.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    10.     {
    11.         VisualElement root = new VisualElement();
    12.  
    13.         if (property.serializedObject.targetObject is MonoBehaviour mb)
    14.         {
    15.             SerializedProperty positionProperty = property.FindPropertyRelative("value");
    16.             Vector3Field exposedField = new Vector3Field(property.name);
    17.             Transform ownerTransform = mb.transform;
    18.             SerializedObject transformSerializedObject = new SerializedObject(mb.transform);
    19.  
    20.             UpdateExposedField(exposedField, ownerTransform, positionProperty.vector3Value);
    21.  
    22.             // Subscribe to Transform changes
    23.             root.TrackSerializedObjectValue(transformSerializedObject, (_) => UpdateExposedField(exposedField, ownerTransform, positionProperty.vector3Value));
    24.  
    25.             // Subscribe to Pivot Mode changes
    26.             Tools.pivotRotationChanged += () =>
    27.             {
    28.                 SerializedProperty positionProperty = property.FindPropertyRelative("value");
    29.                 UpdateExposedField(exposedField, ownerTransform, positionProperty.vector3Value);
    30.             };
    31.  
    32.             // Subscribe to direct input changes
    33.             exposedField.RegisterCallback<ChangeEvent<Vector3>>((changeEvent) =>
    34.             {
    35.                 exposedField.SetValueWithoutNotify(exposedField.value);
    36.                 UpdatePositionProperty(positionProperty, ownerTransform, exposedField.value);
    37.             });
    38.  
    39.             root.Add(exposedField);
    40.         }
    41.  
    42.         return root;
    43.     }
    44.  
    45.     private void UpdatePositionProperty(SerializedProperty positionProperty, Transform ownerTransform, Vector3 localValue)
    46.     {
    47.         if (Tools.pivotRotation == PivotRotation.Local)
    48.         {
    49.             positionProperty.vector3Value = localValue;
    50.         }
    51.         else
    52.         {
    53.             positionProperty.vector3Value = ownerTransform.InverseTransformPoint(localValue);
    54.         }
    55.  
    56.         positionProperty.serializedObject.ApplyModifiedProperties();
    57.     }
    58.  
    59.     private void UpdateExposedField(Vector3Field exposedField, Transform ownerTransform, Vector3 localValue)
    60.     {
    61.         if (Tools.pivotRotation == PivotRotation.Local)
    62.         {
    63.             exposedField.SetValueWithoutNotify(localValue);
    64.         }
    65.         else
    66.         {
    67.             exposedField.SetValueWithoutNotify(ownerTransform.TransformPoint(localValue));
    68.         }
    69.     }
    70. }
     
  7. rawna

    rawna

    Joined:
    Aug 13, 2015
    Posts:
    33
    Do note that you're subscribing to the 'pivotRotationChanged' event every time you show the drawer, but you are not unsubscribing.

    This means as you keep using the editor, the pivot mode change will keep getting slower and taking more memory.

    To confirm this, try adding a Debug.Log("test") in the 'pivotRotationChanged' handler. Then in the hierarchy, select and unselect the game object with the 'LocalPositionTester' a few times. Now when you change the pivot rotation mode, your console will be spammed with many "test" messages (It should only show a single "test" message).


    In your scenario, I would unsubscribe from the event when the root visual element gets disposed. you can do that by registering for 'DetachFromPanelEvent'.

    The code will be something like this:

    Code (CSharp):
    1.  
    2. // Pivot Mode change handler
    3. Action pivotRotationChangedAction = () => {
    4.     SerializedProperty positionProperty = property.FindPropertyRelative("value");
    5.     UpdateExposedField(exposedField, ownerTransform, positionProperty.vector3Value);
    6. };
    7.  
    8. // Subscribe to Pivot Mode changes
    9. Tools.pivotRotationChanged += pivotRotationChangedAction;
    10.  
    11. // Unsubscribe once visual element is detached / disposed
    12. root.RegisterCallback<DetachFromPanelEvent>(e =>
    13.     Tools.pivotRotationChanged -= pivotRotationChanged
    14. );
    15.  
    16.