Skip to content

Instantly share code, notes, and snippets.

@hugohil
Created March 24, 2025 19:34
Show Gist options
  • Save hugohil/4923ba2a5e81995027c639377dc06aec to your computer and use it in GitHub Desktop.
Save hugohil/4923ba2a5e81995027c639377dc06aec to your computer and use it in GitHub Desktop.
Unity UI Polygon
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(CanvasRenderer))]
public class UIPolygon : Graphic {
[Header("UI Polygon")]
[SerializeField] private Vector2[] offsets = new Vector2[] {
new Vector2(-1f, 1f),
new Vector2(1f, 1f),
new Vector2(1f, -1f),
new Vector2(-1f, -1f)
};
private List<Vector2> points = new List<Vector2>();
[SerializeField] private Vector2 padding = new Vector2(0f, 0f);
[SerializeField] private bool fillCenter = true;
[Header("Border")]
[SerializeField] private bool hasBorder = true;
[SerializeField] private float borderWidth = 5f;
[SerializeField] private Color borderColor = Color.black;
[Header("Shadow")]
[SerializeField] private bool hasShadow = true;
[SerializeField] private Color shadowColor = new Color(0f, 0f, 0f, 0.5f);
[SerializeField] private Vector2 shadowOffset = new Vector2(10f, -10f);
private void Start() {
UpdatePoints();
}
private Rect lastRect;
private Vector2 lastPadding;
private void Update() {
Rect currentRect = rectTransform.rect;
if (currentRect != lastRect || padding != lastPadding) {
UpdatePoints();
lastRect = currentRect;
lastPadding = padding;
}
}
#if UNITY_EDITOR
private void OnValidate() {
UpdatePoints();
}
#endif
private void UpdatePoints() {
points.Clear();
Rect rect = rectTransform.rect;
for (int i = 0; i < offsets.Length; i++) {
points.Add(((rect.size * 0.5f) + padding) * offsets[i]);
}
SetAllDirty();
}
protected override void OnPopulateMesh(VertexHelper vh) {
if (points == null || points.Count < 3) {
return;
}
vh.Clear();
int vertexCount = 0;
if (hasShadow) {
vh.AddVert(shadowOffset, shadowColor, Vector2.zero);
vertexCount++;
for (int i = 0; i < points.Count; i++) {
vh.AddVert(points[i] + shadowOffset, shadowColor, Vector2.zero);
vertexCount++;
}
for (int i = 0; i < points.Count; i++) {
vh.AddTriangle(0, i + 1, ((i + 1) % points.Count) + 1);
}
}
if (fillCenter) {
vh.AddVert(Vector2.zero, color, Vector2.zero);
vertexCount++;
for (int i = 0; i < points.Count; i++) {
vh.AddVert(points[i], color, Vector2.zero);
}
for (int i = 0; i < points.Count; i++) {
int idx1 = i + vertexCount;
int idx2 = ((i + 1) % points.Count) + vertexCount;
vh.AddTriangle(vertexCount, idx1, idx2);
}
vertexCount += points.Count;
}
if (hasBorder) {
for (int i = 0; i < points.Count; i++) {
Vector2 p1 = points[i];
Vector2 p2 = points[(i + 1) % points.Count];
Vector2 edge = p2 - p1;
Vector2 edgePerp = Vector2.Perpendicular(edge.normalized);
Vector2 offset = edgePerp * borderWidth;
UIVertex v1 = new UIVertex();
v1.position = p1 + offset;
v1.color = borderColor;
v1.uv0 = Vector2.zero;
UIVertex v2 = new UIVertex();
v2.position = p2 + offset;
v2.color = borderColor;
v2.uv0 = Vector2.zero;
UIVertex v3 = new UIVertex();
v3.position = p2 - offset;
v3.color = borderColor;
v3.uv0 = Vector2.zero;
UIVertex v4 = new UIVertex();
v4.position = p1 - offset;
v4.color = borderColor;
v4.uv0 = Vector2.zero;
vh.AddVert(v1);
vh.AddVert(v2);
vh.AddVert(v3);
vh.AddVert(v4);
int idx = vertexCount;
vh.AddTriangle(idx, idx + 1, idx + 2);
vh.AddTriangle(idx, idx + 2, idx + 3);
vertexCount += 4;
}
for (int i = 0; i < points.Count; i++) {
Vector2 current = points[i];
Vector2 prev = points[(i - 1 + points.Count) % points.Count];
Vector2 next = points[(i + 1) % points.Count];
Vector2 toPrev = (prev - current).normalized;
Vector2 toNext = (next - current).normalized;
Vector2 prevPerp = Vector2.Perpendicular(toPrev);
Vector2 nextPerp = Vector2.Perpendicular(toNext);
// Check if this is an inner corner (concave) or outer corner (convex)
float crossProduct = (toPrev.x * toNext.y) - (toPrev.y * toNext.x);
Vector2 prevOffset = prevPerp * borderWidth * Mathf.Sign(crossProduct);
Vector2 prevCorner = current - prevOffset;
Vector2 nextOffset = nextPerp * borderWidth * Mathf.Sign(crossProduct);
Vector2 nextCorner = current + nextOffset;
float segmentAngle = Vector2.Angle(toPrev, toNext) * Mathf.Deg2Rad;
float miterLength = borderWidth / Mathf.Sin(segmentAngle / 2);
Vector2 miterDir = (nextPerp - prevPerp).normalized * Mathf.Sign(crossProduct);
Vector2 miterOffset = (miterDir * miterLength);
Vector2 miterCorner = current + miterOffset;
UIVertex v1 = new UIVertex();
v1.position = current;
v1.color = borderColor;
v1.uv0 = Vector2.zero;
UIVertex v2 = new UIVertex();
v2.position = miterCorner;
v2.color = borderColor;
v2.uv0 = Vector2.zero;
UIVertex v3 = new UIVertex();
v3.position = prevCorner;
v3.color = borderColor;
v3.uv0 = Vector2.zero;
UIVertex v4 = new UIVertex();
v4.position = nextCorner;
v4.color = borderColor;
v4.uv0 = Vector2.zero;
vh.AddVert(v1);
vh.AddVert(v2);
vh.AddVert(v3);
vh.AddVert(v4);
int idx = vertexCount;
vh.AddTriangle(idx, idx + 1, idx + 2);
vh.AddTriangle(idx, idx + 1, idx + 3);
vertexCount += 4;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment