如何在 CustomPropertyDrawer 中设置 SerializedProperty.propertyType

Posted

技术标签:

【中文标题】如何在 CustomPropertyDrawer 中设置 SerializedProperty.propertyType【英文标题】:How to set SerializedProperty.propertyType in a CustomPropertyDrawer 【发布时间】:2020-01-16 18:27:59 【问题描述】:

我正在使用这两个库:

Unity-SerializableDictionary: https://github.com/starikcetin/Unity-SerializableDictionary

Unity 场景参考: https://github.com/starikcetin/unity-scene-reference

基本上,可序列化字典查看 propertyType 以确定该属性是否可以扩展,并进行以下检查:

static bool CanPropertyBeExpanded(SerializedProperty property)

    switch(property.propertyType)
    
    case SerializedPropertyType.Generic:
    case SerializedPropertyType.Vector4:
    case SerializedPropertyType.Quaternion:
        return true;
    default:
        return false;
    

但是,场景引用似乎注册为可扩展属性,即使它不是。这是因为 - 显然 - Unity 将其注册为 Generic 类型。

我可以通过将SerializedProperty.propertyType 设置为更有意义的类型来解决这个问题,但它是只读的。

那么,如何设置自定义属性抽屉的SerializedProperty.propertyType

【问题讨论】:

【参考方案1】:

没有记录,但类型 Generic 会自动分配给任何自定义类(例如 SceneReference)。您不能更改propertyType,因为它是只读 ...并且您不能告诉编译器将您的自定义类作为其他东西来处理...

即使如果你可以......什么是“更有意义的类型”? SerializedPropertyType 的可用类型是有限的,没有一个对自定义类更有意义。


这里的主要“问题”是:

SerializableDictionary 抽屉通常假设如果您有一个没有自定义 PropertyDrawer 的自定义 (Generic) 类 - 所以使用默认抽屉 - 它的行为与 Quaternion 或 @987654336 的默认抽屉完全相同@:

第一行有标签和折叠部分 字段/内容在下方绘制/仅在属性折叠时绘制

由于SceneReference 的抽屉没有实现此行为,它被绘制在字典的关键字段之上。


所以作为最简单的修复当然你可以简单地删除

case SerializedPropertyType.Generic:

所以SceneAsset(和所有其他自定义类)被视为正常展开的字段 - 口味问题


或者,您可以做的是更改 PropertyDrawerSceneReference 以反映例如的行为。 Quaternion:

添加EditorGUI.Foldout,其标签会更改property.isExpaned 值 将任何内容移到下一行(并且可以选择) 在属性高度和if(!property.isExpanded)的条件中添加一行

可能看起来例如喜欢:

// 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;


现在看起来像例如

[Serializable]
public class TestDict : SerializableDictionary<string, SceneReference>  

public class Example : MonoBehaviour

    public SceneReference NormalReference;

    public TestDict DictExample = new TestDict();


【讨论】:

嘿,感谢您的努力。您想将此作为 PR 发送到 GitHub 上吗?我希望你的名字能作为贡献者出现在上面。 @S.TarıkÇetin 不客气!将其添加到 git。我在 SerializedDictionary 中也有一个错误顺便说一句:当我有冲突的键名(由于两个都是空字符串)并在上下文菜单中点击 Reset 时,我得到了很多异常并且它是不可逆转的(除了注释掉损坏的字典并重新编译) 谢谢!【参考方案2】:

想象一下这样的课程

public class Test

   public string stringProperty;

现在,尝试设置 propertyType 基本上就像试图告诉编译器更改类的属性类型,这是不可能的,因为它已经写成“字符串”。

你能做的是

static bool CanPropertyBeExpanded(SerializedProperty property)

   float height = EditorGUI.GetPropertyHeight(property);
   // Property expandable if its height is twice the single line height.
   return height >= EditorGUIUtility.singleLineHeight * 2;

【讨论】:

因为这个SceneReference 属性基本上两行,这不会改变检查器中的行为。

以上是关于如何在 CustomPropertyDrawer 中设置 SerializedProperty.propertyType的主要内容,如果未能解决你的问题,请参考以下文章

Unity Inspector面板常用的属性

unity PropertyAttribute,干扰其他 PropertyAttributes

如何在图像中找到明亮区域(以及如何在图像中找到阴影区域)

在QGIS中如何添加天地图的WMTS

如何在表单提交后保留文本(如何在提交后不删除自身?)

如何在异步任务中调用意图?或者如何在 onPostExecute 中开始新的活动?