还有一个类似功能的插件SWS
曲线编辑,可用于不规则路线,N个2阶bezier 替代高阶,达到曲线插值 的额性能和速度平衡
为了曲线公衡平滑可以用N个3阶的特性调节细节
BezierMgrEditor.cs
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
[CustomEditor(typeof(BezierMgr))]
public class BezierMgrEditor : Editor
{
private SerializedObject m_Object;
private SerializedProperty m_ShowWayPointInGame;
private SerializedProperty m_IsTest;
private SerializedProperty m_Id;
//called whenever this inspector window is loaded
public void OnEnable()
{
//we create a reference to our script object by passing in the target
m_Object = new SerializedObject(target);
m_ShowWayPointInGame = m_Object.FindProperty("showWayPoint");
m_IsTest = m_Object.FindProperty("isWayPointTest");
m_Id = m_Object.FindProperty("id");
}
//called whenever the inspector gui gets rendered
public override void OnInspectorGUI()
{
//this pulls the relative variables from unity runtime and stores them in the object
m_Object.Update();
this.RenameAll();
GUILayout.BeginHorizontal();
GUILayout.Label("--------------游戏设置--------------");
GUILayout.EndHorizontal();
this.m_Id.intValue = EditorGUILayout.IntField("该路点id:", m_Id.intValue, GUILayout.Width(300.0f));
this.m_ShowWayPointInGame.boolValue = EditorGUILayout.Toggle("游戏中显示路点", m_ShowWayPointInGame.boolValue, GUILayout.Width(300.0f));
this.m_IsTest.boolValue = EditorGUILayout.Toggle("开启路点测试", m_IsTest.boolValue, GUILayout.Width(300.0f));
//waypoint index header
GUILayout.Label("Bezier Points: ", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
if (GUILayout.Button("add"))
{
AddPoint();
}
if (GUILayout.Button("remove"))
{
RemovePoint();
}
if (GUILayout.Button("remove all"))
{
this.RemoveAllPoints();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("---------------------------------");
GUILayout.EndHorizontal();
if (points.Count % 3 != 0)
{
GUILayout.BeginHorizontal();
GUILayout.Label("Error:当前路点不为3的整数倍");
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal();
GUILayout.Label("----------信---息----------");
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("当前二阶Bezier曲线数量:" + points.Count / 3);
GUILayout.EndHorizontal();
for (int i = 0; i < 3; i++)
{
GUILayout.BeginHorizontal();
//indicate each array slot with index number in front of it
//create an object field for every waypoint
// EditorGUILayout.ObjectField(waypoints[i], typeof(Transform), true);
GUILayout.EndHorizontal();
}
//we push our modified variables back to our serialized object
m_Object.ApplyModifiedProperties();
}
ArrayList points = new ArrayList();
public void AddPoint()
{
this.RenameAll();
int count = 3 - points.Count % 3;
for (int j = 0; j < count; j++)
{
GameObject p = GameObject.Instantiate(GameObject.Find("__BezierTemplatePoint"));
if (points.Count > 0)
{
p.transform.position = (points[points.Count - 1] as GameObject).transform.position + Vector3.left * 10.0f;
}
points.Add(p);
p.name = "BezierPoint " + (points.Count);
p.transform.SetParent((m_Object.targetObject as BezierMgr).gameObject.transform);
// p.transform.SetSiblingIndex(1);
this.RenameAll();
}
}
void SyncAll()
{
this.RenameAll();
}
private void RenameAll()
{
//先删除不在记录中的点
var objj = new SerializedObject(target);
var parent = (objj.targetObject as BezierMgr).gameObject;
var pp = parent.GetComponentsInChildren();
ArrayList real = new ArrayList();
for (int i = 1; i < pp.Length; i++)
{
Transform tr = pp[i] as Transform;
real.Add(tr.gameObject);
}
// rename all
int idx = 0;
bool swap = (real.Count) != points.Count && points.Count != 0; //只有不足3的倍数时 才交换,因为可能会删除某个点来作为
points.Clear();
foreach (GameObject obj in real)
{
points.Add(obj);
string point_name_tag = "";
const string Name_Tag_Head = "_Head";
const string Name_Tag_Mid = "_Mid";
const string Name_Tag_End = "_End";
if (idx % 3 == 0)
{
point_name_tag = Name_Tag_Head;
}
else if (idx % 3 == 1)
{
point_name_tag = Name_Tag_Mid;
}
else
{
point_name_tag = Name_Tag_End;
}
string newname = "BezierPoint Rename " + idx;
Transform oldObj = parent.transform.Find(newname + Name_Tag_Head);
if (oldObj == null)
{
oldObj = parent.transform.Find(newname + Name_Tag_Mid);
}
if (oldObj == null)
{
oldObj = parent.transform.Find(newname + Name_Tag_End);
}
if (oldObj != null && swap)
{//replace new position to old position avoid miss info
obj.transform.position = oldObj.position;
}
obj.name = newname + point_name_tag;
++idx;
}
}
bool HasObject(GameObject obj)
{
foreach (GameObject ob in points)
{
if (ob == obj) return true;
}
return false;
}
public void RemovePoint()
{
this.RenameAll();
if (points.Count <= 0) return;
int offset = 1;
if (points.Count % 3 == 0) offset = 3;
for (int i = 0; i < offset; i++)
{
GameObject p = points[points.Count - 1] as GameObject;
GameObject.DestroyImmediate(p);
points.Remove(p);
}
this.RenameAll();
}
public void RemoveAllPoints()
{
var parent = (this.m_Object.targetObject as BezierMgr).gameObject;
var pp = parent.GetComponentsInChildren();
ArrayList real = new ArrayList();
for (int i = 1; i < pp.Length; i++)
{
Transform tr = pp[i] as Transform;
GameObject.DestroyImmediate(tr.gameObject);
}
points.Clear();
}
}
BezierMgr.cs
/*
* Author: caoshanshan
* Email: [email protected]
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 2次 贝塞尔 曲线管理器,用于拼接多个低阶 组合成平滑路径,
public class BezierMgr : MonoBehaviour
{
[Header("生成可显示路点否")]
[SerializeField]
public bool showWayPoint = true;
[Header("显示路点的精度")]
[SerializeField]
public float tolerance = 0.001f;
[Header("显示路点的缩放")]
[SerializeField]
public float scale = 0.1f;
[Header("开启路点测试后,汽车前进速度不会变化,只是测试路径平滑度使用")]
[SerializeField]
public bool isWayPointTest = true;
public int id = 0;//改路点id 一个赛道允许多个路点,用id表示
[HideInInspector]
public static BezierMgr ins;
public BezierMgr()
{
ins = this;
}
private int CURRENT_INDEX = 0;
public Bezier3 GetNextBezier()
{
if (bezier_queue.Count <= 0)
{
this.Awake();
}
if (CURRENT_INDEX >= bezier_queue.Count)
{
CURRENT_INDEX = 0; // for loop
}
int index = CURRENT_INDEX;
CURRENT_INDEX++;
var b = bezier_queue[index] as Bezier3;
return b;
}
// n阶段 bezier points
public static List GetBezierPoints(List pathToCurve, int interpolations)
{
List tempPoints;
List curvedPoints;
int pointsLength = 0;
int curvedLength = 0;
if (interpolations < 1)
interpolations = 1;
pointsLength = pathToCurve.Count;
curvedLength = (pointsLength * Mathf.RoundToInt(interpolations)) - 1;
curvedPoints = new List(curvedLength);
float t = 0.0f;
for (int pointInTimeOnCurve = 0; pointInTimeOnCurve < curvedLength + 1; pointInTimeOnCurve++)
{
t = Mathf.InverseLerp(0, curvedLength, pointInTimeOnCurve);
tempPoints = new List(pathToCurve);
for (int j = pointsLength - 1; j > 0; j--)
{
for (int i = 0; i < j; i++)
{
tempPoints[i] = (1 - t) * tempPoints[i] + t * tempPoints[i + 1];
}
}
curvedPoints.Add(tempPoints[0]);
}
return curvedPoints;
}
public void Init()
{
bezier_queue.Clear();
foreach (GameObject obj in show_queue)
{
GameObject.DestroyImmediate(obj);
}
show_queue.Clear();
CURRENT_INDEX = 0;
}
ArrayList show_queue = new ArrayList();
ArrayList bezier_queue = new ArrayList();
void Awake()
{
this.Init();
this.bezier_queue = this.GetBezierQueue();
}
bool hasShow = false;
ArrayList GetBezierQueue(bool editorMode = false)
{
var points = this.GetComponentsInChildren();
ArrayList ret = new ArrayList();
ArrayList bezier_points = new ArrayList();
int i = 0;
foreach (Transform point in points)
{
if (i != 0 && editorMode == false)
{
// point.gameObject.SetActive(false);
}
bezier_points.Add(point.position);
i++;
}
i--;
bezier_points.RemoveAt(0);
if (i % 3 != 0 && editorMode == false)
{
Debug.LogError("error of number of way point " + bezier_points.Count);
}
for (int ii = 0; ii < bezier_points.Count; ii += 3)
{
//根据路点 生成 2阶bezier 集合
Vector3 point = (Vector3)bezier_points[ii];
var bezier = new Bezier3();
bezier.id = ii / 3;
if (editorMode)
{
try
{
bezier.p0 = (Vector3)bezier_points[ii];
bezier.p1 = (Vector3)bezier_points[ii + 1];
bezier.p2 = (Vector3)bezier_points[ii + 2];
ret.Add(bezier);
}
catch (System.Exception e)
{
}
}
else
{
bezier.p0 = (Vector3)bezier_points[ii];
bezier.p1 = (Vector3)bezier_points[ii + 1];
bezier.p2 = (Vector3)bezier_points[ii + 2];
ret.Add(bezier);
}
}
if (editorMode == false && showWayPoint && hasShow == false)
{
this.bezier_queue = ret;
GameObject cube = GameObject.Find("__WayPointShowCube");
GameObject p = GameObject.Find("__DrawShowPoints");
if (Application.isPlaying)
{
hasShow = true;
foreach (Bezier3 be in bezier_queue)
{
Vector3 p0 = be.p0;
Vector3 p1 = be.p1;
Vector3 p2 = be.p2;
for (float time = 0f; time < 1.0f; time += tolerance)
{
float t = time;
Vector3 pos = be.GetPoint(t);
var obj = GameObject.Instantiate(cube, p.transform);
obj.transform.position = pos;
obj.name = "clone";
obj.transform.localScale = new Vector3(scale, scale, scale);
show_queue.Add(obj);
}
}
}
}
return ret;
}
void Start()
{
}
void Update()
{
}
void OnDrawGizmos()
{
ArrayList bezier_queue = this.GetBezierQueue(true); ;
ArrayList pointss = new ArrayList();
foreach (Bezier3 be in bezier_queue)
{
for (float time = 0f; time < 1.0f; time += 0.001f)
{
float t = time;
Vector3 pos = be.GetPoint(t);
pointss.Add(pos);
}
}
for (int ii = 0; ii < pointss.Count - 3; ii += 2)
{
Gizmos.DrawLine((Vector3)(pointss[ii]), (Vector3)(pointss[ii + 1]));
}
}
}
Bezier3.cs
/*
* Author: caoshanshan
* Email: [email protected]
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//2次贝塞尔曲线
//为了计算性能和精度 暂不用高阶生成,用多个2阶 实时计算 拼接出完整赛道
// 额外的工作是 每个2阶的粘合处平稳过渡
public class Bezier3
{
public Vector3 GetPoint(float t)
{
Vector3 pos = (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2;
return pos;
}
public Bezier3()
{
}
public Vector3 p0 = Vector3.zero;
public Vector3 p1 = Vector3.zero;
public Vector3 p2 = Vector3.zero;
public int id = 0;//id
}