I have a serialized object reference, and I am wanting to make a property drawer that uses the target objects serialized data.
So far I have got this working:
[CustomPropertyDrawer(typeof(Wall))]
public class WallDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
VisualElement result = new VisualElement();
// This just calls Resources.Load or loads from cache.
SingleAssetLoader.Load<VisualTreeAsset>("Wall").CloneTree(result);
var so = new SerializedObject(property.objectReferenceValue as Wall);
result.Bind(so);
result.RegisterCallback((ChangeEvent<string> e) =>
{
so.ApplyModifiedProperties();
});
return result;
}
}
However I can not make it save values changed in the new serialised object. I added in the register callback so see if that would help, but ApplyModifiedProperties returns false.
All that work is being done for you by the UI Toolkit binding system and the Inspector window.
But even if you created some custom EditorWindow with UI bound to your own SerializedObject, with UI Toolkit, you donāt need to call ApplyModifiedProperties() anymore. This is called for you when the UI Toolkit binding system catches the ChangeEvents coming from UI fields.
I am not sure you understood the initial issue. The property in this case is an object field. So I am trying to override the the default display of an object field, and instead have fields that edit the target MonoBehaviour directly.
If I try property.FindPropertyRelative it returns null for any properties on the target as expected, as this is a leaf property with only objectReferenceValue set.
So here I was hoping to bind a new serialised object to the tree below result.
I am basically wanting to insert the inspector for the target object so I donāt have to switch to that objects inspector to edit itās fields.
Inspectors within inspectors are a bit tricky with UI Toolkit. You might be able to use the InspectorElement element and bind it to property.objectReferenceValue. InspectorElement is also what the main Unity Inspector Window uses for each component on your object to auto-generate the UI from SerializedProperties.
That said, you cannot do this directly in CreatePropertyGUI(), as that only gets executed once when you first select your main target object. So you only get the value of property.objectReferenceValue that one time and nothing will change when the value changes. This is because UI Toolkit, unlike IMGUI, is retained mode so the UI is an persisted object hierarchy that gets rendered behind the scenes by the system, not by you.
To get around this, you should still use what I mentioned above - the PropertyField - to create your Object Field from the property. In addition to the PropertyField, create an InspectorElement and add it as the second child of āresultā. Finally, use myPropertyField.RegisterCallback<ChangeEvent<Object>>(callback) to register yourself to changes from the ObjectField.
In this ācallback()ā, you can call Bind() on your InspectorElement with the new value of the ObjectField. Youāll need to also call this callback (maybe an override that takes in just an Object as a parameter instead of a ChangeEvent) on the first CreatePropertyGUI().
So now you will need to manually call Bind() and new SerializedObject(), but only on the property.objectReferenceValue, and not the current property.serializedObject. But you still donāt need to use ApplyModifiedProperties().
If you have trouble with InspectorElement, you can also generate an inspector yourself by iterating the SerializedProperties. Hereās a previous thread that went into some detail on this:
Whew, Thanks for the reply, looks tricky but Iāll give it a go. For my current application, I ended up making a class to contain the variables on the target object, and a local list of that class, and I just been updating the target object manually.
Currently got the adding inspector working, however this causes the inspector I am adding it to, to become empty. Looking at generating inspector myself that is almost what I was doing first off, only I put that inside the drawer, and I couldnāt get the changes to save the the target object.
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
VisualElement result = new VisualElement();
var ie = new InspectorElement();
result.Add(ie);
return result;
}
That alone makes the inspector hosting the property field become empty. I tried adding a label and that did not appear either so not sure this approach will work. It seems the editor doesnāt like inspectors inside inspectors.
Iām probably going to continue to use my method of storing all the data on the gameobject with my custom inspector, and either setting the data on the target, or calling target methods directly from the parent game objects inspector.
This way I can make a property drawer for the data enclosed in itās own class. And if have the data field on the target, that automatically gets the drawer too.
This is the basic setup I have gone with:
[Serializable]
public class MyTargetObjectDataSet
{
public GameObject Target;
public string SomeString;
public float SomeFloatThatRequiresCustomFieldsToCalculate;
}
public class MyObjectThatGetsSelectedBehaviour : MonoBehaviour
{
public float SomeFloat;
public MyTargetObjectDataSet TargetData;
private void OnValidate()
{
TargetData.Target.GetComponent<MyTargetObject>().Data = TargetData;
}
}
public class MyTargetObject : MonoBehaviour
{
private MyTargetObjectDataSet data;
public MyTargetObjectDataSet Data
{
get => data;
set
{
data = value;
OnUpdateData();
}
}
private void OnUpdateData()
{
// Do stuff when data gets updated here
}
}
[CustomPropertyDrawer(typeof(MyTargetObject))]
public class MyTargetObjectDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var result = new VisualElement();
// Load/create the content here
return result;
}
}
[CustomEditor(typeof(MyObjectThatGetsSelectedBehaviour))]
public class MyObjectThatGetsSelectedInspector : Editor
{
public override VisualElement CreateInspectorGUI()
{
var result = new VisualElement();
// Load/create custom inspector here
return result;
}
}
One issue here is I need to manually update the MyTargetObject.data field which I do from OnValidate, however this has limitations as what you can do from there and will update the data field if anything else changes too. Ideally I wanted to catch a any-field-change event on the MyTargetObject property element, but I couldnāt figure a way to do that so ended up with OnValidate.
It still feels like a bit of a work, for just wanting to edit another game object in-situ without having to switch to that game object. In the end I can just keep all the data on the āparentā gameobject and act from there, however would be nice to have the target objects be the primary store for their own data, and be able to edit them directly and from the āparentā game object.