Ability to mask several UI elements at once is a sorely missing feature

I’ve been trying to make a mask that scales and translates to reveal my UI when a player enters the scene. I’m discovering the severe limitations of the UI Masking system which all boil down to one simple problem:

  • Masking is determined by hierarchy

This means that attempting to scale or translate a mask messes with all the elements that must be under the mask in order to work. The common workaround I’ve seen is to

  • unparent child elements
  • transform mask
  • reparent child elements

However this does not work right when contained within the same function. Strange bugs occur where child transforms get set to 0 or otherwise disappear, indicating some kind of mask rendering error. In order to get the mask to update properly, child position/scale values have to be changed during LateUpdate, which means having code logic in two different places (inelegant, hard to maintain, difficult to run multiple instances).

Worse, there are even more strange bugs where changes made during Play persist after stopping, messing up my entire UI setup. Overall, new UI feels like a giant clustercluck. This is my first project using it and I constantly wish I’d gone with old IMGUI from the beginning.

I’m on the latest version of Unity.

I can think of a few ways to fix masking and make it usable:

  • Have masked elements be an array property of the mask, freeing the transform correlation between parent/child that is messing everything up
  • Have a FreeTransform method that will change a parent’s transform while leaving child values untouched (relative to world)
  • Enable some kind of LateTransform that forces a transform’s logic to happen during LateUpdate, or otherwise after the normal transform logic. This would enable de-parent+transform+reparent to work consistently without strange bugs.

The second two options would alter fundamental transform logic so I’m thinking the first is more likely. Unity Technologies, please hear my plea!

Also masking apparently doesn’t work at all with Outline components. See this ScrollView.

Edit: It appears that Masking doesn’t work with Text components that have a Material assigned (font material), and Outline components only work when the Text has Material assigned.

Edit 2: After some shader experimentation I found that combining the font shader and the default shader resulted in something that allows text to mask while using a font material. Here’s the source:

Shader "Custom/Stencil Text" {
    Properties {
        _MainTex ("Font Texture", 2D) = "white" {}
        _Color ("Text Color", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15
    }

    SubShader {

        Tags {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
        }
        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Lighting Off Cull Off ZTest Always ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ UNITY_SINGLE_PASS_STEREO STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON
            #include "UnityCG.cginc"

            struct appdata_t {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            uniform float4 _MainTex_ST;
            uniform fixed4 _Color;

            v2f vert (appdata_t v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.color = v.color * _Color;
                o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = i.color;
                col.a *= tex2D(_MainTex, i.texcoord).a;
                return col;
            }
            ENDCG
        }
    }
}