Hi, In my previous/current title, our UI had a skew to it and boy oh boy was it a pain. All of our UI had this skew baked into the image. We will not do this way again! (likely, if we do a skew, we will likely have the raw image not have the skew and alter OnPopulateMesh to skew the element https://cdn.discordapp.com/attachments/236733544569765892/602235568134422562/skew.gif ) Anyway, I digress. The issue with baking the skew in the image is when it comes to reducing texture size. Ideally box above could technically be 10px by 10px (only corners), then the middle stretched to make the box fit the required size. Unfortunately because of the X skew, the texture can not be scaled on the Y, without artifacting (as it would alter the angle of the skew) I would like to suggest that unity support the .css skew selector to make UI's with skews easy to work with. Thanks for listening
We currently don't support shear transforms in UIElements, we only have position/rotation/scale. https://docs.unity3d.com/2019.3/Documentation/ScriptReference/UIElements.ITransform.html You can do manual skewing in UIElements by providing the transform geometry directly in generateVisualContent, which, I agree, is not as friendly a supporting skew directly in our transforms. https://docs.unity3d.com/2019.3/Doc...ents.VisualElement-generateVisualContent.html
Any examples on how to use generateVisualContent to import transform data of visual element? thanks in advance
Made a simple skew functionality using generateVisualContent, and thought I'd share it: Most of allocation code taken from here: https://docs.unity3d.com/2022.2/Documentation/ScriptReference/UIElements.MeshGenerationContext.html Code (CSharp): public class SkewedElement : VisualElement { private readonly Vertex[] _vertices = new Vertex[4]; private readonly ushort[] _indices = { 0, 1, 2, 2, 3, 0 }; public Texture2D Texture { get; private set; } public float SkewX { get; set; } public float SkewY { get; set; } public SkewedElement(Texture2D texture) { Texture = texture; generateVisualContent += OnGenerateVisualContent; _vertices[0].tint = Color.white; _vertices[1].tint = Color.white; _vertices[2].tint = Color.white; _vertices[3].tint = Color.white; } private void OnGenerateVisualContent(MeshGenerationContext mgc) { if (contentRect.width < 0.01f || contentRect.height < 0.01f) return; _vertices[0].position = new Vector3(contentRect.x, contentRect.height, Vertex.nearZ); _vertices[1].position = new Vector3(contentRect.x, contentRect.y, Vertex.nearZ); _vertices[2].position = new Vector3(contentRect.width, contentRect.y, Vertex.nearZ); _vertices[3].position = new Vector3(contentRect.width, contentRect.height, Vertex.nearZ); var mwd = mgc.Allocate(_vertices.Length, _indices.Length, Texture); var uvRegion = mwd.uvRegion; _vertices[0].uv = new Vector2(0, 0) * uvRegion.size + uvRegion.min; _vertices[1].uv = new Vector2(0, 1) * uvRegion.size + uvRegion.min; _vertices[2].uv = new Vector2(1, 1) * uvRegion.size + uvRegion.min; _vertices[3].uv = new Vector2(1, 0) * uvRegion.size + uvRegion.min; //Skew SkewImage(mwd.vertexCount); mwd.SetAllVertices(_vertices); mwd.SetAllIndices(_indices); } private void SkewImage(float vertexCount) { var xskew = contentRect.height * Mathf.Tan(Mathf.Deg2Rad * SkewX); var yskew = contentRect.width * Mathf.Tan(Mathf.Deg2Rad * SkewY); for (int i = 0; i < vertexCount; i++) { for (int j = 0; j < _vertices.Length; j++) { _vertices[j].position += new Vector3( Mathf.Lerp(0, xskew, (_vertices[j].position.y - contentRect.y) / contentRect.height), Mathf.Lerp(0, yskew, (_vertices[j].position.x - contentRect.x) / contentRect.width)); } } } } If there is a better way, I'd love to hear it.
Hmmm... I tried to extend @Xan_9 's code above. But it seems, I can't override the VisualElement's mesh; generateVisualContent only creates a new mesh. For example, I am also trying to skew a box which has radius and I don't want to use images (because I want to easily animate these boxes). Is there anyway to override the VisualElement's mesh? Or, at least get the VisualElement as Texture2D? I might use Texture2D in the mgc.Allocate(), then (maybe) hide the original VisualElement. I can't find anything in the docs. OR, could you give us any info about when will the UI Toolkit have the skew property? An example (just like in the thread's main post) my extended version (which is usable in the UI Builder, but not finished) Code (CSharp): using UnityEngine; using UnityEngine.UIElements; public class SkewedElement : VisualElement { private readonly Vertex[] _vertices = new Vertex[4]; private readonly ushort[] _indices = {0, 1, 2, 2, 3, 0}; [UnityEngine.Scripting.Preserve] public new class UxmlFactory : UxmlFactory<SkewedElement, UxmlTraits> {} [UnityEngine.Scripting.Preserve] public new class UxmlTraits : VisualElement.UxmlTraits { private readonly UxmlFloatAttributeDescription skewX = new() { name = "skew-x", defaultValue = 0 }; private readonly UxmlFloatAttributeDescription skewY = new() { name = "skew-y", defaultValue = 0 }; public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); var elm = ve as SkewedElement; elm.SkewX = skewX.GetValueFromBag(bag, cc); elm.SkewY = skewY.GetValueFromBag(bag, cc); } } public float SkewX { get; private set; } public float SkewY { get; private set; } public SkewedElement() { generateVisualContent += OnGenerateVisualContent; } private void OnGenerateVisualContent(MeshGenerationContext mgc) { var rect = BorderedRect(); if(rect.width < 0.01f || rect.height < 0.01f) return; var color = resolvedStyle.backgroundColor; _vertices[0].tint = color; _vertices[1].tint = color; _vertices[2].tint = color; _vertices[3].tint = color; _vertices[0].position = new Vector3(rect.x, rect.height, Vertex.nearZ); _vertices[1].position = new Vector3(rect.x, rect.y, Vertex.nearZ); _vertices[2].position = new Vector3(rect.width, rect.y, Vertex.nearZ); _vertices[3].position = new Vector3(rect.width, rect.height, Vertex.nearZ); var mwd = mgc.Allocate(_vertices.Length, _indices.Length); var uvRegion = mwd.uvRegion; _vertices[0].uv = new Vector2(0, 0) * uvRegion.size + uvRegion.min; _vertices[1].uv = new Vector2(0, 1) * uvRegion.size + uvRegion.min; _vertices[2].uv = new Vector2(1, 1) * uvRegion.size + uvRegion.min; _vertices[3].uv = new Vector2(1, 0) * uvRegion.size + uvRegion.min; //Skew SkewImage(mwd.vertexCount, rect); mwd.SetAllVertices(_vertices); mwd.SetAllIndices(_indices); } private void SkewImage(float vertexCount, Rect rect) { var xskew = rect.height * Mathf.Tan(Mathf.Deg2Rad * SkewX); var yskew = rect.width * Mathf.Tan(Mathf.Deg2Rad * SkewY); for(int i = 0; i < vertexCount; i++) { for(int j = 0; j < _vertices.Length; j++) { _vertices[j].position += new Vector3(Mathf.Lerp(0, xskew, (_vertices[j].position.y - rect.y) / rect.height), Mathf.Lerp(0, yskew, (_vertices[j].position.x - rect.x) / rect.width)); } } } private Rect BorderedRect() { var leftWidth = resolvedStyle.borderLeftWidth; var topWidth = resolvedStyle.borderTopWidth; var borderRect = new Rect(new Vector2(-leftWidth, -topWidth), new Vector2(leftWidth + resolvedStyle.borderRightWidth, topWidth + resolvedStyle.borderBottomWidth)); return new Rect(paddingRect.position + borderRect.position, paddingRect.size + borderRect.size); } }
Possibly the UITK vector API could help you constructing your shape instead of manipulating/creating the vertices directly. Depending on what you are doing the texturing options may be lacking in the API to use it. We intend on adding ways to completely override the mesh/mask of the visual element, but there is no ETA yet.
I will definitely use the Vector API when we update our project to Unity 2022.x. It is not in preview anymore, right? Texturing not a problem for us at the moment, but it'd be nice to have '=D By the way, is there transition/animation feature in the Vector API, or should we use coroutines, etc?
I did a skewed Health Bar sample using Vector API with animations. Performance was good. Source code: https://github.com/DragonGround/Ove...34489bc9afe886130232ff2037/CharacterStats.tsx (It's written in TS with OneJS, a tool that allows you to use TS for UI Toolkit)
Sooo, I added rounded corner feature and moved all skew properties to USS. I am using USS color property for tint/background color, border-radius properties for rounded corners, background-image property for background image. --skew-x for x-directional skew, --skew-y for y-directional skew, --skew-background for skewing or not skewing background image. I am also using transform-origin values for skew point. There is no antialiasing and transition for skew values (I added a simple solution, then removed "_skewX = Mathf.Lerp(_skewX, SkewX, 0.5f);"). I couldn't find a solution for these two in a simple way (with using UI Toolkit). By the way, I tried IVisualElementScheduledItem, but didn't like the result, lerping is much easier. Also, I just simply stretching background, I don't need any variation for the background-image. I used the RoundedQuadMesh sample from Bunny83's answer and simplified it for what I need. If I find a way (or if someone helps me) to easily use transition for skew properties in the future, I'd update this post. I think CustomStyleProperty can be automatically support transitions for types like float, int, Color. Code (CSharp): using UnityEngine; using UnityEngine.UIElements; public class SkewedElement : VisualElement { [UnityEngine.Scripting.Preserve] public new class UxmlFactory : UxmlFactory<SkewedElement, UxmlTraits> {} static readonly CustomStyleProperty<float> S_SkewX = new CustomStyleProperty<float>("--skew-x"); static readonly CustomStyleProperty<float> S_SkewY = new CustomStyleProperty<float>("--skew-y"); static readonly CustomStyleProperty<bool> S_SkewBG = new CustomStyleProperty<bool>("--skew-background"); public float SkewX { get; private set; } public float SkewY { get; private set; } public bool SkewBG { get; private set; } private Vertex[] _vertices = {}; private ushort[] _indices = {}; public SkewedElement() { RemoveBGTint(); generateVisualContent += OnGenerateVisualContent; RegisterCallback<CustomStyleResolvedEvent>(OnStyleResolved); } private void OnStyleResolved(CustomStyleResolvedEvent evt) { evt.customStyle.TryGetValue(S_SkewX, out var skewX); evt.customStyle.TryGetValue(S_SkewY, out var skewY); evt.customStyle.TryGetValue(S_SkewBG, out var skewBG); SkewX = skewX; SkewY = skewY; SkewBG = skewBG; RemoveBGTint(); } private void OnGenerateVisualContent(MeshGenerationContext mgc) { var rect = BorderedRect(); if(rect.width < 0.01f || rect.height < 0.01f) return; var backgroundColor = resolvedStyle.color; var radiusTopLeft = resolvedStyle.borderTopLeftRadius; var radiusTopRight = resolvedStyle.borderTopRightRadius; var radiusBottomLeft = resolvedStyle.borderBottomLeftRadius; var radiusBottomRight = resolvedStyle.borderBottomRightRadius; var biggestRadius = radiusTopLeft; if(radiusTopRight > biggestRadius) biggestRadius = radiusTopRight; if(radiusBottomLeft > biggestRadius) biggestRadius = radiusBottomLeft; if(radiusBottomRight > biggestRadius) biggestRadius = radiusBottomRight; var cornerSegments = Mathf.Clamp(Mathf.RoundToInt(biggestRadius / 2) + 3, 5, 12); var vCount = cornerSegments * 4 + 1; _vertices = new Vertex[vCount]; var f = 1f / (cornerSegments - 1); var sPI = Mathf.PI * 0.5f * f; var rW = rect.width * 0.5f; var rH = rect.height * 0.5f; var shortest = rW < rH ? rW : rH; var topLeft = new Vector3(rect.x, rect.y, Vertex.nearZ); var topRight = new Vector3(rect.width, rect.y, Vertex.nearZ); var bottomLeft = new Vector3(rect.x, rect.height, Vertex.nearZ); var bottomRight = new Vector3(rect.width, rect.height, Vertex.nearZ); var radiusTL = Mathf.Clamp(radiusTopLeft, -shortest, shortest); var radiusTR = Mathf.Clamp(radiusTopRight, -shortest, shortest); var radiusBL = Mathf.Clamp(radiusBottomLeft, -shortest, shortest); var radiusBR = Mathf.Clamp(radiusBottomRight, -shortest, shortest); _vertices[0].tint = backgroundColor; _vertices[0].position = new Vector3(rW, rH, Vertex.nearZ); _vertices[0].uv = new Vector2(rW / rect.width, rH / rect.height); for(int i = 0; i < cornerSegments; i++) { var sin = Mathf.Sin(i * sPI); var cos = Mathf.Cos(i * sPI); var v1 = topLeft + new Vector3(radiusTL - cos * radiusTL, radiusTL - sin * radiusTL); var v2 = topRight + new Vector3(-radiusTR + sin * radiusTR, radiusTR - cos * radiusTR); var v3 = bottomRight + new Vector3(-radiusBR + cos * radiusBR, -radiusBR + sin * radiusBR); var v4 = bottomLeft + new Vector3(radiusBL - sin * radiusBL, -radiusBL + cos * radiusBL); var i1 = 1 + i; var i2 = cornerSegments + i1; var i3 = cornerSegments * 2 + i1; var i4 = cornerSegments * 3 + i1; _vertices[i1].tint = backgroundColor; _vertices[i2].tint = backgroundColor; _vertices[i3].tint = backgroundColor; _vertices[i4].tint = backgroundColor; _vertices[i1].position = v1; _vertices[i2].position = v2; _vertices[i3].position = v3; _vertices[i4].position = v4; if(SkewBG) { _vertices[i1].uv = new Vector2(v1.x / rect.width, 1 - v1.y / rect.height); _vertices[i2].uv = new Vector2(v2.x / rect.width, 1 - v2.y / rect.height); _vertices[i3].uv = new Vector2(v3.x / rect.width, 1 - v3.y / rect.height); _vertices[i4].uv = new Vector2(v4.x / rect.width, 1 - v4.y / rect.height); } } var triCount = cornerSegments * 4; _indices = new ushort[triCount * 3]; for(int i = 0; i < triCount; i++) { _indices[i * 3] = 0; _indices[i * 3 + 1] = (ushort) (i + 1); _indices[i * 3 + 2] = (ushort) (i + 2); } _indices[triCount * 3 - 1] = 1; SkewRect(rect); var mwd = mgc.Allocate(_vertices.Length, _indices.Length, resolvedStyle.backgroundImage.texture); mwd.SetAllVertices(_vertices); mwd.SetAllIndices(_indices); } private void SkewRect(Rect rect) { var xOrigin = resolvedStyle.transformOrigin.x / rect.width; var yOrigin = resolvedStyle.transformOrigin.y / rect.height; var xSkew = rect.height * Mathf.Tan(Mathf.Deg2Rad * SkewX); var ySkew = rect.width * Mathf.Tan(Mathf.Deg2Rad * SkewY); for(int i = 0; i < _vertices.Length; i++) { var xLerp = Mathf.LerpUnclamped(0, xSkew, (_vertices[i].position.y - rect.y) / rect.height); var yLerp = Mathf.LerpUnclamped(0, ySkew, (_vertices[i].position.x - rect.x) / rect.width); _vertices[i].position += new Vector3(xLerp - xSkew * xOrigin, yLerp - ySkew * yOrigin); if(!SkewBG) _vertices[i].uv = new Vector2(_vertices[i].position.x / rect.width, 1 - _vertices[i].position.y / rect.height); } } private Rect BorderedRect() { var leftWidth = resolvedStyle.borderLeftWidth; var topWidth = resolvedStyle.borderTopWidth; var borderRect = new Rect(new Vector2(-leftWidth, -topWidth), new Vector2(leftWidth + resolvedStyle.borderRightWidth, topWidth + resolvedStyle.borderBottomWidth)); return new Rect(paddingRect.position + borderRect.position, paddingRect.size + borderRect.size); } private void RemoveBGTint() { var bgTintColor = new StyleColor {value = Color.clear}; style.unityBackgroundImageTintColor = bgTintColor; } } Sample USS code Code (CSharp): SkewedElement { border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; color: rgb(254, 0, 0); width: 100px; height: 100px; position: absolute; left: 922px; top: 230px; transition-duration: 0.5s; --skew-x: 10; --skew-y: 0; } SkewedElement:hover { border-top-left-radius: 20px; border-bottom-left-radius: 20px; border-top-right-radius: 20px; border-bottom-right-radius: 20px; width: 100px; height: 100px; color: rgb(217, 217, 217); --skew-x: 40; --skew-y: 0; --skew-background: true; }