热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

UnityGraphic功能,实现UGUI上三角形,四边形,圆环的绘制

前言这篇简单的纪录下利用Graphic类,实现UGUI圆环的绘制。效果图如下:github目录:https:github.comluck
前言

这篇简单的纪录下利用Graphic类,实现UGUI圆环的绘制。效果图如下:

github目录:https://github.com/luckyWjr/Demo

 

Unity如何绘制图形

我们知道一个图形是由N个顶点,互相连成线,然后填充起来。如三角形有三个顶点,四边形有四个,而圆形可以理解为很多很多个顶点。Unity绘制图形的时候同样需要知道这些顶点信息,而区别在于这些看起来无缝连接的形状,在Untiy中是由一个个三角形拼接起来的。

也就是说,Unity只能绘制出三角形,然后由一个个三角形来组成更复杂的形状。例如四边形是两个三角形组成(下图中是ABC和ADC两个三角形,当然也可以是ABD和BDC两个组成),五边形则是三个,而圆则可以由N个三角形组成,如下图:

要绘制三角形那就需要三个顶点,例如上图中的ABC,Unity提供了UIVertex类来纪录顶点的信息,例如坐标,颜色,uv,顶点法线等。除了三个顶点信息外,我们还需要知道其绘制顺序,如是A->B->C还是A->C->B或者其他。这个顺序会影响到三角形面的朝向(Unity是左手坐标系,绘制方向则是左手手指弯曲的方向,面的朝向即左手大拇指朝向,因此ABC的顺序面朝向屏幕前的我们,ACB的顺序则是朝向屏幕后)由于UGUI默认的Shader:UI/Default,设置了Cuff Off,也就是双面渲染,所以我们看不出差别。

注:Unity内置Shader下载路径:https://unity3d.com/get-unity/download/archive

 

Graphic

前面讲了大致的理念,那么知道顶点信息后如何实现图形的绘制,就是由Graphic类来帮助我们实现。如下图,我们需要自定义一个类继承Graphic,然后重新其OnPopulateMesh方法。(UIGI中的Image也是继承于此)

public class Triangle : Graphic
{protected override void OnPopulateMesh(VertexHelper vh){}
}

注:文件名和类名必须相同,否则在给GameObject挂该组件的时候,会报错:cant add script component because the script class cannot be found...

注:若想要支持RectMask2D功能,则改为继承MaskableGraphic即可

在OnPopulateMesh方法中提供了VertexHelper参数,通过它我们就可以实现添加顶点等操作了。

添加顶点

VertexHelper.AddVert(Vector3 position, Color32 color, Vector2 uv0)

参数是顶点坐标,颜色以及uv,也可以直接添加我们前面提到的UIVertex:

VertexHelper.AddVert(UIVertex v)

在VertexHelper中会有如下List来存放这些顶点的相关信息,添加一个顶点对应的List.Count + 1

List m_Positions;
List m_Colors;
List m_Uv0S;
List m_Uv1S;
List m_Uv2S;
List m_Uv3S;
List m_Normals;
List m_Tangents;

添加三角面

VertexHelper.AddTriangle(int idx0, int idx1, int idx2)

参数是三个顶点存储在VertexHelper中的下标,同时也代表了其绘制顺序,idx0 -> idx1 -> idx2。

在VertexHelper中有如下List来存放这些下标,添加一个三角面对应的List.Count + 3

private List m_Indices;

当前顶点数量

VertexHelper.currentVertCount

对应 m_Positions.Count,添加了几个顶点数量就是几

当前索引数量

VertexHelper.currentIndexCount

对应 m_Indices.Count,添加了几次三角面数量就是N * 3

因此正常的绘制一个四边形,有四个顶点,两个三角面,因此currentVertCount = 4,currentIndexCount = 6

清除所有顶点及索引信息

VertexHelper.Clear()

设置贴图

除了重写OnPopulateMesh方法外,我们还可以重写mainTexture属性来设置贴图,例如

[SerializeField] Sprite m_image;
public override Texture mainTexture => m_image == null ? s_WhiteTexture : m_image.texture;

s_WhiteTexture即是Texture2D.whiteTexture,也就是纯白的图。

SetDirty

如果我们在运行时动态修改了一些属性,需要重新绘制。例如修改了贴图,或者绘制圆的时候,修改了填充比例。我们可以调用下面方法来触发。

//调用后会在下一帧重新执行OnPopulateMesh
SetVerticesDirty();//调用后会在下一帧重新设置material以及Texture
SetMaterialDirty();//调用后会重新布局
SetLayoutDirty();//以上全部调用
SetAllDirty();

 

绘制一个三角形

根据前面的介绍,要绘制一个三角形就很简单了,添加三个顶点,添加一个三角面即可

[ExecuteInEditMode]
public class Triangle : Graphic
{public Vector2 positionA;public Vector2 positionB;public Vector2 positionC;protected override void OnPopulateMesh(VertexHelper vh){vh.Clear();vh.AddVert(positionA, Color.white, Vector2.zero);vh.AddVert(positionB, Color.red, Vector2.zero);vh.AddVert(positionC, Color.green, Vector2.zero);vh.AddTriangle(0, 1, 2);}
}

在Canvas中添加一个GameObject,挂上我们的组件,然后随便设置三个点的坐标即可,效果如下

 

绘制一个四边形

同样的四边形也就很简单了,四个顶点,两个三角面。不过在这边顺便讲一下前面没有提到的uv属性,先来看完整的代码

[ExecuteInEditMode]
public class Quadrangle : Graphic
{public Vector2 positionLeftTop;public Vector2 positionRightTop;public Vector2 positionRightBottom;public Vector2 positionLeftBottom;[SerializeField] Sprite m_image;public override Texture mainTexture &#61;> m_image &#61;&#61; null ? s_WhiteTexture : m_image.texture;UIVertex[] m_vertexes &#61; new UIVertex[4];Vector2[] m_uvs &#61; new Vector2[4];protected override void Start(){m_uvs[0] &#61; new Vector2(0, 1);m_uvs[1] &#61; new Vector2(1, 1);m_uvs[2] &#61; new Vector2(1, 0);m_uvs[3] &#61; new Vector2(0, 0);}protected override void OnPopulateMesh(VertexHelper vh){vh.Clear();for (int i &#61; 0; i <4; i&#43;&#43;){m_vertexes[i].color &#61; color;m_vertexes[i].uv0 &#61; m_uvs[i];}m_vertexes[0].position &#61; positionLeftTop;m_vertexes[1].position &#61; positionRightTop;m_vertexes[2].position &#61; positionRightBottom;m_vertexes[3].position &#61; positionLeftBottom;vh.AddVert(m_vertexes[0]);vh.AddVert(m_vertexes[1]);vh.AddVert(m_vertexes[2]);vh.AddVert(m_vertexes[3]);vh.AddTriangle(0, 2, 1);vh.AddTriangle(0, 3, 2);}
}

相比之前&#xff0c;我们添加了Sprite的设置&#xff0c;同时也为顶点添加了uv属性&#xff0c;效果图如下

正方形&#xff08;就像Image组件了&#xff09;&#xff1a;   不规则形状&#xff1a;

 

顶点的uv属性

uv简单来说就是一个二维坐标&#xff08;u和v的取值范围都是 0-1 &#xff09;&#xff0c;代表着一张贴图每个像素的位置信息。uv(0,0)就代表图片的左下角&#xff0c;uv(1,1)代表图片的右上角。这样上述代码m_uvs中的四个uv值就很好理解了&#xff0c;分别是左上&#xff0c;右上&#xff0c;右下&#xff0c;左下。

接着我们的正方形正好是四个顶点&#xff0c;每个顶点设置相应的uv值&#xff0c;那么在该顶点上就会显示图片中相应uv的像素信息了。两个顶点之间的颜色同样也对应着图片中两点之间的像素信息。&#xff08;顶点的color值要设置为Graphic中的color属性&#xff09;

 

绘制圆和圆环

圆的话就是由N个三角形组成&#xff0c;圆环则是把这些组成圆的三角形切了一刀&#xff0c;也就是变成了一个四边形。由于代码写了注释&#xff0c;这里就不详细介绍了&#xff0c;效果图见文章最上方。

[ExecuteInEditMode]
// Changed to maskableGraphic so it can be masked with RectMask2D
public class Annulus : MaskableGraphic
{public enum ShapeType{Annulus,//圆环Circle,//圆}[SerializeField] Sprite m_image;public ShapeType shapeType;public float innerRadius &#61; 10;//圆环内径&#xff0c;为0即是圆public float outerRadius &#61; 20;//圆环外径[Range(0, 1)] [SerializeField] float m_fillAmount;//填充值[Range(0, 720)] public int segments &#61; 360;//片数&#xff0c;越大锯齿越不明显[SerializeField] Image.Origin360 m_originType;//填充的起点public bool m_isClockwise &#61; true;//填充方向&#xff0c;是否是顺时针填充public override Texture mainTexture &#61;> m_image &#61;&#61; null ? s_WhiteTexture : m_image.texture;float m_originRadian &#61; -1;//根据m_originType设置相关弧度&#xff08;-1表示还没设置过对应的值&#xff09;public float fillAmount{get &#61;> m_fillAmount;set{m_fillAmount &#61; value;SetVerticesDirty();}}public Sprite image{get &#61;> m_image;set{if (m_image &#61;&#61; value)return;m_image &#61; value;SetVerticesDirty();SetMaterialDirty();}}public Image.Origin360 originType{get &#61;> m_originType;set{if (m_originType &#61;&#61; value)return;m_originType &#61; value;SetOriginRadian();SetVerticesDirty();}}public bool isClockwise{get &#61;> m_isClockwise;set{if (m_isClockwise !&#61; value){m_isClockwise &#61; value;SetVerticesDirty();}}}UIVertex[] m_vertexes &#61; new UIVertex[4];Vector2[] m_uvs &#61; new Vector2[4];Vector2[] m_positions &#61; new Vector2[4];protected override void Start(){if (m_originRadian &#61;&#61; -1)SetOriginRadian();m_uvs[0] &#61; new Vector2(0, 1);m_uvs[1] &#61; new Vector2(1, 1);m_uvs[2] &#61; new Vector2(1, 0);m_uvs[3] &#61; new Vector2(0, 0);}protected override void OnPopulateMesh(VertexHelper vh){vh.Clear();//m_fillAmount &#61;&#61; 0&#xff0c;什么也不绘制if (m_fillAmount &#61;&#61; 0) return;#if UNITY_EDITORSetOriginRadian();
#endif//每个面片的角度float degrees &#61; 360f / segments;//需要绘制的面片数量int count &#61; (int)(segments * m_fillAmount);float cos &#61; Mathf.Cos(m_originRadian);float sin &#61; Mathf.Sin(m_originRadian);//计算外环起点&#xff0c;例如m_originRadian &#61; 0&#xff0c;x &#61; -outerRadius&#xff0c;y &#61; 0&#xff0c;所以起点是Left&#xff08;九点钟方向&#xff09;float x &#61; -outerRadius * cos;float y &#61; outerRadius * sin;Vector2 originOuter &#61; new Vector2(x, y);//计算内环起点x &#61; -innerRadius * cos;y &#61; innerRadius * sin;Vector2 originInner &#61; new Vector2(x, y);for (int i &#61; 1; i <&#61; count; i&#43;&#43;){//m_positions[0] 当前面片的外环起点m_positions[0] &#61; originOuter;//当前面片的弧度 &#43; 起始弧度 &#61; 终止弧度float endRadian &#61; i * degrees * Mathf.Deg2Rad * (isClockwise ? 1 : -1) &#43; m_originRadian;cos &#61; Mathf.Cos(endRadian);sin &#61; Mathf.Sin(endRadian);//m_positions[1] 当前面片的外环终点m_positions[1] &#61; new Vector2(-outerRadius * cos, outerRadius * sin);//m_positions[2] 当前面片的内环终点//m_positions[3] 当前面片的内环起点if (shapeType &#61;&#61; ShapeType.Annulus){m_positions[2] &#61; new Vector2(-innerRadius * cos, innerRadius * sin);m_positions[3] &#61; originInner;}else{m_positions[2] &#61; Vector2.zero;m_positions[3] &#61; Vector2.zero;}// 设置顶点的颜色坐标以及uvfor (int j &#61; 0; j <4; j&#43;&#43;){m_vertexes[j].color &#61; color;m_vertexes[j].position &#61; m_positions[j];m_vertexes[j].uv0 &#61; m_uvs[j];}//当前顶点数量int vertCount &#61; vh.currentVertCount;//如果是圆只需要添加三个顶点&#xff0c;创建一个三角面vh.AddVert(m_vertexes[0]);vh.AddVert(m_vertexes[1]);vh.AddVert(m_vertexes[2]);//参数即三角面的顶点绘制顺序vh.AddTriangle(vertCount, vertCount &#43; 2, vertCount &#43; 1);// 如果是圆环就需要添加第四个顶点&#xff0c;并再创建一个三角面if (shapeType &#61;&#61; ShapeType.Annulus){vh.AddVert(m_vertexes[3]);vh.AddTriangle(vertCount, vertCount &#43; 3, vertCount &#43; 2);}//当前面片的终点就是下个面片的起点originOuter &#61; m_positions[1];originInner &#61; m_positions[2];}}//m_originType改变的时候需要重新设置m_originRadianvoid SetOriginRadian(){switch (m_originType){case Image.Origin360.Left:m_originRadian &#61; 0 * Mathf.Deg2Rad;break;case Image.Origin360.Top:m_originRadian &#61; 90 * Mathf.Deg2Rad;break;case Image.Origin360.Right:m_originRadian &#61; 180 * Mathf.Deg2Rad;break;case Image.Origin360.Bottom:m_originRadian &#61; 270 * Mathf.Deg2Rad;break;}}
}

知道原理&#xff0c;我们就可以进行一些魔改&#xff0c;例如fillmount小于1的时候&#xff0c;用别的颜色来填充等&#xff0c;大家可以自由发挥。


推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • Java图形化计算器设计与实现
    本文介绍了使用Java编程语言设计和实现图形化计算器的方法。通过使用swing包和awt包中的组件,作者创建了一个具有按钮监听器和自定义界面尺寸和布局的计算器。文章还分享了在图形化界面设计中的一些心得体会。 ... [详细]
author-avatar
勇敢的心yzw1979_886
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有