Unity基于LineRender划线解决方案
划线类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mode_1;
public class LinesDrawer : MonoBehaviour
{
public bool isDrawCmp { get { return _isDrawCmp; } }
public float linePointsMinDistance;
public float linePointsMinAngle;
public float lineWidth;
public Color lineColor;
public float lineMaxLength;
private Transform linePre;
[HideInInspector]
public Line currentLine;
private LayerMask cantDrawOverLayer;
private bool isLastHit;
private Transform hitDraw;
private bool _isDrawCmp;
private void Start()
{
cantDrawOverLayer = LayerMask.GetMask("CantDrawOver");
linePre = transform.Find("LinePre");
hitDraw = transform.Find("HitDraw");
}
private void Update()
{
if (_isDrawCmp) return;
#if UNITY_ANDROID && !UNITY_EDITOR
if (Input.touchCount == 1)
{
if (Input.GetTouch(0).phase == TouchPhase.Began)
BeginDraw();
if (Input.GetTouch(0).phase == TouchPhase.Moved)
Draw();
if (Input.GetTouch(0).phase == TouchPhase.Ended)
EndDraw();
}
#endif
#if UNITY_EDITOR
if (Input.GetMouseButtonDown(0))
BeginDraw();
if (Input.GetMouseButton(0))
Draw();
if (Input.GetMouseButtonUp(0))
EndDraw();
#endif
}
private void BeginDraw()
{
currentLine = Instantiate(linePre, transform).GetComponent<Line>();
currentLine.gameObject.name = "Line";
currentLine.InitData();
currentLine.UsePhysics(false);
currentLine.SetPointsMinDistance(linePointsMinDistance);
currentLine.SetPiontsMinAngle(linePointsMinAngle);
currentLine.SetLineWidth(lineWidth);
currentLine.SetColor(lineColor);
var pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
currentLine.AddPoint(pos);
SoundMgr.Ins.PlayEffect(E_EffectClip.Draw, true);
}
private void Draw()
{
if (currentLine == null) return;
if (currentLine.curLength >= lineMaxLength)
{
EndDraw();
return;
}
var pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 origin = currentLine.GetLastPoint();
Vector2 vec = (Vector2)pos - origin;
RaycastHit2D hit = Physics2D.CircleCast(origin, lineWidth, vec.normalized, vec.magnitude, cantDrawOverLayer);
if (hit)
{
isLastHit = true;
hitDraw.gameObject.SetActive(true);
HitDraw(origin, pos);
}
else
{
if (isLastHit)
{
isLastHit = false;
hitDraw.gameObject.SetActive(false);
}
currentLine.AddPoint(pos);
EventMgr.Ins.ExecuteEvent(E_EventType.ON_LINE_CHANGE, currentLine.curLength / lineMaxLength);
}
}
private void EndDraw()
{
SoundMgr.Ins.StopEffect(E_EffectClip.Draw);
if (currentLine == null) return;
if (currentLine.pointCount < 2)
{
hitDraw.gameObject.SetActive(false);
Destroy(currentLine.gameObject);
return;
}
_isDrawCmp = true;
hitDraw.gameObject.SetActive(false);
if (currentLine)
{
currentLine.UsePhysics(true);
currentLine.AddColliders();
}
OnDrawEndEvent();
}
private void HitDraw(Vector2 origin, Vector2 pos)
{
Vector2 vecTarget = pos - origin;
hitDraw.transform.position = vecTarget.normalized * vecTarget.magnitude / 2 + origin;
if (vecTarget.y >= 0)
hitDraw.transform.eulerAngles = new Vector3(0, 0, Vector2.Angle(Vector2.right, vecTarget));
else
hitDraw.transform.eulerAngles = new Vector3(0, 0, 360 - Vector2.Angle(Vector2.right, vecTarget));
hitDraw.GetComponent<SpriteRenderer>().size = new Vector2(vecTarget.magnitude, lineWidth);
}
private void OnDrawEndEvent()
{
LvBase baseCtr = GetComponentInParent<LvBase>();
if (baseCtr)
{
baseCtr.OnDrawEndEvent();
}
}
}
line类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Line : MonoBehaviour
{
private LineRenderer lineRenderer;
private EdgeCollider2D edgeCollider;
private Rigidbody2D rigidBody;
[HideInInspector] public List<Vector2> points = new List<Vector2>();
[HideInInspector] public int pointCount = 0;
[HideInInspector] public float curLength;
float pointsMinAngle = 90f;
float pointsMinDistance = 0.1f;
float circleColliderRadius;
bool isUsePhysics = false;
private void Update()
{
if (isUsePhysics)
{
for (int i = 0; i < lineRenderer.positionCount; i++)
{
Vector3 pos = transform.TransformPoint(points[i]);
lineRenderer.SetPosition(i, pos);
}
}
}
public void AddPoint(Vector2 newPoint)
{
if (pointCount == 0)
{
points.Add(newPoint);
++pointCount;
lineRenderer.positionCount = pointCount;
lineRenderer.SetPosition(pointCount - 1, newPoint);
}
else
{
if (Vector2.Distance(newPoint, GetLastPoint()) < pointsMinDistance)
{
return;
}
if (pointCount >= 2)
{
if (Vector2.Angle(newPoint, GetLastPoint()) <= pointsMinAngle)
{
Vector2 lastNormal = (lineRenderer.GetPosition(pointCount - 1) - lineRenderer.GetPosition(pointCount - 2)).normalized;
Vector2 curNormal = ((Vector3)newPoint - lineRenderer.GetPosition(pointCount - 1)).normalized;
Vector2 p0 = lineRenderer.GetPosition(pointCount - 1);
Vector2 p1 = (Vector2)lineRenderer.GetPosition(pointCount - 1) + lastNormal * 0.01f;
Vector2 p2 = (Vector2)lineRenderer.GetPosition(pointCount - 1) + curNormal * 0.01f;
List<Vector3> vecs = new List<Vector3>();
vecs.Add(p0);
vecs.Add(p1);
vecs.Add(p2);
vecs = InitByBezier(vecs, 10);
for (int i = 1; i < vecs.Count; i++)
{
++pointCount;
lineRenderer.positionCount = pointCount;
lineRenderer.SetPosition(pointCount - 1, vecs[i]);
points.Add(vecs[i]);
}
}
}
curLength += Vector2.Distance(newPoint, GetLastPoint());
points.Add(newPoint);
++pointCount;
lineRenderer.positionCount = pointCount;
lineRenderer.SetPosition(pointCount - 1, newPoint);
}
}
public void AddColliders()
{
if (pointCount > 1)
{
edgeCollider.points = points.ToArray();
}
var circleCollider = gameObject.AddComponent<CircleCollider2D>();
circleCollider.offset = points[0];
circleCollider.radius = circleColliderRadius;
var circleCollider2 = gameObject.AddComponent<CircleCollider2D>();
circleCollider2.offset = points[points.Count - 1];
circleCollider2.radius = circleColliderRadius;
}
public Vector2 GetLastPoint()
{
return lineRenderer.GetPosition(pointCount - 1);
}
public void InitData()
{
rigidBody = GetComponent<Rigidbody2D>();
lineRenderer = GetComponent<LineRenderer>();
edgeCollider = GetComponent<EdgeCollider2D>();
lineRenderer.sortingOrder = 10;
gameObject.SetActive(true);
}
public void UsePhysics(bool usePhysics)
{
isUsePhysics = usePhysics;
rigidBody.isKinematic = !usePhysics;
}
public void SetPointsMinDistance(float distance)
{
pointsMinDistance = distance;
}
public void SetPiontsMinAngle(float angle)
{
pointsMinAngle = angle;
}
public void SetLineWidth(float width)
{
lineRenderer.startWidth = width;
lineRenderer.endWidth = width;
circleColliderRadius = width / 2f;
edgeCollider.edgeRadius = circleColliderRadius / 2;
}
public void SetColor(Color color)
{
lineRenderer.startColor = color;
lineRenderer.endColor = color;
}
public void SetCollisionDetection(bool isContinuous)
{
GetComponent<Rigidbody2D>().collisionDetectionMode = isContinuous ? CollisionDetectionMode2D.Continuous : CollisionDetectionMode2D.Discrete;
}
private List<Vector3> InitByBezier(List<Vector3> wayPoint, int pointCount)
{
List<Vector3> tempList = new List<Vector3>();
Vector3 vecPoint = Vector3.zero;
for (int i = 0; i <= pointCount; i++)
{
vecPoint = Bezier(i / (float)pointCount, wayPoint);
tempList.Add(vecPoint);
}
return tempList;
}
private Vector3 Bezier(float t, List<Vector3> points)
{
if (points.Count < 2)
return points[0];
List<Vector3> list = new List<Vector3>();
Vector3 p0p1 = Vector3.zero;
for (int i = 0; i < points.Count - 1; i++)
{
p0p1 = (1 - t) * points[i] + t * points[i + 1];
list.Add(p0p1);
}
return Bezier(t, list);
}
}
主要实现
定义层级CantDrawOver为不可画的区域 实现思路是使用Physics2D.CircleCast 从上一个点到当前划线点的圆形路径射线检测,如果被检测到有不可划线层级 以虚线形式提示。
linerender实际使用的时候,发现如果两帧之间的点夹角小于90度的时候 如果此时转角处的点数量过于少,并且两帧之间的点间距过大。线的宽度会出现问题。所以在上一个点 以上上个点到上一个点的方向 和 当前上个点到当前点的方向 做了0.01单位的延伸 确定了两个点 然后做贝塞尔曲线补点。
关于划线碰撞器的问题 发现网上有很多解决方案 大部分是用的多边形 个人感觉多边形画出来的碰撞器还是有点棱角 最后选择了用Edge碰撞器并在首尾两个点加入两个同样宽度的圆形碰撞器(如果不加好像首尾会穿透刚体)
下面会贴Unity界面截图 不习惯打注释 代码有部分参考网上别的大佬写的 能用的就直接拿来用了。欢迎提问交流。
|