I am in the process of making a node based tool, where I would like to add scene objects to my data (which in its root is a scriptable object).
After some researching, I found ExposedReference, which seems to be exactly what I want/need to get this to work.
After some searching around on how to use ExposedReference with the little to no documentation around on how to get this to work for Scriptable Objects, I managed to get something to work!
For the sake of completion and context of the question, these are the scripts involved:
This is the monobehaviour that implements the IExposedPropertyTable required to resolve the ExposedReference.
public class StoryFlowGraphBehaviour : FlowGraphBehaviour, IExposedPropertyTable
{
[SerializeField] private StoryFlowAsset storyFlowAsset;
// ReSharper disable once Unity.RedundantSerializeFieldAttribute
[SerializeField] private List<PropertyName> propertyNames;
[SerializeField] private List<Object> objects = new List<Object>();
protected void Start()
{
StartFlow(storyFlowAsset);
}
protected override void PerformBindings(IDependencyContainer dependencyContainer)
{
dependencyContainer.Bind<IExposedPropertyTable>().ToInstance(this);
}
public void SetReferenceValue(PropertyName id, Object value)
{
if (PropertyName.IsNullOrEmpty(id))
{
return;
}
int index = propertyNames.IndexOf(id);
if (index > -1)
{
propertyNames[index] = id;
objects[index] = value;
}
else if (value == null)
{
propertyNames.Add(id);
objects.Add(value);
}
}
public Object GetReferenceValue(PropertyName id, out bool idValid)
{
Object result = null;
idValid = false;
int index = propertyNames.IndexOf(id);
if (index > -1)
{
idValid = true;
return objects[index];
}
return null;
}
public void ClearReferenceValue(PropertyName id)
{
int index = propertyNames.IndexOf(id);
if (index > -1)
{
propertyNames.RemoveAt(index);
objects.RemoveAt(index);
}
}
}
This is the Scriptable Object with the ExposedReference in it.
[CreateAssetMenu(fileName = "StoryFlowAsset", menuName = "Story/New StoryFlow Asset")]
public class StoryFlowAsset : FlowGraphAsset
{
public ExposedReference<GameObject> testObject;
}
And this is the Editor script to provide the context, which is the key to be able to reference scene objects in your scriptable object.
[CustomEditor(typeof(StoryFlowGraphBehaviour))]
public class StoryFlowGraphBehaviourEditor : Editor
{
private SerializedProperty asset;
private SerializedObject assetSerializedObject;
public override VisualElement CreateInspectorGUI()
{
VisualElement root = new VisualElement();
if (asset == null)
{
asset = serializedObject.FindProperty("storyFlowAsset");
}
if (EditorGUI.EndChangeCheck())
{
if (asset != null && asset.objectReferenceValue != null)
{
assetSerializedObject = new SerializedObject(asset.objectReferenceValue, target);
}
else
{
assetSerializedObject = null;
}
}
SerializedProperty prop1 = serializedObject.FindProperty("propertyNames");
PropertyField propertyField1 = new PropertyField(prop1);
root.Add(propertyField1);
SerializedProperty prop2 = serializedObject.FindProperty("objects");
PropertyField propertyField2 = new PropertyField(prop2);
root.Add(propertyField2);
if (assetSerializedObject != null)
{
SerializedProperty prop = assetSerializedObject.FindProperty("testObject");
PropertyField field = new PropertyField();
field.BindProperty(prop);
root.Add(field);
}
return root;
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
DrawDefaultInspector();
if (asset == null)
{
asset = serializedObject.FindProperty("storyFlowAsset");
}
if (EditorGUI.EndChangeCheck())
{
if (asset != null && asset.objectReferenceValue != null)
{
assetSerializedObject = new SerializedObject(asset.objectReferenceValue, target);
}
else
{
assetSerializedObject = null;
}
}
if (assetSerializedObject != null)
{
SerializedProperty prop = assetSerializedObject.FindProperty("testObject");
EditorGUILayout.PropertyField(prop);
}
}
}
I provided both the UGUI way and the UI Toolkit way, because simply put: It works as expected in UGUI and it doesn’t work with UI Toolkit.
When using EditorGUILayout.PropertyField(prop); I can see the data being added to the propertyName and object lists, which is needed to resolve this.
However, with the UI Toolkit PropertyField, the data doesn’t get added. I can add the scene objects just fine, but as the data is not added, I cannot resolve it.
So my question is… Why? I don’t know the exact inner workings of (either) properyfields, but I am assuming it has something to do with EditorGUILayout.PropertyField(prop); checking the properties context and calls the IExposedPropertyTable, while the UI Toolkit PropertyField doesn’t.
Is there something I can do to make this work out of the box without manually populating the 2 lists? Perhaps I am missing something?
Is there perhaps a better way to reference scene objects with UI Toolkit in the editor?
Considering the node based tool is all made with UI Toolkit, I am not planning on rewriting this for UGUI.
Any help, tips or insights are very much welcome.