diff --git a/Examples/UICircleProgress.meta b/Examples/UICircleProgress.meta new file mode 100644 index 0000000..55e18e2 --- /dev/null +++ b/Examples/UICircleProgress.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d85457b4b0625c6448836b062a4b33a2 +folderAsset: yes +timeCreated: 1507492783 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/UICircleProgress/ChangeColor.cs b/Examples/UICircleProgress/ChangeColor.cs new file mode 100644 index 0000000..fd845b7 --- /dev/null +++ b/Examples/UICircleProgress/ChangeColor.cs @@ -0,0 +1,69 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI.Extensions; + +public class ChangeColor : MonoBehaviour +{ + public GameObject TargetUICircle; + private Color baseColor; + private Color progressColor; + + private float r, g, b = 0; + private float factor = 1536.145f; + private void Awake() + { + baseColor = TargetUICircle.GetComponent().color; + progressColor = TargetUICircle.GetComponent().ProgressColor; + } + + public void UpdateBaseColor(float value) + { + baseColor = SetFixedColor(value, baseColor.a); + TargetUICircle.GetComponent().color = baseColor; + } + + public void UpdateProgressColor(float value) + { + + progressColor = SetFixedColor(value, progressColor.a); + TargetUICircle.GetComponent().SetProgressColor(progressColor); + } + + private Color SetFixedColor(float value,float alpha) + { + if (value <= 0.166f) + { + g = 0; + r = 255f; + b = 255f - (255f - (value * factor)); + }else if(value <= 0.332f) + { + g = 0; + r = 255f - (255f - ((0.332f - value)*factor)); + b = 255f; + }else if(value <= 0.498f) + { + g = 255f - (255f - ((0.498f - value) * factor)); + r = 0f; + b = 255f; + }else if(value <= 0.664f) + { + g = 255f; + r = 0f; + b = 255f - (255f - ((0.664f - value) * factor)); + }else if(value <= 0.83f) + { + g = 255f; + r = 255f - (255f - ((0.83f - value) * factor)); + b = 0; + }else + { + g = 255f - (255f - ((1 - value) * factor)); + r = 255f; + b = 0; + } + + return new Color(r, g, b, alpha); + } +} diff --git a/Examples/UICircleProgress/ChangeColor.cs.meta b/Examples/UICircleProgress/ChangeColor.cs.meta new file mode 100644 index 0000000..0e0b1e1 --- /dev/null +++ b/Examples/UICircleProgress/ChangeColor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7ae29b862df56504fac8133305afea62 +timeCreated: 1507577225 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/UICircleProgress/ChangeDensity.cs b/Examples/UICircleProgress/ChangeDensity.cs new file mode 100644 index 0000000..b6e9a11 --- /dev/null +++ b/Examples/UICircleProgress/ChangeDensity.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.UI.Extensions; + +public class ChangeDensity : MonoBehaviour +{ + public GameObject MultiColorObject; + public GameObject TextOutputObject; + + private UICircle _uiCircleComponent; + private Text _densityOutput; + + private void Awake() + { + _uiCircleComponent = MultiColorObject.GetComponent(); + _densityOutput = TextOutputObject.GetComponent(); + } + private void OnEnable() + { + _densityOutput.text = _uiCircleComponent.ArcSteps.ToString(); + } + + public void UpdateDensity(float value) + { + _uiCircleComponent.SetArcSteps((int)value); + _densityOutput.text = value.ToString(); + } +} diff --git a/Examples/UICircleProgress/ChangeDensity.cs.meta b/Examples/UICircleProgress/ChangeDensity.cs.meta new file mode 100644 index 0000000..39f3c79 --- /dev/null +++ b/Examples/UICircleProgress/ChangeDensity.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 970b2e1ba28dfa64e8fc65acb4f7e37c +timeCreated: 1507668540 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/UICircleProgress/UICircleProgress.unity b/Examples/UICircleProgress/UICircleProgress.unity new file mode 100644 index 0000000..59dd9a0 Binary files /dev/null and b/Examples/UICircleProgress/UICircleProgress.unity differ diff --git a/Examples/UICircleProgress/UICircleProgress.unity.meta b/Examples/UICircleProgress/UICircleProgress.unity.meta new file mode 100644 index 0000000..e06f557 --- /dev/null +++ b/Examples/UICircleProgress/UICircleProgress.unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 79f0a411768319c48801b886b4530fb9 +timeCreated: 1507494554 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/UICircleProgress/square.psd b/Examples/UICircleProgress/square.psd new file mode 100644 index 0000000..fd7e5a6 Binary files /dev/null and b/Examples/UICircleProgress/square.psd differ diff --git a/Examples/UICircleProgress/square.psd.meta b/Examples/UICircleProgress/square.psd.meta new file mode 100644 index 0000000..c3dc3c7 --- /dev/null +++ b/Examples/UICircleProgress/square.psd.meta @@ -0,0 +1,92 @@ +fileFormatVersion: 2 +guid: 203e0bdef973df44ba779da032273050 +timeCreated: 1507498573 +licenseType: Pro +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Standalone + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: iPhone + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Windows Store Apps + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Primitives/UICircle.cs b/Scripts/Primitives/UICircle.cs index 5321f58..89106ec 100644 --- a/Scripts/Primitives/UICircle.cs +++ b/Scripts/Primitives/UICircle.cs @@ -2,146 +2,219 @@ /// Sourced from - http://forum.unity3d.com/threads/draw-circles-or-primitives-on-the-new-ui-canvas.272488/#post-2293224 /// Updated from - https://bitbucket.org/ddreaper/unity-ui-extensions/issues/65/a-better-uicircle +/// Update 10.9.2017 (tswalker, https://bitbucket.org/tswalker/) +/// +/// * Modified component to utilize vertex stream instead of quads +/// * Improved accuracy of geometry fill to prevent edge "sliding" and redundant tris +/// * Added progress capability to allow component to be used as an indicator +/// * Added methods for use during runtime and event system(s) with other components +/// * Change some terminology of members to reflect other component property changes +/// * Added padding capability +/// * Only utilizes UV0 set for sprite/texture mapping (maps UV to geometry 0,1 boundary) +/// * Sample usage in scene "UICircleProgress" +/// Note: moving the pivot around from center to an edge can cause strange things +/// as well as having the RectTransform be smaller than the Thickness and/or Padding. +/// When making an initial layout for the component, it would be best to test multiple +/// aspect ratios and resolutions to ensure consistent behaviour. + +using System.Collections.Generic; + namespace UnityEngine.UI.Extensions { [AddComponentMenu("UI/Extensions/Primitives/UI Circle")] public class UICircle : UIPrimitiveBase { - [Tooltip("The circular fill percentage of the primitive, affected by FixedToSegments")] - [Range(0, 100)] - [SerializeField] - private int m_fillPercent = 100; - [Tooltip("Should the primitive fill draw by segments or absolute percentage")] - public bool FixedToSegments = false; - [Tooltip("Draw the primitive filled or as a line")] - [SerializeField] - private bool m_fill = true; - [Tooltip("If not filled, the thickness of the primitive line")] - [SerializeField] - private float m_thickness = 5; - [Tooltip("The number of segments to draw the primitive, more segments = smoother primitive")] + [Tooltip("The Arc Invert property will invert the construction of the Arc.")] + public bool ArcInvert = true; + + [Tooltip("The Arc property is a percentage of the entire circumference of the circle.")] + [Range(0, 1)] + public float Arc = 1; + + [Tooltip("The Arc Steps property defines the number of segments that the Arc will be divided into.")] + [Range(0, 1000)] + public int ArcSteps = 100; + + [Tooltip("The Arc Rotation property permits adjusting the geometry orientation around the Z axis.")] [Range(0, 360)] - [SerializeField] - private int m_segments = 360; + public int ArcRotation = 0; + [Tooltip("The Progress property allows the primitive to be used as a progression indicator.")] + [Range(0, 1)] + public float Progress = 0; + private float _progress = 0; - public int FillPercent - { - get { return m_fillPercent; } - set { m_fillPercent = value; SetAllDirty(); } - } + public Color ProgressColor = new Color(255, 255, 255, 255); + public bool Fill = true; //solid circle + public float Thickness = 5; + public int Padding = 0; - public bool Fill - { - get { return m_fill; } - set { m_fill = value; SetAllDirty(); } - } - - public float Thickness - { - get { return m_thickness; } - set { m_thickness = value; SetAllDirty(); } - } - - - void Update() - { - this.m_thickness = (float)Mathf.Clamp(this.m_thickness, 0, rectTransform.rect.width / 2); - } - - public int Segments - { - get { return m_segments; } - set { m_segments = value; SetAllDirty(); } - } + private List indices = new List(); //ordered list of vertices per tri + private List vertices = new List(); + private Vector2 uvCenter = new Vector2(0.5f, 0.5f); protected override void OnPopulateMesh(VertexHelper vh) { - float outer = -rectTransform.pivot.x * rectTransform.rect.width; - float inner = -rectTransform.pivot.x * rectTransform.rect.width + this.m_thickness; - + int _inversion = ArcInvert ? -1 : 1; + float Diameter = (rectTransform.rect.width < rectTransform.rect.height ? rectTransform.rect.width : rectTransform.rect.height) - Padding; //correct for padding and always fit RectTransform + float outerDiameter = -rectTransform.pivot.x * Diameter; + float innerDiameter = -rectTransform.pivot.x * Diameter + Thickness; + vh.Clear(); - - Vector2 prevX = Vector2.zero; - Vector2 prevY = Vector2.zero; - Vector2 uv0 = new Vector2(0, 0); - Vector2 uv1 = new Vector2(0, 1); - Vector2 uv2 = new Vector2(1, 1); - Vector2 uv3 = new Vector2(1, 0); - Vector2 pos0; - Vector2 pos1; - Vector2 pos2; - Vector2 pos3; + indices.Clear(); + vertices.Clear(); - if (FixedToSegments) + int i = 0; + int j = 1; + int k = 0; + + float stepDegree = (Arc * 360f) / ArcSteps; + _progress = ArcSteps * Progress; + float rad = _inversion * Mathf.Deg2Rad * ArcRotation; + float X = Mathf.Cos(rad); + float Y = Mathf.Sin(rad); + + var vertex = UIVertex.simpleVert; + vertex.color = _progress > 0 ? ProgressColor : color; + + //initial vertex + vertex.position = new Vector2(outerDiameter * X, outerDiameter * Y); + vertex.uv0 = new Vector2(vertex.position.x / Diameter + 0.5f, vertex.position.y / Diameter + 0.5f); + vertices.Add(vertex); + + var iV = new Vector2(innerDiameter * X, innerDiameter * Y); + if (Fill) iV = Vector2.zero; //center vertex to pivot + vertex.position = iV; + vertex.uv0 = Fill ? uvCenter : new Vector2(vertex.position.x / Diameter + 0.5f, vertex.position.y / Diameter + 0.5f); + vertices.Add(vertex); + + for (int counter = 1; counter <= ArcSteps; counter++) { - float f = (this.m_fillPercent / 100f); - float degrees = 360f / m_segments; - int fa = (int)((m_segments + 1) * f); + rad = _inversion * Mathf.Deg2Rad * (counter * stepDegree + ArcRotation); + X = Mathf.Cos(rad); + Y = Mathf.Sin(rad); + vertex.color = counter > _progress ? color : ProgressColor; + vertex.position = new Vector2(outerDiameter * X, outerDiameter * Y); + vertex.uv0 = new Vector2(vertex.position.x / Diameter + 0.5f, vertex.position.y / Diameter + 0.5f); + vertices.Add(vertex); - for (int i = 0; i < fa; i++) + //add additional vertex if required and generate indices for tris in clockwise order + if (!Fill) { - float rad = Mathf.Deg2Rad * (i * degrees); - float c = Mathf.Cos(rad); - float s = Mathf.Sin(rad); + vertex.position = new Vector2(innerDiameter * X, innerDiameter * Y); + vertex.uv0 = new Vector2(vertex.position.x / Diameter + 0.5f, vertex.position.y / Diameter + 0.5f); + vertices.Add(vertex); + k = j; + indices.Add(i); + indices.Add(j + 1); + indices.Add(j); + j++; + i = j; + j++; + indices.Add(i); + indices.Add(j); + indices.Add(k); + } + else + { + indices.Add(i); + indices.Add(j + 1); + //Fills (solid circle) with progress require an additional vertex to + // prevent the base circle from becoming a gradient from center to edge + if (counter > _progress) + { + indices.Add(ArcSteps + 2); + } + else + { + indices.Add(1); + } - uv0 = new Vector2(0, 1); - uv1 = new Vector2(1, 1); - uv2 = new Vector2(1, 0); - uv3 = new Vector2(0, 0); - - StepThroughPointsAndFill(outer, inner, ref prevX, ref prevY, out pos0, out pos1, out pos2, out pos3, c, s); - - vh.AddUIVertexQuad(SetVbo(new[] { pos0, pos1, pos2, pos3 }, new[] { uv0, uv1, uv2, uv3 })); + j++; + i = j; } } - else + + //this vertex is added to the end of the list to simplify index ordering on geometry fill + if (Fill) { - float tw = rectTransform.rect.width; - float th = rectTransform.rect.height; - - float angleByStep = (m_fillPercent / 100f * (Mathf.PI * 2f)) / m_segments; - float currentAngle = 0f; - for (int i = 0; i < m_segments + 1; i++) - { - - float c = Mathf.Cos(currentAngle); - float s = Mathf.Sin(currentAngle); - - StepThroughPointsAndFill(outer, inner, ref prevX, ref prevY, out pos0, out pos1, out pos2, out pos3, c, s); - - uv0 = new Vector2(pos0.x / tw + 0.5f, pos0.y / th + 0.5f); - uv1 = new Vector2(pos1.x / tw + 0.5f, pos1.y / th + 0.5f); - uv2 = new Vector2(pos2.x / tw + 0.5f, pos2.y / th + 0.5f); - uv3 = new Vector2(pos3.x / tw + 0.5f, pos3.y / th + 0.5f); - - vh.AddUIVertexQuad(SetVbo(new[] { pos0, pos1, pos2, pos3 }, new[] { uv0, uv1, uv2, uv3 })); - - currentAngle += angleByStep; - } + vertex.position = iV; + vertex.color = color; + vertex.uv0 = uvCenter; + vertices.Add(vertex); } + vh.AddUIVertexStream(vertices, indices); } - private void StepThroughPointsAndFill(float outer, float inner, ref Vector2 prevX, ref Vector2 prevY, out Vector2 pos0, out Vector2 pos1, out Vector2 pos2, out Vector2 pos3, float c, float s) + //the following methods may be used during run-time + //to update the properties of the component + public void SetProgress(float progress) { - pos0 = prevX; - pos1 = new Vector2(outer * c, outer * s); - - if (m_fill) - { - pos2 = Vector2.zero; - pos3 = Vector2.zero; - } - else - { - pos2 = new Vector2(inner * c, inner * s); - pos3 = prevY; - } - - prevX = pos1; - prevY = pos2; + Progress = progress; + SetVerticesDirty(); } + public void SetArcSteps(int steps) + { + ArcSteps = steps; + SetVerticesDirty(); + } + + public void SetInvertArc(bool invert) + { + ArcInvert = invert; + SetVerticesDirty(); + } + + public void SetArcRotation(int rotation) + { + ArcRotation = rotation; + SetVerticesDirty(); + } + + public void SetFill(bool fill) + { + Fill = fill; + SetVerticesDirty(); + } + + public void SetBaseColor(Color color) + { + this.color = color; + SetVerticesDirty(); + } + + public void UpdateBaseAlpha(float value) + { + var _color = this.color; + _color.a = value; + this.color = _color; + SetVerticesDirty(); + } + + public void SetProgressColor(Color color) + { + ProgressColor = color; + SetVerticesDirty(); + } + + public void UpdateProgressAlpha(float value) + { + ProgressColor.a = value; + SetVerticesDirty(); + } + + public void SetPadding(int padding) + { + Padding = padding; + SetVerticesDirty(); + } + + public void SetThickness(int thickness) + { + Thickness = thickness; + SetVerticesDirty(); + } } } \ No newline at end of file