How do I upgrade my custom fields properties? (aka, why is everything internal?!)

I have some custom fields that were created and used before Unity 2023.2 and Unity 6, before the fancy UxmlElement attribute. So they have some custom properties that need to be carried over.

My fields are based on BaseField which now seems to want all the fields in a single property called value and use a UxmlAttributeConverter for serializing the value. That’s fine and neat. Not sure how that will work in the runtime since it’s an editor-only class, but sure.

But how do I transfer my old properties to this new singular value? Looking at Vector3Field I can see that it uses IUxmlSerializedDataCustomAttributeHandler for getting the old values and setting it to the new value property. That is so extremely useful! But of course, it’s internal. And not only is that interface internal, but the UxmlUtility class is also internal! That one I can recreate but come on! I can not stress enough how fed up I am with all the internal types in UI Toolkit.

Before I start ranting, let me ask the question clearly: How can I take my property1 and property2, combine them into the value property, and then remove the old properties?

<rant>
Let me preface this by saying that I make a lot of custom controls and obviously want them to look as native as possible to Unity fields. I’ve struggled my way through ImGUI in the past and I want to say that using UI Toolkit has been a joy. But it hasn’t. It gets a lot right, don’t get me wrong, but as soon as I want to make something look like it belongs, things get complicated. I’ve had to reinvent the wheel so many times and it’s exhausting.

Let’s say I want to make a composite field (a field with multiple inner fields, like a Vector3 field), I could use the publically available BaseCompositeField. It’s perfect! It does all the things I want and applies all the right styles! BUT NO! The DescribeFields method is internal for whatever reason so I can’t use it. Why? So now I’ve had to make my own composite field builder which is basically identical to the built-in one. If Unity changes something, I need to change mine and I don’t want to keep track of every single patch release, especially when this is for a package.

Now if I want to update my field properties from one Unity version to another I’m also stuck. See above issue because I can be bothered to repeat myself.

Some very useful events, like elementAdded and elementRemoved are also internal on VisualElement. Unless I’ve missed some super obvious event callback, you’d have to jerry-rig some event callback on geometry change just to listen if an element was added or removed. These events can be so useful when creating custom controls. I just wanted to add and remove styles when child elements were added :frowning:

I’m sure there is more because I know I’ve been annoyed in the past when making controls, but I can’t remember anything else right now.

The versions in the past will be forever screwed due to this and all my packages will have to keep these workarounds that replicate internal methods, but in the future, please do better. My packages each have their own implementations of features that exist just an internal method away and I can’t keep up anymore to keep them all updated. I would use a shared library, but you can’t depend on git packages, which in itself is another annoying rant.
</rant>

We don’t currently have a solution for upgrading old values. IUxmlSerializedDataCustomAttributeHandler was a temporary solution we added but we have no plans to make it public, we want to remove it. It relies on hand coding the UxmlSerializedData class which is something we want to be generated automatically. We don’t want any custom UxmlSerializedData classes, we are in the process of phasing them out internally. We do want to support upgrading but via the UxmlElement code generator, it’s something we plan to look into developing further. If somethings internal there’s a good chance it’s not suitable for public usage. Public API needs to be able to handle all the edge cases users will throw at it. It needs documenting, testing and any changes need upgrade paths. So sometimes we make parts internal to see how it goes over time and before we have decided if it should go public. Once it’s public we are locked in. That said there are some exceptions to this rule and parts that should be public, we are working at exposing them. IUxmlSerializedDataCustomAttributeHandler just isn’t one of them. UxmlUtility is a bunch of utility methods, syntactic sugar really. They don’t do anything special.

If you find something that you think should be public you can post it here or file a bug report. I’m also happy for you to message me about it. I can’t promise it will be made public but we will consider it.

1 Like

I understand.
Regarding the upgrade method: it just being a temporary solution makes it all feel so rushed. Unity already yells at me that my UxmlTraits are obsolete but there’s no way forward for my fields without risking breaking old projects. So for now I guess I’ll just have to stick with them and mute the warnings?

If I could choose one thing to make public it would the BaseCompositeField and its DescribeFields method. It would personally eliminate so much boilerplate.

I wouldn’t call it rushed. We began work on this project three years ago and launched the initial version in December 2022. Since then, we’ve dedicated considerable efforts to iterating and enhancing it. A significant portion of our time was spent integrating the system into the UI Builder. Additionally, we introduced support for UxmlObjects, a feature that was not previously supported by the UxmlTraits system.

We’ve also made progress in terms of upgrade support, including changes to class names and attribute names. However, we still need to develop more sophisticated support for upgrading multiple fields into one, which is necessary for the composite fields we use internally and your use case.

Ill speak to the team regarding the BaseCompositeField and its DescribeFields method.

One idea that comes to mind as a workaround for the upgrade is something like this:


[UxmlElement]
public class MyElement : VisualElement
{
    [UxmlAttribute]
    public Vector2 myComposite { get; set; }

    [UxmlAttribute, HideInInspector]
    public float x { get => myComposite.x; set => myComposite.x = value; }

    [UxmlAttribute, HideInInspector]
    public float y { get => myComposite.y; set => myComposite.y = value; }
}

The set method for x,y will only be called if the attributes are included in the UXML.
This method wont let you remove the old attributes but it will at least let you upgrade from an existing UXML file.