By rewriting the OnPopulateMesh method, you can customize the vertex information and realize curve drawing. The specific implementation idea is shown in the figure below:
Draw a line segment, a is the starting point, b is the end point, the direction is as shown by the blue arrow, from a to b, the yellow rectangular box represents the width of the final drawn line segment, the line segment is ultimately determined by four vertices, here are recorded as a left , a right, b left, b right.
If you want to calculate the vertex coordinates of the line segment, you need to use the normal direction to make the vector ab and vector3.forward form a plane, then the vector (a left, a right) is perpendicular to this plane, and the length of the vector (a left, a right) is the width of the curve, let the width be W,
Then there are:
Vector3 normal = Vector3.Cross( (b-a).normalized,Vector3.forward).normalized;
a左 = a + normal*w/2;
a右 = a - normal*w/2;
b left = b + normal*w/2;
b右 = b - normal*w/2;
1. Implement basic drawing logic and use the mouse to draw full screen
1. Create a new image and set the size to full screen fill.
2. Create a new script DrawCurveByMouse. The content of the script is as follows
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DrawCurveByMouse : MaskableGraphic
{
/// <summary>
/// 存储整条线的所有顶点信息
/// </summary>
private List<UIVertex[]> vertexQuadList = new List<UIVertex[]>();
protected override void OnPopulateMesh(VertexHelper vh)
{
base.OnPopulateMesh(vh);
vh.Clear();
for (int i = 0; i < vertexQuadList.Count; i++)
{
vh.AddUIVertexQuad(vertexQuadList[i]);
}
}
/// <summary>
/// 记录上一个点的坐标信息
/// </summary>
private Vector3 lastPoint;
private Vector3 lastLeft;
private Vector3 lastRight;
/// <summary>
/// 宽度
/// </summary>
public float halfWidth;
public float smoothStep = 10;
/// <summary>
/// 是否为新曲线
/// </summary>
private bool isNewLine = false;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
isNewLine = true;
lastPoint = ScreenPointToLocalPointInRectangle(Input.mousePosition);
}
if (Input.GetMouseButton(0))
{
DrawLine(ScreenPointToLocalPointInRectangle(Input.mousePosition));
}
}
/// <summary>
/// 屏幕左边转换为矩形框中的本地坐标
/// </summary>
private Vector3 ScreenPointToLocalPointInRectangle(Vector2 screenPoint)
{
Vector2 localPoint = Vector2.zero;
switch (canvas.renderMode)
{
case RenderMode.WorldSpace:
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, canvas.worldCamera,
out localPoint);
break;
case RenderMode.ScreenSpaceCamera:
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, canvas.worldCamera,
out localPoint);
break;
case RenderMode.ScreenSpaceOverlay:
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, null,
out localPoint);
break;
}
return localPoint;
}
private void DrawLine(Vector3 curr)
{
Vector3 dir = curr - lastPoint;
if (dir.magnitude < smoothStep) return;
Vector3 normal = Vector3.Cross(dir.normalized, Vector3.forward).normalized;
if (isNewLine)
{
lastLeft = lastPoint + normal * halfWidth;
lastRight = lastPoint - normal * halfWidth;
isNewLine = false;
}
Vector3 currLeft = curr + normal * halfWidth;
Vector3 currRight = curr - normal * halfWidth;
// Vector3 currLeft = curr + dir.normalized* width;
// Vector3 currRight = curr - dir.normalized* width;
UIVertex[] quad = new UIVertex[4];
quad[0] = new UIVertex();
quad[0].position = lastLeft;
quad[0].color = color;
quad[1] = new UIVertex();
quad[1].position = lastRight;
quad[1].color = color;
quad[2] = new UIVertex();
quad[2].position = currRight;
quad[2].color = color;
quad[3] = new UIVertex();
quad[3].position = currLeft;
quad[3].color = color;
vertexQuadList.Add(quad);
lastLeft = currLeft;
lastRight = currRight;
SetAllDirty();
}
}
2. Limit the drawing range to a rectangle and still use the mouse to draw
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 在矩形框中绘制曲线
/// </summary>
public class DrawCurveInRect : MaskableGraphic, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler,
IPointerExitHandler
{
/// <summary>
/// 存储整条线的所有顶点信息
/// </summary>
private List<UIVertex[]> vertexQuadList = new List<UIVertex[]>();
protected override void OnPopulateMesh(VertexHelper vh)
{
base.OnPopulateMesh(vh);
vh.Clear();
for (int i = 0; i < vertexQuadList.Count; i++)
{
vh.AddUIVertexQuad(vertexQuadList[i]);
}
}
/// <summary>
/// 记录上一个点的坐标信息
/// </summary>
private Vector3 lastPoint;
private Vector3 lastLeft;
private Vector3 lastRight;
/// <summary>
/// 宽度
/// </summary>
public float halfWidth = 2;
public float smoothStep = 10;
/// <summary>
/// 是否为新曲线
/// </summary>
private bool isNewLine = false;
private bool isEnter = false;
private bool isDrawing = false;
private void Update()
{
if (isEnter && isDrawing)
{
DrawLine(ScreenPointToLocalPointInRectangle(Input.mousePosition));
}
}
/// <summary>
/// 屏幕左边转换为矩形框中的本地坐标
/// </summary>
private Vector3 ScreenPointToLocalPointInRectangle(Vector2 screenPoint)
{
Vector2 localPoint = Vector2.zero;
switch (canvas.renderMode)
{
case RenderMode.WorldSpace:
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, canvas.worldCamera,
out localPoint);
break;
case RenderMode.ScreenSpaceCamera:
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, canvas.worldCamera,
out localPoint);
break;
case RenderMode.ScreenSpaceOverlay:
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, null,
out localPoint);
break;
}
return localPoint;
}
private void DrawLine(Vector3 curr)
{
Vector3 dir = curr - lastPoint;
if (dir.magnitude < smoothStep) return;
Vector3 normal = Vector3.Cross(dir.normalized, Vector3.forward).normalized;
if (isNewLine)
{
lastLeft = lastPoint + normal * halfWidth;
lastRight = lastPoint - normal * halfWidth;
isNewLine = false;
}
Vector3 currLeft = curr + normal * halfWidth;
Vector3 currRight = curr - normal * halfWidth;
// Vector3 currLeft = curr + dir.normalized* width;
// Vector3 currRight = curr - dir.normalized* width;
UIVertex[] quad = new UIVertex[4];
quad[0] = new UIVertex();
quad[0].position = lastLeft;
quad[0].color = color;
quad[1] = new UIVertex();
quad[1].position = lastRight;
quad[1].color = color;
quad[2] = new UIVertex();
quad[2].position = currRight;
quad[2].color = color;
quad[3] = new UIVertex();
quad[3].position = currLeft;
quad[3].color = color;
vertexQuadList.Add(quad);
lastLeft = currLeft;
lastRight = currRight;
SetAllDirty();
}
public void OnPointerDown(PointerEventData eventData)
{
isNewLine = true;
isDrawing = true;
lastPoint = ScreenPointToLocalPointInRectangle(eventData.position);
}
public void OnPointerUp(PointerEventData eventData)
{
isDrawing = false;
}
public void OnPointerEnter(PointerEventData eventData)
{
isEnter = true;
}
public void OnPointerExit(PointerEventData eventData)
{
isEnter = false;
isDrawing = false;
}
}
3. By drawing function curves
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
/// <summary>
/// 通过函数绘制曲线
/// </summary>
public class FunctionCurve : MaskableGraphic
{
/// <summary>
/// 存储整条线的所有顶点信息
/// </summary>
private List<UIVertex[]> curveQuads = new List<UIVertex[]>();
[Header("X轴的取值范围,")] public Vector2 rangeX;
[Header("Y轴的取值范围,")] public Vector2 rangeY;
/// <summary>
/// 宽度
/// </summary>
public float halfWidth = 2;
[Header("控制曲线平滑度的步长")] public float smoothStep;
private List<UIVertex[]> pointQuads = new List<UIVertex[]>();
[Header("取样点的颜色")] public Color simpleColor;
[Header("取样点在轴上的值")] public float[] simples;
/// <summary>
/// 宽度
/// </summary>
public float simpleHalfWidth = 2;
protected override void OnPopulateMesh(VertexHelper vh)
{
base.OnPopulateMesh(vh);
vh.Clear();
for (int i = 0; i < curveQuads.Count; i++)
{
vh.AddUIVertexQuad(curveQuads[i]);
}
for (int i = 0; i < pointQuads.Count; i++)
{
vh.AddUIVertexQuad(pointQuads[i]);
}
}
public List<UIVertex[]> CreatCurve()
{
List<UIVertex[]> vertexQuadList = new List<UIVertex[]>();
//1、通过步长计算采样点数
//限制步长,步长应大于单个像素代表的数值;
float minStep = (rangeX.y - rangeX.x) / rectTransform.rect.width; //最小步长
if (smoothStep < minStep)
{
smoothStep = minStep;
}
int count = (int)((rangeX.y - rangeX.x) / smoothStep); //采样数
List<Vector2> points = new List<Vector2>();
float x = 0;
float y = 0;
float x_rate = rectTransform.rect.width / (rangeX.y - rangeX.x);
float y_rate = rectTransform.rect.height / (rangeY.y - rangeY.x);
for (int i = 0; i < count; i++)
{
//2、计算每个采样点的值
x = rangeX.x + smoothStep * i;
y = GetY(x);
//3、将采样点数值转换为坐标值
points.Add(new Vector2(x * x_rate, y * y_rate));
}
for (int i = 1; i < count; i++)
{
if (points[i].y > rectTransform.rect.height) break;
vertexQuadList.Add(GetQuad(points[i - 1], points[i]));
}
return vertexQuadList;
}
public List<UIVertex[]> CreatSimplePoints()
{
List<UIVertex[]> vertexQuadList = new List<UIVertex[]>();
float x_rate = rectTransform.rect.width / (rangeX.y - rangeX.x);
float y_rate = rectTransform.rect.height / (rangeY.y - rangeY.x);
for (int i = 0; i < simples.Length; i++)
{
if (simples[i] >= rangeX.x && simples[i] <= rangeX.y)
{
vertexQuadList.Add(GetSimplePointQuad(new Vector2(simples[i] * x_rate, GetY(simples[i]) * y_rate)));
}
}
return vertexQuadList;
}
private UIVertex[] GetQuad(Vector3 lastPoint, Vector3 curr)
{
Vector3 normal = Vector3.Cross((curr - lastPoint).normalized, Vector3.forward).normalized;
Vector3 lastLeft = lastPoint + normal * halfWidth;
Vector3 lastRight = lastPoint - normal * halfWidth;
Vector3 currLeft = curr + normal * halfWidth;
Vector3 currRight = curr - normal * halfWidth;
UIVertex[] quad = new UIVertex[4];
quad[0] = new UIVertex();
quad[0].position = lastLeft;
quad[0].color = color;
quad[1] = new UIVertex();
quad[1].position = lastRight;
quad[1].color = color;
quad[2] = new UIVertex();
quad[2].position = currRight;
quad[2].color = color;
quad[3] = new UIVertex();
quad[3].position = currLeft;
quad[3].color = color;
return quad;
}
private UIVertex[] GetSimplePointQuad(Vector3 point)
{
UIVertex[] quad = new UIVertex[4];
quad[0] = new UIVertex();
quad[0].position = point + new Vector3(-simpleHalfWidth, simpleHalfWidth);;
quad[0].color = simpleColor;
quad[1] = new UIVertex();
quad[1].position = point + new Vector3(simpleHalfWidth, simpleHalfWidth);;
quad[1].color = simpleColor;
quad[2] = new UIVertex();
quad[2].position = point + new Vector3(simpleHalfWidth, -simpleHalfWidth);;
quad[2].color = simpleColor;
quad[3] = new UIVertex();
quad[3].position = point + new Vector3(-simpleHalfWidth, -simpleHalfWidth);;
quad[3].color = simpleColor;
return quad;
}
public float GetY(float x)
{
return Mathf.Pow(x, 2);
return x;
}
[ContextMenu("Test")]
public void Test()
{
curveQuads = CreatCurve();
pointQuads = CreatSimplePoints();
SetAllDirty();
}
}