using System.Collections.Generic; using UnityEngine; using UnityEngine.Sprites; using UnityEngine.UI; /* Author: Josh H. * Procedural UI Image * assetstore.joshh@gmail.com for feedback or questions */ namespace UnityEngine.UI { [ExecuteInEditMode] [AddComponentMenu("UI/Procedural Image")] public class ProceduralImage : ImageProxy { [SerializeField]private float borderWidth; private ProceduralImageModifier modifier; public Material materialInstance; public Material materialInstanceClip; [SerializeField]private float falloffDistance = 1; public bool needClipping = false; public float BorderWidth { get { return borderWidth; } set { borderWidth = value; this.SetMaterialDirty(); } } public float FalloffDistance { get { return falloffDistance; } set { falloffDistance = value; this.SetMaterialDirty(); } } protected ProceduralImageModifier Modifier { get { if (modifier == null) { //try to get the modifier on the object. modifier = this.GetComponent(); //if we did not find any modifier if(modifier == null){ //Add free modifier ModifierType = typeof(FreeModifier); } } return modifier; } set{ modifier = value; } } /// /// Gets or sets the type of the modifier. Adds a modifier of that type. /// /// The type of the modifier. public System.Type ModifierType { get { return Modifier.GetType(); } set { if(this.GetComponent()!=null){ Destroy(this.GetComponent()); } this.gameObject.AddComponent(value); Modifier = this.GetComponent(); this.SetAllDirty(); } } override protected void OnEnable() { base.OnEnable (); // ScenceMain.MainUpdateEvent += MainUpdate; this.Init (); } /// /// Initializes this instance. /// public void Init (){ // materialInstance = Image.defaultGraphicMaterial; // materialInstanceClip = Image.defaultETC1GraphicMaterial; // this.material = materialInstance; } protected override void OnDisable() { base.OnDisable(); // ScenceMain.MainUpdateEvent -= MainUpdate; } public void Update(){ if (needClipping) { // this.material = materialInstanceClip; } else { //this.material = materialInstance; } this.UpdateMaterial (); } /// /// Prevents radius to get bigger than rect size /// /// The fixed radius. /// border-radius as Vector4 (starting upper-left, clockwise) private Vector4 FixRadius(Vector4 vec){ Rect r = this.rectTransform.rect; vec = new Vector4 (Mathf.Max(vec.x,0),Mathf.Max(vec.y,0),Mathf.Max(vec.z,0),Mathf.Max(vec.w,0)); //float maxRadiusSums = Mathf.Max (vec.x,vec.z) + Mathf.Max (vec.y,vec.w); float scaleFactor = Mathf.Min(r.width/(vec.x+vec.y),r.width/(vec.z+vec.w),r.height/(vec.x+vec.w),r.height/(vec.z+vec.y),1); return vec*scaleFactor; } protected override void OnPopulateMesh(VertexHelper toFill) { //note: Sliced and Tiled have no effect to this currently. if (overrideSprite == null) { base.OnPopulateMesh(toFill); return; } switch (type) { case Type.Simple: GenerateSimpleSprite(toFill); break; case Type.Sliced: GenerateSimpleSprite(toFill); break; case Type.Tiled: GenerateSimpleSprite(toFill); break; case Type.Filled: base.OnPopulateMesh(toFill); break; } } #if UNITY_EDITOR protected override void Reset (){ base.Reset (); OnEnable (); } #endif private Vector4 GetDrawingDimensions(bool shouldPreserveAspect) { var padding = overrideSprite == null ? Vector4.zero : DataUtility.GetPadding(overrideSprite); Rect r = GetPixelAdjustedRect(); var size = overrideSprite == null ? new Vector2(r.width, r.height) : new Vector2(overrideSprite.rect.width, overrideSprite.rect.height); //Debug.Log(string.Format("r:{2}, size:{0}, padding:{1}", size, padding, r)); int spriteW = Mathf.RoundToInt(size.x); int spriteH = Mathf.RoundToInt(size.y); if (shouldPreserveAspect && size.sqrMagnitude > 0.0f) { var spriteRatio = size.x / size.y; var rectRatio = r.width / r.height; if (spriteRatio > rectRatio) { var oldHeight = r.height; r.height = r.width * (1.0f / spriteRatio); r.y += (oldHeight - r.height) * rectTransform.pivot.y; } else { var oldWidth = r.width; r.width = r.height * spriteRatio; r.x += (oldWidth - r.width) * rectTransform.pivot.x; } } var v = new Vector4( padding.x / spriteW, padding.y / spriteH, (spriteW - padding.z) / spriteW, (spriteH - padding.w) / spriteH); v = new Vector4( r.x + r.width * v.x, r.y + r.height * v.y, r.x + r.width * v.z, r.y + r.height * v.w ); return v; } //每个角最大的三角形数,一般5-8个就有不错的圆角效果,设置Max防止不必要的性能浪费 const int MaxTriangleNum = 20; const int MinTriangleNum = 1; public float Radius=10; //使用几个三角形去填充每个角的四分之一圆 [Range(MinTriangleNum, MaxTriangleNum)] public int TriangleNum=6; /// /// Generates the Verticies needed. /// /// vertex helper void GenerateSimpleSprite(VertexHelper vh){ Vector4 v = GetDrawingDimensions(false); Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero; var color32 = color; vh.Clear(); //对radius的值做限制,必须在0-较小的边的1/2的范围内 float radius = Radius; if (radius > (v.z - v.x) / 2) radius = (v.z - v.x) / 2; if (radius > (v.w - v.y) / 2) radius = (v.w - v.y) / 2; if (radius < 0) radius = 0; //计算出uv中对应的半径值坐标轴的半径 float uvRadiusX = radius / (v.z - v.x); float uvRadiusY = radius / (v.w - v.y); //0,1 vh.AddVert(new Vector3(v.x, v.w - radius), color32, new Vector2(uv.x, uv.w - uvRadiusY)); vh.AddVert(new Vector3(v.x, v.y + radius), color32, new Vector2(uv.x, uv.y + uvRadiusY)); //2,3,4,5 vh.AddVert(new Vector3(v.x + radius, v.w), color32, new Vector2(uv.x + uvRadiusX, uv.w)); vh.AddVert(new Vector3(v.x + radius, v.w - radius), color32, new Vector2(uv.x + uvRadiusX, uv.w - uvRadiusY)); vh.AddVert(new Vector3(v.x + radius, v.y + radius), color32, new Vector2(uv.x + uvRadiusX, uv.y + uvRadiusY)); vh.AddVert(new Vector3(v.x + radius, v.y), color32, new Vector2(uv.x + uvRadiusX, uv.y)); //6,7,8,9 vh.AddVert(new Vector3(v.z - radius, v.w), color32, new Vector2(uv.z - uvRadiusX, uv.w)); vh.AddVert(new Vector3(v.z - radius, v.w - radius), color32, new Vector2(uv.z - uvRadiusX, uv.w - uvRadiusY)); vh.AddVert(new Vector3(v.z - radius, v.y + radius), color32, new Vector2(uv.z - uvRadiusX, uv.y + uvRadiusY)); vh.AddVert(new Vector3(v.z - radius, v.y), color32, new Vector2(uv.z - uvRadiusX, uv.y)); //10,11 vh.AddVert(new Vector3(v.z, v.w - radius), color32, new Vector2(uv.z, uv.w - uvRadiusY)); vh.AddVert(new Vector3(v.z, v.y + radius), color32, new Vector2(uv.z, uv.y + uvRadiusY)); //左边的矩形 vh.AddTriangle(1, 0, 3); vh.AddTriangle(1, 4, 3); //中间的矩形 vh.AddTriangle(5, 2, 6); vh.AddTriangle(5, 9, 6); //右边的矩形 vh.AddTriangle(8, 7, 10); vh.AddTriangle(8, 11, 10); //开始构造四个角 List vCenterList = new List(); List uvCenterList = new List(); List vCenterVertList = new List(); //右上角的圆心 vCenterList.Add(new Vector2(v.z - radius, v.w - radius)); uvCenterList.Add(new Vector2(uv.z - uvRadiusX, uv.w - uvRadiusY)); vCenterVertList.Add(7); //左上角的圆心 vCenterList.Add(new Vector2(v.x + radius, v.w - radius)); uvCenterList.Add(new Vector2(uv.x + uvRadiusX, uv.w - uvRadiusY)); vCenterVertList.Add(3); //左下角的圆心 vCenterList.Add(new Vector2(v.x + radius, v.y + radius)); uvCenterList.Add(new Vector2(uv.x + uvRadiusX, uv.y + uvRadiusY)); vCenterVertList.Add(4); //右下角的圆心 vCenterList.Add(new Vector2(v.z - radius, v.y + radius)); uvCenterList.Add(new Vector2(uv.z - uvRadiusX, uv.y + uvRadiusY)); vCenterVertList.Add(8); //每个三角形的顶角 float degreeDelta = (float)(Mathf.PI / 2 / TriangleNum); //当前的角度 float curDegree = 0; for (int i = 0; i < vCenterVertList.Count; i++) { int preVertNum = vh.currentVertCount; for (int j = 0; j <= TriangleNum; j++) { float cosA = Mathf.Cos(curDegree); float sinA = Mathf.Sin(curDegree); Vector3 vPosition = new Vector3(vCenterList[i].x + cosA * radius, vCenterList[i].y + sinA * radius); Vector3 uvPosition = new Vector2(uvCenterList[i].x + cosA * uvRadiusX, uvCenterList[i].y + sinA * uvRadiusY); vh.AddVert(vPosition, color32, uvPosition); curDegree += degreeDelta; } curDegree -= degreeDelta; for (int j = 0; j <= TriangleNum - 1; j++) { vh.AddTriangle(vCenterVertList[i], preVertNum + j + 1, preVertNum + j); } } } /// /// Sets the material values of shader. /// Implementation of IMaterialModifier /// public override Material GetModifiedMaterial (Material baseMaterial){ return base.GetModifiedMaterial(baseMaterial); Rect rect = this.GetComponent ().rect; //get world-space corners of rect Vector3[] corners = new Vector3[4]; rectTransform.GetWorldCorners (corners); float pixelSize = Vector3.Distance (corners [1], corners [2]) / rect.width; pixelSize = pixelSize/falloffDistance; Vector4 radius = FixRadius (Modifier.CalculateRadius (rect)); Material m = MaterialHelper.SetMaterialValues (new ProceduralImageMaterialInfo(rect.width+falloffDistance,rect.height+falloffDistance,1,radius,Mathf.Max(borderWidth,0)),baseMaterial); return base.GetModifiedMaterial (m); } } }