Hi there!
I have a property drawer that implements an ObjectField using UI Toolkit.
I am encountering a couple of visual issues regarding the value change.
I am at a loss right now, and any help would be appreciated.
- Undo does not repaint the ObjectField
The value does go back to what it was: clicking on another object in the hierarchy and back to the object with the property drawer updates the visual. That being said, the visual does not update on the spot.
I tries to repaint, to mark the ObjectField as dirty, to subscribe to the undo callback to do both of the previous tests.
- When the property drawer is part of a prefab, all value changes do not trigger the usual visual feedbacks and I can’t see the changed value in Overrides.
a. The blue bar on the left side does not appear.
b. The text does not have a bold modifier.
c. The value changes, and I can apply it through the Override, but I cannot see what changed.
Is there a utility somewhere I should use to plug those behaviours in?
Are you binding the object field to a serialized property?
Yep.
Binded and updated.
I can use the binded value from the ObjectField also regardless of dos and undos.
Are you using Runtime binding instead of Editor binding? This problem can be caused by improper binding.
What version of Unity are you using? It seems your normal inspector window is done with UI Toolkit, but the overrides panel is done with IMGUI. That hasn’t been the normal behavior in a while, so maybe the problem is that you’re using an old version.
It’s hard to know without more info. If you share some code, you could get better help.
version
latest 2022-3-LTS
I try to avoid using non-LTS versions unless I absolutely need one of the feature or change it brings.
I will download the latest non-LTS for testing purposes though. What you said about the format change for overrides intrigues me.
simplified code
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace Dummy
{
[Serializable]
public class ObjectFieldAsPropertyDrawer
{
public string Guid;
}
[CustomPropertyDrawer(typeof(ObjectFieldAsPropertyDrawer))]
public class ObjectFieldPropertyDrawerUIToolkit : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// VisualElement creation
var container = new VisualElement();
var objectField = new ObjectField(property.displayName)
{
objectType = typeof(SceneAsset),
allowSceneObjects = false
};
objectField.AddToClassList(ObjectField.alignedFieldUssClassName);
// binding
var guidProperty = property.FindPropertyRelative("Guid");
objectField.BindProperty(guidProperty);
// subscribe to value change
objectField.RegisterValueChangedCallback(evt =>
{
var sceneAsset = evt.newValue as SceneAsset;
if (sceneAsset != null)
{
var path = AssetDatabase.GetAssetPath(sceneAsset);
guidProperty.stringValue = AssetDatabase.AssetPathToGUID(path);
}
else
{
guidProperty.stringValue = "";
}
guidProperty.serializedObject.ApplyModifiedProperties();
});
// inject reference
if (!string.IsNullOrEmpty(guidProperty.stringValue))
{
var path = AssetDatabase.GUIDToAssetPath(guidProperty.stringValue);
objectField.value = AssetDatabase.LoadAssetAtPath<SceneAsset>(path);
}
// return the ObjectField
container.Add(objectField);
return container;
}
}
}
using UnityEngine;
namespace Dummy
{
public class ObjectFieldShowcase : MonoBehaviour
{
[SerializeField] private ObjectFieldAsPropertyDrawer _serializedProperty;
}
}
Visual example
The weird bit at the end is me doing undos until I get the one for the SerializedField of ObjectFieldShowcase.
Okay. I see one of the problems. Binding an ObjectField to a string field doesn’t work, as evidenced by the fact that you have to update the field manually.
-
You could add a hidden string field and register a change callback to know when it’s value changes from outside your UI. You won’t get the callback when the field represents multiple different values from different Objects at the same time, though. But you could poll the field’s showMixedValue from time to time to know it.
-
You could use TrackPropertyValue, it’s been buggy for a while though.
-
You could poll for changes from outside of your UI continuously with schedule. None of these three solutions handle prefab overrides and property menu support, though.
-
I have this custom element that let’s you add prefab and property context menu support to anything you want in UITK. You’d have to handle the binding to the string though.
-
You could make a custom Element that inherits from BaseField<string>. This solution supports proper prefab override handling, property menus and mixed values. You’ll have to use the protected BaseField(string label, VisualElement visualInput)
constructor in your code. You can pass null as a visual Input, Unity will generate one that you can get by quering for the inputUssClassName. Then, you can add your ObjectField, without a label to the visual input. You should also override UpdateMixedValueContent and propagate your field’s showMixedValue to the ObjectField there.
Now. The problem where you click the overrides panel and it says “No GUI Implemented” is weirder. That problem only happens when Unity tries to use IMGUI for a drawer and the drawer doesn’t override the OnGUI method. Either your CreatePropertyGUI is returning null in some cases for some reason, or Unity is not using UITK in the overrides window for that inspector for some reason. I’ve checked version 2022.3 in my windows PC and it does use UITK there, so I don’t know what’s happening in your side.
EDIT
If you choose to go the custom BaseField route, you should add the alignedFieldUssClassName to it so that it is aligned in the Inspector. You should also set the margins of your ObjectField to 0.
Hey!
I’m a bit late, but I have an update on things.
Binding
Although I did not use any of your proposals per say, it gave me an idea that worked for my use case.
I used a UnityEngine.Object to reference the SceneAsset that I use as type for the object field.
Binding to that object solved the binding issue that I overlooked.
After that, it’s only a question of updating the guid reference on the object field ValueChanged callback for the rest of my use case.
Override GUI
Now for the override GUI issue, this is a bit odd in that the solution that I found from this post implies that we are still required to override the OnGUI as such:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUILayout.PropertyField(property);
}
It overrides it in a way that shows all fields from the class with the property drawer (guid, and object in this case). Which is perfectly fine to me.
Contextual Menu Manipulator
There is yet another issue where the pasting from the ContextualMenuManipulator in relation to the class with the property drawer on top led to unity crashing.
My guess is that it might be because I have two fields in the class used by the property drawer and unity gets confused somehow.
I did not find a proper solution to this for my use case though.
I can override the manipulator and implement my own logic, but it makes the Apply to Foo quite a nightmare to handle.
Closing thoughts
This whole ordeal is a mess, to be honest.
I have encountered a lot of issues that one might not expect to encounter from an implementation that would otherwise seem very simple at first glance.
Unless someone from Unity can tell me that I am doing things very wrong (I’d gladly take it), I will consider this reply as the solution paired with what @oscarAbraham provided above.
Cheers!