What I am attempting to do is create a custom UIElements control for use during runtime via UIElements->Panel(PanelRenderer).
In the Game window in editor, the custom control(CustomToolbar) renders without issue. However, once built to a player executable, I get the following error in the Player.log. “Element ‘CustomToolbar’ has no registered factory method.” and in the game it shows “Unknown type:‘CustomToolbar’”.
I created a factory class nested within my CustomToolbar class.
public new class UxmlFactory : UxmlFactory<CustomToolbar> { }
I disabled managed code stripping by setting the managed stripping level to disabled, I also tried adding a preserve attribute to the factory class to ensure that it wasn’t being optimized out on the built assemblies. However, none of these efforts have had an effect on the “Element ‘CustomToolbar’ has no registered factory method.” error.
I turned on full trace logging and was able to get the following error to the Player.log. Below is the full error.
This is the line from my UXML that is attempting to load the custom control.
Any help would be greatly appreciated.
Has anyone been able to get custom UIElement controls to successfully load in a built player executable?
I added the preserve assembly attribute and it had no effect on the error, I have managed stripping level set to disabled, so I wasn’t expecting to do anything. Below is the code with the added preserve attribute.
[assembly: Preserve]
public class CustomToolbar : VisualElement
{
Thanks for the advice, but I think the issue is with the VisualElementFactoryRegistry within the UnityEngine.UIElementsModule.dll
from what it looks like after investigating the following log line:
I decompiled UnityEngine.UIElementsModule.dll to do some digging, the error is originating at the following line:
bool flag = !VisualElementFactoryRegistry.TryGetValue(asset.fullTypeName, out list);
After digging into VisualElementFactoryRegistry, it appears that the following are the only registered factory classes:
private static void RegisterEngineFactories()
{
IUxmlFactory[] array = new IUxmlFactory[]
{
new UxmlRootElementFactory(),
new Button.UxmlFactory(),
new VisualElement.UxmlFactory(),
new IMGUIContainer.UxmlFactory(),
new Image.UxmlFactory(),
new Label.UxmlFactory(),
new RepeatButton.UxmlFactory(),
new ScrollView.UxmlFactory(),
new Scroller.UxmlFactory(),
new Slider.UxmlFactory(),
new SliderInt.UxmlFactory(),
new MinMaxSlider.UxmlFactory(),
new Toggle.UxmlFactory(),
new TextField.UxmlFactory(),
new TemplateContainer.UxmlFactory(),
new Box.UxmlFactory(),
new PopupWindow.UxmlFactory(),
new ListView.UxmlFactory(),
new TreeView.UxmlFactory(),
new Foldout.UxmlFactory(),
new BindableElement.UxmlFactory()
};
IUxmlFactory[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
IUxmlFactory factory = array2[i];
VisualElementFactoryRegistry.RegisterFactory(factory);
}
}
After analyzing the dll, VisualElementFactoryRegistry has an access level of internal, so all references to the class should only be found within the assembly. The only use of the RegisterFactory method is in RegisterEngineFactories(), not allowing any custom controls.
I remember seeing this issue a while back and it got fixed. We use reflection to auto-detect Factory types and instantiate them but obviously that failed in your case. That part of the code has changed a bit since 0.0.4 but I’ll try to repro with your setup. In the meantime, you’ll need to modify some code straight into our preview package to open up some internal apis, or call them via reflection:
Call this with an instance of your type: UXMLRuntimeFactories.RegisterFactory(new CustomToolbar.UxmlFactory());
This code is in UIElements Runtime\Runtime\UxmlRuntimeFactories.cs. If you want to modify it, copy the UIElements Runtime folder straight into your Packages folder and remove the line from your manifest.json
With your help pointing out that method, I had overlooked the reference to VisualElementFactoryRegistry within the UIElements.Runtime package, I have tracked down the issue. The problem appears to be with the order that methods execute within UIElements.Runtime package.
Turns out that the factory methods were getting registered, but it was happening late.
UXMLRuntimeFactories uses the RuntimeInitializeOnLoadMethod attribute.
[RuntimeInitializeOnLoadMethod]
internal static void RegisterUserFactories()
{
HashSet<string> userAssemblies = new HashSet<string>(InternalBridge.GetAllUserAssemblies());
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
if (!(userAssemblies.Contains(assembly.GetName().Name + ".dll")))
continue;
var types = assembly.GetTypes();
foreach (Type type in types)
{
if (typeof(IUxmlFactory).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
var factory = (IUxmlFactory)Activator.CreateInstance(type);
InternalBridge.RegisterFactory(factory);
}
}
}
}
The problem resides within the PanelRenderer initializing on Awake(), which happens before the factory classes are registered.
This code is encapsulated within the com.unity.ui.runtime package. You mentioned above that the code had changed, is there a newer version of the package available for 2019.3.13f1 beyond 0.0.4-preview?
I would prefer to keep my projects referencing the https://packages.unity.com registry to be able to get updates for new packages.
I run in the same issue. I created a custum UIElement, styled stuff, tried it play mode, works fine.
Created a build and got the same errors.
My custom UIElements are not inside of an assembly definition.
When I put them into one → same errors
When I add [assemby: Preserve] → same errors
When I modified the package and added the RegisterFactory Line → same errors
I am using Unity 2020.1.0.b12
and the latest runtime package.
Lol I got a solution that does’nt need modifiing the package.
Just add the code in a Awake() Method of a class that is constructed prior to the PanelRenderer()
#if !UNITY_EDITOR
private void Awake()
{
//Register your UILements like this
InternalBridge.RegisterFactory(new ButtonTranslatable.UxmlFactory());
}
#endif
Unfortunatelly with the new version “1.0.0-preview.3” my fix does not work anymore:
“InternalBrigde” is now “VisualElementFactoryRegistry”, which is ok. But tthat class and its methods are not public anymore. So you noew have to modifiy the package in order for it to work
Thanks for reporting. We can’t repro this just yet but are aware of the problem now. We’ll also look at opening up VisualElementFactoryRegistry’s public API so the workaround…works around again.
For those interested in how I fixed this package for our internal distribution, I removed the RuntimeInitializeOnLoadMethod attribute from the RegisterUserFactories method and added a call to the RegisterUserFactories method in the awake of the PanelRenderer.
Runtime/UXMLRuntimeFactories.cs
#if !UNITY_EDITOR
//[RuntimeInitializeOnLoadMethod]
internal static void RegisterUserFactories()
{
HashSet<string> userAssemblies = new HashSet<string>(InternalBridge.GetAllUserAssemblies());
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
if (!(userAssemblies.Contains(assembly.GetName().Name + ".dll")))
continue;
var types = assembly.GetTypes();
foreach (Type type in types)
{
if (typeof(IUxmlFactory).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
var factory = (IUxmlFactory)Activator.CreateInstance(type);
InternalBridge.RegisterFactory(factory);
}
}
}
}
#endif
Runtime/PanelRenderer.cs
protected void Awake()
{
#if UNITY_EDITOR
m_OldEnableLiveUpdate = m_EnableLiveUpdate;
#endif
#if !UNITY_EDITOR
UXMLRuntimeFactories.RegisterUserFactories();
#endif
// We try to call Initialize as soon as possible.
Cleanup();
Initialize();
}