Skip to content

Commit 0ae398b

Browse files
authored
Quality of Life Update
I added some quality of life update in this commit. Mostly to make it easier to use for new user without needing to look at the code. 1. Added a custom Editor script to display Vector4 in separate labels for the four corners in the ImageWithIndependentRoundedCorners. This makes it very clear which corner is which. Previously it was just a Vector4 with w, x, y, z. Now it's Top Left, Top Right, Bottom Right, Bottom Left. 2. Added a line in OnDestroy() to set the UI Image material to null after the component is removed. Previously, when you delete this component, the UI Image material goes to "Missing". Now it's properly reset to null. 3. Added a validation in OnEnable(). Now you can either has ImageWithIndependentCorners or ImageWithRoundedCorners. You can't have both in the same object. I also added [ExecuteInEditMode] so this validation will run properly. 4. Added [DisallowMultipleComponent] to both scripts. Only one of these components can exist in every gameobject, since only one of them can influence the shader anyway. 5. Added a default value for the radius when the component is added. This gives the user instant feedback that the script works without needing to alter the value first. 6. Make the radius value consistent between two components. When ImageWIthRoundedCorners and ImageWithIndependentRoundedCorners have the same radius value, the ImageWithIndependentRoundedCorners appear to have bigger radius. To compensate this, I multiply the radius by two in ImageWithRoundedCorners as a quick fix. Maybe you can figure it the root cause further. 7. If the user swapped the ImageWithRoundedCorners with ImageWithIndependentCorners, the radius value will be carried over. The other way also works using the radius Vector4.x value. <img src="https://i.imgur.com/M54vlGi.jpeg" width=100% height=100%>
1 parent f80f59f commit 0ae398b

2 files changed

Lines changed: 213 additions & 156 deletions

File tree

Lines changed: 143 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,143 @@
1-
using UnityEngine;
2-
using UnityEngine.UI;
3-
4-
namespace Nobi.UiRoundedCorners {
5-
[RequireComponent(typeof(RectTransform))]
6-
public class ImageWithIndependentRoundedCorners : MonoBehaviour {
7-
private static readonly int prop_halfSize = Shader.PropertyToID("_halfSize");
8-
private static readonly int prop_radiuses = Shader.PropertyToID("_r");
9-
private static readonly int prop_rect2props = Shader.PropertyToID("_rect2props");
10-
11-
// Vector2.right rotated clockwise by 45 degrees
12-
private static readonly Vector2 wNorm = new Vector2(.7071068f, -.7071068f);
13-
// Vector2.right rotated counter-clockwise by 45 degrees
14-
private static readonly Vector2 hNorm = new Vector2(.7071068f, .7071068f);
15-
16-
public Vector4 r;
17-
private Material material;
18-
19-
// xy - position,
20-
// zw - halfSize
21-
[HideInInspector, SerializeField] private Vector4 rect2props;
22-
[HideInInspector, SerializeField] private MaskableGraphic image;
23-
24-
private void OnValidate() {
25-
Validate();
26-
Refresh();
27-
}
28-
29-
private void OnEnable() {
30-
Validate();
31-
Refresh();
32-
}
33-
34-
private void OnRectTransformDimensionsChange() {
35-
if (enabled && material != null) {
36-
Refresh();
37-
}
38-
}
39-
40-
private void OnDestroy() {
41-
DestroyHelper.Destroy(material);
42-
image = null;
43-
material = null;
44-
}
45-
46-
public void Validate() {
47-
if (material == null) {
48-
material = new Material(Shader.Find("UI/RoundedCorners/IndependentRoundedCorners"));
49-
}
50-
51-
if (image == null) {
52-
TryGetComponent(out image);
53-
}
54-
55-
if (image != null) {
56-
image.material = material;
57-
}
58-
}
59-
60-
public void Refresh() {
61-
var rect = ((RectTransform)transform).rect;
62-
RecalculateProps(rect.size);
63-
material.SetVector(prop_rect2props, rect2props);
64-
material.SetVector(prop_halfSize, rect.size * .5f);
65-
material.SetVector(prop_radiuses, r);
66-
}
67-
68-
private void RecalculateProps(Vector2 size) {
69-
// Vector that goes from left to right sides of rect2
70-
var aVec = new Vector2(size.x, -size.y + r.x + r.z);
71-
72-
// Project vector aVec to wNorm to get magnitude of rect2 width vector
73-
var halfWidth = Vector2.Dot(aVec, wNorm) * .5f;
74-
rect2props.z = halfWidth;
75-
76-
77-
// Vector that goes from bottom to top sides of rect2
78-
var bVec = new Vector2(size.x, size.y - r.w - r.y);
79-
80-
// Project vector bVec to hNorm to get magnitude of rect2 height vector
81-
var halfHeight = Vector2.Dot(bVec, hNorm) * .5f;
82-
rect2props.w = halfHeight;
83-
84-
85-
// Vector that goes from left to top sides of rect2
86-
var efVec = new Vector2(size.x - r.x - r.y, 0);
87-
88-
// Vector that goes from point E to point G, which is top-left of rect2
89-
var egVec = hNorm * Vector2.Dot(efVec, hNorm);
90-
91-
// Position of point E relative to center of coord system
92-
var ePoint = new Vector2(r.x - (size.x / 2), size.y / 2);
93-
94-
// Origin of rect2 relative to center of coord system
95-
// ePoint + egVec == vector to top-left corner of rect2
96-
// wNorm * halfWidth + hNorm * -halfHeight == vector from top-left corner to center
97-
var origin = ePoint + egVec + wNorm * halfWidth + hNorm * -halfHeight;
98-
rect2props.x = origin.x;
99-
rect2props.y = origin.y;
100-
}
101-
}
102-
}
1+
using Nobi.UiRoundedCorners;
2+
using UnityEditor;
3+
using UnityEngine;
4+
using UnityEngine.UI;
5+
6+
namespace Nobi.UiRoundedCorners {
7+
[ExecuteInEditMode] //Required to do validation with OnEnable()
8+
[DisallowMultipleComponent] //You can only have one of these in every object
9+
[RequireComponent(typeof(RectTransform))]
10+
public class ImageWithIndependentRoundedCorners : MonoBehaviour {
11+
private static readonly int prop_halfSize = Shader.PropertyToID("_halfSize");
12+
private static readonly int prop_radiuses = Shader.PropertyToID("_r");
13+
private static readonly int prop_rect2props = Shader.PropertyToID("_rect2props");
14+
15+
// Vector2.right rotated clockwise by 45 degrees
16+
private static readonly Vector2 wNorm = new Vector2(.7071068f, -.7071068f);
17+
// Vector2.right rotated counter-clockwise by 45 degrees
18+
private static readonly Vector2 hNorm = new Vector2(.7071068f, .7071068f);
19+
20+
public Vector4 r = new Vector4(40f, 40f, 40f, 40f);
21+
private Material material;
22+
23+
// xy - position,
24+
// zw - halfSize
25+
[HideInInspector, SerializeField] private Vector4 rect2props;
26+
[HideInInspector, SerializeField] private MaskableGraphic image;
27+
28+
private void OnValidate() {
29+
Validate();
30+
Refresh();
31+
}
32+
33+
private void OnEnable() {
34+
//You can only add either ImageWithRoundedCorners or ImageWithIndependentRoundedCorners
35+
//It will replace the other component when added into the object.
36+
var other = GetComponent<ImageWithRoundedCorners>();
37+
if (other != null)
38+
{
39+
r = Vector4.one * other.radius; //When it does, transfer the radius value to this script
40+
DestroyHelper.Destroy(other);
41+
}
42+
43+
Validate();
44+
Refresh();
45+
}
46+
47+
private void OnRectTransformDimensionsChange() {
48+
if (enabled && material != null) {
49+
Refresh();
50+
}
51+
}
52+
53+
private void OnDestroy() {
54+
image.material = null; //This makes so that when the component is removed, the UI material returns to null
55+
56+
DestroyHelper.Destroy(material);
57+
image = null;
58+
material = null;
59+
}
60+
61+
public void Validate() {
62+
if (material == null) {
63+
material = new Material(Shader.Find("UI/RoundedCorners/IndependentRoundedCorners"));
64+
}
65+
66+
if (image == null) {
67+
TryGetComponent(out image);
68+
}
69+
70+
if (image != null) {
71+
image.material = material;
72+
}
73+
}
74+
75+
public void Refresh() {
76+
var rect = ((RectTransform)transform).rect;
77+
RecalculateProps(rect.size);
78+
material.SetVector(prop_rect2props, rect2props);
79+
material.SetVector(prop_halfSize, rect.size * .5f);
80+
material.SetVector(prop_radiuses, r);
81+
}
82+
83+
private void RecalculateProps(Vector2 size) {
84+
// Vector that goes from left to right sides of rect2
85+
var aVec = new Vector2(size.x, -size.y + r.x + r.z);
86+
87+
// Project vector aVec to wNorm to get magnitude of rect2 width vector
88+
var halfWidth = Vector2.Dot(aVec, wNorm) * .5f;
89+
rect2props.z = halfWidth;
90+
91+
92+
// Vector that goes from bottom to top sides of rect2
93+
var bVec = new Vector2(size.x, size.y - r.w - r.y);
94+
95+
// Project vector bVec to hNorm to get magnitude of rect2 height vector
96+
var halfHeight = Vector2.Dot(bVec, hNorm) * .5f;
97+
rect2props.w = halfHeight;
98+
99+
100+
// Vector that goes from left to top sides of rect2
101+
var efVec = new Vector2(size.x - r.x - r.y, 0);
102+
103+
// Vector that goes from point E to point G, which is top-left of rect2
104+
var egVec = hNorm * Vector2.Dot(efVec, hNorm);
105+
106+
// Position of point E relative to center of coord system
107+
var ePoint = new Vector2(r.x - (size.x / 2), size.y / 2);
108+
109+
// Origin of rect2 relative to center of coord system
110+
// ePoint + egVec == vector to top-left corner of rect2
111+
// wNorm * halfWidth + hNorm * -halfHeight == vector from top-left corner to center
112+
var origin = ePoint + egVec + wNorm * halfWidth + hNorm * -halfHeight;
113+
rect2props.x = origin.x;
114+
rect2props.y = origin.y;
115+
}
116+
}
117+
}
118+
119+
/// <summary>
120+
/// Display Vector4 as 4 separate fields for each corners.
121+
/// It's way easier to use than w,x,y,z in Vector4.
122+
/// </summary>
123+
#if UNITY_EDITOR
124+
[CustomEditor(typeof(ImageWithIndependentRoundedCorners))]
125+
public class Vector4Editor : Editor
126+
{
127+
public override void OnInspectorGUI()
128+
{
129+
//DrawDefaultInspector();
130+
131+
serializedObject.Update();
132+
133+
SerializedProperty vector4Prop = serializedObject.FindProperty("r");
134+
135+
EditorGUILayout.PropertyField(vector4Prop.FindPropertyRelative("x"), new GUIContent("Top Left Corner"));
136+
EditorGUILayout.PropertyField(vector4Prop.FindPropertyRelative("y"), new GUIContent("Top Right Corner"));
137+
EditorGUILayout.PropertyField(vector4Prop.FindPropertyRelative("w"), new GUIContent("Bottom Left Corner"));
138+
EditorGUILayout.PropertyField(vector4Prop.FindPropertyRelative("z"), new GUIContent("Bottom Right Corner"));
139+
140+
serializedObject.ApplyModifiedProperties();
141+
}
142+
}
143+
#endif
Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,71 @@
1-
using UnityEngine;
2-
using UnityEngine.UI;
3-
4-
namespace Nobi.UiRoundedCorners {
5-
[RequireComponent(typeof(RectTransform))]
6-
public class ImageWithRoundedCorners : MonoBehaviour {
7-
private static readonly int Props = Shader.PropertyToID("_WidthHeightRadius");
8-
9-
public float radius;
10-
private Material material;
11-
12-
[HideInInspector, SerializeField] private MaskableGraphic image;
13-
14-
private void OnValidate() {
15-
Validate();
16-
Refresh();
17-
}
18-
19-
private void OnDestroy() {
20-
DestroyHelper.Destroy(material);
21-
image = null;
22-
material = null;
23-
}
24-
25-
private void OnEnable() {
26-
Validate();
27-
Refresh();
28-
}
29-
30-
private void OnRectTransformDimensionsChange() {
31-
if (enabled && material != null) {
32-
Refresh();
33-
}
34-
}
35-
36-
public void Validate() {
37-
if (material == null) {
38-
material = new Material(Shader.Find("UI/RoundedCorners/RoundedCorners"));
39-
}
40-
41-
if (image == null) {
42-
TryGetComponent(out image);
43-
}
44-
45-
if (image != null) {
46-
image.material = material;
47-
}
48-
}
49-
50-
public void Refresh() {
51-
var rect = ((RectTransform)transform).rect;
52-
material.SetVector(Props, new Vector4(rect.width, rect.height, radius, 0));
53-
}
54-
}
1+
using UnityEngine;
2+
using UnityEngine.UI;
3+
4+
namespace Nobi.UiRoundedCorners {
5+
[ExecuteInEditMode] //Required to check the OnEnable function
6+
[DisallowMultipleComponent] //You can only have one of these in every object.
7+
[RequireComponent(typeof(RectTransform))]
8+
public class ImageWithRoundedCorners : MonoBehaviour {
9+
private static readonly int Props = Shader.PropertyToID("_WidthHeightRadius");
10+
11+
public float radius = 40f;
12+
private Material material;
13+
14+
[HideInInspector, SerializeField] private MaskableGraphic image;
15+
16+
private void OnValidate() {
17+
Validate();
18+
Refresh();
19+
}
20+
21+
private void OnDestroy() {
22+
image.material = null; //This makes so that when the component is removed, the UI material returns to null
23+
24+
DestroyHelper.Destroy(material);
25+
image = null;
26+
material = null;
27+
}
28+
29+
private void OnEnable() {
30+
//You can only add either ImageWithRoundedCorners or ImageWithIndependentRoundedCorners
31+
//It will replace the other component when added into the object.
32+
var other = GetComponent<ImageWithIndependentRoundedCorners>();
33+
if (other != null)
34+
{
35+
radius = other.r.x; //When it does, transfer the radius value to this script
36+
DestroyHelper.Destroy(other);
37+
}
38+
39+
Validate();
40+
Refresh();
41+
}
42+
43+
private void OnRectTransformDimensionsChange() {
44+
if (enabled && material != null) {
45+
Refresh();
46+
}
47+
}
48+
49+
public void Validate() {
50+
if (material == null) {
51+
material = new Material(Shader.Find("UI/RoundedCorners/RoundedCorners"));
52+
}
53+
54+
if (image == null) {
55+
TryGetComponent(out image);
56+
}
57+
58+
if (image != null) {
59+
image.material = material;
60+
}
61+
}
62+
63+
public void Refresh() {
64+
var rect = ((RectTransform)transform).rect;
65+
66+
//Multiply radius value by 2 to make the radius value appear consistent with ImageWithIndependentRoundedCorners script.
67+
//Right now, the ImageWithIndependentRoundedCorners appears to have double the radius than this.
68+
material.SetVector(Props, new Vector4(rect.width, rect.height, radius * 2, 0));
69+
}
70+
}
5571
}

0 commit comments

Comments
 (0)