Custom UI Element don't work in build

Hi,

I tried to make a custom visual element for may package and ran in some issues when building to windows.
So I decided to check the UnityRoyal demo to see how it does things.

Unfortulatly, while the demo works fine in the editor, it does not seem to work properly in builds.

Trying to build with the ‘legacy’ build widows (not the scriptable build pipeline) does build and open the game for windows.
Howerver it seems that all custom visual element are not displayed, only the main menu, which is just plain built in ui element, works.

Building in development mode give several unfound uss error (see attachment).

Also, I don’t get how the link is made between the C# custom visual element and their uxml. There seem to be no reference at all between the two. Is there some sort of auto mapping convention based on the file names ?

PS : when building my pacakge (with the scriptable built pipline (using dots…)) I get different messages :

  • VisualElementAsset has a RuleIndex but no inlineStyleSheet
  • Element ‘WaynGroup.Mgm.Ability.UI.AbilityUIElement’ has no registered factory method.

6104283–664227–Player.zip (3.25 KB)

Have you built the addressables for the project? You can do that by finding AddressableAssetSettings under Assets/AddressableAssetsData, selecting “Manage Groups” on the Inspector and clicking Build. When the addressables are not built, things don’t work out as intended (though I’m not sure it’s exactly what you’re experiencing, but it’s worth trying).

By “custom visual element” something like the Card UI? If so, if you open the UXML for it (Assets/UI/Uxml/CardUI.uxml) you’ll see that right in the beginning you have the link done (plus the C# class itself has some special declarations inside it, like the UxmlTraits and Uxml Factory:

<UnityRoyale.CardElement class="card--container">

1 Like

Hello,

Not sure which platform you are building for, but you may need add to add the [Preserve] attribute to your AbilityUIElement class if your are building for IL2CPP. UI Toolkit uses reflection to discover types used by UXML.

That worked thanks !

Ok I see, I’ll try to define mine in a similar way to see if it works.
Today I’m referencing the VisualAssetTree by loading it from Resources.Load.
I understand taht defining it though it’s full name in the tag in the UXML should be enougth as there is nothing in the UxmlTraits and Uxml Factory of the Card UI C# script.

I tried building my package for windows and webgl (I don’t think I used IL2CPP for windows, webgl does i belive), I’ll give that a try in addition to changing the way I define my custom uxml.

@JuliaP_Unity , I realise know that we do diferent things. My AbilityUIElement define childs for itself that it need to operate. For the CardElement in the UnityRoyal, eventhough the Card need some visual element to function, you chose to define them in the UI builder has children of the CardElement. So if someone was to remove some of the children, the CardElement would break. That’s what I try to avoid by defining the needed children of my custom element. You either use the custom element (and its’ children) or you don’t use it at all because it would not work propoerly if it misses some of it’s children.

@antoine-unity ,
I tired to build with IL2CPP with the preseve attribute without any luck. I get the same behavior building with the ‘legacy’ build settings or the new scriptable build pipeline (with IL2CPP or Mono):

First my non custom ui element (this one uses the custom element but is not one itself) don’t find it’s USS file in hte builds : Style sheet not found for path "Packages/wayn-group.mgm.ability/Runtime/UI/Resources/AbilityBar.uss"

Second my custom AbilityUIElement fail to load because of :

Element 'WaynGroup.Mgm.Ability.UI.AbilityUIElement' has no registered factory method.

Looking at where the error is thrown I tried to play with various ways of defining the namespace but nothing worked :

internal static VisualElement Create(VisualElementAsset asset, CreationContext ctx)
        {
            List<IUxmlFactory> factoryList;
            if (!VisualElementFactoryRegistry.TryGetValue(asset.fullTypeName, out factoryList))
            {
                if (asset.fullTypeName.StartsWith("UnityEngine.Experimental.UIElements.") || asset.fullTypeName.StartsWith("UnityEditor.Experimental.UIElements."))
                {
                    string experimentalTypeName = asset.fullTypeName.Replace(".Experimental.UIElements", ".UIElements");
                    if (!VisualElementFactoryRegistry.TryGetValue(experimentalTypeName, out factoryList))
                    {
                        Debug.LogErrorFormat("Element '{0}' has no registered factory method.", asset.fullTypeName);
                        return new Label(string.Format("Unknown type: '{0}'", asset.fullTypeName));
                    }
                }
                else if (asset.fullTypeName == UxmlRootElementFactory.k_ElementName)
                {
                    // Support UXML without namespace for backward compatibility.
                    VisualElementFactoryRegistry.TryGetValue(typeof(UxmlRootElementFactory).Namespace + "." + asset.fullTypeName, out factoryList);
                }
                else
                {
// FAILS HERE ####################################
                    Debug.LogErrorFormat("Element '{0}' has no registered factory method.", asset.fullTypeName);
                    return new Label(string.Format("Unknown type: '{0}'", asset.fullTypeName));
// FAILS HERE ####################################
                }
            }

            IUxmlFactory factory = null;
            foreach (IUxmlFactory f in factoryList)
            {
                if (f.AcceptsAttributeBag(asset, ctx))
                {
                    factory = f;
                    break;
                }
            }

            if (factory == null)
            {
                Debug.LogErrorFormat("Element '{0}' has a no factory that accept the set of XML attributes specified.", asset.fullTypeName);
                return new Label(string.Format("Type with no factory: '{0}'", asset.fullTypeName));
            }

            VisualElement res = factory.Create(asset, ctx);
            if (res != null)
            {
                AssignClassListFromAssetToElement(asset, res);
                AssignStyleSheetFromAssetToElement(asset, res);
            }

            return res;
        }

Here is the definition of my element :

using Unity.Entities;
using Unity.Mathematics;

using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.UIElements;

namespace WaynGroup.Mgm.Ability.UI
{

    [Preserve]
    class AbilityUIElement : VisualElement
    {
        [Preserve]
        public new class UxmlFactory : UxmlFactory<AbilityUIElement, UxmlTraits> { }

        [Preserve]
        public new class UxmlTraits : VisualElement.UxmlTraits { }

        public AbilityUIElement()
        {
            VisualTreeAsset visualTree = Resources.Load<VisualTreeAsset>("AbilityUIElement");
            visualTree.CloneTree(this);
            UnassignAbility();
            SetTime(0);
            SetBackGroundFill(0);
        }


        private ScriptableAbility _ability;
        private Entity _owner;
        private int _index;
        private EntityManager _entityManager;

        public void AssignAbility(Entity owner, int index, ScriptableAbility ability, EntityManager entityManager)
        {
            _owner = owner;
            _index = index;
            _ability = ability;
            _entityManager = entityManager;
            this.Q(name: "Icon").style.backgroundImage = new StyleBackground(_ability.Icon);
        }

        public void UnassignAbility()
        {
            _owner = Entity.Null;
            _ability = null;
            this.Q(name: "Icon").style.backgroundImage = null;
        }

        public bool IsAssigned => Entity.Null != _owner && _ability != null;


        public void UpdateCoolDown()
        {
            if (!IsAssigned) return;
            (float, float) remainingTime = _entityManager.GetBuffer<AbilityBuffer>(_owner)[_index].Ability.CoolDown.ComputeRemainingTime();
            SetTime(remainingTime.Item1);
            SetBackGroundFill(remainingTime.Item2);
        }

        private void SetBackGroundFill(float fill)
        {
            VisualElement bg = this.Q(name: "coolDownBackground");

            if (fill <= 0)
            {
                bg.visible = false;
                return;
            }
            if (!bg.visible) bg.visible = true;

            bg.style.height = bg.parent.worldBound.height * math.clamp(fill, 0, 1);
        }

        private void SetTime(float seconds)
        {
            Label label = this.Q<Label>(name: "coolDownTime");

            if (seconds <= 0)
            {
                label.visible = false;
                return;
            }
            if (!label.visible) label.visible = true;
            seconds = math.round(seconds * 10) / 10;
            label.text = $"{seconds}s";
        }

        private void SetIcon(Texture2D icon)
        {
            this.Q(name = "Icon").style.backgroundImage = new StyleBackground(icon);
        }

    }
}

The UXML associated with it :

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement name="Shape" class="abolitySlotShape">
        <Style src="AbilityUIElement.uss" />
        <ui:VisualElement name="Icon" class="abilityIcon" style="position: absolute;" />
        <ui:VisualElement name="coolDown" style="width: 100%; height: 100%;">
            <ui:VisualElement name="filler" style="background-color: rgba(0, 0, 0, 0); flex-grow: 1;" />
            <ui:VisualElement name="coolDownBackground" style="background-color: rgba(255, 0, 0, 0.6); flex-grow: 0; height: 10px;" />
        </ui:VisualElement>
        <ui:Label text="0.5" name="coolDownTime" class="coolDownTimeText" />
        <ui:Button text="Button" class="abilityButton" />
    </ui:VisualElement>
</ui:UXML>

and the USS:

.abolitySlotShape {
    width: 128px;
    height: 128px;
    background-color: rgba(128, 128, 128, 0.5);
    overflow: hidden;
    border-top-left-radius: 8px;
    border-bottom-left-radius: 8px;
    border-top-right-radius: 8px;
    border-bottom-right-radius: 8px;
    border-left-width: 2px;
    border-right-width: 2px;
    border-top-width: 2px;
    border-bottom-width: 2px;
    border-left-color: rgb(255, 255, 255);
    border-right-color: rgb(255, 255, 255);
    border-top-color: rgb(255, 255, 255);
    border-bottom-color: rgb(255, 255, 255);
}

.abilityIcon {
    background-image: url('/Packages/wayn-group.mgm.ability/Runtime/UI/sword.png');
    width: 100%;
    height: 100%;
    opacity: 0.33;
    display: flex;
}

.coolDownTimeText {
    -unity-font-style: bold;
    font-size: 60px;
    color: rgb(0, 255, 255);
    -unity-text-align: middle-center;
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
}

.abilityButton {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    flex-grow: 1;
    flex-shrink: 1;
    align-items: center;
    justify-content: center;
    margin-left: 0;
    margin-right: 0;
    margin-top: 0;
    margin-bottom: 0;
    padding-left: 0;
    padding-right: 0;
    padding-top: 0;
    padding-bottom: 0;
    color: rgba(0, 0, 0, 0);
    background-color: rgba(0, 0, 0, 0);
    border-left-width: 0;
    border-right-width: 0;
    border-top-width: 0;
    border-bottom-width: 0;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    border-left-color: rgba(0, 0, 0, 0);
    border-right-color: rgba(0, 0, 0, 0);
    border-top-color: rgba(0, 0, 0, 0);
    border-bottom-color: rgba(0, 0, 0, 0);
}

Also in attachement my full package (runing in 2020.2.0a15)

If any one has any idea of what I’m doing wrong I’d welcome the help.

6110165–665249–wayn-group.mgm.ability-0.4.0-preview.tgz.zip (216 KB)

@WAYNGames so for your case you can keep cloning the VisualTreeAsset in your VisualElement extending class, that’s the suggested way as well as declaring all fields in code if you prefer. It was, indeed, used a bit differently on the Unity Royale example.

1 Like

Ok just tried to ‘import’ the card element from the UnityRoyal to my package. I just kept the mimum to just display the archer background image (while still keeping the CardElement cutom visual element).
It work in editor but suffers the same issue in the build…

I tried the other way around, ‘importing’ my custom element to the UnityRoyal Project and same thing with my custom element, works in editor, not in build…

Then tried to ‘port’ the CardUI from the UnityRoyal project ot use a uxml definition for it’s children adn while I think I messed up a bit the position in the deck in the porcess, this does work both in editor and build…

I tried to go overboard with the [Preseve] by puttin it on every single element of the class with no change to the behavior.

I guess I’m doing something wrong but I can’t find what… :rage:

Can you check if your UXML is defining inline styles and using white spaces (or even line breaks) between style property declarations? There’s a known issue right now with that on the built player so that may be what you’re experiencing when you say it works in the editor but not in the build.

This is what inline styles should look like to work:
style="width: 20px;height: 50px;"

And these won’t work in built versions right now, only in the editor:
Example 1
style="width: 20px; height: 50px;"

Example 2

height: 50px;"```

@WAYNGames I am able to reproduce your issue with the custom element not being found in a player build and it’s under investigation. That problem does not seem present for me when using Mono, only with IL2CPP.

There was a workaround some time ago, it was to call Internal Bridge.RegisterFactory(new Custom Element.xmlFactory()) from a c# script at the start, but this method is not accessible in the last version.

This is another topic about the same problem:

Thanks everyone, I’m not crazy :slight_smile:
I’ll try again with mono but I think I still had the issue.
I’ll also try to go back to an empty project and make the simplest custom elements to have, if necessary, a very small repro project.

@antoine-unity , @JuliaP_Unity

OK, I’m sorry to tell that even with an empty project and the simplest custom Element it does not work (including with mono).

I created an empty project with the 3D template, then added the folowing items :

  • An empty custom visual element, it just extends the VisualElement and define a Factory and Traits without any override.
using UnityEngine.UIElements;

public class CustomUIElement : VisualElement
{

    public new class UxmlFactory : UxmlFactory<CustomUIElement, UxmlTraits> { }


    public new class UxmlTraits : VisualElement.UxmlTraits { }

}
  • A uxml that contains a simple hierarchy with the cusom element, and 3 labele before, after and as a child of the custom element,
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
    <ui:Label text="Before Custom Element" />
    <CustomUIElement>
        <ui:Label text="Child of Custom Element" />
    </CustomUIElement>
    <ui:Label text="After Custom Element" />
</ui:UXML>
  • a panel settings (no changes)

Then added a game object to the hierachy with a UIDocuement referencing the panel settings and uxml.

Now here are the results in various configurations :

6116987--666395--upload_2020-7-22_13-24-17.png

I started testing with 2020.2.0a15 with pretty much the same result appart from the zero sized panel which seems to be completly fixed. (FYI that is the topic of one of my reported bug : 1261447 com.unity.ui runtime does not take sacle into account Open 7/7/2020 10:07 PM)

Then I saw that the announcment post said :

So I droped it (too bad as the zero sized panel issue is fixed in alpha version.

I created a bug report (1265062 The simplest custom UI Element are not working in builds Open 7/22/2020 (Today) 1:32 PM) with the full project and all the build and player log of each build for each configuration and a copy of the above decription of the issue.

I sincerely hope this will help you help me :smile:

EDIT/PS : just to be sure, I checked that I had the latest driver for my Graphics Card and restarted my PC before rebuilding wiht mono to try again. Exact same result anyways…

Hello,

I can confirm this is a bug on our end.

Thanks for reporting it.

Cool, thanks.
FYI I tested with the final release of 2020.1 and there was no change in behavior from the beta.

Hey, the package 1.0.0-preview.6 is out and should fix this.
See : UI Toolkit - Latest Version: 1.0.0-preview.18 - Unity Engine - Unity Discussions

1 Like

Works perfectly, thanks !