1.1 针对 Unity自定义UI组件(一)函数篇(上) 的完善版本,功能的拓展
1.2 代码实现2D网格绘制
1.3 如何在自定义UI组件上添加文字
绘制原理:
Unity中UI的现实也是基于三角面的绘制,三角面是绘制的最小单位,比如给出三个顶点,即可绘制出一个三角形。一个矩形的绘制是由两个三角面拼合而成,三角面三个顶点应该逆时针绘制,逆时针绘制是朝着屏幕可见,顺时针绘制是逆着屏幕可见(大家都应该遇到过模型只能看到一面的情况,比如Unity自带的Cube,当相机进入Cube中后,是看不见Cube里面的面的,就像传统CS,CF游戏一样,但是如果我们瞬时间绘制一次,逆时针绘制一次就是双面可见)。
逆时针三角面渲染顶点顺序:012 230
顺时针三角面渲染顶点顺序:021 320
在UI组件的绘制中,VertexHelp支持直接绘制矩形,只要调用VertexHelp.AddUIVertexQuad(Vertex[])传入由矩形的四个顶点构成的顶点数组,即可绘制,更多详细网格绘制原理,可参照官网Mesh或者期待后续的网格绘制基础篇。
函数图的绘制原理:其实曲线的绘制是由很多个小的矩形组合而成的,只要绘制的密集程度足够高,就可以看起来像是连续的曲线,其实本质上很多个矩形再由很多个三角面组合成的,详细的同学就会发现在我们这个脚本绘制的曲线图中,只要缩放的足够大,就可以看到矩形组合的断截面。
当然我们如果为了更好的效果,可以把断截面也修补好,就是在断截的地方再绘制一个矩形,将其弥补。
基础框架搭建(XY轴、单位、网格等基础数据)
///
/// 函数图基础 XY轴 刻度等
///
[Serializable]
public class FunctionalGraphBase
{
///
/// 是否显示刻度
///
public bool ShowScale = false;
///
/// 是否显示XY轴单位
///
public bool ShowXYAxisUnit = true;
///
/// X轴单位
///
public string XAxisUnit = "XUnit";
///
/// Y轴单位
///
public string YAxisUnit = "YUnit";
///
/// 单位字体大小
///
[Range(12,30)]public int FontSize = 16;
///
/// 字体颜色
///
public Color FontColor = Color.black;
///
/// XY轴刻度
///
[Range(20f,100f)]public float ScaleValue = 50f;
///
/// 刻度的长度
///
[Range(2, 10)] public float ScaleLenght = 5.0f;
///
/// XY轴宽度
///
[Range(2f , 20f)]public float XYAxisWidth = 2.0f;
///
/// XY轴颜色
///
public Color XYAxisColor = Color.gray;
///
/// 网格Enum
///
public enum E_MeshType
{
None,
FullLine,
ImaglinaryLine
}
///
/// 网格类型
///
public E_MeshType MeshType = E_MeshType.None;
///
/// 网格线段宽度
///
[Range(1.0f,10f)]public float MeshLineWidth = 2.0f;
///
/// 网格颜色
///
public Color MeshColor = Color.gray;
///
/// 虚线的长度
///
[Range(0.5f,20)]public float ImaglinaryLineWidth = 8.0f;
///
/// 虚线空格长度
///
[Range(0.5f,10f)]public float SpaceingWidth = 5.0f;
}
根据基础框架的数据信息绘制坐标轴和网格
public class FunctionalGraph : MaskableGraphic
{
public FunctionalGraphBase GraphBase = new FunctionalGraphBase();
public List Formulas;
private RectTransform _myRect;
private Vector2 _xPoint;
private Vector2 _yPoint;
//绘制坐标轴上的文字
private void OnGUI()
{
if ( GraphBase.ShowXYAxisUnit )
{
Vector3 result = transform.localPosition;
Vector3 realPosition = getScreenPosition(transform , ref result);
GUIStyle guiStyleX = new GUIStyle();
guiStyleX.normal.textColor = GraphBase.FontColor;
guiStyleX.fontSize = GraphBase.FontSize;
guiStyleX.fontStyle = FontStyle.Bold;
guiStyleX.alignment = TextAnchor.MiddleLeft;
GUI.Label(new Rect(local2Screen(realPosition , _xPoint) + new Vector2(20 , 0) , new Vector2(0 , 0)) , GraphBase.XAxisUnit , guiStyleX);
GUIStyle guiStyleY = new GUIStyle();
guiStyleY.normal.textColor = GraphBase.FontColor;
guiStyleY.fontSize = GraphBase.FontSize;
guiStyleY.fontStyle = FontStyle.Bold;
guiStyleY.alignment = TextAnchor.MiddleCenter;
GUI.Label(new Rect(local2Screen(realPosition , _yPoint) - new Vector2(0 , 20) , new Vector2(0 , 0)) , GraphBase.YAxisUnit , guiStyleY);
}
}
///
/// 初始化函数信息
///
private void Init()
{
_myRect = this.rectTransform;
Formulas = new List
{
new FunctionFormula(Mathf.Sin, Color.red, 2.0f),
new FunctionFormula(Mathf.Cos, Color.green, 2.0f),
new FunctionFormula(Mathf.Log10,Color.yellow,2.0f)
};
}
///
/// 重写这个类以绘制UI
///
///
protected override void OnPopulateMesh(VertexHelper vh)
{
Init();
vh.Clear();
#region 基础框架的绘制
// 绘制X轴
float lenght = _myRect.sizeDelta.x;
Vector2 leftPoint = new Vector2(-lenght / 2.0f , 0);
Vector2 rightPoint = new Vector2(lenght / 2.0f , 0);
vh.AddUIVertexQuad(GetQuad(leftPoint , rightPoint , GraphBase.XYAxisColor , GraphBase.XYAxisWidth));
// 绘制X轴的箭头
float arrowUnit = GraphBase.XYAxisWidth * 3;
Vector2 firstPointX = rightPoint + new Vector2(0 , arrowUnit);
Vector2 secondPointX = rightPoint;
Vector2 thirdPointX = rightPoint + new Vector2(0 , -arrowUnit);
Vector2 fourPointX = rightPoint + new Vector2(Mathf.Sqrt(3) * arrowUnit , 0);
vh.AddUIVertexQuad(GetQuad(firstPointX , secondPointX , thirdPointX , fourPointX , GraphBase.XYAxisColor));
// 绘制Y轴
float height = _myRect.sizeDelta.y;
Vector2 downPoint = new Vector2(0 , -height / 2.0f);
Vector2 upPoint = new Vector2(0 , height / 2.0f);
vh.AddUIVertexQuad(GetQuad(downPoint , upPoint , GraphBase.XYAxisColor , GraphBase.XYAxisWidth));
// 绘制Y轴的箭头
Vector2 firstPointY = upPoint + new Vector2(arrowUnit , 0);
Vector2 secondPointY = upPoint;
Vector2 thirdPointY = upPoint + new Vector2(-arrowUnit , 0);
Vector2 fourPointY = upPoint + new Vector2(0 , Mathf.Sqrt(3) * arrowUnit);
vh.AddUIVertexQuad(GetQuad(firstPointY , secondPointY , thirdPointY , fourPointY , GraphBase.XYAxisColor));
if (GraphBase.ShowXYAxisUnit)
{
_xPoint = rightPoint;
_yPoint = upPoint;
}
#region 刻度的绘制
if ( GraphBase.ShowScale )
{
// X 轴的正方向
for ( int i = 1 ; i * GraphBase.ScaleValue < _myRect.sizeDelta.x / 2.0f ; i++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(GraphBase.ScaleValue * i , 0);
Vector2 secongPoint = firstPoint + new Vector2(0 , GraphBase.ScaleLenght);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.XYAxisColor));
}
// X 轴的负方向
for ( int i = 1 ; i * -GraphBase.ScaleValue > -_myRect.sizeDelta.x / 2.0f ; i++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-GraphBase.ScaleValue * i , 0);
Vector2 secongPoint = firstPoint + new Vector2(0 , GraphBase.ScaleLenght);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.XYAxisColor));
}
// Y 轴正方向
for ( int y = 1 ; y * GraphBase.ScaleValue < _myRect.sizeDelta.y / 2.0f ; y++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(0 , y * GraphBase.ScaleValue);
Vector2 secongPoint = firstPoint + new Vector2(GraphBase.ScaleLenght , 0);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.XYAxisColor));
}
// Y 轴负方向
for ( int y = 1 ; y * -GraphBase.ScaleValue > -_myRect.sizeDelta.y / 2.0f ; y++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(0 , y * -GraphBase.ScaleValue);
Vector2 secongPoint = firstPoint + new Vector2(GraphBase.ScaleLenght , 0);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.XYAxisColor));
}
}
#endregion
#region 根据网格类型绘制网格
switch (GraphBase.MeshType)
{
case FunctionalGraphBase.E_MeshType.None:
break;
case FunctionalGraphBase.E_MeshType.FullLine:
// X 轴的正方向
for ( int i = 1 ; i * GraphBase.ScaleValue < _myRect.sizeDelta.x / 2.0f ; i++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(GraphBase.ScaleValue * i , -_myRect.sizeDelta.y / 2.0f);
Vector2 secongPoint = firstPoint + new Vector2(0 ,_myRect.sizeDelta.y);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.MeshColor,GraphBase.MeshLineWidth));
}
// X 轴的负方向
for ( int i = 1 ; i * -GraphBase.ScaleValue > -_myRect.sizeDelta.x / 2.0f ; i++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-GraphBase.ScaleValue * i , -_myRect.sizeDelta.y / 2.0f);
Vector2 secongPoint = firstPoint + new Vector2(0 , _myRect.sizeDelta.y);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.MeshColor , GraphBase.MeshLineWidth));
}
// Y 轴正方向
for ( int y = 1 ; y * GraphBase.ScaleValue < _myRect.sizeDelta.y / 2.0f ; y++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-_myRect.sizeDelta.x / 2.0f , y * GraphBase.ScaleValue);
Vector2 secongPoint = firstPoint + new Vector2(_myRect.sizeDelta.x , 0);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.MeshColor , GraphBase.MeshLineWidth));
}
// Y 轴负方向
for ( int y = 1 ; y * -GraphBase.ScaleValue > -_myRect.sizeDelta.y / 2.0f ; y++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-_myRect.sizeDelta.x / 2.0f , -y * GraphBase.ScaleValue);
Vector2 secongPoint = firstPoint + new Vector2(_myRect.sizeDelta.x , 0);
vh.AddUIVertexQuad(GetQuad(firstPoint , secongPoint , GraphBase.MeshColor , GraphBase.MeshLineWidth));
}
break;
case FunctionalGraphBase.E_MeshType.ImaglinaryLine:
// X 轴的正方向
for ( int i = 1 ; i * GraphBase.ScaleValue < _myRect.sizeDelta.x / 2.0f ; i++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(GraphBase.ScaleValue * i , -_myRect.sizeDelta.y / 2.0f);
Vector2 secondPoint = firstPoint + new Vector2(0 , _myRect.sizeDelta.y);
GetImaglinaryLine(ref vh,firstPoint , secondPoint,GraphBase.MeshColor,GraphBase.ImaglinaryLineWidth,GraphBase.SpaceingWidth);
}
// X 轴的负方向
for ( int i = 1 ; i * -GraphBase.ScaleValue > -_myRect.sizeDelta.x / 2.0f ; i++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-GraphBase.ScaleValue * i , -_myRect.sizeDelta.y / 2.0f);
Vector2 secondPoint = firstPoint + new Vector2(0 , _myRect.sizeDelta.y);
GetImaglinaryLine(ref vh , firstPoint , secondPoint , GraphBase.MeshColor , GraphBase.ImaglinaryLineWidth , GraphBase.SpaceingWidth);
}
// Y 轴正方向
for ( int y = 1 ; y * GraphBase.ScaleValue < _myRect.sizeDelta.y / 2.0f ; y++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-_myRect.sizeDelta.x / 2.0f , y * GraphBase.ScaleValue);
Vector2 secondPoint = firstPoint + new Vector2(_myRect.sizeDelta.x , 0);
GetImaglinaryLine(ref vh , firstPoint , secondPoint , GraphBase.MeshColor , GraphBase.ImaglinaryLineWidth , GraphBase.SpaceingWidth);
}
// Y 轴负方向
for ( int y = 1 ; y * -GraphBase.ScaleValue > -_myRect.sizeDelta.y / 2.0f ; y++ )
{
Vector2 firstPoint = Vector2.zero + new Vector2(-_myRect.sizeDelta.x / 2.0f , -y * GraphBase.ScaleValue);
Vector2 secondPoint = firstPoint + new Vector2(_myRect.sizeDelta.x , 0);
GetImaglinaryLine(ref vh , firstPoint , secondPoint , GraphBase.MeshColor , GraphBase.ImaglinaryLineWidth , GraphBase.SpaceingWidth);
}
break;
}
#endregion
#endregion
}
//通过两个端点绘制矩形
private UIVertex[] GetQuad( Vector2 startPos , Vector2 endPos , Color color0 , float lineWidth = 2.0f )
{
float dis = Vector2.Distance(startPos , endPos);
float y = lineWidth * 0.5f * ( endPos.x - startPos.x ) / dis;
float x = lineWidth * 0.5f * ( endPos.y - startPos.y ) / dis;
if ( y <= 0 ) y = -y;
else x = -x;
UIVertex[] vertex = new UIVertex[4];
vertex[0].position = new Vector3(startPos.x + x , startPos.y + y);
vertex[1].position = new Vector3(endPos.x + x , endPos.y + y);
vertex[2].position = new Vector3(endPos.x - x , endPos.y - y);
vertex[3].position = new Vector3(startPos.x - x , startPos.y - y);
for ( int i = 0 ; i < vertex.Length ; i++ ) vertex[i].color = color0;
return vertex;
}
//通过四个顶点绘制矩形
private UIVertex[] GetQuad( Vector2 first , Vector2 second , Vector2 third , Vector2 four , Color color0 )
{
UIVertex[] vertexs = new UIVertex[4];
vertexs[0] = GetUIVertex(first , color0);
vertexs[1] = GetUIVertex(second , color0);
vertexs[2] = GetUIVertex(third , color0);
vertexs[3] = GetUIVertex(four , color0);
return vertexs;
}
//构造UIVertex
private UIVertex GetUIVertex( Vector2 point , Color color0 )
{
UIVertex vertex = new UIVertex
{
position = point ,
color = color0 ,
uv0 = new Vector2(0 , 0)
};
return vertex;
}
//绘制虚线
private void GetImaglinaryLine(ref VertexHelper vh, Vector2 first , Vector2 second , Color color0 ,float imaginaryLenght, float spaceingWidth , float lineWidth = 2.0f )
{
if ( first.y.Equals(second.y) ) // X轴
{
Vector2 indexSecond = first + new Vector2(imaginaryLenght , 0);
while (indexSecond.x < second.x)
{
vh.AddUIVertexQuad(GetQuad(first , indexSecond , color0));
first = indexSecond + new Vector2(spaceingWidth , 0);
indexSecond = first + new Vector2(imaginaryLenght , 0);
if ( indexSecond.x > second.x )
{
indexSecond = new Vector2(second.x , indexSecond.y);
vh.AddUIVertexQuad(GetQuad(first , indexSecond , color0));
}
}
}
if ( first.x.Equals(second.x) ) // Y轴
{
Vector2 indexSecond = first + new Vector2(0 , imaginaryLenght);
while (indexSecond.y < second.y)
{
vh.AddUIVertexQuad(GetQuad(first , indexSecond , color0));
first = indexSecond + new Vector2(0 , spaceingWidth);
indexSecond = first + new Vector2(0 , imaginaryLenght);
if ( indexSecond.y > second.y )
{
indexSecond = new Vector2(indexSecond.x , second.y);
vh.AddUIVertexQuad(GetQuad(first , indexSecond , color0));
}
}
}
}
//本地坐标转化屏幕坐标绘制GUI文字
private Vector2 local2Screen( Vector2 parentPos , Vector2 localPosition )
{
Vector2 pos = localPosition + parentPos;
float xValue, yValue = 0;
if ( pos.x > 0 )
xValue = pos.x + Screen.width / 2.0f;
else
xValue = Screen.width / 2.0f - Mathf.Abs(pos.x);
if ( pos.y > 0 )
yValue = Screen.height / 2.0f - pos.y;
else
yValue = Screen.height / 2.0f + Mathf.Abs(pos.y);
return new Vector2(xValue , yValue);
}
//递归计算位置
private Vector2 getScreenPosition( Transform trans , ref Vector3 result )
{
if ( null != trans.parent && null != trans.parent.parent )
{
result += trans.parent.localPosition;
getScreenPosition(trans.parent , ref result);
}
if ( null != trans.parent && null == trans.parent.parent )
return result;
return result;
}
}
绘制函数图:
函数图的基本类型:
///
/// 函数公式
///
[Serializable]
public class FunctionFormula
{
///
/// 函数表达式
///
public Func Formula;
///
/// 函数图对应线条颜色
///
public Color FormulaColor;
public float FormulaWidth;
public FunctionFormula( ) { }
public FunctionFormula( Func formula , Color formulaColor , float width )
{
Formula = formula;
FormulaColor = formulaColor;
FormulaWidth = width;
}
public Vector2 GetResult( float xValue ,float scaleValue )
{
return new Vector2(xValue , Formula(xValue / scaleValue) * scaleValue);
}
}
绘制代码是在OnPopulateMesh中加一段代码:其中 x+= unitPixel就是每隔unitPixel隔像素就绘制一个矩形,来拼合成曲线,曲线的圆滑程度取决于这个值,这个代码中unitPixel与一个单位占多少个像素有关,就是以下第一句代码,这样的做法是为了在保证效果的前提下优化计算量。
float unitPixel = 100 / GraphBase.ScaleValue;
foreach ( var functionFormula in Formulas )
{
Vector2 startPos = functionFormula.GetResult(-_myRect.sizeDelta.x / 2.0f, GraphBase.ScaleValue);
for ( float x = -_myRect.sizeDelta.x / 2.0f + 1 ; x < _myRect.sizeDelta.x / 2.0f ; x += unitPixel )
{
Vector2 endPos = functionFormula.GetResult(x , GraphBase.ScaleValue);
vh.AddUIVertexQuad(GetQuad(startPos , endPos , functionFormula.FormulaColor , functionFormula.FormulaWidth));
startPos = endPos;
}
}
CSDN下载链接:http://download.csdn.net/detail/qq_29579137/9827966