I am using these two libraries:
Unity-SerializableDictionary: https://github.com/starikcetin/Unity-SerializableDictionary
Unity Scene Reference: https://github.com/starikcetin/unity-scene-reference
Basically, the serializable dictionary looks at the propertyType to determine if the property can be expanded or not, with the following check:
static bool CanPropertyBeExpanded(SerializedProperty property)
{
switch(property.propertyType)
{
case SerializedPropertyType.Generic:
case SerializedPropertyType.Vector4:
case SerializedPropertyType.Quaternion:
return true;
default:
return false;
}
}
However, it appears that Scene Reference is registered as an expandable property even though it isn't. This is because -apparently- Unity registers it as of type Generic
.
I can solve this simply by setting the SerializedProperty.propertyType
to a more meaningful type, but it is read-only.
So, how can I set the SerializedProperty.propertyType
of a custom property drawer?
It isn't documented but the type Generic
is automatically assigned for any custom class (like e.g. SceneReference
). You can NOT change the propertyType
since it is read-only ... and you can't tell the compiler to handle your custom class as something else ...
Even if you could ... what would be a "more meaningful type"? The available types for SerializedPropertyType
are limited and none of them is more meaningful for a custom class.
The main "issue" here is:
The SerializableDictionary
drawer simply assumes that usually if you have a custom (Generic
) class without a custom PropertyDrawer
- so using the default drawer - it behaves exactly like the default drawer of Quaternion
or Vector4
:
Since the drawer for SceneReference
doesn't implement this behavior it is drawn on top of the dictionary's key-field.
So as simplest fix of course you can simply remove the
case SerializedPropertyType.Generic:
so the SceneAsset
(and all other custom classes) is treated like a normal unfolded field - a matter of taste
Alternatively what you could do about it is change the PropertyDrawer
of SceneReference
to reflect the behavior of e.g. Quaternion
:
EditorGUI.Foldout
with the label which changes the property.isExpaned
valueif(!property.isExpanded)
Might look e.g. like:
// Made these two const btw
private const float PAD_SIZE = 2f;
private const float FOOTER_HEIGHT = 10f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Move this up
EditorGUI.BeginProperty(position, GUIContent.none, property);
{
// Here we add the foldout using a single line height, the label and change
// the value of property.isExpanded
property.isExpanded = EditorGUI.Foldout(new Rect(position.x, position.y, position.width, lineHeight), property.isExpanded, label);
// Now you want to draw the content only if you unfold this property
if (property.isExpanded)
{
// Optional: Indent the content
//EditorGUI.indentLevel++;
//{
// reduce the height by one line and move the content one line below
position.height -= lineHeight;
position.y += lineHeight;
var sceneAssetProperty = GetSceneAssetProperty(property);
// Draw the Box Background
position.height -= FOOTER_HEIGHT;
GUI.Box(EditorGUI.IndentedRect(position), GUIContent.none, EditorStyles.helpBox);
position = boxPadding.Remove(position);
position.height = lineHeight;
// Draw the main Object field
label.tooltip = "The actual Scene Asset reference.\nOn serialize this is also stored as the asset's path.";
var sceneControlID = GUIUtility.GetControlID(FocusType.Passive);
EditorGUI.BeginChangeCheck();
{
// removed the label here since we already have it in the foldout before
sceneAssetProperty.objectReferenceValue = EditorGUI.ObjectField(position, sceneAssetProperty.objectReferenceValue, typeof(SceneAsset), false);
}
var buildScene = BuildUtils.GetBuildScene(sceneAssetProperty.objectReferenceValue);
if (EditorGUI.EndChangeCheck())
{
// If no valid scene asset was selected, reset the stored path accordingly
if (buildScene.scene == null) GetScenePathProperty(property).stringValue = string.Empty;
}
position.y += paddedLine;
if (!buildScene.assetGUID.Empty())
{
// Draw the Build Settings Info of the selected Scene
DrawSceneInfoGUI(position, buildScene, sceneControlID + 1);
}
// Optional: If enabled before reset the indentlevel
//}
//EditorGUI.indentLevel--;
}
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var sceneAssetProperty = GetSceneAssetProperty(property);
// Add an additional line and check if property.isExpanded
var lines = property.isExpanded ? sceneAssetProperty.objectReferenceValue != null ? 3 : 2 : 1;
// If this oneliner is confusing you - it does the same as
//var line = 3; // Fully expanded and with info
//if(sceneAssetProperty.objectReferenceValue == null) line = 2;
//if(!property.isExpanded) line = 1;
return boxPadding.vertical + lineHeight * lines + PAD_SIZE * (lines - 1) + FOOTER_HEIGHT;
}
Now it looks like e.g.
[Serializable]
public class TestDict : SerializableDictionary<string, SceneReference> { }
public class Example : MonoBehaviour
{
public SceneReference NormalReference;
public TestDict DictExample = new TestDict();
}