近日,项目需求:前提:三维空间
1.动态添加(删除)路径点,通过两个以上的路径点来绘制曲线,删除点之后不影响其他点绘制曲线;
2.每个路径点都可以被拖拽发生位移,可以通过锁定某个轴,使该轴不发生位移;
3.每个路径点处有两个可调节点(首尾只有一个调节点),可通过调节点来调节曲线切线(速度方向线)斜率,以达到平滑曲线;
4.使该曲线形成路径,隐藏曲线,Player沿曲线完成自动寻路。
先看效果,再贴代码。(使用贝塞尔曲线公式与LineRenderer绘制3D可调节曲线)
代码如下:两个脚本。
1. DMDrawCurve.cs 挂载到任意对象即可
该脚本实现绘制曲线,动态添加(删除)点,发生位移时更新绘制曲线,以及隐藏该曲线。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DM.Editor.View
{
[RequireComponent(typeof(LineRenderer))]
public class DMDrawCurve : MonoBehaviour
{
public List m_allPoints;
private GameObject m_anchorPoint;
private GameObject m_controlPoint;
private GameObject m_pointParent;
private LineRenderer m_lineRenderer;
private int m_curveCount = 0;
private int SEGMENT_COUNT = 60;//曲线取点个数(取点越多这个长度越趋向于精确)
private static DMDrawCurve m_instance;
public static DMDrawCurve Instance
{
get {
if (null == m_instance)
m_instance = new DMDrawCurve();
return m_instance;
}
}
void Awake()
{
if (null == m_instance)
m_instance = this;
SetLine();
if (null == m_anchorPoint)
m_anchorPoint = Resources.Load("Prefabs/AnchorPoint") as GameObject;
if (null == m_controlPoint)
m_controlPoint = Resources.Load("Prefabs/ControlPoint") as GameObject;
}
void SetLine()
{
if (null == m_lineRenderer)
m_lineRenderer = GetComponent();
m_lineRenderer.material = Resources.Load("Materials/Line") as Material;
m_lineRenderer.startColor = Color.red;
m_lineRenderer.endColor = Color.green;
m_lineRenderer.widthMultiplier = 0.2f;
}
public void Init(GameObject player)
{//初始化一个基准点(Player)
if (player == null) return;
GameObject anchorPoint = LoadPoint(m_anchorPoint, player.transform.position);
m_allPoints.Add(anchorPoint.transform);
}
public void AddPoint(Vector3 anchorPointPos)
{
//初始化时m_allPoints添加了一个player
if (m_allPoints.Count == 0) return;
Transform lastPoint = m_allPoints[m_allPoints.Count - 1];
GameObject controlPoint2 = LoadPoint(m_controlPoint, lastPoint.position+new Vector3(0,0,-1));
GameObject controlPoint = LoadPoint(m_controlPoint, anchorPointPos + new Vector3(0, 0, 1));
GameObject anchorPoint = LoadPoint(m_anchorPoint, anchorPointPos);
anchorPoint.GetComponent().m_controlObject = controlPoint;
lastPoint.GetComponent().m_controlObject2 = controlPoint2;
m_allPoints.Add(controlPoint2.transform);
m_allPoints.Add(controlPoint.transform);
m_allPoints.Add(anchorPoint.transform);
DrawCurve();
}
public void DeletePoint(GameObject anchorPoint)
{
if (anchorPoint == null) return;
CurvePointControl curvePoint = anchorPoint.GetComponent();
if (curvePoint && anchorPoint.tag.Equals("AnchorPoint"))
{
if (curvePoint.m_controlObject)
{
m_allPoints.Remove(curvePoint.m_controlObject.transform);
Destroy(curvePoint.m_controlObject);
}
if (curvePoint.m_controlObject2)
{
m_allPoints.Remove(curvePoint.m_controlObject2.transform);
Destroy(curvePoint.m_controlObject2);
}
if (m_allPoints.IndexOf(curvePoint.transform) == (m_allPoints.Count - 1))
{//先判断删除的是最后一个元素再移除
m_allPoints.Remove(curvePoint.transform);
Transform lastPoint = m_allPoints[m_allPoints.Count - 2];
GameObject lastPointCtrObject = lastPoint.GetComponent().m_controlObject2;
if (lastPointCtrObject)
{
m_allPoints.Remove(lastPointCtrObject.transform);
Destroy(lastPointCtrObject);
lastPoint.GetComponent().m_controlObject2 = null;
}
}
else
{
m_allPoints.Remove(curvePoint.transform);
}
Destroy(anchorPoint);
if(m_allPoints.Count == 1)
{
m_lineRenderer.positionCount = 0;
}
}
DrawCurve();
}
public void UpdateLine(GameObject anchorPoint, Vector3 offsetPos1, Vector3 offsetPos2)
{
if (anchorPoint == null) return;
if (anchorPoint.tag.Equals("AnchorPoint"))
{
CurvePointControl curvePoint = anchorPoint.GetComponent();
if (curvePoint)
{
if (curvePoint.m_controlObject)
curvePoint.m_controlObject.transform.position = anchorPoint.transform.position + offsetPos1;
if (curvePoint.m_controlObject2)
curvePoint.m_controlObject2.transform.position = anchorPoint.transform.position + offsetPos2;
}
}
DrawCurve();
}
public List HiddenLine(bool isHidden=false)
{
m_pointParent.SetActive(isHidden);
m_lineRenderer.enabled = isHidden;
List pathPoints = new List();
if(!isHidden)
{
for(int i = 0; i < m_lineRenderer.positionCount; i++)
{
pathPoints.Add(m_lineRenderer.GetPosition(i));
}
}
return pathPoints;
}
private void DrawCurve()//画曲线
{
if (m_allPoints.Count < 4) return;
m_curveCount = (int)m_allPoints.Count / 3;
for (int j = 0; j < m_curveCount; j++)
{
for (int i = 1; i <= SEGMENT_COUNT; i++)
{
float t = (float)i / (float)SEGMENT_COUNT;
int nodeIndex = j * 3;
Vector3 pixel = CalculateCubicBezierPoint(t, m_allPoints[nodeIndex].position, m_allPoints[nodeIndex + 1].position, m_allPoints[nodeIndex + 2].position, m_allPoints[nodeIndex + 3].position);
m_lineRenderer.positionCount = j * SEGMENT_COUNT + i;
m_lineRenderer.SetPosition((j * SEGMENT_COUNT) + (i - 1), pixel);
}
}
}
private GameObject LoadPoint(GameObject pointPrefab,Vector3 pos)
{
if (pointPrefab == null)
{
Debug.LogError("The Prefab is Null!");
return null;
}
if (null == m_pointParent)
m_pointParent = new GameObject("AllPoints");
GameObject pointClone = Instantiate(pointPrefab);
pointClone.name = pointClone.name.Replace("(Clone)", "");
pointClone.transform.SetParent(m_pointParent.transform);
pointClone.transform.position = pos;
return pointClone;
}
//贝塞尔曲线公式:B(t)=P0*(1-t)^3 + 3*P1*t(1-t)^2 + 3*P2*t^2*(1-t) + P3*t^3 ,t属于[0,1].
Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 p = uuu * p0;
p += 3 * uu * t * p1;
p += 3 * u * tt * p2;
p += ttt * p3;
return p;
}
}
}
2. CurvePointControl.cs (挂载到路径点与调节点上(两个预制体))
该脚本实现每个路径点对应的两个调节点,以及绘制调节点与路径点之间的切线。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DM.Editor.View
{
public class CurvePointControl : MonoBehaviour
{
[Header("锁定X轴")]
public bool m_isLockX = false;
[Header("锁定Y轴")]
public bool m_isLockY = true;
[Header("锁定Z轴")]
public bool m_isLockZ = false;
[HideInInspector]
public GameObject m_controlObject;
[HideInInspector]
public GameObject m_controlObject2;
private Vector3 offsetPos1 = Vector3.zero;
private Vector3 offsetPos2 = Vector3.zero;
private LineRenderer lineRenderer;
void Start()
{
if (gameObject.tag.Equals("AnchorPoint") && !lineRenderer)
lineRenderer = gameObject.AddComponent();
if (lineRenderer)
{
lineRenderer.sortingOrder = 1;
lineRenderer.material = new Material(Shader.Find("Particles/Alpha Blended"));
lineRenderer.startColor = lineRenderer.endColor = Color.yellow;
lineRenderer.widthMultiplier = 0.03f;
lineRenderer.positionCount = 0;
}
}
void OnMouseDown()
{
if (!gameObject.tag.Equals("AnchorPoint")) return;
OffsetPos();
}
public List OffsetPos()
{
List offsetPosList = new List();
if (m_controlObject)
offsetPos1 = m_controlObject.transform.position - transform.position;
if (m_controlObject2)
offsetPos2 = m_controlObject2.transform.position - transform.position;
offsetPosList.Add(offsetPos1);
offsetPosList.Add(offsetPos2);
return offsetPosList;
}
void OnMouseDrag()
{
//if (gameObject.tag.Equals("AnchorPoint")) return;
Vector3 pos0 = Camera.main.WorldToScreenPoint(transform.position);
Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, pos0.z);
Vector3 mousePosInWorld= Camera.main.ScreenToWorldPoint(mousePos);
Vector3 thisPos = mousePosInWorld;
if (m_isLockX)
thisPos.x = transform.position.x;
if (m_isLockY)
thisPos.y = transform.position.y;
if (m_isLockZ)
thisPos.z = transform.position.z;
transform.position = thisPos;
DMDrawCurve.Instance.UpdateLine(gameObject, offsetPos1, offsetPos2);
}
private void DrawControlLine()
{
if (!gameObject.tag.Equals("AnchorPoint") || (!m_controlObject && !m_controlObject2)) return;
if (lineRenderer)
{
lineRenderer.positionCount = (m_controlObject && m_controlObject2) ? 3 : 2;
if (m_controlObject && !m_controlObject2)
{
lineRenderer.SetPosition(0, m_controlObject.transform.position);
lineRenderer.SetPosition(1, transform.position);
}
if (m_controlObject2 && !m_controlObject)
{
lineRenderer.SetPosition(0, transform.position);
lineRenderer.SetPosition(1, m_controlObject2.transform.position);
}
if (m_controlObject && m_controlObject2)
{
lineRenderer.SetPosition(0, m_controlObject.transform.position);
lineRenderer.SetPosition(1, transform.position);
lineRenderer.SetPosition(2, m_controlObject2.transform.position);
}
}
}
void Update()
{
DrawControlLine();
}
}
}
对应Inspector,如图
3. Test.cs (任意挂载)
该脚本实现Player自动寻路。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DM.Editor.View
{
public class Test : MonoBehaviour
{
public GameObject m_player;
public List m_pathPoints;
void Start()
{
DMDrawCurve.Instance.Init(m_player);
}
IEnumerator Move()
{
if (m_pathPoints.Count == 0) yield break;
int item = 1;
while (true)
{
m_player.transform.LookAt(m_pathPoints[item]);
m_player.transform.position = Vector3.Lerp(m_pathPoints[item - 1], m_pathPoints[item], 1f);
item++;
if (item >= m_pathPoints.Count)
{
item = 1;
yield break;
}
yield return new WaitForEndOfFrame();
}
}
void Update()
{
if (Input.GetKey(KeyCode.LeftControl) && (Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(1)))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (Input.GetMouseButtonUp(0) && hit.collider.tag.Equals("Terrain"))
{
Vector3 pointPos = new Vector3(hit.point.x, m_player.transform.position.y, hit.point.z);
DMDrawCurve.Instance.AddPoint(pointPos);
}
else if (Input.GetMouseButtonUp(1) && hit.collider.tag.Equals("AnchorPoint"))
{
DMDrawCurve.Instance.DeletePoint(hit.collider.gameObject);
}
}
}
if (Input.GetKeyUp(KeyCode.A))
m_pathPoints = DMDrawCurve.Instance.HiddenLine(false);
else if (Input.GetKeyUp(KeyCode.Escape))
{
DMDrawCurve.Instance.HiddenLine(true);
m_pathPoints.Clear();
}
if (Input.GetKeyUp(KeyCode.B))
{
StartCoroutine(Move());
}
}
}
}
注意:需要添加两个Tag值(Terrain,AnchorPoint),也可动态添加,一个给场景地面,一个给路径点(AnchorPoint),调节点不需要Tag。
开发测试Demo以及.unitypackage的网盘链接如下,需要的自行下载:
链接:https://pan.baidu.com/s/1dwIOxcMB-Lhq_Tlxenb4fQ 密码:7g8y
以上就是近日开发结果,如有不足,请批评指正。如有疑问,请留言,看到自然回复。
如转载,请注明出处:IT_yanghui的博客